1use super::TestOutputDisplay;
9use crate::reporter::events::{CancelReason, ExecutionResult};
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: ExecutionResult,
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 execution_result = ExecutionResult::Pass;
217 let actual = status_levels.compute_output_on_test_finished(
218 display,
219 cancel_status,
220 test_status_level,
221 test_final_status_level,
222 execution_result,
223 );
224
225 assert!(!actual.write_status_line);
226 }
227
228 #[proptest(cases = 64)]
229 fn on_test_finished_write_status_line(
230 display: TestOutputDisplay,
231 cancel_status: Option<CancelReason>,
232 #[filter(StatusLevel::Pass >= #test_status_level)] test_status_level: StatusLevel,
233 test_final_status_level: FinalStatusLevel,
234 ) {
235 let status_levels = StatusLevels {
236 status_level: StatusLevel::Pass,
237 final_status_level: FinalStatusLevel::Fail,
238 };
239
240 let execution_result = ExecutionResult::Pass;
241 let actual = status_levels.compute_output_on_test_finished(
242 display,
243 cancel_status,
244 test_status_level,
245 test_final_status_level,
246 execution_result,
247 );
248 assert!(actual.write_status_line);
249 }
250
251 #[proptest(cases = 64)]
252 fn on_test_finished_with_interrupt(
253 display: TestOutputDisplay,
255 test_status_level: StatusLevel,
259 test_final_status_level: FinalStatusLevel,
260 ) {
261 let status_levels = StatusLevels {
262 status_level: StatusLevel::Pass,
263 final_status_level: FinalStatusLevel::Fail,
264 };
265
266 let execution_result = ExecutionResult::Pass;
267 let actual = status_levels.compute_output_on_test_finished(
268 display,
269 Some(CancelReason::Interrupt),
270 test_status_level,
271 test_final_status_level,
272 execution_result,
273 );
274 assert!(!actual.show_immediate);
275 assert_eq!(actual.store_final, OutputStoreFinal::No);
276 }
277
278 #[proptest(cases = 64)]
279 fn on_test_finished_dont_show_immediate(
280 #[filter(!#display.is_immediate())] display: TestOutputDisplay,
281 cancel_status: Option<CancelReason>,
282 test_status_level: StatusLevel,
284 test_final_status_level: FinalStatusLevel,
285 ) {
286 let status_levels = StatusLevels {
287 status_level: StatusLevel::Pass,
288 final_status_level: FinalStatusLevel::Fail,
289 };
290
291 let execution_result = ExecutionResult::Pass;
292 let actual = status_levels.compute_output_on_test_finished(
293 display,
294 cancel_status,
295 test_status_level,
296 test_final_status_level,
297 execution_result,
298 );
299 assert!(!actual.show_immediate);
300 }
301
302 #[proptest(cases = 64)]
303 fn on_test_finished_show_immediate(
304 #[filter(#display.is_immediate())] display: TestOutputDisplay,
305 #[filter(#cancel_status <= Some(CancelReason::Signal))] cancel_status: Option<CancelReason>,
306 test_status_level: StatusLevel,
308 test_final_status_level: FinalStatusLevel,
309 ) {
310 let status_levels = StatusLevels {
311 status_level: StatusLevel::Pass,
312 final_status_level: FinalStatusLevel::Fail,
313 };
314
315 let execution_result = ExecutionResult::Pass;
316 let actual = status_levels.compute_output_on_test_finished(
317 display,
318 cancel_status,
319 test_status_level,
320 test_final_status_level,
321 execution_result,
322 );
323 assert!(actual.show_immediate);
324 }
325
326 #[proptest(cases = 64)]
329 fn on_test_finished_dont_store_final(
330 #[filter(!#display.is_final())] display: TestOutputDisplay,
331 cancel_status: Option<CancelReason>,
332 test_status_level: StatusLevel,
334 #[filter(FinalStatusLevel::Fail < #test_final_status_level)]
336 test_final_status_level: FinalStatusLevel,
337 ) {
338 let status_levels = StatusLevels {
339 status_level: StatusLevel::Pass,
340 final_status_level: FinalStatusLevel::Fail,
341 };
342
343 let execution_result = ExecutionResult::Pass;
344 let actual = status_levels.compute_output_on_test_finished(
345 display,
346 cancel_status,
347 test_status_level,
348 test_final_status_level,
349 execution_result,
350 );
351 assert_eq!(actual.store_final, OutputStoreFinal::No);
352 }
353
354 #[proptest(cases = 64)]
357 fn on_test_finished_store_final_1(
358 #[filter(#cancel_status <= Some(CancelReason::Signal))] cancel_status: Option<CancelReason>,
359 test_status_level: StatusLevel,
361 test_final_status_level: FinalStatusLevel,
362 ) {
363 let status_levels = StatusLevels {
364 status_level: StatusLevel::Pass,
365 final_status_level: FinalStatusLevel::Fail,
366 };
367
368 let execution_result = ExecutionResult::Pass;
369 let actual = status_levels.compute_output_on_test_finished(
370 TestOutputDisplay::Final,
371 cancel_status,
372 test_status_level,
373 test_final_status_level,
374 execution_result,
375 );
376 assert_eq!(
377 actual.store_final,
378 OutputStoreFinal::Yes {
379 display_output: true
380 }
381 );
382 }
383
384 #[proptest(cases = 64)]
387 fn on_test_finished_store_final_2(
388 #[filter(#cancel_status < Some(CancelReason::Signal))] cancel_status: Option<CancelReason>,
389 test_status_level: StatusLevel,
390 test_final_status_level: FinalStatusLevel,
391 ) {
392 let status_levels = StatusLevels {
393 status_level: StatusLevel::Pass,
394 final_status_level: FinalStatusLevel::Fail,
395 };
396
397 let execution_result = ExecutionResult::Pass;
398 let actual = status_levels.compute_output_on_test_finished(
399 TestOutputDisplay::ImmediateFinal,
400 cancel_status,
401 test_status_level,
402 test_final_status_level,
403 execution_result,
404 );
405 assert_eq!(
406 actual.store_final,
407 OutputStoreFinal::Yes {
408 display_output: true
409 }
410 );
411 }
412
413 #[proptest(cases = 64)]
416 fn on_test_finished_store_final_3(
417 test_status_level: StatusLevel,
418 test_final_status_level: FinalStatusLevel,
419 ) {
420 let status_levels = StatusLevels {
421 status_level: StatusLevel::Pass,
422 final_status_level: FinalStatusLevel::Fail,
423 };
424
425 let execution_result = ExecutionResult::Pass;
426 let actual = status_levels.compute_output_on_test_finished(
427 TestOutputDisplay::ImmediateFinal,
428 Some(CancelReason::Signal),
429 test_status_level,
430 test_final_status_level,
431 execution_result,
432 );
433 assert_eq!(
434 actual.store_final,
435 OutputStoreFinal::Yes {
436 display_output: false,
437 }
438 );
439 }
440
441 #[proptest(cases = 64)]
443 fn on_test_finished_store_final_4(
444 #[filter(!#display.is_final())] display: TestOutputDisplay,
445 #[filter(#cancel_status <= Some(CancelReason::Signal))] cancel_status: Option<CancelReason>,
446 test_status_level: StatusLevel,
448 #[filter(FinalStatusLevel::Fail >= #test_final_status_level)]
450 test_final_status_level: FinalStatusLevel,
451 ) {
452 let status_levels = StatusLevels {
453 status_level: StatusLevel::Pass,
454 final_status_level: FinalStatusLevel::Fail,
455 };
456
457 let execution_result = ExecutionResult::Pass;
458 let actual = status_levels.compute_output_on_test_finished(
459 display,
460 cancel_status,
461 test_status_level,
462 test_final_status_level,
463 execution_result,
464 );
465 assert_eq!(
466 actual.store_final,
467 OutputStoreFinal::Yes {
468 display_output: false,
469 }
470 );
471 }
472
473 #[test]
474 fn on_test_finished_terminated_by_nextest() {
475 use crate::reporter::events::{AbortStatus, FailureStatus};
476
477 let status_levels = StatusLevels {
478 status_level: StatusLevel::Pass,
479 final_status_level: FinalStatusLevel::Fail,
480 };
481
482 #[cfg(unix)]
484 {
485 let execution_result = ExecutionResult::Fail {
486 failure_status: FailureStatus::Abort(AbortStatus::UnixSignal(libc::SIGTERM)),
487 leaked: false,
488 };
489
490 let actual = status_levels.compute_output_on_test_finished(
491 TestOutputDisplay::ImmediateFinal,
492 Some(CancelReason::TestFailureImmediate),
493 StatusLevel::Fail,
494 FinalStatusLevel::Fail,
495 execution_result,
496 );
497
498 assert!(
499 !actual.show_immediate,
500 "should not show immediate for SIGTERM during TestFailureImmediate"
501 );
502 assert_eq!(
503 actual.store_final,
504 OutputStoreFinal::No,
505 "should not store final for SIGTERM during TestFailureImmediate"
506 );
507 }
508
509 #[cfg(windows)]
511 {
512 let execution_result = ExecutionResult::Fail {
513 failure_status: FailureStatus::Abort(AbortStatus::JobObject),
514 leaked: false,
515 };
516
517 let actual = status_levels.compute_output_on_test_finished(
518 TestOutputDisplay::ImmediateFinal,
519 Some(CancelReason::TestFailureImmediate),
520 StatusLevel::Fail,
521 FinalStatusLevel::Fail,
522 execution_result,
523 );
524
525 assert!(
526 !actual.show_immediate,
527 "should not show immediate for JobObject during TestFailureImmediate"
528 );
529 assert_eq!(
530 actual.store_final,
531 OutputStoreFinal::No,
532 "should not store final for JobObject during TestFailureImmediate"
533 );
534 }
535
536 let execution_result = ExecutionResult::Fail {
538 failure_status: FailureStatus::ExitCode(1),
539 leaked: false,
540 };
541
542 let actual = status_levels.compute_output_on_test_finished(
543 TestOutputDisplay::ImmediateFinal,
544 Some(CancelReason::TestFailureImmediate),
545 StatusLevel::Fail,
546 FinalStatusLevel::Fail,
547 execution_result,
548 );
549
550 assert!(
551 actual.show_immediate,
552 "should show immediate for natural failure during TestFailureImmediate"
553 );
554 assert_eq!(
555 actual.store_final,
556 OutputStoreFinal::Yes {
557 display_output: true
558 },
559 "should store final for natural failure"
560 );
561
562 #[cfg(unix)]
564 {
565 let execution_result = ExecutionResult::Fail {
566 failure_status: FailureStatus::Abort(AbortStatus::UnixSignal(libc::SIGTERM)),
567 leaked: false,
568 };
569
570 let actual = status_levels.compute_output_on_test_finished(
571 TestOutputDisplay::ImmediateFinal,
572 Some(CancelReason::Signal), StatusLevel::Fail,
574 FinalStatusLevel::Fail,
575 execution_result,
576 );
577
578 assert!(
579 actual.show_immediate,
580 "should show immediate for user-initiated SIGTERM"
581 );
582 assert_eq!(
583 actual.store_final,
584 OutputStoreFinal::Yes {
585 display_output: false
586 },
587 "should store but not display final"
588 );
589 }
590 }
591}