1 | //! Various schemes for reporting messages during testing or after testing is done. |
2 | |
3 | use crate::{test_result::TestResult, Errors}; |
4 | |
5 | use std::{ |
6 | fmt::Debug, |
7 | panic::RefUnwindSafe, |
8 | path::{Path, PathBuf}, |
9 | }; |
10 | pub use text::*; |
11 | pub mod debug; |
12 | mod text; |
13 | #[cfg (feature = "gha" )] |
14 | pub use gha::*; |
15 | #[cfg (feature = "gha" )] |
16 | mod gha; |
17 | |
18 | /// A generic way to handle the output of this crate. |
19 | pub 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)] |
38 | pub 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. |
47 | pub 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. |
74 | pub 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 |
80 | impl Summary for () {} |
81 | |
82 | /// Emit nothing |
83 | impl 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 |
104 | pub struct SilentStatus { |
105 | /// Forwarded to `TestStatus::revision` |
106 | pub revision: String, |
107 | /// Forwarded to `TestStatus::path` |
108 | pub path: PathBuf, |
109 | } |
110 | |
111 | impl 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 | |
144 | impl<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 | |
186 | impl<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 |
213 | impl<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 | |
231 | impl<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 | |
262 | impl<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 | |
279 | impl 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 | |