compiletest/runtest/
coverage.rsuse std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Command;
use glob::glob;
use crate::common::{UI_COVERAGE, UI_COVERAGE_MAP};
use crate::runtest::{Emit, ProcRes, TestCx, WillExecute};
use crate::util::static_regex;
impl<'test> TestCx<'test> {
fn coverage_dump_path(&self) -> &Path {
self.config
.coverage_dump_path
.as_deref()
.unwrap_or_else(|| self.fatal("missing --coverage-dump"))
}
pub(super) fn run_coverage_map_test(&self) {
let coverage_dump_path = self.coverage_dump_path();
let (proc_res, llvm_ir_path) = self.compile_test_and_save_ir();
if !proc_res.status.success() {
self.fatal_proc_rec("compilation failed!", &proc_res);
}
drop(proc_res);
let mut dump_command = Command::new(coverage_dump_path);
dump_command.arg(llvm_ir_path);
let proc_res = self.run_command_to_procres(&mut dump_command);
if !proc_res.status.success() {
self.fatal_proc_rec("coverage-dump failed!", &proc_res);
}
let kind = UI_COVERAGE_MAP;
let expected_coverage_dump = self.load_expected_output(kind);
let actual_coverage_dump = self.normalize_output(&proc_res.stdout, &[]);
let coverage_dump_errors =
self.compare_output(kind, &actual_coverage_dump, &expected_coverage_dump);
if coverage_dump_errors > 0 {
self.fatal_proc_rec(
&format!("{coverage_dump_errors} errors occurred comparing coverage output."),
&proc_res,
);
}
}
pub(super) fn run_coverage_run_test(&self) {
let should_run = self.run_if_enabled();
let proc_res = self.compile_test(should_run, Emit::None);
if !proc_res.status.success() {
self.fatal_proc_rec("compilation failed!", &proc_res);
}
drop(proc_res);
if let WillExecute::Disabled = should_run {
return;
}
let profraw_path = self.output_base_dir().join("default.profraw");
let profdata_path = self.output_base_dir().join("default.profdata");
if profraw_path.exists() {
std::fs::remove_file(&profraw_path).unwrap();
}
if profdata_path.exists() {
std::fs::remove_file(&profdata_path).unwrap();
}
let proc_res = self.exec_compiled_test_general(
&[("LLVM_PROFILE_FILE", &profraw_path.to_str().unwrap())],
false,
);
if self.props.failure_status.is_some() {
self.check_correct_failure_status(&proc_res);
} else if !proc_res.status.success() {
self.fatal_proc_rec("test run failed!", &proc_res);
}
drop(proc_res);
let mut profraw_paths = vec![profraw_path];
let mut bin_paths = vec![self.make_exe_name()];
if self.config.suite == "coverage-run-rustdoc" {
self.run_doctests_for_coverage(&mut profraw_paths, &mut bin_paths);
}
let proc_res = self.run_llvm_tool("llvm-profdata", |cmd| {
cmd.args(["merge", "--sparse", "--output"]);
cmd.arg(&profdata_path);
cmd.args(&profraw_paths);
});
if !proc_res.status.success() {
self.fatal_proc_rec("llvm-profdata merge failed!", &proc_res);
}
drop(proc_res);
let proc_res = self.run_llvm_tool("llvm-cov", |cmd| {
cmd.args(["show", "--format=text", "--show-line-counts-or-regions"]);
let coverage_dump_path = self.coverage_dump_path();
cmd.arg("--Xdemangler").arg(coverage_dump_path);
cmd.arg("--Xdemangler").arg("--demangle");
cmd.arg("--instr-profile");
cmd.arg(&profdata_path);
for bin in &bin_paths {
cmd.arg("--object");
cmd.arg(bin);
}
cmd.args(&self.props.llvm_cov_flags);
});
if !proc_res.status.success() {
self.fatal_proc_rec("llvm-cov show failed!", &proc_res);
}
let kind = UI_COVERAGE;
let expected_coverage = self.load_expected_output(kind);
let normalized_actual_coverage =
self.normalize_coverage_output(&proc_res.stdout).unwrap_or_else(|err| {
self.fatal_proc_rec(&err, &proc_res);
});
let coverage_errors =
self.compare_output(kind, &normalized_actual_coverage, &expected_coverage);
if coverage_errors > 0 {
self.fatal_proc_rec(
&format!("{} errors occurred comparing coverage output.", coverage_errors),
&proc_res,
);
}
}
fn run_doctests_for_coverage(
&self,
profraw_paths: &mut Vec<PathBuf>,
bin_paths: &mut Vec<PathBuf>,
) {
let profraws_dir = self.output_base_dir().join("doc_profraws");
let bins_dir = self.output_base_dir().join("doc_bins");
if profraws_dir.try_exists().unwrap() {
std::fs::remove_dir_all(&profraws_dir).unwrap();
}
if bins_dir.try_exists().unwrap() {
std::fs::remove_dir_all(&bins_dir).unwrap();
}
let mut rustdoc_cmd =
Command::new(self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed"));
rustdoc_cmd.env("LLVM_PROFILE_FILE", profraws_dir.join("%p-%m.profraw"));
rustdoc_cmd.args(["--test", "-Cinstrument-coverage"]);
rustdoc_cmd.args(["--crate-name", "workaround_for_79771"]);
rustdoc_cmd.arg("-Zunstable-options");
rustdoc_cmd.arg("--persist-doctests");
rustdoc_cmd.arg(&bins_dir);
rustdoc_cmd.arg("-L");
rustdoc_cmd.arg(self.aux_output_dir_name());
rustdoc_cmd.arg(&self.testpaths.file);
let proc_res = self.compose_and_run_compiler(rustdoc_cmd, None, self.testpaths);
if !proc_res.status.success() {
self.fatal_proc_rec("rustdoc --test failed!", &proc_res)
}
fn glob_iter(path: impl AsRef<Path>) -> impl Iterator<Item = PathBuf> {
let path_str = path.as_ref().to_str().unwrap();
let iter = glob(path_str).unwrap();
iter.map(Result::unwrap)
}
for p in glob_iter(profraws_dir.join("*.profraw")) {
profraw_paths.push(p);
}
for p in glob_iter(bins_dir.join("**/*")) {
let is_bin = p.is_file()
&& match p.extension() {
None => true,
Some(ext) => ext == OsStr::new("exe"),
};
if is_bin {
bin_paths.push(p);
}
}
}
fn run_llvm_tool(&self, name: &str, configure_cmd_fn: impl FnOnce(&mut Command)) -> ProcRes {
let tool_path = self
.config
.llvm_bin_dir
.as_ref()
.expect("this test expects the LLVM bin dir to be available")
.join(name);
let mut cmd = Command::new(tool_path);
configure_cmd_fn(&mut cmd);
self.run_command_to_procres(&mut cmd)
}
fn normalize_coverage_output(&self, coverage: &str) -> Result<String, String> {
let normalized = self.normalize_output(coverage, &[]);
let normalized = Self::anonymize_coverage_line_numbers(&normalized);
let mut lines = normalized.lines().collect::<Vec<_>>();
Self::sort_coverage_file_sections(&mut lines)?;
Self::sort_coverage_subviews(&mut lines)?;
let joined_lines = lines.iter().flat_map(|line| [line, "\n"]).collect::<String>();
Ok(joined_lines)
}
fn anonymize_coverage_line_numbers(coverage: &str) -> String {
let coverage = static_regex!(r"(?m:^)(?<prefix>(?: \|)*) *[0-9]+\|")
.replace_all(&coverage, "${prefix} LL|");
let coverage = static_regex!(r"(?m:^)(?<prefix>(?: \|)+ Branch \()[0-9]+:")
.replace_all(&coverage, "${prefix}LL:");
let coverage =
static_regex!(r"(?m:^)(?<prefix>(?: \|)+---> MC/DC Decision Region \()[0-9]+:(?<middle>[0-9]+\) to \()[0-9]+:")
.replace_all(&coverage, "${prefix}LL:${middle}LL:");
let coverage =
static_regex!(r"(?m:^)(?<prefix>(?: \|)+ Condition C[0-9]+ --> \()[0-9]+:")
.replace_all(&coverage, "${prefix}LL:");
coverage.into_owned()
}
fn sort_coverage_file_sections(coverage_lines: &mut Vec<&str>) -> Result<(), String> {
let mut sections = coverage_lines.split(|line| line.is_empty()).collect::<Vec<_>>();
if !sections.last().is_some_and(|last| last.is_empty()) {
return Err("coverage report should end with an extra blank line".to_owned());
}
let except_last = sections.len() - 1;
(&mut sections[..except_last]).sort();
let joined = sections.join(&[""] as &[_]);
assert_eq!(joined.len(), coverage_lines.len());
*coverage_lines = joined;
Ok(())
}
fn sort_coverage_subviews(coverage_lines: &mut Vec<&str>) -> Result<(), String> {
let mut output_lines = Vec::new();
let mut subviews: Vec<Vec<&str>> = Vec::new();
fn flush<'a>(subviews: &mut Vec<Vec<&'a str>>, output_lines: &mut Vec<&'a str>) {
if subviews.is_empty() {
return;
}
let mut subviews = std::mem::take(subviews);
let except_last = subviews.len() - 1;
(&mut subviews[..except_last]).sort();
for view in subviews {
for line in view {
output_lines.push(line);
}
}
}
for (line, line_num) in coverage_lines.iter().zip(1..) {
if line.starts_with(" ------------------") {
subviews.push(vec![line]);
} else if line.starts_with(" |") {
subviews
.last_mut()
.ok_or(format!(
"unexpected subview line outside of a subview on line {line_num}"
))?
.push(line);
} else {
flush(&mut subviews, &mut output_lines);
output_lines.push(line);
}
}
flush(&mut subviews, &mut output_lines);
assert!(subviews.is_empty());
assert_eq!(output_lines.len(), coverage_lines.len());
*coverage_lines = output_lines;
Ok(())
}
}