1//! Various schemes for reporting messages during testing or after testing is done.
2
3use crate::{test_result::TestResult, Errors};
4
5use std::{
6 fmt::Debug,
7 panic::RefUnwindSafe,
8 path::{Path, PathBuf},
9};
10pub use text::*;
11pub mod debug;
12mod text;
13#[cfg(feature = "gha")]
14pub use gha::*;
15#[cfg(feature = "gha")]
16mod gha;
17
18/// A generic way to handle the output of this crate.
19pub trait StatusEmitter: Sync + RefUnwindSafe {
20 /// Invoked the moment we know a test will later be run.
21 /// Useful for progress bars and such.
22 fn register_test(&self, path: PathBuf) -> Box<dyn TestStatus + 'static>;
23
24 /// Create a report about the entire test run at the end.
25 #[allow(clippy::type_complexity)]
26 fn finalize(
27 &self,
28 failed: usize,
29 succeeded: usize,
30 ignored: usize,
31 filtered: usize,
32 aborted: bool,
33 ) -> Box<dyn Summary>;
34}
35
36/// Some configuration options for revisions
37#[derive(Debug, Clone, Copy)]
38pub enum RevisionStyle {
39 /// Things like dependencies or aux files building are not really nested
40 /// below the build, but it is waiting on it.
41 Separate,
42 /// Always show them, even if rendering to a file
43 Show,
44}
45
46/// Information about a specific test run.
47pub trait TestStatus: Send + Sync + RefUnwindSafe {
48 /// Create a copy of this test for a new revision.
49 fn for_revision(&self, revision: &str, style: RevisionStyle) -> Box<dyn TestStatus>;
50
51 /// Create a copy of this test for a new path.
52 fn for_path(&self, path: &Path) -> Box<dyn TestStatus>;
53
54 /// Invoked before each failed test prints its errors along with a drop guard that can
55 /// gets invoked afterwards.
56 fn failed_test<'a>(
57 &'a self,
58 cmd: &'a str,
59 stderr: &'a [u8],
60 stdout: &'a [u8],
61 ) -> Box<dyn Debug + 'a>;
62
63 /// A test has finished, handle the result immediately.
64 fn done(&self, _result: &TestResult, _aborted: bool) {}
65
66 /// The path of the test file.
67 fn path(&self) -> &Path;
68
69 /// The revision, usually an empty string.
70 fn revision(&self) -> &str;
71}
72
73/// Report a summary at the end of a test run.
74pub trait Summary {
75 /// A test has finished, handle the result.
76 fn test_failure(&mut self, _status: &dyn TestStatus, _errors: &Errors) {}
77}
78
79/// Report no summary
80impl Summary for () {}
81
82/// Emit nothing
83impl StatusEmitter for () {
84 fn register_test(&self, path: PathBuf) -> Box<dyn TestStatus> {
85 Box::new(SilentStatus {
86 path,
87 revision: String::new(),
88 })
89 }
90
91 fn finalize(
92 &self,
93 _failed: usize,
94 _succeeded: usize,
95 _ignored: usize,
96 _filtered: usize,
97 _aborted: bool,
98 ) -> Box<dyn Summary> {
99 Box::new(())
100 }
101}
102
103/// When you need a dummy value that doesn't actually print anything
104pub struct SilentStatus {
105 /// Forwarded to `TestStatus::revision`
106 pub revision: String,
107 /// Forwarded to `TestStatus::path`
108 pub path: PathBuf,
109}
110
111impl TestStatus for SilentStatus {
112 fn for_revision(&self, revision: &str, _style: RevisionStyle) -> Box<dyn TestStatus> {
113 Box::new(SilentStatus {
114 revision: revision.into(),
115 path: self.path.clone(),
116 })
117 }
118
119 fn for_path(&self, path: &Path) -> Box<dyn TestStatus> {
120 Box::new(SilentStatus {
121 revision: self.revision.clone(),
122 path: path.to_path_buf(),
123 })
124 }
125
126 fn failed_test<'a>(
127 &'a self,
128 _cmd: &'a str,
129 _stderr: &'a [u8],
130 _stdout: &'a [u8],
131 ) -> Box<dyn Debug + 'a> {
132 Box::new(())
133 }
134
135 fn path(&self) -> &Path {
136 &self.path
137 }
138
139 fn revision(&self) -> &str {
140 &self.revision
141 }
142}
143
144impl<T: TestStatus, U: TestStatus> TestStatus for (T, U) {
145 fn done(&self, result: &TestResult, aborted: bool) {
146 self.0.done(result, aborted);
147 self.1.done(result, aborted);
148 }
149
150 fn failed_test<'a>(
151 &'a self,
152 cmd: &'a str,
153 stderr: &'a [u8],
154 stdout: &'a [u8],
155 ) -> Box<dyn Debug + 'a> {
156 Box::new((
157 self.0.failed_test(cmd, stderr, stdout),
158 self.1.failed_test(cmd, stderr, stdout),
159 ))
160 }
161
162 fn path(&self) -> &Path {
163 let path = self.0.path();
164 assert_eq!(path, self.1.path());
165 path
166 }
167
168 fn revision(&self) -> &str {
169 let rev = self.0.revision();
170 assert_eq!(rev, self.1.revision());
171 rev
172 }
173
174 fn for_revision(&self, revision: &str, style: RevisionStyle) -> Box<dyn TestStatus> {
175 Box::new((
176 self.0.for_revision(revision, style),
177 self.1.for_revision(revision, style),
178 ))
179 }
180
181 fn for_path(&self, path: &Path) -> Box<dyn TestStatus> {
182 Box::new((self.0.for_path(path), self.1.for_path(path)))
183 }
184}
185
186impl<T: StatusEmitter, U: StatusEmitter> StatusEmitter for (T, U) {
187 fn register_test(&self, path: PathBuf) -> Box<dyn TestStatus> {
188 Box::new((
189 self.0.register_test(path.clone()),
190 self.1.register_test(path),
191 ))
192 }
193
194 fn finalize(
195 &self,
196 failures: usize,
197 succeeded: usize,
198 ignored: usize,
199 filtered: usize,
200 aborted: bool,
201 ) -> Box<dyn Summary> {
202 Box::new((
203 self.1
204 .finalize(failed:failures, succeeded, ignored, filtered, aborted),
205 self.0
206 .finalize(failed:failures, succeeded, ignored, filtered, aborted),
207 ))
208 }
209}
210
211/// Forwards directly to `T`, exists only so that tuples can be used with `cfg` to filter
212/// out individual fields
213impl<T: StatusEmitter> StatusEmitter for (T,) {
214 fn register_test(&self, path: PathBuf) -> Box<dyn TestStatus> {
215 self.0.register_test(path.clone())
216 }
217
218 fn finalize(
219 &self,
220 failures: usize,
221 succeeded: usize,
222 ignored: usize,
223 filtered: usize,
224 aborted: bool,
225 ) -> Box<dyn Summary> {
226 self.0
227 .finalize(failed:failures, succeeded, ignored, filtered, aborted)
228 }
229}
230
231impl<T: TestStatus + ?Sized> TestStatus for Box<T> {
232 fn done(&self, result: &TestResult, aborted: bool) {
233 (**self).done(result, aborted);
234 }
235
236 fn path(&self) -> &Path {
237 (**self).path()
238 }
239
240 fn revision(&self) -> &str {
241 (**self).revision()
242 }
243
244 fn for_revision(&self, revision: &str, style: RevisionStyle) -> Box<dyn TestStatus> {
245 (**self).for_revision(revision, style)
246 }
247
248 fn for_path(&self, path: &Path) -> Box<dyn TestStatus> {
249 (**self).for_path(path)
250 }
251
252 fn failed_test<'a>(
253 &'a self,
254 cmd: &'a str,
255 stderr: &'a [u8],
256 stdout: &'a [u8],
257 ) -> Box<dyn Debug + 'a> {
258 (**self).failed_test(cmd, stderr, stdout)
259 }
260}
261
262impl<T: StatusEmitter + ?Sized> StatusEmitter for Box<T> {
263 fn register_test(&self, path: PathBuf) -> Box<dyn TestStatus> {
264 (**self).register_test(path)
265 }
266
267 fn finalize(
268 &self,
269 failures: usize,
270 succeeded: usize,
271 ignored: usize,
272 filtered: usize,
273 aborted: bool,
274 ) -> Box<dyn Summary> {
275 (**self).finalize(failed:failures, succeeded, ignored, filtered, aborted)
276 }
277}
278
279impl Summary for (Box<dyn Summary>, Box<dyn Summary>) {
280 fn test_failure(&mut self, status: &dyn TestStatus, errors: &Errors) {
281 self.0.test_failure(status, errors);
282 self.1.test_failure(status, errors);
283 }
284}
285