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, ShouldRun, 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 should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
36        run.never()
37    }
38
39    fn run(self, builder: &Builder<'_>) -> Self::Output {
40        if !builder.config.cmd.record() || builder.config.dry_run() {
41            return RecordFailedTests { failed_tests_path: None };
42        }
43
44        let failed_tests_path = builder.config.record_failed_tests_path.clone();
45        println!(
46            "setting up tracking of failed tests in {} (`--record` was passed)",
47            failed_tests_path.display()
48        );
49        if failed_tests_path.exists() {
50            println!("deleting previously recorded failed tests");
51            t!(fs::remove_file(&failed_tests_path));
52        }
53        RecordFailedTests { failed_tests_path: Some(failed_tests_path) }
54    }
55}
56
57pub fn collect_previously_failed_tests(failed_tests_file_path: &PathBuf) -> Vec<PathBuf> {
58    let mut paths = BTreeSet::new();
59
60    println!(
61        "`--rerun` passed so looking for failed tests in {}",
62        failed_tests_file_path.display()
63    );
64
65    let lines: Vec<String> = match File::open(failed_tests_file_path) {
66        Ok(f) => t!(BufReader::new(f).lines().collect()),
67        Err(e) if e.kind() == ErrorKind::NotFound => {
68            println!(
69                "WARNING: failed tests file doesn't exist: `--rerun` only makes sense after a previous test run with `--record`"
70            );
71            return Vec::new();
72        }
73        Err(e) => t!(Err(e)),
74    };
75
76    const MAX_RERUN_PRINTS: usize = 10;
77
78    for line in lines {
79        let trimmed = line.as_str().trim();
80        let without_revision =
81            trimmed.rsplit_once("#").map(|(before, _)| before).unwrap_or(trimmed);
82        let without_suite_prefix = without_revision
83            .strip_prefix("[")
84            .and_then(|rest| rest.split_once("]"))
85            .map(|(_, after)| after.trim())
86            .unwrap_or(without_revision);
87
88        let failed_test_path = PathBuf::from(without_suite_prefix.to_string());
89        if paths.insert(failed_test_path.clone()) {
90            if paths.len() == 1 {
91                println!("rerunning previously failed tests:");
92            }
93            if paths.len() <= MAX_RERUN_PRINTS {
94                println!("    {}", failed_test_path.display());
95            }
96        }
97    }
98
99    if paths.len() > MAX_RERUN_PRINTS {
100        println!("    and {} more...", paths.len() - MAX_RERUN_PRINTS)
101    }
102
103    if paths.is_empty() {
104        println!(
105            "WARNING: failed tests file doesn't contain any failed tests: `--rerun` only makes sense after a previous test run with `--record`"
106        );
107    }
108
109    paths.into_iter().collect()
110}