Skip to content

Commit 05ae578

Browse files
committed
feat: set up comprehensive Python testing infrastructure with Poetry
- Add Poetry as package manager with pyproject.toml configuration - Configure pytest with coverage reporting and custom markers - Create testing directory structure (tests/unit, tests/integration) - Add comprehensive conftest.py with reusable fixtures - Set up Poetry script commands for running tests - Update .gitignore with testing and Claude settings - Add validation tests to verify infrastructure setup
1 parent 017f488 commit 05ae578

File tree

8 files changed

+4006
-0
lines changed

8 files changed

+4006
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,9 @@ dmypy.json
130130

131131
# Pyre type checker
132132
.pyre/
133+
134+
# Claude settings
135+
.claude/*
136+
137+
# Poetry lock file is NOT ignored (keep it in version control)
138+
# poetry.lock

poetry.lock

Lines changed: 3614 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
[tool.poetry]
2+
name = "p3"
3+
version = "0.1.0"
4+
description = "Programming Puzzles and Code Competitions"
5+
authors = ["P3 Contributors"]
6+
readme = "README.md"
7+
packages = [{include = "generators"}, {include = "solvers"}]
8+
9+
[tool.poetry.dependencies]
10+
python = "^3.8"
11+
# Core dependencies from ICLR2023/src/requirements.txt
12+
tqdm = "*"
13+
# orderedset = "*" # Has build issues, using built-in OrderedDict instead
14+
numpy = "*"
15+
astor = "*"
16+
scikit-learn = "*"
17+
fire = "*"
18+
strictfire = "*"
19+
pebble = "*"
20+
# Other common dependencies found in the project
21+
datasets = "*"
22+
transformers = "^4.30.0"
23+
24+
[tool.poetry.group.dev.dependencies]
25+
pytest = "^7.4.0"
26+
pytest-cov = "^4.1.0"
27+
pytest-mock = "^3.11.1"
28+
29+
[tool.poetry.scripts]
30+
test = "pytest:main"
31+
tests = "pytest:main"
32+
33+
[tool.pytest.ini_options]
34+
minversion = "7.0"
35+
testpaths = ["tests"]
36+
python_files = ["test_*.py", "*_test.py"]
37+
python_classes = ["Test*"]
38+
python_functions = ["test_*"]
39+
addopts = [
40+
"-ra",
41+
"--strict-markers",
42+
"--strict-config",
43+
"--cov=generators",
44+
"--cov=solvers",
45+
"--cov-branch",
46+
"--cov-report=term-missing:skip-covered",
47+
"--cov-report=html:htmlcov",
48+
"--cov-report=xml:coverage.xml",
49+
# "--cov-fail-under=80", # Disabled for infrastructure testing
50+
]
51+
markers = [
52+
"unit: Unit tests",
53+
"integration: Integration tests",
54+
"slow: Slow tests that should be run less frequently",
55+
]
56+
57+
[tool.coverage.run]
58+
source = ["generators", "solvers"]
59+
omit = [
60+
"*/tests/*",
61+
"*/test_*.py",
62+
"*/__pycache__/*",
63+
"*/site-packages/*",
64+
]
65+
66+
[tool.coverage.report]
67+
precision = 2
68+
show_missing = true
69+
skip_covered = false
70+
# fail_under = 80 # Disabled for infrastructure testing
71+
exclude_lines = [
72+
"pragma: no cover",
73+
"def __repr__",
74+
"raise AssertionError",
75+
"raise NotImplementedError",
76+
"if __name__ == .__main__.:",
77+
"if TYPE_CHECKING:",
78+
"if typing.TYPE_CHECKING:",
79+
]
80+
81+
[tool.coverage.html]
82+
directory = "htmlcov"
83+
84+
[tool.coverage.xml]
85+
output = "coverage.xml"
86+
87+
[build-system]
88+
requires = ["poetry-core"]
89+
build-backend = "poetry.core.masonry.api"

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Test package initialization

tests/conftest.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""Shared pytest fixtures and configuration for all tests."""
2+
import os
3+
import sys
4+
import tempfile
5+
from pathlib import Path
6+
from typing import Generator, Dict, Any
7+
8+
import pytest
9+
10+
# Add project root to Python path
11+
PROJECT_ROOT = Path(__file__).parent.parent
12+
sys.path.insert(0, str(PROJECT_ROOT))
13+
14+
15+
@pytest.fixture
16+
def temp_dir() -> Generator[Path, None, None]:
17+
"""Create a temporary directory for test files."""
18+
with tempfile.TemporaryDirectory() as tmpdir:
19+
yield Path(tmpdir)
20+
21+
22+
@pytest.fixture
23+
def temp_file(temp_dir: Path) -> Generator[Path, None, None]:
24+
"""Create a temporary file for testing."""
25+
temp_path = temp_dir / "test_file.txt"
26+
temp_path.write_text("test content")
27+
yield temp_path
28+
29+
30+
@pytest.fixture
31+
def mock_config() -> Dict[str, Any]:
32+
"""Provide a mock configuration dictionary."""
33+
return {
34+
"debug": True,
35+
"max_iterations": 100,
36+
"timeout": 30,
37+
"output_dir": "/tmp/test_output",
38+
"model_name": "test_model",
39+
}
40+
41+
42+
@pytest.fixture
43+
def sample_puzzle_data() -> Dict[str, Any]:
44+
"""Provide sample puzzle data for testing."""
45+
return {
46+
"name": "test_puzzle",
47+
"description": "A test puzzle for unit testing",
48+
"input": [1, 2, 3, 4, 5],
49+
"expected_output": 15,
50+
"difficulty": "easy",
51+
"tags": ["math", "sum"],
52+
}
53+
54+
55+
@pytest.fixture
56+
def sample_code_snippet() -> str:
57+
"""Provide a sample code snippet for testing."""
58+
return """
59+
def solve(input_data):
60+
'''Solve the puzzle by summing all numbers.'''
61+
return sum(input_data)
62+
"""
63+
64+
65+
@pytest.fixture
66+
def mock_environment_variables(monkeypatch):
67+
"""Mock environment variables for testing."""
68+
test_env = {
69+
"TEST_MODE": "true",
70+
"LOG_LEVEL": "DEBUG",
71+
"OUTPUT_FORMAT": "json",
72+
}
73+
for key, value in test_env.items():
74+
monkeypatch.setenv(key, value)
75+
return test_env
76+
77+
78+
@pytest.fixture(scope="session")
79+
def project_root() -> Path:
80+
"""Return the project root directory."""
81+
return PROJECT_ROOT
82+
83+
84+
@pytest.fixture
85+
def generators_path(project_root: Path) -> Path:
86+
"""Return the path to the generators module."""
87+
return project_root / "generators"
88+
89+
90+
@pytest.fixture
91+
def solvers_path(project_root: Path) -> Path:
92+
"""Return the path to the solvers module."""
93+
return project_root / "solvers"
94+
95+
96+
@pytest.fixture
97+
def clean_imports():
98+
"""Clean up imports to ensure test isolation."""
99+
modules_to_remove = []
100+
for module in sys.modules:
101+
if module.startswith(("generators", "solvers")):
102+
modules_to_remove.append(module)
103+
104+
yield
105+
106+
for module in modules_to_remove:
107+
if module in sys.modules:
108+
del sys.modules[module]
109+
110+
111+
@pytest.fixture(autouse=True)
112+
def reset_random_seed():
113+
"""Reset random seed for reproducible tests."""
114+
import random
115+
import numpy as np
116+
117+
random.seed(42)
118+
np.random.seed(42)
119+
120+
yield
121+
122+
# Reset after test
123+
random.seed()
124+
np.random.seed()
125+
126+
127+
def pytest_configure(config):
128+
"""Configure pytest with custom settings."""
129+
config.addinivalue_line(
130+
"markers", "unit: Unit tests"
131+
)
132+
config.addinivalue_line(
133+
"markers", "integration: Integration tests"
134+
)
135+
config.addinivalue_line(
136+
"markers", "slow: Slow tests that should be run less frequently"
137+
)
138+
139+
140+
def pytest_collection_modifyitems(config, items):
141+
"""Modify test collection to add markers based on test location."""
142+
for item in items:
143+
# Add markers based on test file location
144+
if "unit" in str(item.fspath):
145+
item.add_marker(pytest.mark.unit)
146+
elif "integration" in str(item.fspath):
147+
item.add_marker(pytest.mark.integration)
148+
149+
# Add slow marker to tests with "slow" in their name
150+
if "slow" in item.nodeid.lower():
151+
item.add_marker(pytest.mark.slow)

tests/integration/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Integration test package initialization

0 commit comments

Comments
 (0)