Skip to content

Commit b6fcc50

Browse files
committed
Add option for generating coverage reports
Add a `--coverage` option in the `test` subcommand of the miri script. This option, when set, will generate a coverage report after running the tests. `cargo-binutils` is needed as a dependency to generate the reports.
1 parent 9045930 commit b6fcc50

File tree

4 files changed

+78
-3
lines changed

4 files changed

+78
-3
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ perf.data.old
1212
flamegraph.svg
1313
tests/native-lib/libtestlib.so
1414
.auto-*
15+
*.profraw
16+
*.profdata

ci/ci.sh

+3
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,6 @@ case $HOST_TARGET in
176176
exit 1
177177
;;
178178
esac
179+
180+
## Smoke test for coverage reports
181+
./miri test --coverage do_not_match_a_test

miri-script/src/commands.rs

+67-2
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ impl Command {
172172
Command::Install { flags } => Self::install(flags),
173173
Command::Build { flags } => Self::build(flags),
174174
Command::Check { flags } => Self::check(flags),
175-
Command::Test { bless, flags, target } => Self::test(bless, flags, target),
175+
Command::Test { bless, flags, target, coverage } =>
176+
Self::test(bless, flags, target, coverage),
176177
Command::Run { dep, verbose, many_seeds, target, edition, flags } =>
177178
Self::run(dep, verbose, many_seeds, target, edition, flags),
178179
Command::Doc { flags } => Self::doc(flags),
@@ -458,16 +459,28 @@ impl Command {
458459
Ok(())
459460
}
460461

461-
fn test(bless: bool, mut flags: Vec<String>, target: Option<String>) -> Result<()> {
462+
fn test(
463+
bless: bool,
464+
mut flags: Vec<String>,
465+
target: Option<String>,
466+
coverage: bool,
467+
) -> Result<()> {
462468
let mut e = MiriEnv::new()?;
463469

470+
if coverage {
471+
let rustflags = e.sh.var("RUSTFLAGS")?;
472+
let rustflags = format!("{rustflags} -C instrument-coverage");
473+
e.sh.set_var("RUSTFLAGS", rustflags);
474+
}
475+
464476
// Prepare a sysroot. (Also builds cargo-miri, which we need.)
465477
e.build_miri_sysroot(/* quiet */ false, target.as_deref())?;
466478

467479
// Forward information to test harness.
468480
if bless {
469481
e.sh.set_var("RUSTC_BLESS", "Gesundheit");
470482
}
483+
471484
if let Some(target) = target {
472485
// Tell the harness which target to test.
473486
e.sh.set_var("MIRI_TEST_TARGET", target);
@@ -479,9 +492,61 @@ impl Command {
479492
// Then test, and let caller control flags.
480493
// Only in root project as `cargo-miri` has no tests.
481494
e.test(".", &flags)?;
495+
496+
if coverage {
497+
Self::show_coverage_report(&e)?;
498+
}
482499
Ok(())
483500
}
484501

502+
fn show_coverage_report(e: &MiriEnv) -> Result<()> {
503+
let profraw_files: Vec<_> = Self::profraw_files(".")?;
504+
505+
let sysroot = cmd!(e.sh, "rustc --print sysroot").read()?;
506+
507+
let rustlib = Self::rustlib(sysroot)?;
508+
let mut profdata_bin = rustlib.clone();
509+
profdata_bin.push("llvm-profdata");
510+
511+
// Merge the profraw files
512+
let profraw_files_cloned = profraw_files.iter();
513+
cmd!(e.sh, "{profdata_bin} merge -sparse {profraw_files_cloned...} -o merged.profdata")
514+
.quiet()
515+
.run()?;
516+
517+
// Create the coverage report.
518+
let miri_dir = e.miri_dir.as_os_str();
519+
let mut cov_bin = rustlib.clone();
520+
cov_bin.push("llvm-cov");
521+
let suffix = if cfg!(windows) { ".exe" } else { "" };
522+
cmd!(
523+
e.sh,
524+
"{cov_bin} report --instr-profile=merged.profdata --object {miri_dir}/target/debug/miri{suffix} --sources src/"
525+
).quiet().run()?;
526+
527+
// Delete artifacts.
528+
cmd!(e.sh, "rm {profraw_files...} merged.profdata").quiet().run()?;
529+
Ok(())
530+
}
531+
532+
fn rustlib(sysroot: String) -> Result<PathBuf> {
533+
let mut pathbuf = PathBuf::from(sysroot);
534+
pathbuf.push("lib");
535+
pathbuf.push("rustlib");
536+
pathbuf.push(rustc_version::version_meta()?.host);
537+
pathbuf.push("bin");
538+
Ok(pathbuf)
539+
}
540+
541+
fn profraw_files(path: &str) -> Result<Vec<OsString>> {
542+
Ok(std::fs::read_dir(path)?
543+
.filter_map(|r| r.ok())
544+
.map(|e| e.path())
545+
.filter(|p| p.extension().map(|e| e == "profraw").unwrap_or(false))
546+
.map(|p| p.as_os_str().to_os_string())
547+
.collect())
548+
}
549+
485550
fn run(
486551
dep: bool,
487552
verbose: bool,

miri-script/src/main.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ pub enum Command {
3434
/// The cross-interpretation target.
3535
/// If none then the host is the target.
3636
target: Option<String>,
37+
/// Produce coverage report if set.
38+
coverage: bool,
3739
/// Flags that are passed through to the test harness.
3840
flags: Vec<String>,
3941
},
@@ -158,9 +160,12 @@ fn main() -> Result<()> {
158160
let mut target = None;
159161
let mut bless = false;
160162
let mut flags = Vec::new();
163+
let mut coverage = false;
161164
loop {
162165
if args.get_long_flag("bless")? {
163166
bless = true;
167+
} else if args.get_long_flag("coverage")? {
168+
coverage = true;
164169
} else if let Some(val) = args.get_long_opt("target")? {
165170
target = Some(val);
166171
} else if let Some(flag) = args.get_other() {
@@ -169,7 +174,7 @@ fn main() -> Result<()> {
169174
break;
170175
}
171176
}
172-
Command::Test { bless, flags, target }
177+
Command::Test { bless, flags, target, coverage }
173178
}
174179
Some("run") => {
175180
let mut dep = false;

0 commit comments

Comments
 (0)