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
42 changes: 41 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
pytest_cache/
.coverage.local.*
.coverage.*.local
test-results/
.test_cache/

# Translations
*.mo
Expand Down Expand Up @@ -99,7 +104,7 @@ ipython_config.py
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# poetry.lock is NOT ignored - we want to track it for reproducibility

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
Expand Down Expand Up @@ -168,3 +173,38 @@ cython_debug/
*tmp*

*.exe

# Claude Code settings
.claude/*

# Virtual environments (additional patterns)
.venv*/
venv*/
virtualenv/
.virtualenvs/

# IDE and editor files
.vscode/
*.swp
*.swo
*.swn
*.bak
*~
.DS_Store

# Build artifacts
*.pyd
*.whl
dist/
build/
eggs/
.eggs/
*.egg-info/
*.egg

# Testing artifacts
.mutmut-cache
.stestr/
test-reports/
junit.xml
.benchmarks/
2,329 changes: 2,329 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

121 changes: 121 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
[tool.poetry]
name = "avatarrex"
version = "0.1.0"
description = "AvatarREx: Real-time Expressive Full-body Avatars"
authors = ["Your Name <you@example.com>"]
readme = "README.md"
license = "MIT"
package-mode = false

[tool.poetry.dependencies]
python = "^3.8"
glfw = "^2.4.0"
libigl = "*"
joblib = "^1.0.0"
numpy = "^1.24.0"
opencv-python = "^4.5.5"
plyfile = "^1.0.0"
PyOpenGL = "^3.1.0"
pyrender = "^0.1.45"
# pytorch3d = "^0.7.4" # Temporarily commented - install manually if needed
pyyaml = "^6.0"
scikit-image = "^0.21.0"
scikit-learn = "^1.3.0"
screeninfo = "^0.8.1"
setuptools = "*"
torch = "^2.0.1"
torchvision = "^0.15.2"
tqdm = "^4.65.0"
trimesh = "^3.23.0"

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



[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py", "tests.py"]
python_classes = ["Test*", "*Tests"]
python_functions = ["test_*", "*_test"]
addopts = [
"-ra",
"--strict-markers",
"--strict-config",
"--cov=.",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
# "--cov-fail-under=80", # Commented out for initial setup
"-vv",
"--tb=short",
"--maxfail=3",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests",
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning",
]

[tool.coverage.run]
source = ["."]
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
"*/site-packages/*",
"*/distutils/*",
"*/venv/*",
"*/virtualenv/*",
"setup.py",
"*/migrations/*",
"*/.tox/*",
"*/.eggs/*",
"docs/*",
"htmlcov/*",
".git/*",
"gaussians/diff_gaussian_rasterization_depth_alpha/*",
"network/styleunet/setup.py",
"utils/posevocab_custom_ops/setup.py",
"utils/root_finding/setup.py",
]

[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = true
fail_under = 80
exclude_lines = [
"pragma: no cover",
"def __repr__",
"def __str__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"if typing.TYPE_CHECKING:",
"@abstractmethod",
"@abc.abstractmethod",
"except ImportError:",
"class .*Protocol:",
"class .*typing.Protocol:",
]

[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.
163 changes: 163 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
"""Shared pytest fixtures and configuration for all tests."""

import os
import tempfile
from pathlib import Path
from typing import Generator, Dict, Any

import pytest
import yaml
import numpy as np
import torch


@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
"""Create a temporary directory for test files."""
with tempfile.TemporaryDirectory() as tmpdir:
yield Path(tmpdir)


@pytest.fixture
def sample_config() -> Dict[str, Any]:
"""Create a sample configuration dictionary for testing."""
return {
"exp_name": "test_experiment",
"dataset": {
"name": "test_dataset",
"batch_size": 4,
"num_workers": 2,
},
"model": {
"name": "test_model",
"learning_rate": 0.001,
"hidden_dim": 128,
},
"training": {
"num_epochs": 10,
"checkpoint_interval": 5,
"log_interval": 100,
},
"paths": {
"data_dir": "/tmp/test_data",
"output_dir": "/tmp/test_output",
"checkpoint_dir": "/tmp/test_checkpoints",
},
}


@pytest.fixture
def mock_config_file(temp_dir: Path, sample_config: Dict[str, Any]) -> Path:
"""Create a temporary config file with sample configuration."""
config_path = temp_dir / "test_config.yaml"
with open(config_path, "w") as f:
yaml.dump(sample_config, f)
return config_path


@pytest.fixture
def sample_tensor_data() -> Dict[str, torch.Tensor]:
"""Create sample tensor data for testing."""
return {
"positions": torch.randn(100, 3),
"features": torch.randn(100, 64),
"labels": torch.randint(0, 10, (100,)),
"batch": torch.randn(4, 3, 224, 224),
}


@pytest.fixture
def sample_numpy_data() -> Dict[str, np.ndarray]:
"""Create sample numpy array data for testing."""
return {
"points": np.random.randn(100, 3).astype(np.float32),
"colors": np.random.rand(100, 3).astype(np.float32),
"normals": np.random.randn(100, 3).astype(np.float32),
"indices": np.random.randint(0, 100, size=(50, 3), dtype=np.int32),
}


@pytest.fixture
def mock_model_checkpoint(temp_dir: Path) -> Path:
"""Create a mock model checkpoint file."""
checkpoint_path = temp_dir / "model_checkpoint.pth"
checkpoint_data = {
"epoch": 10,
"model_state_dict": {"layer.weight": torch.randn(10, 10)},
"optimizer_state_dict": {"param_groups": []},
"loss": 0.123,
}
torch.save(checkpoint_data, checkpoint_path)
return checkpoint_path


@pytest.fixture
def mock_data_file(temp_dir: Path, sample_numpy_data: Dict[str, np.ndarray]) -> Path:
"""Create a mock data file in numpy format."""
data_path = temp_dir / "test_data.npz"
np.savez_compressed(data_path, **sample_numpy_data)
return data_path


@pytest.fixture
def mock_obj_file(temp_dir: Path) -> Path:
"""Create a simple mock OBJ file for testing."""
obj_path = temp_dir / "test_mesh.obj"
obj_content = """# Test OBJ file
v 0.0 0.0 0.0
v 1.0 0.0 0.0
v 0.0 1.0 0.0
v 0.0 0.0 1.0
f 1 2 3
f 1 2 4
f 1 3 4
f 2 3 4
"""
with open(obj_path, "w") as f:
f.write(obj_content)
return obj_path


@pytest.fixture
def mock_environment_variables(monkeypatch):
"""Mock environment variables for testing."""
test_env_vars = {
"CUDA_VISIBLE_DEVICES": "0",
"PYTHONPATH": "/tmp/test_path",
"DATA_ROOT": "/tmp/test_data_root",
}
for key, value in test_env_vars.items():
monkeypatch.setenv(key, value)
return test_env_vars


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


@pytest.fixture
def device():
"""Get the appropriate device for testing (CPU or CUDA if available)."""
return torch.device("cuda" if torch.cuda.is_available() else "cpu")


@pytest.fixture
def small_batch_size():
"""Return a small batch size suitable for testing."""
return 2


@pytest.fixture
def cleanup_gpu_memory():
"""Cleanup GPU memory after test execution."""
yield
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.synchronize()
Empty file added tests/integration/__init__.py
Empty file.
Loading