151 lines
4.4 KiB
Python
151 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
McRogueFace Test Runner
|
|
Runs all headless tests and reports results.
|
|
|
|
Usage:
|
|
python3 tests/run_tests.py # Run all tests
|
|
python3 tests/run_tests.py unit # Run only unit tests
|
|
python3 tests/run_tests.py -v # Verbose output
|
|
"""
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
# Configuration
|
|
TESTS_DIR = Path(__file__).parent
|
|
BUILD_DIR = TESTS_DIR.parent / "build"
|
|
MCROGUEFACE = BUILD_DIR / "mcrogueface"
|
|
TIMEOUT = 10 # seconds per test
|
|
|
|
# Test directories to run (in order)
|
|
TEST_DIRS = ['unit', 'integration', 'regression']
|
|
|
|
# ANSI colors
|
|
GREEN = '\033[92m'
|
|
RED = '\033[91m'
|
|
YELLOW = '\033[93m'
|
|
RESET = '\033[0m'
|
|
BOLD = '\033[1m'
|
|
|
|
def get_screenshot_checksum(test_dir):
|
|
"""Get checksums of any PNG files in build directory."""
|
|
checksums = {}
|
|
for png in BUILD_DIR.glob("*.png"):
|
|
with open(png, 'rb') as f:
|
|
checksums[png.name] = hashlib.md5(f.read()).hexdigest()[:8]
|
|
return checksums
|
|
|
|
def run_test(test_path, verbose=False):
|
|
"""Run a single test and return (passed, duration, output)."""
|
|
start = time.time()
|
|
|
|
# Clean any existing screenshots
|
|
for png in BUILD_DIR.glob("test_*.png"):
|
|
png.unlink()
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[str(MCROGUEFACE), '--headless', '--exec', str(test_path)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=TIMEOUT,
|
|
cwd=str(BUILD_DIR)
|
|
)
|
|
duration = time.time() - start
|
|
passed = result.returncode == 0
|
|
output = result.stdout + result.stderr
|
|
|
|
# Check for PASS/FAIL in output
|
|
if 'FAIL' in output and 'PASS' not in output.split('FAIL')[-1]:
|
|
passed = False
|
|
|
|
return passed, duration, output
|
|
|
|
except subprocess.TimeoutExpired:
|
|
return False, TIMEOUT, "TIMEOUT"
|
|
except Exception as e:
|
|
return False, 0, str(e)
|
|
|
|
def find_tests(directory):
|
|
"""Find all test files in a directory."""
|
|
test_dir = TESTS_DIR / directory
|
|
if not test_dir.exists():
|
|
return []
|
|
return sorted(test_dir.glob("*.py"))
|
|
|
|
def main():
|
|
verbose = '-v' in sys.argv or '--verbose' in sys.argv
|
|
|
|
# Determine which directories to test
|
|
dirs_to_test = []
|
|
for arg in sys.argv[1:]:
|
|
if arg in TEST_DIRS:
|
|
dirs_to_test.append(arg)
|
|
if not dirs_to_test:
|
|
dirs_to_test = TEST_DIRS
|
|
|
|
print(f"{BOLD}McRogueFace Test Runner{RESET}")
|
|
print(f"Testing: {', '.join(dirs_to_test)}")
|
|
print("=" * 60)
|
|
|
|
results = {'pass': 0, 'fail': 0, 'total_time': 0}
|
|
failures = []
|
|
|
|
for test_dir in dirs_to_test:
|
|
tests = find_tests(test_dir)
|
|
if not tests:
|
|
continue
|
|
|
|
print(f"\n{BOLD}{test_dir}/{RESET} ({len(tests)} tests)")
|
|
|
|
for test_path in tests:
|
|
test_name = test_path.name
|
|
passed, duration, output = run_test(test_path, verbose)
|
|
results['total_time'] += duration
|
|
|
|
if passed:
|
|
results['pass'] += 1
|
|
status = f"{GREEN}PASS{RESET}"
|
|
else:
|
|
results['fail'] += 1
|
|
status = f"{RED}FAIL{RESET}"
|
|
failures.append((test_dir, test_name, output))
|
|
|
|
# Get screenshot checksums if any were generated
|
|
checksums = get_screenshot_checksum(BUILD_DIR)
|
|
checksum_str = ""
|
|
if checksums:
|
|
checksum_str = f" [{', '.join(f'{k}:{v}' for k,v in checksums.items())}]"
|
|
|
|
print(f" {status} {test_name} ({duration:.2f}s){checksum_str}")
|
|
|
|
if verbose and not passed:
|
|
print(f" Output: {output[:200]}...")
|
|
|
|
# Summary
|
|
print("\n" + "=" * 60)
|
|
total = results['pass'] + results['fail']
|
|
pass_rate = (results['pass'] / total * 100) if total > 0 else 0
|
|
|
|
print(f"{BOLD}Results:{RESET} {results['pass']}/{total} passed ({pass_rate:.1f}%)")
|
|
print(f"{BOLD}Time:{RESET} {results['total_time']:.2f}s")
|
|
|
|
if failures:
|
|
print(f"\n{RED}{BOLD}Failures:{RESET}")
|
|
for test_dir, test_name, output in failures:
|
|
print(f" {test_dir}/{test_name}")
|
|
if verbose:
|
|
# Show last few lines of output
|
|
lines = output.strip().split('\n')[-5:]
|
|
for line in lines:
|
|
print(f" {line}")
|
|
|
|
sys.exit(0 if results['fail'] == 0 else 1)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|