Files
QwenClaw-with-Auth/skills/openclaw-skills/skills/samuelkahessay/code-mentor/scripts/run_tests.py

335 lines
9.8 KiB
Python

#!/usr/bin/env python3
"""
Test Runner - Execute and format test results
Supports:
- pytest (Python)
- unittest (Python)
- jest (JavaScript)
- JUnit (Java)
Usage:
python run_tests.py <test_file>
python run_tests.py <test_directory>
python run_tests.py --framework pytest
"""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
class TestResult:
"""Represents test execution results."""
def __init__(self):
self.passed = 0
self.failed = 0
self.errors = 0
self.skipped = 0
self.total = 0
self.duration = 0.0
self.failures = []
def to_dict(self):
return {
'passed': self.passed,
'failed': self.failed,
'errors': self.errors,
'skipped': self.skipped,
'total': self.total,
'duration': self.duration,
'failures': self.failures
}
class TestRunner:
"""Base class for test runners."""
def __init__(self, target):
self.target = target
def run(self) -> TestResult:
raise NotImplementedError
class PytestRunner(TestRunner):
"""Run pytest tests."""
def run(self) -> TestResult:
result = TestResult()
try:
# Run pytest with verbose output and JSON report
cmd = [
'python', '-m', 'pytest',
self.target,
'-v',
'--tb=short'
]
process = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
# Parse output
output = process.stdout + process.stderr
lines = output.split('\n')
for line in lines:
if ' PASSED' in line:
result.passed += 1
elif ' FAILED' in line:
result.failed += 1
# Extract test name and failure info
test_name = line.split('::')[1].split(' ')[0] if '::' in line else 'unknown'
result.failures.append({
'test': test_name,
'message': 'See output for details'
})
elif ' ERROR' in line:
result.errors += 1
elif ' SKIPPED' in line:
result.skipped += 1
# Extract duration
if 'passed in' in line or 'failed in' in line:
try:
duration_str = line.split(' in ')[1].split('s')[0]
result.duration = float(duration_str)
except:
pass
result.total = result.passed + result.failed + result.errors + result.skipped
return result
except FileNotFoundError:
print("Error: pytest not found. Install with: pip install pytest", file=sys.stderr)
sys.exit(1)
except subprocess.TimeoutExpired:
print("Error: Tests timed out after 60 seconds", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error running tests: {e}", file=sys.stderr)
sys.exit(1)
class UnittestRunner(TestRunner):
"""Run unittest tests."""
def run(self) -> TestResult:
result = TestResult()
try:
cmd = [
'python', '-m', 'unittest',
'discover',
'-s', self.target,
'-v'
]
process = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
output = process.stdout + process.stderr
lines = output.split('\n')
for line in lines:
if ' ... ok' in line:
result.passed += 1
elif 'FAIL:' in line:
result.failed += 1
test_name = line.replace('FAIL:', '').strip()
result.failures.append({
'test': test_name,
'message': 'See output for details'
})
elif 'ERROR:' in line:
result.errors += 1
# Parse summary line
for line in reversed(lines):
if 'Ran ' in line and ' test' in line:
try:
result.total = int(line.split('Ran ')[1].split(' test')[0])
except:
pass
break
return result
except FileNotFoundError:
print("Error: unittest module not found", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error running tests: {e}", file=sys.stderr)
sys.exit(1)
class JestRunner(TestRunner):
"""Run Jest tests."""
def run(self) -> TestResult:
result = TestResult()
try:
cmd = ['npx', 'jest', self.target, '--verbose']
process = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
output = process.stdout + process.stderr
lines = output.split('\n')
for line in lines:
if '' in line or 'PASS' in line:
result.passed += 1
elif '' in line or 'FAIL' in line:
result.failed += 1
# Parse summary
for line in lines:
if 'Tests:' in line:
parts = line.split(',')
for part in parts:
if 'passed' in part:
try:
result.passed = int(part.split()[0])
except:
pass
elif 'failed' in part:
try:
result.failed = int(part.split()[0])
except:
pass
result.total = result.passed + result.failed
return result
except FileNotFoundError:
print("Error: Jest not found. Install with: npm install --save-dev jest", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error running tests: {e}", file=sys.stderr)
sys.exit(1)
def detect_framework(target):
"""Detect testing framework to use."""
# Check if it's a Python file
if target.endswith('.py') or os.path.isdir(target):
# Check for pytest markers
if os.path.isfile(target):
with open(target, 'r') as f:
content = f.read()
if 'import pytest' in content or '@pytest' in content:
return 'pytest'
elif 'import unittest' in content or 'class Test' in content:
return 'unittest'
else:
# Check for pytest.ini or setup.cfg
if os.path.exists('pytest.ini') or os.path.exists('setup.cfg'):
return 'pytest'
return 'unittest'
# Check if it's JavaScript
elif target.endswith('.js') or target.endswith('.test.js'):
return 'jest'
return 'pytest' # Default
def run_tests(target, framework=None, output_format='text'):
"""Run tests and format output."""
if not os.path.exists(target):
print(f"Error: Target '{target}' not found", file=sys.stderr)
sys.exit(1)
# Detect framework if not specified
if framework is None:
framework = detect_framework(target)
# Create appropriate runner
if framework == 'pytest':
runner = PytestRunner(target)
elif framework == 'unittest':
runner = UnittestRunner(target)
elif framework == 'jest':
runner = JestRunner(target)
else:
print(f"Error: Unsupported framework '{framework}'", file=sys.stderr)
sys.exit(1)
print(f"\nRunning tests with {framework}...")
print(f"Target: {target}\n")
# Run tests
result = runner.run()
# Output results
if output_format == 'json':
print(json.dumps(result.to_dict(), indent=2))
else:
print("=" * 60)
print("TEST RESULTS")
print("=" * 60)
print(f"Total: {result.total}")
print(f"Passed: {result.passed}")
print(f"Failed: {result.failed}")
if result.errors > 0:
print(f"Errors: {result.errors}")
if result.skipped > 0:
print(f"Skipped: {result.skipped}")
if result.duration > 0:
print(f"Duration: {result.duration:.2f}s")
print()
if result.failures:
print("FAILURES:")
for failure in result.failures:
print(f" - {failure['test']}")
if failure.get('message'):
print(f" {failure['message']}")
print()
# Summary
if result.failed == 0 and result.errors == 0:
print("✓ All tests passed!")
else:
print(f"{result.failed + result.errors} test(s) failed")
print()
def main():
parser = argparse.ArgumentParser(description='Run and format test results')
parser.add_argument('target', help='Test file or directory to run')
parser.add_argument('--framework', choices=['pytest', 'unittest', 'jest'],
help='Testing framework to use (auto-detected if not specified)')
parser.add_argument('--format', choices=['text', 'json'], default='text',
help='Output format (default: text)')
args = parser.parse_args()
run_tests(args.target, args.framework, args.format)
if __name__ == '__main__':
main()