1use super::Tolerance;
2
3#[derive(Debug)]
5pub enum RegressionOutcome {
6 Pass {
8 baseline_total: usize,
9 current_total: usize,
10 },
11 Exceeded {
13 baseline_total: usize,
14 current_total: usize,
15 tolerance: Tolerance,
16 type_deltas: Vec<(&'static str, isize)>,
18 },
19 Skipped { reason: &'static str },
21}
22
23impl RegressionOutcome {
24 #[must_use]
26 pub const fn is_failure(&self) -> bool {
27 matches!(self, Self::Exceeded { .. })
28 }
29
30 #[must_use]
32 #[cfg(test)]
33 pub fn to_json(&self) -> serde_json::Value {
34 match self {
35 Self::Pass {
36 baseline_total,
37 current_total,
38 } => serde_json::json!({
39 "status": "pass",
40 "baseline_total": baseline_total,
41 "current_total": current_total,
42 "delta": *current_total as isize - *baseline_total as isize,
43 "exceeded": false,
44 }),
45 Self::Exceeded {
46 baseline_total,
47 current_total,
48 tolerance,
49 ..
50 } => {
51 let (tolerance_value, tolerance_kind) = match tolerance {
52 Tolerance::Percentage(pct) => (*pct, "percentage"),
53 Tolerance::Absolute(abs) => (*abs as f64, "absolute"),
54 };
55 serde_json::json!({
56 "status": "exceeded",
57 "baseline_total": baseline_total,
58 "current_total": current_total,
59 "delta": *current_total as isize - *baseline_total as isize,
60 "tolerance": tolerance_value,
61 "tolerance_kind": tolerance_kind,
62 "exceeded": true,
63 })
64 }
65 Self::Skipped { reason } => serde_json::json!({
66 "status": "skipped",
67 "reason": reason,
68 "exceeded": false,
69 }),
70 }
71 }
72}
73
74pub fn print_regression_outcome(outcome: &RegressionOutcome) {
76 match outcome {
77 RegressionOutcome::Pass {
78 baseline_total,
79 current_total,
80 } => {
81 let delta = *current_total as isize - *baseline_total as isize;
82 let sign = if delta >= 0 { "+" } else { "" };
83 eprintln!(
84 "Regression check passed: {current_total} issues (baseline: {baseline_total}, \
85 delta: {sign}{delta})"
86 );
87 }
88 RegressionOutcome::Exceeded {
89 baseline_total,
90 current_total,
91 tolerance,
92 type_deltas,
93 } => {
94 let delta = *current_total as isize - *baseline_total as isize;
95 let tol_str = match tolerance {
96 Tolerance::Percentage(pct) => format!("{pct}%"),
97 Tolerance::Absolute(abs) => format!("{abs}"),
98 };
99 eprintln!(
100 "Regression detected: {current_total} issues (baseline: {baseline_total}, \
101 delta: +{delta}, tolerance: {tol_str})"
102 );
103 for (name, d) in type_deltas {
104 let sign = if *d > 0 { "+" } else { "" };
105 eprintln!(" {name}: {sign}{d}");
106 }
107 }
108 RegressionOutcome::Skipped { .. } => {}
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn pass_outcome_json() {
118 let outcome = RegressionOutcome::Pass {
119 baseline_total: 10,
120 current_total: 10,
121 };
122 let json = outcome.to_json();
123 assert_eq!(json["status"], "pass");
124 assert_eq!(json["exceeded"], false);
125 assert_eq!(json["delta"], 0);
126 }
127
128 #[test]
129 fn exceeded_outcome_json() {
130 let outcome = RegressionOutcome::Exceeded {
131 baseline_total: 10,
132 current_total: 15,
133 tolerance: Tolerance::Percentage(2.0),
134 type_deltas: vec![("unused_files", 5)],
135 };
136 let json = outcome.to_json();
137 assert_eq!(json["status"], "exceeded");
138 assert_eq!(json["exceeded"], true);
139 assert_eq!(json["delta"], 5);
140 assert_eq!(json["tolerance_kind"], "percentage");
141 }
142
143 #[test]
144 fn skipped_outcome_json() {
145 let outcome = RegressionOutcome::Skipped {
146 reason: "test reason",
147 };
148 let json = outcome.to_json();
149 assert_eq!(json["status"], "skipped");
150 assert_eq!(json["exceeded"], false);
151 }
152
153 #[test]
154 fn regression_outcome_is_failure() {
155 let pass = RegressionOutcome::Pass {
156 baseline_total: 10,
157 current_total: 10,
158 };
159 assert!(!pass.is_failure());
160
161 let exceeded = RegressionOutcome::Exceeded {
162 baseline_total: 10,
163 current_total: 15,
164 tolerance: Tolerance::Absolute(2),
165 type_deltas: vec![],
166 };
167 assert!(exceeded.is_failure());
168
169 let skipped = RegressionOutcome::Skipped { reason: "test" };
170 assert!(!skipped.is_failure());
171 }
172
173 #[test]
174 fn exceeded_outcome_json_absolute() {
175 let outcome = RegressionOutcome::Exceeded {
176 baseline_total: 10,
177 current_total: 15,
178 tolerance: Tolerance::Absolute(2),
179 type_deltas: vec![("unused_files", 5)],
180 };
181 let json = outcome.to_json();
182 assert_eq!(json["status"], "exceeded");
183 assert_eq!(json["tolerance_kind"], "absolute");
184 assert_eq!(json["tolerance"], 2.0);
185 assert_eq!(json["delta"], 5);
186 }
187
188 #[test]
189 fn pass_outcome_json_with_improvement() {
190 let outcome = RegressionOutcome::Pass {
191 baseline_total: 10,
192 current_total: 5,
193 };
194 let json = outcome.to_json();
195 assert_eq!(json["status"], "pass");
196 assert_eq!(json["delta"], -5);
197 assert_eq!(json["exceeded"], false);
198 }
199
200 #[test]
201 fn print_pass_outcome_does_not_panic() {
202 let outcome = RegressionOutcome::Pass {
203 baseline_total: 10,
204 current_total: 8,
205 };
206 print_regression_outcome(&outcome);
207 }
208
209 #[test]
210 fn print_exceeded_outcome_does_not_panic() {
211 let outcome = RegressionOutcome::Exceeded {
212 baseline_total: 10,
213 current_total: 15,
214 tolerance: Tolerance::Percentage(2.0),
215 type_deltas: vec![("unused_files", 5), ("unused_exports", -2)],
216 };
217 print_regression_outcome(&outcome);
218 }
219
220 #[test]
221 fn print_exceeded_outcome_absolute_does_not_panic() {
222 let outcome = RegressionOutcome::Exceeded {
223 baseline_total: 10,
224 current_total: 15,
225 tolerance: Tolerance::Absolute(2),
226 type_deltas: vec![("unused_files", 3), ("unresolved_imports", 2)],
227 };
228 print_regression_outcome(&outcome);
229 }
230
231 #[test]
232 fn print_skipped_outcome_does_not_panic() {
233 let outcome = RegressionOutcome::Skipped {
234 reason: "test reason",
235 };
236 print_regression_outcome(&outcome);
237 }
238
239 #[test]
240 fn print_exceeded_with_empty_deltas_does_not_panic() {
241 let outcome = RegressionOutcome::Exceeded {
242 baseline_total: 10,
243 current_total: 15,
244 tolerance: Tolerance::Absolute(0),
245 type_deltas: vec![],
246 };
247 print_regression_outcome(&outcome);
248 }
249}