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