1use super::TestOutputDisplay;
9use crate::reporter::events::{CancelReason, ExecutionResultDescription};
10use serde::Deserialize;
11
12#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
17#[cfg_attr(test, derive(test_strategy::Arbitrary))]
18#[serde(rename_all = "kebab-case")]
19#[non_exhaustive]
20pub enum StatusLevel {
21 None,
23
24 Fail,
26
27 Retry,
29
30 Slow,
32
33 Leak,
35
36 Pass,
38
39 Skip,
41
42 All,
44}
45
46#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
54#[cfg_attr(test, derive(test_strategy::Arbitrary))]
55#[serde(rename_all = "kebab-case")]
56#[non_exhaustive]
57pub enum FinalStatusLevel {
58 None,
60
61 Fail,
63
64 #[serde(alias = "retry")]
66 Flaky,
67
68 Slow,
70
71 Skip,
73
74 Leak,
76
77 Pass,
79
80 All,
82}
83
84pub(crate) struct StatusLevels {
85 pub(crate) status_level: StatusLevel,
86 pub(crate) final_status_level: FinalStatusLevel,
87}
88
89impl StatusLevels {
90 pub(super) fn compute_output_on_test_finished(
91 &self,
92 display: TestOutputDisplay,
93 cancel_status: Option<CancelReason>,
94 test_status_level: StatusLevel,
95 test_final_status_level: FinalStatusLevel,
96 execution_result: &ExecutionResultDescription,
97 ) -> OutputOnTestFinished {
98 let write_status_line = self.status_level >= test_status_level;
99
100 let is_immediate = display.is_immediate();
101 let is_final = display.is_final() || self.final_status_level >= test_final_status_level;
104
105 let terminated_by_nextest = cancel_status == Some(CancelReason::TestFailureImmediate)
109 && execution_result.is_termination_failure();
110
111 let show_immediate =
147 is_immediate && cancel_status <= Some(CancelReason::Signal) && !terminated_by_nextest;
148
149 let store_final = if cancel_status == Some(CancelReason::Interrupt) || terminated_by_nextest
150 {
151 OutputStoreFinal::No
153 } else if is_final && cancel_status < Some(CancelReason::Signal)
154 || !is_immediate && is_final && cancel_status == Some(CancelReason::Signal)
155 {
156 OutputStoreFinal::Yes {
157 display_output: display.is_final(),
158 }
159 } else if is_immediate && is_final && cancel_status == Some(CancelReason::Signal) {
160 OutputStoreFinal::Yes {
163 display_output: false,
164 }
165 } else {
166 OutputStoreFinal::No
167 };
168
169 OutputOnTestFinished {
170 write_status_line,
171 show_immediate,
172 store_final,
173 }
174 }
175}
176
177#[derive(Debug, PartialEq, Eq)]
178pub(super) struct OutputOnTestFinished {
179 pub(super) write_status_line: bool,
180 pub(super) show_immediate: bool,
181 pub(super) store_final: OutputStoreFinal,
182}
183
184#[derive(Debug, PartialEq, Eq)]
185pub(super) enum OutputStoreFinal {
186 No,
188
189 Yes { display_output: bool },
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use test_strategy::proptest;
198
199 #[proptest(cases = 64)]
205 fn on_test_finished_dont_write_status_line(
206 display: TestOutputDisplay,
207 cancel_status: Option<CancelReason>,
208 #[filter(StatusLevel::Pass < #test_status_level)] test_status_level: StatusLevel,
209 test_final_status_level: FinalStatusLevel,
210 ) {
211 let status_levels = StatusLevels {
212 status_level: StatusLevel::Pass,
213 final_status_level: FinalStatusLevel::Fail,
214 };
215
216 let actual = status_levels.compute_output_on_test_finished(
217 display,
218 cancel_status,
219 test_status_level,
220 test_final_status_level,
221 &ExecutionResultDescription::Pass,
222 );
223
224 assert!(!actual.write_status_line);
225 }
226
227 #[proptest(cases = 64)]
228 fn on_test_finished_write_status_line(
229 display: TestOutputDisplay,
230 cancel_status: Option<CancelReason>,
231 #[filter(StatusLevel::Pass >= #test_status_level)] test_status_level: StatusLevel,
232 test_final_status_level: FinalStatusLevel,
233 ) {
234 let status_levels = StatusLevels {
235 status_level: StatusLevel::Pass,
236 final_status_level: FinalStatusLevel::Fail,
237 };
238
239 let actual = status_levels.compute_output_on_test_finished(
240 display,
241 cancel_status,
242 test_status_level,
243 test_final_status_level,
244 &ExecutionResultDescription::Pass,
245 );
246 assert!(actual.write_status_line);
247 }
248
249 #[proptest(cases = 64)]
250 fn on_test_finished_with_interrupt(
251 display: TestOutputDisplay,
253 test_status_level: StatusLevel,
257 test_final_status_level: FinalStatusLevel,
258 ) {
259 let status_levels = StatusLevels {
260 status_level: StatusLevel::Pass,
261 final_status_level: FinalStatusLevel::Fail,
262 };
263
264 let actual = status_levels.compute_output_on_test_finished(
265 display,
266 Some(CancelReason::Interrupt),
267 test_status_level,
268 test_final_status_level,
269 &ExecutionResultDescription::Pass,
270 );
271 assert!(!actual.show_immediate);
272 assert_eq!(actual.store_final, OutputStoreFinal::No);
273 }
274
275 #[proptest(cases = 64)]
276 fn on_test_finished_dont_show_immediate(
277 #[filter(!#display.is_immediate())] display: TestOutputDisplay,
278 cancel_status: Option<CancelReason>,
279 test_status_level: StatusLevel,
281 test_final_status_level: FinalStatusLevel,
282 ) {
283 let status_levels = StatusLevels {
284 status_level: StatusLevel::Pass,
285 final_status_level: FinalStatusLevel::Fail,
286 };
287
288 let actual = status_levels.compute_output_on_test_finished(
289 display,
290 cancel_status,
291 test_status_level,
292 test_final_status_level,
293 &ExecutionResultDescription::Pass,
294 );
295 assert!(!actual.show_immediate);
296 }
297
298 #[proptest(cases = 64)]
299 fn on_test_finished_show_immediate(
300 #[filter(#display.is_immediate())] display: TestOutputDisplay,
301 #[filter(#cancel_status <= Some(CancelReason::Signal))] cancel_status: Option<CancelReason>,
302 test_status_level: StatusLevel,
304 test_final_status_level: FinalStatusLevel,
305 ) {
306 let status_levels = StatusLevels {
307 status_level: StatusLevel::Pass,
308 final_status_level: FinalStatusLevel::Fail,
309 };
310
311 let actual = status_levels.compute_output_on_test_finished(
312 display,
313 cancel_status,
314 test_status_level,
315 test_final_status_level,
316 &ExecutionResultDescription::Pass,
317 );
318 assert!(actual.show_immediate);
319 }
320
321 #[proptest(cases = 64)]
324 fn on_test_finished_dont_store_final(
325 #[filter(!#display.is_final())] display: TestOutputDisplay,
326 cancel_status: Option<CancelReason>,
327 test_status_level: StatusLevel,
329 #[filter(FinalStatusLevel::Fail < #test_final_status_level)]
331 test_final_status_level: FinalStatusLevel,
332 ) {
333 let status_levels = StatusLevels {
334 status_level: StatusLevel::Pass,
335 final_status_level: FinalStatusLevel::Fail,
336 };
337
338 let actual = status_levels.compute_output_on_test_finished(
339 display,
340 cancel_status,
341 test_status_level,
342 test_final_status_level,
343 &ExecutionResultDescription::Pass,
344 );
345 assert_eq!(actual.store_final, OutputStoreFinal::No);
346 }
347
348 #[proptest(cases = 64)]
351 fn on_test_finished_store_final_1(
352 #[filter(#cancel_status <= Some(CancelReason::Signal))] cancel_status: Option<CancelReason>,
353 test_status_level: StatusLevel,
355 test_final_status_level: FinalStatusLevel,
356 ) {
357 let status_levels = StatusLevels {
358 status_level: StatusLevel::Pass,
359 final_status_level: FinalStatusLevel::Fail,
360 };
361
362 let actual = status_levels.compute_output_on_test_finished(
363 TestOutputDisplay::Final,
364 cancel_status,
365 test_status_level,
366 test_final_status_level,
367 &ExecutionResultDescription::Pass,
368 );
369 assert_eq!(
370 actual.store_final,
371 OutputStoreFinal::Yes {
372 display_output: true
373 }
374 );
375 }
376
377 #[proptest(cases = 64)]
380 fn on_test_finished_store_final_2(
381 #[filter(#cancel_status < Some(CancelReason::Signal))] cancel_status: Option<CancelReason>,
382 test_status_level: StatusLevel,
383 test_final_status_level: FinalStatusLevel,
384 ) {
385 let status_levels = StatusLevels {
386 status_level: StatusLevel::Pass,
387 final_status_level: FinalStatusLevel::Fail,
388 };
389
390 let actual = status_levels.compute_output_on_test_finished(
391 TestOutputDisplay::ImmediateFinal,
392 cancel_status,
393 test_status_level,
394 test_final_status_level,
395 &ExecutionResultDescription::Pass,
396 );
397 assert_eq!(
398 actual.store_final,
399 OutputStoreFinal::Yes {
400 display_output: true
401 }
402 );
403 }
404
405 #[proptest(cases = 64)]
408 fn on_test_finished_store_final_3(
409 test_status_level: StatusLevel,
410 test_final_status_level: FinalStatusLevel,
411 ) {
412 let status_levels = StatusLevels {
413 status_level: StatusLevel::Pass,
414 final_status_level: FinalStatusLevel::Fail,
415 };
416
417 let actual = status_levels.compute_output_on_test_finished(
418 TestOutputDisplay::ImmediateFinal,
419 Some(CancelReason::Signal),
420 test_status_level,
421 test_final_status_level,
422 &ExecutionResultDescription::Pass,
423 );
424 assert_eq!(
425 actual.store_final,
426 OutputStoreFinal::Yes {
427 display_output: false,
428 }
429 );
430 }
431
432 #[proptest(cases = 64)]
434 fn on_test_finished_store_final_4(
435 #[filter(!#display.is_final())] display: TestOutputDisplay,
436 #[filter(#cancel_status <= Some(CancelReason::Signal))] cancel_status: Option<CancelReason>,
437 test_status_level: StatusLevel,
439 #[filter(FinalStatusLevel::Fail >= #test_final_status_level)]
441 test_final_status_level: FinalStatusLevel,
442 ) {
443 let status_levels = StatusLevels {
444 status_level: StatusLevel::Pass,
445 final_status_level: FinalStatusLevel::Fail,
446 };
447
448 let actual = status_levels.compute_output_on_test_finished(
449 display,
450 cancel_status,
451 test_status_level,
452 test_final_status_level,
453 &ExecutionResultDescription::Pass,
454 );
455 assert_eq!(
456 actual.store_final,
457 OutputStoreFinal::Yes {
458 display_output: false,
459 }
460 );
461 }
462
463 #[test]
464 fn on_test_finished_terminated_by_nextest() {
465 use crate::reporter::events::{AbortDescription, FailureDescription, SIGTERM};
466
467 let status_levels = StatusLevels {
468 status_level: StatusLevel::Pass,
469 final_status_level: FinalStatusLevel::Fail,
470 };
471
472 {
474 let execution_result = ExecutionResultDescription::Fail {
475 failure: FailureDescription::Abort {
476 abort: AbortDescription::UnixSignal {
477 signal: SIGTERM,
478 name: Some("TERM".into()),
479 },
480 },
481 leaked: false,
482 };
483
484 let actual = status_levels.compute_output_on_test_finished(
485 TestOutputDisplay::ImmediateFinal,
486 Some(CancelReason::TestFailureImmediate),
487 StatusLevel::Fail,
488 FinalStatusLevel::Fail,
489 &execution_result,
490 );
491
492 assert!(
493 !actual.show_immediate,
494 "should not show immediate for SIGTERM during TestFailureImmediate"
495 );
496 assert_eq!(
497 actual.store_final,
498 OutputStoreFinal::No,
499 "should not store final for SIGTERM during TestFailureImmediate"
500 );
501 }
502
503 {
505 let execution_result = ExecutionResultDescription::Fail {
506 failure: FailureDescription::Abort {
507 abort: AbortDescription::WindowsJobObject,
508 },
509 leaked: false,
510 };
511
512 let actual = status_levels.compute_output_on_test_finished(
513 TestOutputDisplay::ImmediateFinal,
514 Some(CancelReason::TestFailureImmediate),
515 StatusLevel::Fail,
516 FinalStatusLevel::Fail,
517 &execution_result,
518 );
519
520 assert!(
521 !actual.show_immediate,
522 "should not show immediate for JobObject during TestFailureImmediate"
523 );
524 assert_eq!(
525 actual.store_final,
526 OutputStoreFinal::No,
527 "should not store final for JobObject during TestFailureImmediate"
528 );
529 }
530
531 let execution_result = ExecutionResultDescription::Fail {
533 failure: FailureDescription::ExitCode { code: 1 },
534 leaked: false,
535 };
536
537 let actual = status_levels.compute_output_on_test_finished(
538 TestOutputDisplay::ImmediateFinal,
539 Some(CancelReason::TestFailureImmediate),
540 StatusLevel::Fail,
541 FinalStatusLevel::Fail,
542 &execution_result,
543 );
544
545 assert!(
546 actual.show_immediate,
547 "should show immediate for natural failure during TestFailureImmediate"
548 );
549 assert_eq!(
550 actual.store_final,
551 OutputStoreFinal::Yes {
552 display_output: true
553 },
554 "should store final for natural failure"
555 );
556
557 {
559 let execution_result = ExecutionResultDescription::Fail {
560 failure: FailureDescription::Abort {
561 abort: AbortDescription::UnixSignal {
562 signal: SIGTERM,
563 name: Some("TERM".into()),
564 },
565 },
566 leaked: false,
567 };
568
569 let actual = status_levels.compute_output_on_test_finished(
570 TestOutputDisplay::ImmediateFinal,
571 Some(CancelReason::Signal), StatusLevel::Fail,
573 FinalStatusLevel::Fail,
574 &execution_result,
575 );
576
577 assert!(
578 actual.show_immediate,
579 "should show immediate for user-initiated SIGTERM"
580 );
581 assert_eq!(
582 actual.store_final,
583 OutputStoreFinal::Yes {
584 display_output: false
585 },
586 "should store but not display final"
587 );
588 }
589 }
590}