|
| 1 | +use std::path::PathBuf; |
| 2 | + |
| 3 | +use anyhow::{Context, Result}; |
| 4 | +use path_macro::path; |
| 5 | +use tempfile::TempDir; |
| 6 | +use xshell::cmd; |
| 7 | + |
| 8 | +use crate::util::MiriEnv; |
| 9 | + |
| 10 | +/// CoverageReport can generate code coverage reports for miri. |
| 11 | +pub struct CoverageReport { |
| 12 | + /// path is a temporary directory where intermediate coverage artifacts will be stored. |
| 13 | + /// (The final output will be stored in a permanent location.) |
| 14 | + path: TempDir, |
| 15 | +} |
| 16 | + |
| 17 | +impl CoverageReport { |
| 18 | + /// Creates a new CoverageReport. |
| 19 | + /// |
| 20 | + /// # Errors |
| 21 | + /// |
| 22 | + /// An error will be returned if a temporary directory could not be created. |
| 23 | + pub fn new() -> Result<Self> { |
| 24 | + Ok(Self { path: TempDir::new()? }) |
| 25 | + } |
| 26 | + |
| 27 | + /// add_env_vars will add the required environment variables to MiriEnv `e`. |
| 28 | + pub fn add_env_vars(&self, e: &mut MiriEnv) -> Result<()> { |
| 29 | + let mut rustflags = e.sh.var("RUSTFLAGS")?; |
| 30 | + rustflags.push_str(" -C instrument-coverage"); |
| 31 | + e.sh.set_var("RUSTFLAGS", rustflags); |
| 32 | + |
| 33 | + // Copy-pasting from: https://doc.rust-lang.org/rustc/instrument-coverage.html#instrumentation-based-code-coverage |
| 34 | + // The format symbols below have the following meaning: |
| 35 | + // - %p - The process ID. |
| 36 | + // - %Nm - the instrumented binary’s signature: |
| 37 | + // The runtime creates a pool of N raw profiles, used for on-line |
| 38 | + // profile merging. The runtime takes care of selecting a raw profile |
| 39 | + // from the pool, locking it, and updating it before the program |
| 40 | + // exits. N must be between 1 and 9, and defaults to 1 if omitted |
| 41 | + // (with simply %m). |
| 42 | + // |
| 43 | + // Additionally the default for LLVM_PROFILE_FILE is default_%m_%p.profraw. |
| 44 | + // So we just use the same template, replacing "default" with "miri". |
| 45 | + let file_template = self.path.path().join("miri_%m_%p.profraw"); |
| 46 | + e.sh.set_var("LLVM_PROFILE_FILE", file_template); |
| 47 | + Ok(()) |
| 48 | + } |
| 49 | + |
| 50 | + /// show_coverage_report will print coverage information using the artifact |
| 51 | + /// files in `self.path`. |
| 52 | + pub fn show_coverage_report(&self, e: &MiriEnv) -> Result<()> { |
| 53 | + let profraw_files = self.profraw_files()?; |
| 54 | + |
| 55 | + let profdata_bin = path!(e.libdir / ".." / "bin" / "llvm-profdata"); |
| 56 | + |
| 57 | + let merged_file = path!(e.miri_dir / "target" / "coverage.profdata"); |
| 58 | + |
| 59 | + // Merge the profraw files |
| 60 | + cmd!(e.sh, "{profdata_bin} merge -sparse {profraw_files...} -o {merged_file}") |
| 61 | + .quiet() |
| 62 | + .run()?; |
| 63 | + |
| 64 | + // Create the coverage report. |
| 65 | + let cov_bin = path!(e.libdir / ".." / "bin" / "llvm-cov"); |
| 66 | + let miri_bin = |
| 67 | + e.build_get_binary(".").context("failed to get filename of miri executable")?; |
| 68 | + cmd!( |
| 69 | + e.sh, |
| 70 | + "{cov_bin} report --instr-profile={merged_file} --object {miri_bin} --sources src/" |
| 71 | + ) |
| 72 | + .run()?; |
| 73 | + |
| 74 | + println!("Profile data saved in {}", merged_file.display()); |
| 75 | + Ok(()) |
| 76 | + } |
| 77 | + |
| 78 | + /// profraw_files returns the profraw files in `self.path`. |
| 79 | + /// |
| 80 | + /// # Errors |
| 81 | + /// |
| 82 | + /// An error will be returned if `self.path` can't be read. |
| 83 | + fn profraw_files(&self) -> Result<Vec<PathBuf>> { |
| 84 | + Ok(std::fs::read_dir(&self.path)? |
| 85 | + .filter_map(|r| r.ok()) |
| 86 | + .filter(|e| e.file_type().is_ok_and(|t| t.is_file())) |
| 87 | + .map(|e| e.path()) |
| 88 | + .filter(|p| p.extension().is_some_and(|e| e == "profraw")) |
| 89 | + .collect()) |
| 90 | + } |
| 91 | +} |
0 commit comments