Skip to content

Commit 8f36706

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 8f36706

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed

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

+70-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,64 @@ 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+
}
499+
Ok(())
500+
}
501+
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+
let cargo_miri_profraw_files = Self::profraw_files("cargo-miri")?;
529+
cmd!(e.sh, "rm {profraw_files...} {cargo_miri_profraw_files...} merged.profdata")
530+
.quiet()
531+
.run()?;
482532
Ok(())
483533
}
484534

535+
fn rustlib(sysroot: String) -> Result<PathBuf> {
536+
let mut pathbuf = PathBuf::from(sysroot);
537+
pathbuf.push("lib");
538+
pathbuf.push("rustlib");
539+
pathbuf.push(rustc_version::version_meta()?.host);
540+
pathbuf.push("bin");
541+
Ok(pathbuf)
542+
}
543+
544+
fn profraw_files(path: &str) -> Result<Vec<OsString>> {
545+
Ok(std::fs::read_dir(path)?
546+
.filter_map(|r| r.ok())
547+
.map(|e| e.path())
548+
.filter(|p| p.extension().map(|e| e == "profraw").unwrap_or(false))
549+
.map(|p| p.as_os_str().to_os_string())
550+
.collect())
551+
}
552+
485553
fn run(
486554
dep: bool,
487555
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)