lean_ctx/core/patterns/
test.rs1pub 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}