Skip to content

feat: Set up comprehensive Python testing infrastructure with Poetry #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,6 @@ dmypy.json

# Pyre type checker
.pyre/

# Claude settings
.claude/*
2,257 changes: 2,257 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
[tool.poetry]
name = "simmim"
version = "0.1.0"
description = "SimMIM: A Simple Framework for Masked Image Modeling"
authors = ["Your Name <email@example.com>"]
readme = "README.md"
license = "MIT"
package-mode = false

[tool.poetry.dependencies]
python = "^3.8"
pyyaml = "*"
scipy = "*"
termcolor = "*"
timm = "*"
yacs = "*"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.0"


[tool.pytest.ini_options]
minversion = "6.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-ra",
"--strict-markers",
"--strict-config",
"--cov=data",
"--cov=models",
"--cov-branch",
"--cov-report=term-missing",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Tests that take a long time to run",
]

[tool.coverage.run]
source = ["data", "models", "*.py"]
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
"*/conftest.py",
"setup.py",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"def __str__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
show_missing = true
precision = 2

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
3 changes: 3 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
# Simple test runner script
poetry run pytest "$@"
Empty file added tests/__init__.py
Empty file.
123 changes: 123 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import os
import tempfile
import shutil
from pathlib import Path
import pytest
import yaml
from unittest.mock import MagicMock


@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path)


@pytest.fixture
def mock_config():
"""Create a mock configuration object."""
config = MagicMock()
config.MODEL = MagicMock()
config.MODEL.TYPE = 'swin'
config.MODEL.NAME = 'swin_base'
config.MODEL.DROP_PATH_RATE = 0.1
config.MODEL.NUM_CLASSES = 1000

config.DATA = MagicMock()
config.DATA.IMG_SIZE = 224
config.DATA.BATCH_SIZE = 32
config.DATA.DATA_PATH = '/path/to/data'

config.TRAIN = MagicMock()
config.TRAIN.EPOCHS = 100
config.TRAIN.BASE_LR = 1e-4
config.TRAIN.WEIGHT_DECAY = 0.05

return config


@pytest.fixture
def sample_yaml_config(temp_dir):
"""Create a sample YAML configuration file."""
config_data = {
'MODEL': {
'TYPE': 'vit',
'NAME': 'vit_base',
'NUM_CLASSES': 1000,
'DROP_PATH_RATE': 0.1
},
'DATA': {
'IMG_SIZE': 224,
'BATCH_SIZE': 64,
'DATA_PATH': '/data/imagenet'
},
'TRAIN': {
'EPOCHS': 300,
'BASE_LR': 5e-4,
'WEIGHT_DECAY': 0.05
}
}

config_path = temp_dir / 'test_config.yaml'
with open(config_path, 'w') as f:
yaml.dump(config_data, f)

return config_path


@pytest.fixture
def mock_dataset():
"""Create a mock dataset object."""
dataset = MagicMock()
dataset.__len__ = MagicMock(return_value=1000)
dataset.__getitem__ = MagicMock(return_value=(MagicMock(), 0))
return dataset


@pytest.fixture
def mock_model():
"""Create a mock model object."""
model = MagicMock()
model.forward = MagicMock(return_value=MagicMock(shape=(32, 1000)))
model.parameters = MagicMock(return_value=[MagicMock() for _ in range(10)])
return model


@pytest.fixture
def sample_image_tensor():
"""Create a sample image tensor for testing."""
try:
import torch
return torch.randn(1, 3, 224, 224)
except ImportError:
# Return a mock if torch is not available during testing
return MagicMock(shape=(1, 3, 224, 224))


@pytest.fixture
def captured_output():
"""Capture stdout and stderr for testing print outputs."""
import sys
from io import StringIO

old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = StringIO()
sys.stderr = StringIO()

yield sys.stdout, sys.stderr

sys.stdout = old_stdout
sys.stderr = old_stderr


@pytest.fixture(autouse=True)
def reset_modules():
"""Reset imported modules to ensure clean test state."""
import sys
modules_to_reset = [m for m in sys.modules.keys() if m.startswith(('models', 'data'))]
for module in modules_to_reset:
sys.modules.pop(module, None)
yield
Empty file added tests/integration/__init__.py
Empty file.
102 changes: 102 additions & 0 deletions tests/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import pytest
import sys
import os
from pathlib import Path


class TestSetupValidation:
"""Validation tests to ensure the testing infrastructure is properly configured."""

def test_pytest_is_importable(self):
"""Test that pytest can be imported successfully."""
import pytest
assert pytest is not None

def test_pytest_cov_is_importable(self):
"""Test that pytest-cov can be imported successfully."""
import pytest_cov
assert pytest_cov is not None

def test_pytest_mock_is_importable(self):
"""Test that pytest-mock can be imported successfully."""
import pytest_mock
assert pytest_mock is not None

def test_project_structure_exists(self):
"""Test that the expected project structure exists."""
project_root = Path(__file__).parent.parent

assert (project_root / 'tests').exists()
assert (project_root / 'tests' / '__init__.py').exists()
assert (project_root / 'tests' / 'unit').exists()
assert (project_root / 'tests' / 'unit' / '__init__.py').exists()
assert (project_root / 'tests' / 'integration').exists()
assert (project_root / 'tests' / 'integration' / '__init__.py').exists()
assert (project_root / 'tests' / 'conftest.py').exists()

def test_conftest_fixtures_available(self, temp_dir, mock_config, mock_dataset, mock_model):
"""Test that conftest fixtures are available and working."""
assert temp_dir is not None
assert temp_dir.exists()

assert mock_config is not None
assert hasattr(mock_config, 'MODEL')
assert hasattr(mock_config, 'DATA')
assert hasattr(mock_config, 'TRAIN')

assert mock_dataset is not None
assert callable(getattr(mock_dataset, '__len__', None))
assert callable(getattr(mock_dataset, '__getitem__', None))

assert mock_model is not None
assert hasattr(mock_model, 'forward')
assert hasattr(mock_model, 'parameters')

@pytest.mark.unit
def test_unit_marker_works(self):
"""Test that the unit test marker is properly configured."""
assert True

@pytest.mark.integration
def test_integration_marker_works(self):
"""Test that the integration test marker is properly configured."""
assert True

@pytest.mark.slow
def test_slow_marker_works(self):
"""Test that the slow test marker is properly configured."""
assert True

def test_project_imports_work(self):
"""Test that project modules can be imported."""
import importlib
import importlib.util

# Check if modules exist without importing them
data_spec = importlib.util.find_spec("data")
models_spec = importlib.util.find_spec("models")

assert data_spec is not None, "data module not found"
assert models_spec is not None, "models module not found"

def test_coverage_configured(self):
"""Test that coverage is properly configured."""
import subprocess
result = subprocess.run(
[sys.executable, '-m', 'pytest', '--help'],
capture_output=True,
text=True
)
assert '--cov' in result.stdout
assert '--cov-report' in result.stdout


def test_fixtures_can_be_used(temp_dir, mock_config, sample_yaml_config, mock_dataset, mock_model, sample_image_tensor, captured_output):
"""Test that all expected fixtures can be used."""
assert temp_dir.exists()
assert mock_config is not None
assert sample_yaml_config.exists()
assert mock_dataset is not None
assert mock_model is not None
assert sample_image_tensor is not None
assert captured_output is not None
Empty file added tests/unit/__init__.py
Empty file.