Skip to content
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
52 changes: 52 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,55 @@ events.*
*.pkl
*.h5
*.dat

# Testing
.pytest_cache/
.coverage
htmlcov/
coverage.xml
*.cover
*.py,cover
.hypothesis/
.tox/
.nox/
*.log

# Claude
.claude/*

# Virtual environments
venv/
ENV/
env/
.venv/

# Poetry
dist/
# Note: Don't ignore poetry.lock - it should be committed

# Build artifacts
build/
develop-eggs/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.project
.pydevproject
.settings/

# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
3,582 changes: 3,582 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
[tool.poetry]
name = "sefa"
version = "0.1.0"
description = "Closed-Form Factorization of Latent Semantics in GANs"
authors = ["SeFa Contributors"]
readme = "README.md"
packages = [{include = "models"}]

[tool.poetry.dependencies]
python = "^3.8"
torch = ">=1.6.0"
numpy = ">=1.18.0"
streamlit = ">=0.70.0"
tqdm = ">=4.50.0"

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

[tool.poetry.scripts]
test = "pytest:main"
tests = "pytest:main"

[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-ra",
"--strict-markers",
"--cov=sefa",
"--cov=utils",
"--cov=interface",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=0",
"--tb=short",
"-v"
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow tests that take significant time"
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning"
]

[tool.coverage.run]
source = ["sefa", "utils", "interface"]
branch = true
omit = [
"*/tests/*",
"*/__init__.py",
"*/setup.py",
"*/SessionState.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"
]
precision = 2
show_missing = true
skip_covered = false
fail_under = 80

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

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

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
133 changes: 133 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Shared pytest fixtures and configuration for all tests."""
import os
import tempfile
import shutil
from pathlib import Path
from typing import Generator, Dict, Any
import pytest
import torch
import numpy as np


@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path)


@pytest.fixture
def mock_model_config() -> Dict[str, Any]:
"""Provide a mock model configuration for testing."""
return {
'model_name': 'test_model',
'resolution': 256,
'latent_dim': 512,
'num_layers': 8,
'device': 'cpu'
}


@pytest.fixture
def sample_latent_codes() -> torch.Tensor:
"""Generate sample latent codes for testing."""
batch_size = 4
latent_dim = 512
return torch.randn(batch_size, latent_dim)


@pytest.fixture
def sample_numpy_array() -> np.ndarray:
"""Generate a sample numpy array for testing."""
return np.random.randn(10, 512).astype(np.float32)


@pytest.fixture
def mock_generator_output() -> torch.Tensor:
"""Mock output from a generator model."""
batch_size = 4
channels = 3
height = width = 256
return torch.randn(batch_size, channels, height, width)


@pytest.fixture
def device() -> torch.device:
"""Get the appropriate device for testing (CPU by default)."""
return torch.device('cpu')


@pytest.fixture
def test_data_dir(tmp_path: Path) -> Path:
"""Create a temporary data directory with test files."""
data_dir = tmp_path / "test_data"
data_dir.mkdir()

# Create some sample files
(data_dir / "latents.npy").touch()
(data_dir / "config.json").touch()

return data_dir


@pytest.fixture(autouse=True)
def reset_torch_seed():
"""Reset random seeds before each test for reproducibility."""
torch.manual_seed(42)
np.random.seed(42)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(42)


@pytest.fixture
def mock_streamlit_session():
"""Mock Streamlit session state for interface testing."""
class MockSessionState:
def __init__(self):
self.model = None
self.latent_codes = None
self.boundaries = {}
self.current_step = 0

return MockSessionState()


@pytest.fixture(scope="session")
def test_models_dir() -> Path:
"""Get the models directory path."""
return Path(__file__).parent.parent / "models"


@pytest.fixture
def cleanup_cuda():
"""Cleanup CUDA memory after tests."""
yield
if torch.cuda.is_available():
torch.cuda.empty_cache()


def pytest_configure(config):
"""Configure pytest with custom settings."""
# Add custom markers
config.addinivalue_line(
"markers", "gpu: mark test to run only when GPU is available"
)
config.addinivalue_line(
"markers", "requires_model: mark test that requires pre-trained model"
)


def pytest_collection_modifyitems(config, items):
"""Modify test collection to handle special cases."""
for item in items:
# Skip GPU tests if no GPU available
if "gpu" in item.keywords and not torch.cuda.is_available():
skip_gpu = pytest.mark.skip(reason="GPU not available")
item.add_marker(skip_gpu)


@pytest.fixture
def capture_stdout(capsys):
"""Capture stdout for testing print statements."""
yield capsys
Empty file added tests/integration/__init__.py
Empty file.
Loading