|
| 1 | +""" |
| 2 | +Test suite for validating all code examples in the repository. |
| 3 | +
|
| 4 | +This module ensures that: |
| 5 | +1. All example files can be imported without syntax errors |
| 6 | +2. Code examples follow correct import patterns |
| 7 | +3. Example scripts are executable and contain no broken imports |
| 8 | +""" |
| 9 | + |
| 10 | +import os |
| 11 | +import sys |
| 12 | +import importlib.util |
| 13 | +from pathlib import Path |
| 14 | + |
| 15 | + |
| 16 | +def test_all_python_files_have_valid_syntax(): |
| 17 | + """Verify all Python files in the repository have valid syntax.""" |
| 18 | + repo_root = Path(__file__).parent.parent |
| 19 | + python_files = list(repo_root.rglob('*.py')) |
| 20 | + |
| 21 | + # Skip test files and hidden directories |
| 22 | + python_files = [ |
| 23 | + f for f in python_files |
| 24 | + if not f.parts[0].startswith('.') |
| 25 | + and 'test' not in str(f) |
| 26 | + and '__pycache__' not in str(f) |
| 27 | + ] |
| 28 | + |
| 29 | + errors = [] |
| 30 | + for py_file in python_files: |
| 31 | + try: |
| 32 | + with open(py_file, 'r', encoding='utf-8') as f: |
| 33 | + compile(f.read(), str(py_file), 'exec') |
| 34 | + except SyntaxError as e: |
| 35 | + errors.append(f"{py_file}: {e}") |
| 36 | + |
| 37 | + assert not errors, f"Syntax errors found:\n" + "\n".join(errors) |
| 38 | + |
| 39 | + |
| 40 | +def test_games_modules_importable(): |
| 41 | + """Verify all game modules can be imported.""" |
| 42 | + repo_root = Path(__file__).parent.parent |
| 43 | + games_dir = repo_root / 'games' |
| 44 | + |
| 45 | + if not games_dir.exists(): |
| 46 | + return # Skip if games directory doesn't exist |
| 47 | + |
| 48 | + errors = [] |
| 49 | + for game_folder in games_dir.iterdir(): |
| 50 | + if not game_folder.is_dir(): |
| 51 | + continue |
| 52 | + |
| 53 | + py_files = list(game_folder.glob('*.py')) |
| 54 | + for py_file in py_files: |
| 55 | + try: |
| 56 | + spec = importlib.util.spec_from_file_location(py_file.stem, py_file) |
| 57 | + if spec and spec.loader: |
| 58 | + module = importlib.util.module_from_spec(spec) |
| 59 | + sys.modules[py_file.stem] = module |
| 60 | + # Don't execute, just verify it can be loaded |
| 61 | + except Exception as e: |
| 62 | + errors.append(f"{py_file}: {str(e)}") |
| 63 | + |
| 64 | + assert not errors, f"Failed to import game modules:\n" + "\n".join(errors) |
| 65 | + |
| 66 | + |
| 67 | +def test_math_modules_importable(): |
| 68 | + """Verify all math modules can be imported.""" |
| 69 | + repo_root = Path(__file__).parent.parent |
| 70 | + math_dir = repo_root / 'math' |
| 71 | + |
| 72 | + if not math_dir.exists(): |
| 73 | + return # Skip if math directory doesn't exist |
| 74 | + |
| 75 | + errors = [] |
| 76 | + for math_folder in math_dir.iterdir(): |
| 77 | + if not math_folder.is_dir(): |
| 78 | + continue |
| 79 | + |
| 80 | + py_files = list(math_folder.glob('*.py')) |
| 81 | + for py_file in py_files: |
| 82 | + try: |
| 83 | + spec = importlib.util.spec_from_file_location(py_file.stem, py_file) |
| 84 | + if spec and spec.loader: |
| 85 | + module = importlib.util.module_from_spec(spec) |
| 86 | + sys.modules[py_file.stem] = module |
| 87 | + # Don't execute, just verify it can be loaded |
| 88 | + except Exception as e: |
| 89 | + errors.append(f"{py_file}: {str(e)}") |
| 90 | + |
| 91 | + assert not errors, f"Failed to import math modules:\n" + "\n".join(errors) |
| 92 | + |
| 93 | + |
| 94 | +def test_utilities_modules_importable(): |
| 95 | + """Verify all utility modules can be imported.""" |
| 96 | + repo_root = Path(__file__).parent.parent |
| 97 | + utilities_dir = repo_root / 'utilities' |
| 98 | + |
| 99 | + if not utilities_dir.exists(): |
| 100 | + return # Skip if utilities directory doesn't exist |
| 101 | + |
| 102 | + errors = [] |
| 103 | + for util_folder in utilities_dir.iterdir(): |
| 104 | + if not util_folder.is_dir(): |
| 105 | + continue |
| 106 | + |
| 107 | + py_files = list(util_folder.glob('*.py')) |
| 108 | + for py_file in py_files: |
| 109 | + try: |
| 110 | + spec = importlib.util.spec_from_file_location(py_file.stem, py_file) |
| 111 | + if spec and spec.loader: |
| 112 | + module = importlib.util.module_from_spec(spec) |
| 113 | + sys.modules[py_file.stem] = module |
| 114 | + # Don't execute, just verify it can be loaded |
| 115 | + except Exception as e: |
| 116 | + errors.append(f"{py_file}: {str(e)}") |
| 117 | + |
| 118 | + assert not errors, f"Failed to import utility modules:\n" + "\n".join(errors) |
| 119 | + |
| 120 | + |
| 121 | +def test_no_broken_imports(): |
| 122 | + """Check for broken imports in all Python files.""" |
| 123 | + repo_root = Path(__file__).parent.parent |
| 124 | + python_files = list(repo_root.rglob('*.py')) |
| 125 | + |
| 126 | + # Skip test files and hidden directories |
| 127 | + python_files = [ |
| 128 | + f for f in python_files |
| 129 | + if not f.parts[0].startswith('.') |
| 130 | + and 'test' not in str(f) |
| 131 | + and '__pycache__' not in str(f) |
| 132 | + ] |
| 133 | + |
| 134 | + errors = [] |
| 135 | + for py_file in python_files: |
| 136 | + with open(py_file, 'r', encoding='utf-8') as f: |
| 137 | + content = f.read() |
| 138 | + lines = content.split('\n') |
| 139 | + |
| 140 | + for line_num, line in enumerate(lines, 1): |
| 141 | + line = line.strip() |
| 142 | + |
| 143 | + # Check for import statements |
| 144 | + if line.startswith('import ') or line.startswith('from '): |
| 145 | + # Check for common patterns of broken imports |
| 146 | + if line.endswith('import') or line.endswith('from'): |
| 147 | + errors.append(f"{py_file}:{line_num}: Incomplete import statement") |
| 148 | + |
| 149 | + # Check for double dots or invalid characters |
| 150 | + if '..' in line.split('import')[0]: |
| 151 | + errors.append(f"{py_file}:{line_num}: Invalid import path") |
| 152 | + |
| 153 | + assert not errors, f"Broken imports found:\n" + "\n".join(errors) |
| 154 | + |
| 155 | + |
| 156 | +if __name__ == '__main__': |
| 157 | + import pytest |
| 158 | + pytest.main([__file__, '-v']) |
0 commit comments