From 8f36706eb3022cf60cce15be2eb91224db6fd3a8 Mon Sep 17 00:00:00 2001 From: Konstantin Andrikopoulos Date: Wed, 9 Oct 2024 02:02:42 +0200 Subject: [PATCH] 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. --- ci/ci.sh | 3 ++ miri-script/src/commands.rs | 72 +++++++++++++++++++++++++++++++++++-- miri-script/src/main.rs | 7 +++- 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/ci/ci.sh b/ci/ci.sh index 689bc6d46f..12e590bae7 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -176,3 +176,6 @@ case $HOST_TARGET in exit 1 ;; esac + +## Smoke test for coverage reports +./miri test --coverage do_not_match_a_test \ No newline at end of file diff --git a/miri-script/src/commands.rs b/miri-script/src/commands.rs index 36175c8dd2..6e1bd79769 100644 --- a/miri-script/src/commands.rs +++ b/miri-script/src/commands.rs @@ -172,7 +172,8 @@ impl Command { Command::Install { flags } => Self::install(flags), Command::Build { flags } => Self::build(flags), Command::Check { flags } => Self::check(flags), - Command::Test { bless, flags, target } => Self::test(bless, flags, target), + Command::Test { bless, flags, target, coverage } => + Self::test(bless, flags, target, coverage), Command::Run { dep, verbose, many_seeds, target, edition, flags } => Self::run(dep, verbose, many_seeds, target, edition, flags), Command::Doc { flags } => Self::doc(flags), @@ -458,9 +459,20 @@ impl Command { Ok(()) } - fn test(bless: bool, mut flags: Vec, target: Option) -> Result<()> { + fn test( + bless: bool, + mut flags: Vec, + target: Option, + coverage: bool, + ) -> Result<()> { let mut e = MiriEnv::new()?; + if coverage { + let rustflags = e.sh.var("RUSTFLAGS")?; + let rustflags = format!("{rustflags} -C instrument-coverage"); + e.sh.set_var("RUSTFLAGS", rustflags); + } + // Prepare a sysroot. (Also builds cargo-miri, which we need.) e.build_miri_sysroot(/* quiet */ false, target.as_deref())?; @@ -468,6 +480,7 @@ impl Command { if bless { e.sh.set_var("RUSTC_BLESS", "Gesundheit"); } + if let Some(target) = target { // Tell the harness which target to test. e.sh.set_var("MIRI_TEST_TARGET", target); @@ -479,9 +492,64 @@ impl Command { // Then test, and let caller control flags. // Only in root project as `cargo-miri` has no tests. e.test(".", &flags)?; + + if coverage { + Self::show_coverage_report(&e)?; + } + Ok(()) + } + + fn show_coverage_report(e: &MiriEnv) -> Result<()> { + let profraw_files: Vec<_> = Self::profraw_files(".")?; + + let sysroot = cmd!(e.sh, "rustc --print sysroot").read()?; + + let rustlib = Self::rustlib(sysroot)?; + let mut profdata_bin = rustlib.clone(); + profdata_bin.push("llvm-profdata"); + + // Merge the profraw files + let profraw_files_cloned = profraw_files.iter(); + cmd!(e.sh, "{profdata_bin} merge -sparse {profraw_files_cloned...} -o merged.profdata") + .quiet() + .run()?; + + // Create the coverage report. + let miri_dir = e.miri_dir.as_os_str(); + let mut cov_bin = rustlib.clone(); + cov_bin.push("llvm-cov"); + let suffix = if cfg!(windows) { ".exe" } else { "" }; + cmd!( + e.sh, + "{cov_bin} report --instr-profile=merged.profdata --object {miri_dir}/target/debug/miri{suffix} --sources src/" + ).quiet().run()?; + + // Delete artifacts. + let cargo_miri_profraw_files = Self::profraw_files("cargo-miri")?; + cmd!(e.sh, "rm {profraw_files...} {cargo_miri_profraw_files...} merged.profdata") + .quiet() + .run()?; Ok(()) } + fn rustlib(sysroot: String) -> Result { + let mut pathbuf = PathBuf::from(sysroot); + pathbuf.push("lib"); + pathbuf.push("rustlib"); + pathbuf.push(rustc_version::version_meta()?.host); + pathbuf.push("bin"); + Ok(pathbuf) + } + + fn profraw_files(path: &str) -> Result> { + Ok(std::fs::read_dir(path)? + .filter_map(|r| r.ok()) + .map(|e| e.path()) + .filter(|p| p.extension().map(|e| e == "profraw").unwrap_or(false)) + .map(|p| p.as_os_str().to_os_string()) + .collect()) + } + fn run( dep: bool, verbose: bool, diff --git a/miri-script/src/main.rs b/miri-script/src/main.rs index 0620f3aaf0..ed41ed85aa 100644 --- a/miri-script/src/main.rs +++ b/miri-script/src/main.rs @@ -34,6 +34,8 @@ pub enum Command { /// The cross-interpretation target. /// If none then the host is the target. target: Option, + /// Produce coverage report if set. + coverage: bool, /// Flags that are passed through to the test harness. flags: Vec, }, @@ -158,9 +160,12 @@ fn main() -> Result<()> { let mut target = None; let mut bless = false; let mut flags = Vec::new(); + let mut coverage = false; loop { if args.get_long_flag("bless")? { bless = true; + } else if args.get_long_flag("coverage")? { + coverage = true; } else if let Some(val) = args.get_long_opt("target")? { target = Some(val); } else if let Some(flag) = args.get_other() { @@ -169,7 +174,7 @@ fn main() -> Result<()> { break; } } - Command::Test { bless, flags, target } + Command::Test { bless, flags, target, coverage } } Some("run") => { let mut dep = false;