toolplane-python-client 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. toolplane/__init__.py +106 -0
  2. toolplane/common/__init__.py +93 -0
  3. toolplane/common/base_config.py +129 -0
  4. toolplane/common/base_connection_manager.py +171 -0
  5. toolplane/common/base_session_manager.py +321 -0
  6. toolplane/common/base_tool_manager.py +347 -0
  7. toolplane/common/constants.py +47 -0
  8. toolplane/common/utils.py +310 -0
  9. toolplane/core/__init__.py +67 -0
  10. toolplane/core/config.py +107 -0
  11. toolplane/core/connection.py +285 -0
  12. toolplane/core/errors.py +298 -0
  13. toolplane/core/machine.py +480 -0
  14. toolplane/core/request.py +775 -0
  15. toolplane/core/session.py +332 -0
  16. toolplane/core/session_context.py +514 -0
  17. toolplane/core/task.py +130 -0
  18. toolplane/core/tool.py +329 -0
  19. toolplane/http_core/__init__.py +37 -0
  20. toolplane/http_core/http_config.py +97 -0
  21. toolplane/http_core/http_connection.py +409 -0
  22. toolplane/http_core/http_machine.py +298 -0
  23. toolplane/http_core/http_request.py +748 -0
  24. toolplane/http_core/http_session.py +348 -0
  25. toolplane/http_core/http_session_context.py +491 -0
  26. toolplane/http_core/http_task.py +101 -0
  27. toolplane/http_core/http_tool.py +400 -0
  28. toolplane/interfaces/__init__.py +27 -0
  29. toolplane/interfaces/client_interface.py +122 -0
  30. toolplane/interfaces/connection_interface.py +193 -0
  31. toolplane/interfaces/event_interface.py +290 -0
  32. toolplane/interfaces/request_interface.py +439 -0
  33. toolplane/interfaces/session_interface.py +288 -0
  34. toolplane/interfaces/tool_interface.py +441 -0
  35. toolplane/proto/__init__.py +0 -0
  36. toolplane/proto/service_pb2.py +315 -0
  37. toolplane/proto/service_pb2_grpc.py +2240 -0
  38. toolplane/provider_cli.py +268 -0
  39. toolplane/provider_registry.py +77 -0
  40. toolplane/provider_runtime.py +302 -0
  41. toolplane/toolkits/__init__.py +0 -0
  42. toolplane/toolkits/standalone_tools/__init__.py +0 -0
  43. toolplane/toolkits/standalone_tools/create_directory.py +94 -0
  44. toolplane/toolkits/standalone_tools/create_file.py +124 -0
  45. toolplane/toolkits/standalone_tools/file_search.py +229 -0
  46. toolplane/toolkits/standalone_tools/grep_search.py +372 -0
  47. toolplane/toolkits/standalone_tools/launcher.py +146 -0
  48. toolplane/toolkits/standalone_tools/list_dir.py +395 -0
  49. toolplane/toolkits/standalone_tools/read_file.py +346 -0
  50. toolplane/toolkits/standalone_tools/replace_string_in_file.py +407 -0
  51. toolplane/toolkits/standalone_tools/run_tests.py +66 -0
  52. toolplane/toolkits/standalone_tools/semantic_search.py +485 -0
  53. toolplane/toolkits/standalone_tools/standalone_toolkit.py +979 -0
  54. toolplane/toolkits/standalone_tools/test_failure_analysis.py +618 -0
  55. toolplane/toolkits/standalone_tools/test_standalone_toolkit.py +517 -0
  56. toolplane/toolkits/swe/__init__.py +35 -0
  57. toolplane/toolkits/swe/create_directory.py +15 -0
  58. toolplane/toolkits/swe/create_file.py +15 -0
  59. toolplane/toolkits/swe/descriptions.py +273 -0
  60. toolplane/toolkits/swe/execute_bash.py +93 -0
  61. toolplane/toolkits/swe/file_editor.py +775 -0
  62. toolplane/toolkits/swe/file_search.py +16 -0
  63. toolplane/toolkits/swe/finish.py +50 -0
  64. toolplane/toolkits/swe/grep_search.py +19 -0
  65. toolplane/toolkits/swe/list_dir.py +407 -0
  66. toolplane/toolkits/swe/read_file.py +18 -0
  67. toolplane/toolkits/swe/replace_string_in_file.py +17 -0
  68. toolplane/toolkits/swe/search.py +260 -0
  69. toolplane/toolkits/swe/semantic_search.py +20 -0
  70. toolplane/toolkits/swe/str_replace_editor.py +647 -0
  71. toolplane/toolkits/swe/submit.py +29 -0
  72. toolplane/toolkits/swe/swe_toolkit.py +1296 -0
  73. toolplane/toolplane_client.py +686 -0
  74. toolplane/toolplane_http_client.py +681 -0
  75. toolplane/utils/__init__.py +3 -0
  76. toolplane/utils/schema.py +146 -0
  77. toolplane_python_client-0.1.0.dist-info/METADATA +543 -0
  78. toolplane_python_client-0.1.0.dist-info/RECORD +81 -0
  79. toolplane_python_client-0.1.0.dist-info/WHEEL +5 -0
  80. toolplane_python_client-0.1.0.dist-info/entry_points.txt +2 -0
  81. toolplane_python_client-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,618 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Description: Test failure analysis tool for examining and debugging test failures.
4
+
5
+ This tool analyzes test failures, provides detailed error analysis, and suggests
6
+ potential fixes based on common failure patterns.
7
+
8
+ Parameters:
9
+ test_output (string, optional): Path to test output file or direct test output.
10
+ test_framework (string, optional): Test framework used (pytest, unittest, jest, etc.).
11
+ verbose (boolean, optional): Show detailed analysis (default: False).
12
+ suggest_fixes (boolean, optional): Suggest potential fixes (default: True).
13
+ group_by_type (boolean, optional): Group failures by error type (default: True).
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import os
19
+ import re
20
+ import sys
21
+ from collections import defaultdict
22
+ from pathlib import Path
23
+ from typing import Any, Dict, List, Optional, Tuple
24
+
25
+
26
+ def analyze_test_failures(
27
+ test_output: str = None,
28
+ test_framework: str = None,
29
+ verbose: bool = False,
30
+ suggest_fixes: bool = True,
31
+ group_by_type: bool = True,
32
+ ) -> Dict[str, Any]:
33
+ """
34
+ Analyze test failures and provide detailed analysis.
35
+
36
+ Args:
37
+ test_output: Path to test output file or direct output
38
+ test_framework: Test framework used
39
+ verbose: Show detailed analysis
40
+ suggest_fixes: Suggest potential fixes
41
+ group_by_type: Group failures by error type
42
+
43
+ Returns:
44
+ Dictionary containing analysis results
45
+ """
46
+ # Get test output content
47
+ if test_output and os.path.exists(test_output):
48
+ with open(test_output, "r", encoding="utf-8", errors="ignore") as f:
49
+ output_content = f.read()
50
+ elif test_output:
51
+ output_content = test_output
52
+ else:
53
+ # Read from stdin
54
+ output_content = sys.stdin.read()
55
+
56
+ # Auto-detect test framework if not specified
57
+ if not test_framework:
58
+ test_framework = detect_test_framework(output_content)
59
+
60
+ # Parse failures based on framework
61
+ failures = parse_failures(output_content, test_framework)
62
+
63
+ # Analyze failures
64
+ analysis = analyze_failures(failures, suggest_fixes, group_by_type)
65
+
66
+ # Add metadata
67
+ analysis["metadata"] = {
68
+ "test_framework": test_framework,
69
+ "total_failures": len(failures),
70
+ "unique_error_types": len(set(f["error_type"] for f in failures)),
71
+ "verbose": verbose,
72
+ "suggest_fixes": suggest_fixes,
73
+ }
74
+
75
+ return analysis
76
+
77
+
78
+ def detect_test_framework(output: str) -> str:
79
+ """Auto-detect the test framework from output."""
80
+ output_lower = output.lower()
81
+
82
+ if "pytest" in output_lower or "::" in output:
83
+ return "pytest"
84
+ elif "unittest" in output_lower or "test_" in output:
85
+ return "unittest"
86
+ elif "jest" in output_lower or "describe(" in output:
87
+ return "jest"
88
+ elif "rspec" in output_lower or "describe " in output:
89
+ return "rspec"
90
+ elif "mocha" in output_lower:
91
+ return "mocha"
92
+ elif "phpunit" in output_lower:
93
+ return "phpunit"
94
+ elif "cargo test" in output_lower:
95
+ return "cargo"
96
+ elif "go test" in output_lower:
97
+ return "go"
98
+ else:
99
+ return "unknown"
100
+
101
+
102
+ def parse_failures(output: str, framework: str) -> List[Dict[str, Any]]:
103
+ """Parse test failures based on the framework."""
104
+ if framework == "pytest":
105
+ return parse_pytest_failures(output)
106
+ elif framework == "unittest":
107
+ return parse_unittest_failures(output)
108
+ elif framework == "jest":
109
+ return parse_jest_failures(output)
110
+ else:
111
+ return parse_generic_failures(output)
112
+
113
+
114
+ def parse_pytest_failures(output: str) -> List[Dict[str, Any]]:
115
+ """Parse pytest failure output."""
116
+ failures = []
117
+
118
+ # First, try to find formal FAILURES section
119
+ failure_section_match = re.search(
120
+ r"=+ FAILURES =+.*?(?=^=+|\Z)", output, re.MULTILINE | re.DOTALL
121
+ )
122
+
123
+ if failure_section_match:
124
+ failure_section = failure_section_match.group(0)
125
+
126
+ # Split individual failures
127
+ individual_failures = re.split(
128
+ r"^_+ (.+?) _+$", failure_section, flags=re.MULTILINE
129
+ )
130
+
131
+ for i in range(1, len(individual_failures), 2):
132
+ if i + 1 >= len(individual_failures):
133
+ break
134
+
135
+ test_name = individual_failures[i].strip()
136
+ failure_content = individual_failures[i + 1].strip()
137
+
138
+ # Extract file and line info
139
+ file_match = re.search(r"(\S+\.py):(\d+):", failure_content)
140
+ file_path = file_match.group(1) if file_match else None
141
+ line_number = int(file_match.group(2)) if file_match else None
142
+
143
+ # Extract error type and message
144
+ error_match = re.search(
145
+ r"(AssertionError|TypeError|ValueError|AttributeError|KeyError|IndexError|ImportError|NameError|RuntimeError|Exception)[:>]?\s*(.*)",
146
+ failure_content,
147
+ re.DOTALL,
148
+ )
149
+ error_type = error_match.group(1) if error_match else "Unknown"
150
+ error_message = error_match.group(2).strip() if error_match else ""
151
+
152
+ # Extract assertion details
153
+ assertion_match = re.search(r"assert (.+)", failure_content)
154
+ assertion = assertion_match.group(1) if assertion_match else None
155
+
156
+ # Extract traceback
157
+ traceback_lines = []
158
+ for line in failure_content.split("\n"):
159
+ if re.match(r'^\s+File "', line) or re.match(r"^\s+", line):
160
+ traceback_lines.append(line.strip())
161
+
162
+ failure = {
163
+ "test_name": test_name,
164
+ "file_path": file_path,
165
+ "line_number": line_number,
166
+ "error_type": error_type,
167
+ "error_message": error_message,
168
+ "assertion": assertion,
169
+ "traceback": traceback_lines,
170
+ "full_output": failure_content,
171
+ }
172
+
173
+ failures.append(failure)
174
+
175
+ # Also look for simple FAILED lines (common in pytest short output)
176
+ failed_lines = re.findall(r"FAILED (.+?) - (.+)", output)
177
+ for test_name, error_info in failed_lines:
178
+ # Extract error type from error_info
179
+ error_match = re.search(
180
+ r"(AssertionError|TypeError|ValueError|AttributeError|KeyError|IndexError|ImportError|NameError|RuntimeError|Exception)[:>]?\s*(.*)",
181
+ error_info,
182
+ )
183
+ error_type = error_match.group(1) if error_match else "Unknown"
184
+ error_message = error_match.group(2).strip() if error_match else error_info
185
+
186
+ failure = {
187
+ "test_name": test_name,
188
+ "file_path": None,
189
+ "line_number": None,
190
+ "error_type": error_type,
191
+ "error_message": error_message,
192
+ "assertion": None,
193
+ "traceback": [],
194
+ "full_output": f"FAILED {test_name} - {error_info}",
195
+ }
196
+
197
+ failures.append(failure)
198
+
199
+ return failures
200
+
201
+
202
+ def parse_unittest_failures(output: str) -> List[Dict[str, Any]]:
203
+ """Parse unittest failure output."""
204
+ failures = []
205
+
206
+ # Find FAIL or ERROR sections
207
+ fail_pattern = r"(FAIL|ERROR): (\S+) \((\S+)\)\n-+\n(.*?)(?=\n\n|\Z)"
208
+ matches = re.findall(fail_pattern, output, re.DOTALL)
209
+
210
+ for match in matches:
211
+ failure_type, method_name, class_name, content = match
212
+
213
+ # Extract file and line info
214
+ file_match = re.search(r'File "([^"]+)", line (\d+)', content)
215
+ file_path = file_match.group(1) if file_match else None
216
+ line_number = int(file_match.group(2)) if file_match else None
217
+
218
+ # Extract error type and message
219
+ error_match = re.search(
220
+ r"(AssertionError|TypeError|ValueError|AttributeError|KeyError|IndexError|ImportError|NameError|RuntimeError|Exception)[:>]?\s*(.*)",
221
+ content,
222
+ re.DOTALL,
223
+ )
224
+ error_type = error_match.group(1) if error_match else failure_type
225
+ error_message = error_match.group(2).strip() if error_match else ""
226
+
227
+ failure = {
228
+ "test_name": f"{class_name}.{method_name}",
229
+ "file_path": file_path,
230
+ "line_number": line_number,
231
+ "error_type": error_type,
232
+ "error_message": error_message,
233
+ "full_output": content,
234
+ }
235
+
236
+ failures.append(failure)
237
+
238
+ return failures
239
+
240
+
241
+ def parse_jest_failures(output: str) -> List[Dict[str, Any]]:
242
+ """Parse Jest failure output."""
243
+ failures = []
244
+
245
+ # Find test failures
246
+ fail_pattern = r"● (.+?)\n\n(.*?)(?=\n ●|\n\nTest Suites|\Z)"
247
+ matches = re.findall(fail_pattern, output, re.DOTALL)
248
+
249
+ for match in matches:
250
+ test_name, content = match
251
+
252
+ # Extract file and line info
253
+ file_match = re.search(r"at (.+?):(\d+):(\d+)", content)
254
+ file_path = file_match.group(1) if file_match else None
255
+ line_number = int(file_match.group(2)) if file_match else None
256
+
257
+ # Extract error type and message
258
+ error_match = re.search(
259
+ r"(Error|TypeError|ReferenceError|SyntaxError)[:>]?\s*(.*)",
260
+ content,
261
+ re.DOTALL,
262
+ )
263
+ error_type = error_match.group(1) if error_match else "Unknown"
264
+ error_message = error_match.group(2).strip() if error_match else ""
265
+
266
+ failure = {
267
+ "test_name": test_name,
268
+ "file_path": file_path,
269
+ "line_number": line_number,
270
+ "error_type": error_type,
271
+ "error_message": error_message,
272
+ "full_output": content,
273
+ }
274
+
275
+ failures.append(failure)
276
+
277
+ return failures
278
+
279
+
280
+ def parse_generic_failures(output: str) -> List[Dict[str, Any]]:
281
+ """Parse generic test failure output."""
282
+ failures = []
283
+
284
+ # Look for common error patterns
285
+ error_patterns = [
286
+ r"(AssertionError|TypeError|ValueError|AttributeError|KeyError|IndexError|ImportError|NameError|RuntimeError|Exception)[:>]?\s*(.*)",
287
+ r"FAILED (.+?) - (.+)",
288
+ r"ERROR (.+?) - (.+)",
289
+ r"✕ (.+)",
290
+ ]
291
+
292
+ for pattern in error_patterns:
293
+ matches = re.findall(pattern, output, re.MULTILINE)
294
+ for match in matches:
295
+ if len(match) >= 2:
296
+ error_type = match[0]
297
+ error_message = match[1]
298
+
299
+ failure = {
300
+ "test_name": "Unknown",
301
+ "file_path": None,
302
+ "line_number": None,
303
+ "error_type": error_type,
304
+ "error_message": error_message,
305
+ "full_output": str(match),
306
+ }
307
+
308
+ failures.append(failure)
309
+
310
+ return failures
311
+
312
+
313
+ def analyze_failures(
314
+ failures: List[Dict[str, Any]], suggest_fixes: bool, group_by_type: bool
315
+ ) -> Dict[str, Any]:
316
+ """Analyze parsed failures and provide insights."""
317
+ analysis = {"failures": failures, "summary": {}, "patterns": {}, "suggestions": []}
318
+
319
+ if not failures:
320
+ return analysis
321
+
322
+ # Group failures by error type
323
+ if group_by_type:
324
+ error_groups = defaultdict(list)
325
+ for failure in failures:
326
+ error_groups[failure["error_type"]].append(failure)
327
+
328
+ analysis["error_groups"] = dict(error_groups)
329
+
330
+ # Generate summary statistics
331
+ analysis["summary"] = {
332
+ "total_failures": len(failures),
333
+ "unique_tests": len(set(f["test_name"] for f in failures)),
334
+ "unique_files": len(set(f["file_path"] for f in failures if f["file_path"])),
335
+ "most_common_error": (
336
+ max(
337
+ set(f["error_type"] for f in failures),
338
+ key=lambda x: sum(1 for f in failures if f["error_type"] == x),
339
+ )
340
+ if failures
341
+ else "None"
342
+ ),
343
+ }
344
+
345
+ # Identify patterns
346
+ analysis["patterns"] = identify_patterns(failures)
347
+
348
+ # Generate suggestions
349
+ if suggest_fixes:
350
+ analysis["suggestions"] = generate_suggestions(failures, analysis["patterns"])
351
+
352
+ return analysis
353
+
354
+
355
+ def identify_patterns(failures: List[Dict[str, Any]]) -> Dict[str, Any]:
356
+ """Identify common patterns in test failures."""
357
+ patterns = {
358
+ "common_errors": {},
359
+ "file_hotspots": {},
360
+ "assertion_patterns": [],
361
+ "import_errors": [],
362
+ }
363
+
364
+ # Count error types
365
+ error_counts = defaultdict(int)
366
+ for failure in failures:
367
+ error_counts[failure["error_type"]] += 1
368
+
369
+ patterns["common_errors"] = dict(error_counts)
370
+
371
+ # Find file hotspots
372
+ file_counts = defaultdict(int)
373
+ for failure in failures:
374
+ if failure["file_path"]:
375
+ file_counts[failure["file_path"]] += 1
376
+
377
+ patterns["file_hotspots"] = dict(file_counts)
378
+
379
+ # Find assertion patterns
380
+ assertion_patterns = []
381
+ for failure in failures:
382
+ if failure.get("assertion"):
383
+ assertion_patterns.append(failure["assertion"])
384
+
385
+ patterns["assertion_patterns"] = assertion_patterns
386
+
387
+ # Find import errors
388
+ import_errors = []
389
+ for failure in failures:
390
+ if failure["error_type"] in ["ImportError", "ModuleNotFoundError"]:
391
+ import_errors.append(failure["error_message"])
392
+
393
+ patterns["import_errors"] = import_errors
394
+
395
+ return patterns
396
+
397
+
398
+ def generate_suggestions(
399
+ failures: List[Dict[str, Any]], patterns: Dict[str, Any]
400
+ ) -> List[Dict[str, Any]]:
401
+ """Generate suggestions for fixing test failures."""
402
+ suggestions = []
403
+
404
+ # Suggestions based on common error types
405
+ for error_type, count in patterns["common_errors"].items():
406
+ if error_type == "AssertionError":
407
+ suggestions.append(
408
+ {
409
+ "type": "fix_suggestion",
410
+ "priority": "high",
411
+ "message": f"You have {count} assertion failures. Review test expectations and actual values.",
412
+ "actions": [
413
+ "Check if test data has changed",
414
+ "Verify expected vs actual values",
415
+ "Update assertions if requirements changed",
416
+ ],
417
+ }
418
+ )
419
+ elif error_type == "ImportError":
420
+ suggestions.append(
421
+ {
422
+ "type": "fix_suggestion",
423
+ "priority": "high",
424
+ "message": f"You have {count} import errors. Check dependencies and module paths.",
425
+ "actions": [
426
+ "Install missing dependencies",
427
+ "Check PYTHONPATH or module paths",
428
+ "Verify module names and locations",
429
+ ],
430
+ }
431
+ )
432
+ elif error_type == "AttributeError":
433
+ suggestions.append(
434
+ {
435
+ "type": "fix_suggestion",
436
+ "priority": "medium",
437
+ "message": f"You have {count} attribute errors. Check object interfaces and method names.",
438
+ "actions": [
439
+ "Verify object has expected attributes/methods",
440
+ "Check for typos in attribute names",
441
+ "Ensure objects are properly initialized",
442
+ ],
443
+ }
444
+ )
445
+ elif error_type == "TypeError":
446
+ suggestions.append(
447
+ {
448
+ "type": "fix_suggestion",
449
+ "priority": "medium",
450
+ "message": f"You have {count} type errors. Check function arguments and return types.",
451
+ "actions": [
452
+ "Verify function signatures",
453
+ "Check argument types being passed",
454
+ "Ensure proper type conversions",
455
+ ],
456
+ }
457
+ )
458
+
459
+ # Suggestions based on file hotspots
460
+ for file_path, count in patterns["file_hotspots"].items():
461
+ if count > 1:
462
+ suggestions.append(
463
+ {
464
+ "type": "code_review",
465
+ "priority": "medium",
466
+ "message": f"File {file_path} has {count} failing tests. Consider reviewing this file.",
467
+ "actions": [
468
+ "Review recent changes to this file",
469
+ "Check for systematic issues",
470
+ "Consider refactoring if needed",
471
+ ],
472
+ }
473
+ )
474
+
475
+ # Suggestions based on import errors
476
+ if patterns["import_errors"]:
477
+ unique_imports = set(patterns["import_errors"])
478
+ suggestions.append(
479
+ {
480
+ "type": "environment",
481
+ "priority": "high",
482
+ "message": f'Missing imports detected: {", ".join(list(unique_imports)[:3])}...',
483
+ "actions": [
484
+ "Check requirements.txt or package.json",
485
+ "Run pip install or npm install",
486
+ "Verify virtual environment activation",
487
+ ],
488
+ }
489
+ )
490
+
491
+ return suggestions
492
+
493
+
494
+ def format_analysis(analysis: Dict[str, Any], output_format: str) -> str:
495
+ """Format analysis results for display."""
496
+ if output_format == "json":
497
+ return json.dumps(analysis, indent=2)
498
+
499
+ lines = []
500
+
501
+ # Header
502
+ lines.append("TEST FAILURE ANALYSIS")
503
+ lines.append("=" * 50)
504
+
505
+ # Summary
506
+ summary = analysis["summary"]
507
+ lines.append(f"Total failures: {summary['total_failures']}")
508
+ lines.append(f"Unique tests: {summary['unique_tests']}")
509
+ lines.append(f"Unique files: {summary['unique_files']}")
510
+ lines.append(f"Most common error: {summary['most_common_error']}")
511
+ lines.append("")
512
+
513
+ # Error groups
514
+ if "error_groups" in analysis:
515
+ lines.append("ERROR GROUPS:")
516
+ lines.append("-" * 30)
517
+ for error_type, group_failures in analysis["error_groups"].items():
518
+ lines.append(f"{error_type}: {len(group_failures)} failures")
519
+ for failure in group_failures[:3]: # Show first 3
520
+ lines.append(f" - {failure['test_name']}")
521
+ if len(group_failures) > 3:
522
+ lines.append(f" ... and {len(group_failures) - 3} more")
523
+ lines.append("")
524
+
525
+ # Suggestions
526
+ if analysis["suggestions"]:
527
+ lines.append("SUGGESTIONS:")
528
+ lines.append("-" * 30)
529
+ for suggestion in analysis["suggestions"]:
530
+ lines.append(f"[{suggestion['priority'].upper()}] {suggestion['message']}")
531
+ for action in suggestion["actions"]:
532
+ lines.append(f" • {action}")
533
+ lines.append("")
534
+
535
+ # Detailed failures
536
+ lines.append("DETAILED FAILURES:")
537
+ lines.append("-" * 30)
538
+ for i, failure in enumerate(analysis["failures"][:5], 1): # Show first 5
539
+ lines.append(f"{i}. {failure['test_name']}")
540
+ if failure["file_path"]:
541
+ lines.append(f" File: {failure['file_path']}:{failure['line_number']}")
542
+ lines.append(f" Error: {failure['error_type']}")
543
+ lines.append(f" Message: {failure['error_message'][:100]}...")
544
+ lines.append("")
545
+
546
+ if len(analysis["failures"]) > 5:
547
+ lines.append(f"... and {len(analysis['failures']) - 5} more failures")
548
+
549
+ return "\n".join(lines)
550
+
551
+
552
+ def main():
553
+ parser = argparse.ArgumentParser(
554
+ description="Test failure analysis tool for examining and debugging test failures."
555
+ )
556
+ parser.add_argument(
557
+ "--test_output", type=str, help="Path to test output file or direct test output"
558
+ )
559
+ parser.add_argument(
560
+ "--test_framework",
561
+ choices=[
562
+ "pytest",
563
+ "unittest",
564
+ "jest",
565
+ "rspec",
566
+ "mocha",
567
+ "phpunit",
568
+ "cargo",
569
+ "go",
570
+ ],
571
+ help="Test framework used",
572
+ )
573
+ parser.add_argument(
574
+ "--verbose",
575
+ action="store_true",
576
+ default=False,
577
+ help="Show detailed analysis (default: False)",
578
+ )
579
+ parser.add_argument(
580
+ "--suggest_fixes",
581
+ action="store_true",
582
+ default=True,
583
+ help="Suggest potential fixes (default: True)",
584
+ )
585
+ parser.add_argument(
586
+ "--group_by_type",
587
+ action="store_true",
588
+ default=True,
589
+ help="Group failures by error type (default: True)",
590
+ )
591
+ parser.add_argument(
592
+ "--output_format",
593
+ choices=["default", "json"],
594
+ default="default",
595
+ help="Output format (default: default)",
596
+ )
597
+
598
+ args = parser.parse_args()
599
+
600
+ try:
601
+ analysis = analyze_test_failures(
602
+ test_output=args.test_output,
603
+ test_framework=args.test_framework,
604
+ verbose=args.verbose,
605
+ suggest_fixes=args.suggest_fixes,
606
+ group_by_type=args.group_by_type,
607
+ )
608
+
609
+ output = format_analysis(analysis, args.output_format)
610
+ print(output)
611
+
612
+ except Exception as e:
613
+ print(f"Error analyzing test failures: {e}", file=sys.stderr)
614
+ sys.exit(1)
615
+
616
+
617
+ if __name__ == "__main__":
618
+ main()