test_r_core/output/
mod.rs1mod 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 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}