Skip to main content

lean_ctx/core/patterns/
test.rs

1pub fn compress(output: &str) -> Option<String> {
2    if let Some(r) = try_pytest(output) {
3        return Some(r);
4    }
5    if let Some(r) = try_vitest(output) {
6        return Some(r);
7    }
8    if let Some(r) = try_jest(output) {
9        return Some(r);
10    }
11    if let Some(r) = try_go_test(output) {
12        return Some(r);
13    }
14    if let Some(r) = try_rspec(output) {
15        return Some(r);
16    }
17    None
18}
19
20fn try_pytest(output: &str) -> Option<String> {
21    if !output.contains("test session starts") && !output.contains("pytest") {
22        return None;
23    }
24
25    let mut passed = 0u32;
26    let mut failed = 0u32;
27    let mut skipped = 0u32;
28    let mut time = String::new();
29    let mut failures = Vec::new();
30
31    for line in output.lines() {
32        let trimmed = line.trim();
33        if (trimmed.contains("passed") || trimmed.contains("failed") || trimmed.contains("error"))
34            && (trimmed.starts_with('=') || trimmed.starts_with('-'))
35        {
36            for word in trimmed.split_whitespace() {
37                if let Some(n) = word.strip_suffix("passed").or_else(|| {
38                    if trimmed.contains(" passed") {
39                        word.parse::<u32>().ok().map(|_| word)
40                    } else {
41                        None
42                    }
43                }) {
44                    if let Ok(v) = n.trim().parse::<u32>() {
45                        passed = v;
46                    }
47                }
48            }
49            if let Some(pos) = trimmed.find(" passed") {
50                let before = &trimmed[..pos];
51                if let Some(num_str) = before.split_whitespace().last() {
52                    if let Ok(v) = num_str.parse::<u32>() {
53                        passed = v;
54                    }
55                }
56            }
57            if let Some(pos) = trimmed.find(" failed") {
58                let before = &trimmed[..pos];
59                if let Some(num_str) = before.split_whitespace().last() {
60                    if let Ok(v) = num_str.parse::<u32>() {
61                        failed = v;
62                    }
63                }
64            }
65            if let Some(pos) = trimmed.find(" skipped") {
66                let before = &trimmed[..pos];
67                if let Some(num_str) = before.split_whitespace().last() {
68                    if let Ok(v) = num_str.parse::<u32>() {
69                        skipped = v;
70                    }
71                }
72            }
73            if let Some(pos) = trimmed.find(" in ") {
74                time = trimmed[pos + 4..].trim_end_matches('=').trim().to_string();
75            }
76        }
77        if trimmed.starts_with("FAILED ") {
78            failures.push(
79                trimmed
80                    .strip_prefix("FAILED ")
81                    .unwrap_or(trimmed)
82                    .to_string(),
83            );
84        }
85    }
86
87    if passed == 0 && failed == 0 {
88        return None;
89    }
90
91    let mut result = format!("pytest: {passed} passed");
92    if failed > 0 {
93        result.push_str(&format!(", {failed} failed"));
94    }
95    if skipped > 0 {
96        result.push_str(&format!(", {skipped} skipped"));
97    }
98    if !time.is_empty() {
99        result.push_str(&format!(" ({time})"));
100    }
101
102    for f in failures.iter().take(5) {
103        result.push_str(&format!("\n  FAIL: {f}"));
104    }
105
106    Some(result)
107}
108
109fn try_jest(output: &str) -> Option<String> {
110    if !output.contains("Tests:") && !output.contains("Test Suites:") {
111        return None;
112    }
113
114    let mut suites_line = String::new();
115    let mut tests_line = String::new();
116    let mut time_line = String::new();
117
118    for line in output.lines() {
119        let trimmed = line.trim();
120        if trimmed.starts_with("Test Suites:") {
121            suites_line = trimmed.to_string();
122        } else if trimmed.starts_with("Tests:") {
123            tests_line = trimmed.to_string();
124        } else if trimmed.starts_with("Time:") {
125            time_line = trimmed.to_string();
126        }
127    }
128
129    if tests_line.is_empty() {
130        return None;
131    }
132
133    let mut result = String::new();
134    if !suites_line.is_empty() {
135        result.push_str(&suites_line);
136        result.push('\n');
137    }
138    result.push_str(&tests_line);
139    if !time_line.is_empty() {
140        result.push('\n');
141        result.push_str(&time_line);
142    }
143
144    Some(result)
145}
146
147fn try_go_test(output: &str) -> Option<String> {
148    if !output.contains("--- PASS") && !output.contains("--- FAIL") && !output.contains("PASS\n") {
149        return None;
150    }
151
152    let mut passed = 0u32;
153    let mut failed = 0u32;
154    let mut failures = Vec::new();
155    let mut packages = Vec::new();
156
157    for line in output.lines() {
158        let trimmed = line.trim();
159        if trimmed.starts_with("--- PASS:") {
160            passed += 1;
161        } else if trimmed.starts_with("--- FAIL:") {
162            failed += 1;
163            failures.push(
164                trimmed
165                    .strip_prefix("--- FAIL: ")
166                    .unwrap_or(trimmed)
167                    .to_string(),
168            );
169        } else if trimmed.starts_with("ok ") || trimmed.starts_with("FAIL\t") {
170            packages.push(trimmed.to_string());
171        }
172    }
173
174    if passed == 0 && failed == 0 {
175        return None;
176    }
177
178    let mut result = format!("go test: {passed} passed");
179    if failed > 0 {
180        result.push_str(&format!(", {failed} failed"));
181    }
182
183    for pkg in &packages {
184        result.push_str(&format!("\n  {pkg}"));
185    }
186
187    for f in failures.iter().take(5) {
188        result.push_str(&format!("\n  FAIL: {f}"));
189    }
190
191    Some(result)
192}
193
194fn try_vitest(output: &str) -> Option<String> {
195    if !output.contains("PASS") && !output.contains("FAIL") {
196        return None;
197    }
198    if !output.contains(" Tests ") && !output.contains("Test Files") {
199        return None;
200    }
201
202    let mut test_files_line = String::new();
203    let mut tests_line = String::new();
204    let mut duration_line = String::new();
205    let mut failures = Vec::new();
206
207    for line in output.lines() {
208        let trimmed = line.trim();
209        let plain = strip_ansi(trimmed);
210        if plain.contains("Test Files") {
211            test_files_line = plain.clone();
212        } else if plain.starts_with("Tests") && plain.contains("passed") {
213            tests_line = plain.clone();
214        } else if plain.contains("Duration") || plain.contains("Time") {
215            if plain.contains("ms") || plain.contains("s") {
216                duration_line = plain.clone();
217            }
218        } else if plain.contains("FAIL")
219            && (plain.contains(".test.") || plain.contains(".spec.") || plain.contains("_test."))
220        {
221            failures.push(plain.clone());
222        }
223    }
224
225    if tests_line.is_empty() && test_files_line.is_empty() {
226        return None;
227    }
228
229    let mut result = String::new();
230    if !test_files_line.is_empty() {
231        result.push_str(&test_files_line);
232    }
233    if !tests_line.is_empty() {
234        if !result.is_empty() {
235            result.push('\n');
236        }
237        result.push_str(&tests_line);
238    }
239    if !duration_line.is_empty() {
240        result.push('\n');
241        result.push_str(&duration_line);
242    }
243
244    for f in failures.iter().take(10) {
245        result.push_str(&format!("\n  FAIL: {f}"));
246    }
247
248    Some(result)
249}
250
251fn strip_ansi(s: &str) -> String {
252    let mut result = String::with_capacity(s.len());
253    let mut in_escape = false;
254    for c in s.chars() {
255        if c == '\x1b' {
256            in_escape = true;
257        } else if in_escape {
258            if c.is_ascii_alphabetic() {
259                in_escape = false;
260            }
261        } else {
262            result.push(c);
263        }
264    }
265    result
266}
267
268fn try_rspec(output: &str) -> Option<String> {
269    if !output.contains("examples") || !output.contains("failures") {
270        return None;
271    }
272
273    for line in output.lines().rev() {
274        let trimmed = line.trim();
275        if trimmed.contains("example") && trimmed.contains("failure") {
276            return Some(format!("rspec: {trimmed}"));
277        }
278    }
279
280    None
281}