test_r_core/output/
mod.rs

1mod ipc;
2mod json;
3mod junit;
4mod pretty;
5mod terse;
6
7use crate::args::{Arguments, FormatSetting};
8use crate::internal::{RegisteredTest, TestResult};
9use std::io::{Seek, SeekFrom, Write};
10use std::path::PathBuf;
11use std::sync::Arc;
12use std::time::Duration;
13
14pub trait TestRunnerOutput: Send + Sync {
15    fn start_suite(&self, tests: &[RegisteredTest]);
16    fn start_running_test(&self, test: &RegisteredTest, idx: usize, count: usize);
17    fn repeat_running_test(
18        &self,
19        test: &RegisteredTest,
20        idx: usize,
21        count: usize,
22        attempt: usize,
23        max_attempts: usize,
24        reason: &str,
25    );
26    fn finished_running_test(
27        &self,
28        test: &RegisteredTest,
29        idx: usize,
30        count: usize,
31        result: &TestResult,
32    );
33    fn finished_suite(
34        &self,
35        registered_tests: &[RegisteredTest],
36        results: &[(RegisteredTest, TestResult)],
37        exec_time: Duration,
38    );
39    fn test_list(&self, registered_tests: &[RegisteredTest]);
40
41    fn warning(&self, message: &str) {
42        eprintln!("{}", message);
43    }
44}
45
46pub fn test_runner_output(args: &Arguments) -> Arc<dyn TestRunnerOutput> {
47    if args.ipc.is_some() {
48        Arc::new(ipc::IpcWorkerOutput::new())
49    } else if args.quiet {
50        Arc::new(terse::Terse::new())
51    } else {
52        let logfile = args.logfile.as_ref().map(PathBuf::from);
53        match args.format.unwrap_or_default() {
54            FormatSetting::Pretty => Arc::new(pretty::Pretty::new(
55                args.color.unwrap_or_default(),
56                args.show_output,
57                logfile,
58                args.report_time,
59                args.unit_test_threshold(),
60                args.integration_test_threshold(),
61                args.show_stats,
62            )),
63            FormatSetting::Terse => Arc::new(terse::Terse::new()),
64            FormatSetting::Json => Arc::new(json::Json::new(args.show_output, logfile)),
65            FormatSetting::Junit => Arc::new(junit::JUnit::new(args.show_output, logfile)),
66        }
67    }
68}
69
70struct LogFile {
71    pub file: std::fs::File,
72}
73
74impl LogFile {
75    fn new(mut path: PathBuf, merged: bool) -> Self {
76        let cwd = std::env::current_dir().unwrap();
77        if path.is_relative() {
78            path = cwd.join(path);
79        }
80
81        if !path.parent().unwrap().exists() {
82            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
83        }
84
85        if !merged {
86            // Because of https://github.com/rust-lang/rust/issues/105424 we have to generate a unique log file name
87            // otherwise the core test runner will overwrite it
88            let uuid = uuid::Uuid::new_v4();
89            let stem = path
90                .file_stem()
91                .map(|s| s.to_string_lossy().to_string())
92                .unwrap_or_default();
93            let extension = path
94                .extension()
95                .map(|s| s.to_string_lossy().to_string())
96                .unwrap_or_default();
97            path.set_file_name(format!("{}-{}.{}", stem, uuid, extension));
98        }
99
100        eprintln!("Logging to {}", path.to_string_lossy());
101
102        let file = std::fs::OpenOptions::new()
103            .create(true)
104            .append(true)
105            .open(path.clone())
106            .unwrap_or_else(|_| panic!("Failed to open log file {}", path.to_string_lossy()));
107        LogFile { file }
108    }
109}
110
111enum StdoutOrLogFile {
112    Stdout(std::io::Stdout),
113    LogFile(LogFile),
114}
115
116impl StdoutOrLogFile {
117    fn reset_log_file(&mut self) -> std::io::Result<bool> {
118        if let StdoutOrLogFile::LogFile(logfile) = self {
119            logfile.file.set_len(0)?;
120            logfile.file.seek(SeekFrom::Start(0))?;
121            logfile.file.flush()?;
122            Ok(true)
123        } else {
124            Ok(false)
125        }
126    }
127}
128
129impl Write for StdoutOrLogFile {
130    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
131        match self {
132            StdoutOrLogFile::Stdout(stdout) => stdout.write(buf),
133            StdoutOrLogFile::LogFile(logfile) => logfile.file.write(buf),
134        }
135    }
136
137    fn flush(&mut self) -> std::io::Result<()> {
138        match self {
139            StdoutOrLogFile::Stdout(stdout) => stdout.flush(),
140            StdoutOrLogFile::LogFile(logfile) => logfile.file.flush(),
141        }
142    }
143}