Advanced test runner with parallel execution and performance tracking.
| 50 | performance_summary: Dict[str, float] |
| 51 | |
| 52 | class TestRunner: |
| 53 | """Advanced test runner with parallel execution and performance tracking.""" |
| 54 | |
| 55 | def __init__(self): |
| 56 | self.project_root = Path(__file__).parent |
| 57 | self.test_dir = self.project_root / "tests" |
| 58 | self.src_dir = self.project_root / "src" |
| 59 | |
| 60 | def detect_pytest_binary(self) -> str: |
| 61 | """Detect the appropriate pytest binary.""" |
| 62 | # Try venv first, then global |
| 63 | venv_pytest = self.project_root / "venv" / "Scripts" / "pytest.exe" |
| 64 | if venv_pytest.exists(): |
| 65 | return str(venv_pytest) |
| 66 | |
| 67 | # Try alternative venv location |
| 68 | venvs_pytest = self.project_root / "venvs" / "Scripts" / "pytest.exe" |
| 69 | if venvs_pytest.exists(): |
| 70 | return str(venvs_pytest) |
| 71 | |
| 72 | # Fall back to global pytest |
| 73 | return "pytest" |
| 74 | |
| 75 | def get_changed_files(self) -> List[str]: |
| 76 | """Get list of changed files using git (if available).""" |
| 77 | try: |
| 78 | result = subprocess.run( |
| 79 | ["git", "diff", "--name-only", "HEAD~1"], |
| 80 | capture_output=True, text=True, timeout=5 |
| 81 | ) |
| 82 | if result.returncode == 0: |
| 83 | return [f for f in result.stdout.strip().split('\n') if f.endswith('.py')] |
| 84 | except (subprocess.TimeoutExpired, FileNotFoundError): |
| 85 | pass |
| 86 | return [] |
| 87 | |
| 88 | def map_files_to_tests(self, changed_files: List[str]) -> List[str]: |
| 89 | """Map changed source files to their corresponding test files.""" |
| 90 | test_files = [] |
| 91 | |
| 92 | for file_path in changed_files: |
| 93 | if file_path.startswith('src/'): |
| 94 | # Convert src/module.py to tests/test_module.py |
| 95 | module_name = Path(file_path).stem |
| 96 | test_file = self.test_dir / f"test_{module_name}.py" |
| 97 | if test_file.exists(): |
| 98 | test_files.append(str(test_file)) |
| 99 | |
| 100 | # Also check headless tests |
| 101 | headless_test = self.test_dir / "headless" / f"test_{module_name}.py" |
| 102 | if headless_test.exists(): |
| 103 | test_files.append(str(headless_test)) |
| 104 | |
| 105 | elif file_path.startswith('tests/'): |
| 106 | # Direct test file change |
| 107 | if Path(file_path).exists(): |
| 108 | test_files.append(file_path) |
| 109 |