Skip to content

Commit a1bf072

Browse files
raylimCopilot
andcommitted
test: add shared fixtures and improve test infrastructure (#105)
- Add project-wide tests/conftest.py with common path fixtures, seed reset, mocks, and skip marks (skip_if_missing_testdata, skip_if_no_gpu) - Implement --use-gpu pytest option via pytest_addoption and use_gpu fixture so the gpu_compatible marker is functional - Guard all CUDA calls in reset_seed behind torch.cuda.is_available() to prevent failures in CPU-only environments - Add CLI-specific tests/mussel/cli/conftest.py with test_data_path_cli alias fixture - Register gpu_compatible marker in pyproject.toml Addresses review comments: - No unused heavy imports at collection time - --use-gpu flag properly registered via pytest_addoption - CUDA API calls guarded for CPU-only torch installs Co-authored-by: Copilot <[email protected]>
1 parent 1cdb53a commit a1bf072

3 files changed

Lines changed: 148 additions & 0 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ addopts = "-v --tb=short"
142142
markers = [
143143
"slow: marks tests as slow and require significant time to run (deselect with '-m \"not slow\"')",
144144
"integration: marks integration tests that perform actual model inference",
145+
"gpu_compatible: marks tests that support --use-gpu flag for faster execution (use the use_gpu fixture to check)",
145146
]
146147

147148
[project.scripts]

tests/conftest.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
"""
2+
Shared pytest configuration and fixtures for Mussel tests.
3+
4+
This conftest.py provides common test fixtures and configuration
5+
used across multiple test files.
6+
"""
7+
8+
import os
9+
from pathlib import Path
10+
from typing import Generator
11+
12+
import pytest
13+
import torch
14+
15+
16+
@pytest.fixture
17+
def test_data_path() -> str:
18+
"""Return path to test data directory."""
19+
return os.path.join(os.path.dirname(__file__), "testdata")
20+
21+
22+
@pytest.fixture
23+
def svs_slide_path(test_data_path: str) -> str:
24+
"""Return path to test slide file."""
25+
return os.path.join(test_data_path, "948176.svs")
26+
27+
28+
@pytest.fixture
29+
def patch_h5_path(test_data_path: str) -> str:
30+
"""Return path to test patch HDF5 file."""
31+
return os.path.join(test_data_path, "948176.patch.h5")
32+
33+
34+
@pytest.fixture
35+
def classifier_pkl_path(test_data_path: str) -> str:
36+
"""Return path to test classifier pickle file."""
37+
return os.path.join(test_data_path, "simple_classifier.pkl")
38+
39+
40+
@pytest.fixture
41+
def annotation_csv_path(test_data_path: str) -> str:
42+
"""Return path to test annotation CSV file."""
43+
return os.path.join(test_data_path, "948176.annotation.csv")
44+
45+
46+
@pytest.fixture
47+
def features_pt_path(test_data_path: str) -> str:
48+
"""Return path to test features pickle file."""
49+
return os.path.join(test_data_path, "948176.features.pt")
50+
51+
52+
@pytest.fixture
53+
def class_embedding_pt_path(test_data_path: str) -> str:
54+
"""Return path to test class embedding file."""
55+
return os.path.join(test_data_path, "class_embedding.pt")
56+
57+
58+
def pytest_addoption(parser: pytest.Parser) -> None:
59+
parser.addoption(
60+
"--use-gpu",
61+
action="store_true",
62+
default=False,
63+
help="Run gpu_compatible tests on GPU instead of CPU.",
64+
)
65+
66+
67+
@pytest.fixture
68+
def use_gpu(request: pytest.FixtureRequest) -> bool:
69+
"""Return True if --use-gpu was passed and a GPU is available."""
70+
return request.config.getoption("--use-gpu") and torch.cuda.is_available()
71+
72+
73+
@pytest.fixture(autouse=True)
74+
def reset_seed() -> Generator[None, None, None]:
75+
"""Reset random seed to ensure reproducible test results."""
76+
seed = 42
77+
torch.manual_seed(seed)
78+
if torch.cuda.is_available():
79+
torch.cuda.manual_seed_all(seed)
80+
torch.backends.cudnn.deterministic = True
81+
torch.backends.cudnn.benchmark = False
82+
yield
83+
if torch.cuda.is_available():
84+
torch.cuda.empty_cache()
85+
86+
87+
@pytest.fixture
88+
def cpu_device() -> torch.device:
89+
"""Return CPU device for testing."""
90+
return torch.device("cpu")
91+
92+
93+
@pytest.fixture
94+
def mock_model_factory():
95+
"""Provide a mock model factory for testing without loading models."""
96+
from unittest.mock import MagicMock
97+
98+
mock_model = MagicMock()
99+
mock_model_fun = MagicMock(return_value=torch.randn(1, 2048))
100+
mock_model.get_model_fun.return_value = mock_model_fun
101+
mock_model.get_preprocessing_fun.return_value = None
102+
103+
mock_factory = MagicMock()
104+
mock_factory.return_value = MagicMock(get_model=MagicMock(return_value=mock_model))
105+
106+
return mock_factory
107+
108+
109+
@pytest.fixture
110+
def mock_segment_tissue():
111+
"""Provide mock tissue segmentation results."""
112+
from unittest.mock import MagicMock
113+
114+
mock_coords = [[0, 0], [256, 0], [0, 256]]
115+
mock_polygon = MagicMock()
116+
mock_grid = MagicMock()
117+
118+
mock_segment = MagicMock(return_value=(mock_polygon, mock_grid, mock_coords, None))
119+
120+
return mock_segment
121+
122+
123+
# Skip test if required test data is unavailable
124+
skip_if_missing_testdata = pytest.mark.skipif(
125+
not Path(os.path.dirname(__file__), "testdata").exists(),
126+
reason="Test data not available - requires testdata directory",
127+
)
128+
129+
# Skip test if GPU is unavailable
130+
skip_if_no_gpu = pytest.mark.skipif(
131+
not torch.cuda.is_available(),
132+
reason="GPU is required for this test",
133+
)

tests/mussel/cli/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""
2+
Shared pytest configuration and fixtures for CLI tests.
3+
4+
Fixtures from tests/conftest.py are automatically available here via pytest's
5+
conftest discovery — no explicit imports needed.
6+
"""
7+
8+
import pytest
9+
10+
11+
@pytest.fixture
12+
def test_data_path_cli(test_data_path: str) -> str:
13+
"""Return path to test data directory (alias for CLI tests)."""
14+
return test_data_path

0 commit comments

Comments
 (0)