test_r_core/output/
mod.rs1mod ctrf;
2mod ipc;
3mod json;
4mod junit;
5mod pretty;
6mod term_progress;
7mod terse;
8
9use crate::args::{Arguments, FormatSetting};
10use crate::internal::{RegisteredTest, TestResult};
11use std::io::{Seek, SeekFrom, Write};
12use std::path::PathBuf;
13use std::sync::Arc;
14use std::time::Duration;
15
16pub trait TestRunnerOutput: Send + Sync {
17 fn start_suite(&self, tests: &[RegisteredTest]);
18 fn start_running_test(&self, test: &RegisteredTest, idx: usize, count: usize);
19 fn repeat_running_test(
20 &self,
21 test: &RegisteredTest,
22 idx: usize,
23 count: usize,
24 attempt: usize,
25 max_attempts: usize,
26 reason: &str,
27 );
28 fn finished_running_test(
29 &self,
30 test: &RegisteredTest,
31 idx: usize,
32 count: usize,
33 result: &TestResult,
34 );
35 fn finished_suite(
36 &self,
37 registered_tests: &[RegisteredTest],
38 results: &[(RegisteredTest, TestResult)],
39 exec_time: Duration,
40 );
41 fn test_list(&self, registered_tests: &[RegisteredTest]);
42
43 fn warning(&self, message: &str) {
44 eprintln!("{message}");
45 }
46}
47
48pub(crate) fn write_failure_summary_to_stderr(
49 results: &[(RegisteredTest, TestResult)],
50 exec_time: Duration,
51) {
52 let failed: Vec<_> = results
53 .iter()
54 .filter(|(_, result)| result.is_failed())
55 .collect();
56 if failed.is_empty() {
57 return;
58 }
59 let passed = results
60 .iter()
61 .filter(|(_, result)| result.is_passed() || result.is_benchmarked())
62 .count();
63 let ignored = results
64 .iter()
65 .filter(|(_, result)| result.is_ignored())
66 .count();
67
68 eprintln!();
69 eprintln!(
70 "test result: FAILED; {} passed; {} failed; {} ignored; finished in {:.3}s",
71 passed,
72 failed.len(),
73 ignored,
74 exec_time.as_secs_f64()
75 );
76 eprintln!();
77 eprintln!("Failed tests:");
78 for (test, result) in &failed {
79 eprintln!(
80 " - {} ({})",
81 test.fully_qualified_name(),
82 result.failure_message().as_deref().unwrap_or("???"),
83 );
84 }
85 eprintln!();
86}
87
88pub fn test_runner_output(args: &Arguments) -> Arc<dyn TestRunnerOutput> {
89 if args.ipc.is_some() {
90 Arc::new(ipc::IpcWorkerOutput::new())
91 } else if args.quiet {
92 Arc::new(terse::Terse::new())
93 } else {
94 let logfile = args.logfile.as_ref().map(PathBuf::from);
95 match args.format.unwrap_or_default() {
96 FormatSetting::Pretty => Arc::new(pretty::Pretty::new(
97 args.color.unwrap_or_default(),
98 args.show_output,
99 logfile,
100 args.report_time,
101 args.unit_test_threshold(),
102 args.integration_test_threshold(),
103 args.show_stats,
104 )),
105 FormatSetting::Terse => Arc::new(terse::Terse::new()),
106 FormatSetting::Json => Arc::new(json::Json::new(args.show_output, logfile)),
107 FormatSetting::Junit => Arc::new(junit::JUnit::new(args.show_output, logfile)),
108 FormatSetting::Ctrf => Arc::new(ctrf::Ctrf::new(args.show_output, logfile)),
109 }
110 }
111}
112
113struct LogFile {
114 pub file: std::fs::File,
115}
116
117impl LogFile {
118 fn new(mut path: PathBuf, merged: bool) -> Self {
119 let cwd = std::env::current_dir().unwrap();
120 if path.is_relative() {
121 path = cwd.join(path);
122 }
123
124 if !path.parent().unwrap().exists() {
125 std::fs::create_dir_all(path.parent().unwrap()).unwrap();
126 }
127
128 if !merged {
129 let uuid = uuid::Uuid::new_v4();
132 let stem = path
133 .file_stem()
134 .map(|s| s.to_string_lossy().to_string())
135 .unwrap_or_default();
136 let extension = path
137 .extension()
138 .map(|s| s.to_string_lossy().to_string())
139 .unwrap_or_default();
140 path.set_file_name(format!("{stem}-{uuid}.{extension}"));
141 }
142
143 eprintln!("Logging to {}", path.to_string_lossy());
144
145 let file = std::fs::OpenOptions::new()
146 .create(true)
147 .append(true)
148 .open(path.clone())
149 .unwrap_or_else(|_| panic!("Failed to open log file {}", path.to_string_lossy()));
150 LogFile { file }
151 }
152}
153
154enum StdoutOrLogFile {
155 Stdout(std::io::Stdout),
156 LogFile(LogFile),
157}
158
159impl StdoutOrLogFile {
160 fn reset_log_file(&mut self) -> std::io::Result<bool> {
161 if let StdoutOrLogFile::LogFile(logfile) = self {
162 logfile.file.set_len(0)?;
163 logfile.file.seek(SeekFrom::Start(0))?;
164 logfile.file.flush()?;
165 Ok(true)
166 } else {
167 Ok(false)
168 }
169 }
170}
171
172impl Write for StdoutOrLogFile {
173 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
174 match self {
175 StdoutOrLogFile::Stdout(stdout) => stdout.write(buf),
176 StdoutOrLogFile::LogFile(logfile) => logfile.file.write(buf),
177 }
178 }
179
180 fn flush(&mut self) -> std::io::Result<()> {
181 match self {
182 StdoutOrLogFile::Stdout(stdout) => stdout.flush(),
183 StdoutOrLogFile::LogFile(logfile) => logfile.file.flush(),
184 }
185 }
186}