* CI/CD Optimization: Guard Rails, Black Linting, and Pre-commit Hooks - Fixed all test collection errors (Selenium imports, fixture paths, syntax) - Implemented pre-commit hooks with Black formatting and isort - Created comprehensive Makefile with test targets (unit, integration, functional, e2e) - Added pytest.ini with marker definitions for test categorization - Established guard rails to prevent future collection errors - Wrapped optional dependencies (Selenium, MoviePy) in try-except blocks - Added conftest_markers for automatic test categorization This ensures a smooth development stream with: - Fast feedback loops (pre-commit checks before push) - Consistent code formatting (Black) - Reliable CI/CD (no collection errors, proper test isolation) - Clear test organization (unit, integration, functional, E2E) * Fix CI/CD test failures: - Export templates from dashboard.app - Fix model name assertion in test_agent.py - Fix platform-agnostic path resolution in test_path_resolution.py - Skip Docker tests in test_docker_deployment.py if docker not available - Fix test_model_fallback_chain logic in test_ollama_integration.py * Add preventative pre-commit checks and Docker test skipif decorators: - Create pre_commit_checks.py script for common CI failures - Add skipif decorators to Docker tests - Improve test robustness for CI environments
184 lines
5.5 KiB
Python
Executable File
184 lines
5.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Pre-commit checks for common CI failures.
|
|
|
|
This script runs before commits to catch issues early:
|
|
- ImportError regressions (missing exports from modules)
|
|
- Model name assertions (config mismatches)
|
|
- Platform-specific path issues
|
|
- Syntax errors in test files
|
|
"""
|
|
|
|
import sys
|
|
import subprocess
|
|
from pathlib import Path
|
|
import ast
|
|
import re
|
|
|
|
|
|
def check_imports():
|
|
"""Check for common ImportError issues."""
|
|
issues = []
|
|
|
|
# Check that dashboard.app exports 'templates'
|
|
try:
|
|
from dashboard.app import templates
|
|
print("✓ dashboard.app exports 'templates'")
|
|
except ImportError as e:
|
|
issues.append(f"✗ ImportError in dashboard.app: {e}")
|
|
|
|
# Check that integrations.shortcuts.siri is importable
|
|
try:
|
|
from integrations.shortcuts.siri import get_setup_guide
|
|
print("✓ integrations.shortcuts.siri exports 'get_setup_guide'")
|
|
except ImportError as e:
|
|
issues.append(f"✗ ImportError in integrations.shortcuts.siri: {e}")
|
|
|
|
return issues
|
|
|
|
|
|
def check_model_config():
|
|
"""Check that model configuration is consistent."""
|
|
issues = []
|
|
|
|
try:
|
|
from config import settings
|
|
from timmy.agent import DEFAULT_MODEL_FALLBACKS
|
|
|
|
# Ensure configured model is valid
|
|
if not settings.ollama_model:
|
|
issues.append("✗ OLLAMA_MODEL is not configured")
|
|
else:
|
|
print(f"✓ OLLAMA_MODEL is configured: {settings.ollama_model}")
|
|
|
|
# Ensure fallback chain is not empty
|
|
if not DEFAULT_MODEL_FALLBACKS:
|
|
issues.append("✗ DEFAULT_MODEL_FALLBACKS is empty")
|
|
else:
|
|
print(f"✓ DEFAULT_MODEL_FALLBACKS has {len(DEFAULT_MODEL_FALLBACKS)} models")
|
|
|
|
except Exception as e:
|
|
issues.append(f"✗ Error checking model config: {e}")
|
|
|
|
return issues
|
|
|
|
|
|
def check_test_syntax():
|
|
"""Check for syntax errors in test files."""
|
|
issues = []
|
|
tests_dir = Path("tests").resolve()
|
|
|
|
for test_file in tests_dir.rglob("test_*.py"):
|
|
try:
|
|
with open(test_file, "r") as f:
|
|
ast.parse(f.read())
|
|
print(f"✓ {test_file.relative_to(tests_dir.parent)} has valid syntax")
|
|
except SyntaxError as e:
|
|
issues.append(f"✗ Syntax error in {test_file.relative_to(tests_dir.parent)}: {e}")
|
|
|
|
return issues
|
|
|
|
|
|
def check_platform_specific_tests():
|
|
"""Check for platform-specific test assertions."""
|
|
issues = []
|
|
|
|
# Check for hardcoded /Users/ paths in tests
|
|
tests_dir = Path("tests").resolve()
|
|
for test_file in tests_dir.rglob("test_*.py"):
|
|
with open(test_file, "r") as f:
|
|
content = f.read()
|
|
if 'startswith("/Users/")' in content:
|
|
issues.append(
|
|
f"✗ {test_file.relative_to(tests_dir.parent)} has hardcoded /Users/ path. "
|
|
"Use Path.home() instead for platform compatibility."
|
|
)
|
|
|
|
if not issues:
|
|
print("✓ No platform-specific hardcoded paths found in tests")
|
|
|
|
return issues
|
|
|
|
|
|
def check_docker_availability():
|
|
"""Check if Docker tests will be skipped properly."""
|
|
issues = []
|
|
|
|
# Check that Docker tests have proper skipif decorators
|
|
tests_dir = Path("tests").resolve()
|
|
docker_test_files = list(tests_dir.rglob("test_docker*.py"))
|
|
|
|
if docker_test_files:
|
|
for test_file in docker_test_files:
|
|
with open(test_file, "r") as f:
|
|
content = f.read()
|
|
has_skipif = "@pytest.mark.skipif" in content or "pytestmark = pytest.mark.skipif" in content
|
|
if not has_skipif and "docker" in content.lower():
|
|
issues.append(
|
|
f"✗ {test_file.relative_to(tests_dir.parent)} has Docker tests but no skipif decorator"
|
|
)
|
|
|
|
if not issues:
|
|
print("✓ Docker tests have proper skipif decorators")
|
|
|
|
return issues
|
|
|
|
|
|
def check_black_formatting():
|
|
"""Check that files are formatted with Black."""
|
|
issues = []
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
["black", "--check", "src/", "tests/", "--quiet"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
issues.append(
|
|
"✗ Code formatting issues detected. Run 'black src/ tests/' to fix."
|
|
)
|
|
else:
|
|
print("✓ Code formatting is correct (Black)")
|
|
except FileNotFoundError:
|
|
print("⚠ Black not found. Install with: pip install black")
|
|
except subprocess.TimeoutExpired:
|
|
print("⚠ Black check timed out")
|
|
except Exception as e:
|
|
print(f"⚠ Black check failed: {e}")
|
|
|
|
return issues
|
|
|
|
|
|
def main():
|
|
"""Run all pre-commit checks."""
|
|
print("\n🔍 Running pre-commit checks...\n")
|
|
|
|
all_issues = []
|
|
|
|
# Run all checks
|
|
all_issues.extend(check_imports())
|
|
all_issues.extend(check_model_config())
|
|
all_issues.extend(check_test_syntax())
|
|
all_issues.extend(check_platform_specific_tests())
|
|
all_issues.extend(check_docker_availability())
|
|
all_issues.extend(check_black_formatting())
|
|
|
|
# Report results
|
|
print("\n" + "=" * 70)
|
|
if all_issues:
|
|
print(f"\n❌ Found {len(all_issues)} issue(s):\n")
|
|
for issue in all_issues:
|
|
print(f" {issue}")
|
|
print("\n" + "=" * 70)
|
|
return 1
|
|
else:
|
|
print("\n✅ All pre-commit checks passed!\n")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|