Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """Cross-platform pytest runner that syncs dependencies before running tests.""" | |
| import shutil | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| def clean_caches(project_root: Path) -> None: | |
| """Remove pytest and Python cache directories and files. | |
| Comprehensively removes all cache files and directories to ensure | |
| clean test runs. Only scans specific directories to avoid resource | |
| exhaustion from scanning large directories like .venv on Windows. | |
| """ | |
| # Directories to scan for caches (only project code, not dependencies) | |
| scan_dirs = ["src", "tests", ".pre-commit-hooks"] | |
| # Directories to exclude (to avoid resource issues) | |
| exclude_dirs = { | |
| ".venv", | |
| "venv", | |
| "ENV", | |
| "env", | |
| ".git", | |
| "node_modules", | |
| "dist", | |
| "build", | |
| ".eggs", | |
| "reference_repos", | |
| "folder", | |
| } | |
| # Comprehensive list of cache patterns to remove | |
| cache_patterns = [ | |
| ".pytest_cache", | |
| "__pycache__", | |
| "*.pyc", | |
| "*.pyo", | |
| "*.pyd", | |
| ".mypy_cache", | |
| ".ruff_cache", | |
| ".coverage", | |
| "coverage.xml", | |
| "htmlcov", | |
| ".hypothesis", # Hypothesis testing framework cache | |
| ".tox", # Tox cache (if used) | |
| ".cache", # General Python cache | |
| ] | |
| def should_exclude(path: Path) -> bool: | |
| """Check if a path should be excluded from cache cleanup.""" | |
| # Check if any parent directory is in exclude list | |
| for parent in path.parents: | |
| if parent.name in exclude_dirs: | |
| return True | |
| # Check if the path itself is excluded | |
| if path.name in exclude_dirs: | |
| return True | |
| return False | |
| cleaned = [] | |
| # Only scan specific directories to avoid resource exhaustion | |
| for scan_dir in scan_dirs: | |
| scan_path = project_root / scan_dir | |
| if not scan_path.exists(): | |
| continue | |
| for pattern in cache_patterns: | |
| if "*" in pattern: | |
| # Handle glob patterns for files | |
| try: | |
| for cache_file in scan_path.rglob(pattern): | |
| if should_exclude(cache_file): | |
| continue | |
| try: | |
| if cache_file.is_file(): | |
| cache_file.unlink() | |
| cleaned.append(str(cache_file.relative_to(project_root))) | |
| except OSError: | |
| pass # Ignore errors (file might be locked or already deleted) | |
| except OSError: | |
| pass # Ignore errors during directory traversal | |
| else: | |
| # Handle directory patterns | |
| try: | |
| for cache_dir in scan_path.rglob(pattern): | |
| if should_exclude(cache_dir): | |
| continue | |
| try: | |
| if cache_dir.is_dir(): | |
| shutil.rmtree(cache_dir, ignore_errors=True) | |
| cleaned.append(str(cache_dir.relative_to(project_root))) | |
| except OSError: | |
| pass # Ignore errors (directory might be locked) | |
| except OSError: | |
| pass # Ignore errors during directory traversal | |
| # Also clean root-level caches (like .pytest_cache in project root) | |
| root_cache_patterns = [ | |
| ".pytest_cache", | |
| ".mypy_cache", | |
| ".ruff_cache", | |
| ".coverage", | |
| "coverage.xml", | |
| "htmlcov", | |
| ".hypothesis", | |
| ".tox", | |
| ".cache", | |
| ".pytest", | |
| ] | |
| for pattern in root_cache_patterns: | |
| cache_path = project_root / pattern | |
| if cache_path.exists(): | |
| try: | |
| if cache_path.is_dir(): | |
| shutil.rmtree(cache_path, ignore_errors=True) | |
| elif cache_path.is_file(): | |
| cache_path.unlink() | |
| cleaned.append(pattern) | |
| except OSError: | |
| pass | |
| # Also remove any .pyc files in root directory | |
| try: | |
| for pyc_file in project_root.glob("*.pyc"): | |
| try: | |
| pyc_file.unlink() | |
| cleaned.append(pyc_file.name) | |
| except OSError: | |
| pass | |
| except OSError: | |
| pass | |
| if cleaned: | |
| print( | |
| f"Cleaned {len(cleaned)} cache items: {', '.join(cleaned[:10])}{'...' if len(cleaned) > 10 else ''}" | |
| ) | |
| else: | |
| print("No cache files found to clean") | |
| def run_command( | |
| cmd: list[str], check: bool = True, shell: bool = False, cwd: str | None = None | |
| ) -> int: | |
| """Run a command and return exit code.""" | |
| try: | |
| result = subprocess.run( | |
| cmd, | |
| check=check, | |
| shell=shell, | |
| cwd=cwd, | |
| env=None, # Use current environment, uv will handle venv | |
| ) | |
| return result.returncode | |
| except subprocess.CalledProcessError as e: | |
| return e.returncode | |
| except FileNotFoundError: | |
| print(f"Error: Command not found: {cmd[0]}") | |
| return 1 | |
| def main() -> int: | |
| """Main entry point.""" | |
| import os | |
| # Get the project root (where pyproject.toml is) | |
| script_dir = Path(__file__).parent | |
| project_root = script_dir.parent | |
| # Change to project root to ensure uv works correctly | |
| os.chdir(project_root) | |
| # Clean caches before running tests | |
| print("Cleaning pytest and Python caches...") | |
| clean_caches(project_root) | |
| # Check if uv is available | |
| if run_command(["uv", "--version"], check=False) != 0: | |
| print("Error: uv not found. Please install uv: https://github.com/astral-sh/uv") | |
| return 1 | |
| # Parse arguments | |
| test_type = sys.argv[1] if len(sys.argv) > 1 else "unit" | |
| extra_args = sys.argv[2:] if len(sys.argv) > 2 else [] | |
| # Sync dependencies - always include dev | |
| # Note: embeddings dependencies are now in main dependencies, not optional | |
| # Use --extra dev for [project.optional-dependencies].dev (not --dev which is for [dependency-groups]) | |
| sync_cmd = ["uv", "sync", "--extra", "dev"] | |
| print(f"Syncing dependencies for {test_type} tests...") | |
| if run_command(sync_cmd, cwd=project_root) != 0: | |
| return 1 | |
| # Build pytest command - use uv run to ensure correct environment | |
| if test_type == "unit": | |
| pytest_args = [ | |
| "tests/unit/", | |
| "-v", | |
| "-m", | |
| "not openai and not embedding_provider", | |
| "--tb=short", | |
| "-p", | |
| "no:logfire", | |
| "--cache-clear", # Clear pytest cache before running | |
| ] | |
| elif test_type == "embeddings": | |
| pytest_args = [ | |
| "tests/", | |
| "-v", | |
| "-m", | |
| "local_embeddings", | |
| "--tb=short", | |
| "-p", | |
| "no:logfire", | |
| "--cache-clear", # Clear pytest cache before running | |
| ] | |
| else: | |
| pytest_args = [] | |
| pytest_args.extend(extra_args) | |
| # Use uv run python -m pytest to ensure we use the venv's pytest | |
| # This is more reliable than uv run pytest which might find system pytest | |
| pytest_cmd = ["uv", "run", "python", "-m", "pytest", *pytest_args] | |
| print(f"Running {test_type} tests...") | |
| return run_command(pytest_cmd, cwd=project_root) | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |