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