1use crate::model::{AttemptRow, TestStatus};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum FailureClass {
5 DeterministicPass,
6 DeterministicFail,
7 Flaky, Unstable, Error, Skipped,
11}
12
13pub fn classify_attempts(attempts: &[AttemptRow]) -> FailureClass {
14 use TestStatus::*;
15
16 if attempts.is_empty() {
17 return FailureClass::Error;
18 }
19
20 if attempts.len() == 1 {
21 if let Skipped = attempts[0].status {
22 return FailureClass::Skipped;
23 }
24 }
25
26 let has_error = attempts.iter().any(|a| matches!(a.status, Error));
27 if has_error {
28 return FailureClass::Error;
29 }
30
31 let statuses: Vec<TestStatus> = attempts.iter().map(|a| a.status.clone()).collect();
32
33 let any_fail = statuses.iter().any(|s| matches!(s, Fail));
34 let any_pass = statuses.iter().any(|s| matches!(s, Pass | Warn | Flaky));
35 if any_fail && any_pass {
41 let first_fail_idx = statuses.iter().position(|s| matches!(s, Fail));
46 let first_pass_idx = statuses.iter().position(|s| matches!(s, Pass)); if let (Some(fail_i), Some(pass_i)) = (first_fail_idx, first_pass_idx) {
49 if fail_i < pass_i {
50 let last = statuses.last().unwrap();
53 if matches!(last, Pass) {
54 return FailureClass::Flaky;
55 }
56 }
57 }
58 return FailureClass::Unstable;
59 }
60
61 if any_fail {
62 return FailureClass::DeterministicFail;
63 }
64
65 FailureClass::DeterministicPass
67}