Skip to content

Commit 9d2564a

Browse files
committed
Expand run-coverage to support the remaining coverage-reports tests
1 parent d05653c commit 9d2564a

File tree

1 file changed

+115
-3
lines changed

1 file changed

+115
-3
lines changed

src/tools/compiletest/src/runtest.rs

+115-3
Original file line numberDiff line numberDiff line change
@@ -502,11 +502,18 @@ impl<'test> TestCx<'test> {
502502
}
503503
drop(proc_res);
504504

505+
let mut profraw_paths = vec![profraw_path];
506+
let mut bin_paths = vec![self.make_exe_name()];
507+
508+
if self.config.suite == "run-coverage-rustdoc" {
509+
self.run_doctests_for_coverage(&mut profraw_paths, &mut bin_paths);
510+
}
511+
505512
// Run `llvm-profdata merge` to index the raw coverage output.
506513
let proc_res = self.run_llvm_tool("llvm-profdata", |cmd| {
507514
cmd.args(["merge", "--sparse", "--output"]);
508515
cmd.arg(&profdata_path);
509-
cmd.arg(&profraw_path);
516+
cmd.args(&profraw_paths);
510517
});
511518
if !proc_res.status.success() {
512519
self.fatal_proc_rec("llvm-profdata merge failed!", &proc_res);
@@ -523,8 +530,10 @@ impl<'test> TestCx<'test> {
523530
cmd.arg("--instr-profile");
524531
cmd.arg(&profdata_path);
525532

526-
cmd.arg("--object");
527-
cmd.arg(&self.make_exe_name());
533+
for bin in &bin_paths {
534+
cmd.arg("--object");
535+
cmd.arg(bin);
536+
}
528537
});
529538
if !proc_res.status.success() {
530539
self.fatal_proc_rec("llvm-cov show failed!", &proc_res);
@@ -553,6 +562,82 @@ impl<'test> TestCx<'test> {
553562
}
554563
}
555564

565+
/// Run any doctests embedded in this test file, and add any resulting
566+
/// `.profraw` files and doctest executables to the given vectors.
567+
fn run_doctests_for_coverage(
568+
&self,
569+
profraw_paths: &mut Vec<PathBuf>,
570+
bin_paths: &mut Vec<PathBuf>,
571+
) {
572+
// Put .profraw files and doctest executables in dedicated directories,
573+
// to make it easier to glob them all later.
574+
let profraws_dir = self.output_base_dir().join("doc_profraws");
575+
let bins_dir = self.output_base_dir().join("doc_bins");
576+
577+
// Remove existing directories to prevent cross-run interference.
578+
if profraws_dir.try_exists().unwrap() {
579+
std::fs::remove_dir_all(&profraws_dir).unwrap();
580+
}
581+
if bins_dir.try_exists().unwrap() {
582+
std::fs::remove_dir_all(&bins_dir).unwrap();
583+
}
584+
585+
let mut rustdoc_cmd =
586+
Command::new(self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed"));
587+
588+
// In general there will be multiple doctest binaries running, so we
589+
// tell the profiler runtime to write their coverage data into separate
590+
// profraw files.
591+
rustdoc_cmd.env("LLVM_PROFILE_FILE", profraws_dir.join("%p-%m.profraw"));
592+
593+
rustdoc_cmd.args(["--test", "-Cinstrument-coverage"]);
594+
595+
// Without this, the doctests complain about not being able to find
596+
// their enclosing file's crate for some reason.
597+
rustdoc_cmd.args(["--crate-name", "workaround_for_79771"]);
598+
599+
// Persist the doctest binaries so that `llvm-cov show` can read their
600+
// embedded coverage mappings later.
601+
rustdoc_cmd.arg("-Zunstable-options");
602+
rustdoc_cmd.arg("--persist-doctests");
603+
rustdoc_cmd.arg(&bins_dir);
604+
605+
rustdoc_cmd.arg("-L");
606+
rustdoc_cmd.arg(self.aux_output_dir_name());
607+
608+
rustdoc_cmd.arg(&self.testpaths.file);
609+
610+
let proc_res = self.compose_and_run_compiler(rustdoc_cmd, None);
611+
if !proc_res.status.success() {
612+
self.fatal_proc_rec("rustdoc --test failed!", &proc_res)
613+
}
614+
615+
fn glob_iter(path: impl AsRef<Path>) -> impl Iterator<Item = PathBuf> {
616+
let path_str = path.as_ref().to_str().unwrap();
617+
let iter = glob(path_str).unwrap();
618+
iter.map(Result::unwrap)
619+
}
620+
621+
// Find all profraw files in the profraw directory.
622+
for p in glob_iter(profraws_dir.join("*.profraw")) {
623+
profraw_paths.push(p);
624+
}
625+
// Find all executables in the `--persist-doctests` directory, while
626+
// avoiding other file types (e.g. `.pdb` on Windows). This doesn't
627+
// need to be perfect, as long as it can handle the files actually
628+
// produced by `rustdoc --test`.
629+
for p in glob_iter(bins_dir.join("**/*")) {
630+
let is_bin = p.is_file()
631+
&& match p.extension() {
632+
None => true,
633+
Some(ext) => ext == OsStr::new("exe"),
634+
};
635+
if is_bin {
636+
bin_paths.push(p);
637+
}
638+
}
639+
}
640+
556641
fn run_llvm_tool(&self, name: &str, configure_cmd_fn: impl FnOnce(&mut Command)) -> ProcRes {
557642
let tool_path = self
558643
.config
@@ -582,12 +667,39 @@ impl<'test> TestCx<'test> {
582667

583668
let mut lines = normalized.lines().collect::<Vec<_>>();
584669

670+
Self::sort_coverage_file_sections(&mut lines)?;
585671
Self::sort_coverage_subviews(&mut lines)?;
586672

587673
let joined_lines = lines.iter().flat_map(|line| [line, "\n"]).collect::<String>();
588674
Ok(joined_lines)
589675
}
590676

677+
/// Coverage reports can describe multiple source files, separated by
678+
/// blank lines. The order of these files is unpredictable (since it
679+
/// depends on implementation details), so we need to sort the file
680+
/// sections into a consistent order before comparing against a snapshot.
681+
fn sort_coverage_file_sections(coverage_lines: &mut Vec<&str>) -> Result<(), String> {
682+
// Group the lines into file sections, separated by blank lines.
683+
let mut sections = coverage_lines.split(|line| line.is_empty()).collect::<Vec<_>>();
684+
685+
// The last section should be empty, representing an extra trailing blank line.
686+
if !sections.last().is_some_and(|last| last.is_empty()) {
687+
return Err("coverage report should end with an extra blank line".to_owned());
688+
}
689+
690+
// Sort the file sections (not including the final empty "section").
691+
let except_last = sections.len() - 1;
692+
(&mut sections[..except_last]).sort();
693+
694+
// Join the file sections back into a flat list of lines, with
695+
// sections separated by blank lines.
696+
let joined = sections.join(&[""] as &[_]);
697+
assert_eq!(joined.len(), coverage_lines.len());
698+
*coverage_lines = joined;
699+
700+
Ok(())
701+
}
702+
591703
fn sort_coverage_subviews(coverage_lines: &mut Vec<&str>) -> Result<(), String> {
592704
let mut output_lines = Vec::new();
593705

0 commit comments

Comments
 (0)