Skip to main content

bootstrap/core/build_steps/test/
failed_tests.rs

1use std::collections::BTreeSet;
2use std::fs::{self, File};
3use std::io::{BufRead, BufReader, ErrorKind};
4use std::path::{Path, PathBuf};
5
6use crate::core::builder::{Builder, Step};
7use crate::t;
8
9#[derive(Clone)]
10pub struct RecordFailedTests {
11    failed_tests_path: Option<PathBuf>,
12}
13
14impl RecordFailedTests {
15    pub fn path(&self) -> Option<&Path> {
16        self.failed_tests_path.as_deref()
17    }
18}
19
20/// This step is run as a dependency of most testing steps.
21/// Upon running, a file is created for failed tests to be recorded in if `--record` is passed on
22/// the command line.
23///
24/// This step is the only way to get access to a token type called [`RecordFailedTests`].
25/// Having this token type signifies the fact that a file was created to store failed tests in,
26/// and is required to create a `Renderer`, the type that renders the outputs of tests.
27///
28/// If `--rerun` isn't passed, or we're in dry-run mode, running this step is a no-op,
29/// and the `RecordFailedTest` type doesn't (need to) signify anything.
30#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
31pub struct SetupFailedTestsFile;
32impl Step for SetupFailedTestsFile {
33    type Output = RecordFailedTests;
34
35    fn run(self, builder: &Builder<'_>) -> Self::Output {
36        if !builder.config.cmd.record() || builder.config.dry_run() {
37            return RecordFailedTests { failed_tests_path: None };
38        }
39
40        let failed_tests_path = builder.config.record_failed_tests_path.clone();
41        println!(
42            "setting up tracking of failed tests in {} (`--record` was passed)",
43            failed_tests_path.display()
44        );
45        if failed_tests_path.exists() {
46            println!("deleting previously recorded failed tests");
47            t!(fs::remove_file(&failed_tests_path));
48        }
49        RecordFailedTests { failed_tests_path: Some(failed_tests_path) }
50    }
51}
52
53pub fn collect_previously_failed_tests(failed_tests_file_path: &PathBuf) -> Vec<PathBuf> {
54    let mut paths = BTreeSet::new();
55
56    println!(
57        "`--rerun` passed so looking for failed tests in {}",
58        failed_tests_file_path.display()
59    );
60
61    let lines: Vec<String> = match File::open(failed_tests_file_path) {
62        Ok(f) => t!(BufReader::new(f).lines().collect()),
63        Err(e) if e.kind() == ErrorKind::NotFound => {
64            println!(
65                "WARNING: failed tests file doesn't exist: `--rerun` only makes sense after a previous test run with `--record`"
66            );
67            return Vec::new();
68        }
69        Err(e) => t!(Err(e)),
70    };
71
72    const MAX_RERUN_PRINTS: usize = 10;
73
74    for line in lines {
75        let trimmed = line.as_str().trim();
76        let without_revision =
77            trimmed.rsplit_once("#").map(|(before, _)| before).unwrap_or(trimmed);
78        let without_suite_prefix = without_revision
79            .strip_prefix("[")
80            .and_then(|rest| rest.split_once("]"))
81            .map(|(_, after)| after.trim())
82            .unwrap_or(without_revision);
83
84        let failed_test_path = PathBuf::from(without_suite_prefix.to_string());
85        if paths.insert(failed_test_path.clone()) {
86            if paths.len() == 1 {
87                println!("rerunning previously failed tests:");
88            }
89            if paths.len() <= MAX_RERUN_PRINTS {
90                println!("    {}", failed_test_path.display());
91            }
92        }
93    }
94
95    if paths.len() > MAX_RERUN_PRINTS {
96        println!("    and {} more...", paths.len() - MAX_RERUN_PRINTS)
97    }
98
99    if paths.is_empty() {
100        println!(
101            "WARNING: failed tests file doesn't contain any failed tests: `--rerun` only makes sense after a previous test run with `--record`"
102        );
103    }
104
105    paths.into_iter().collect()
106}