Skip to content

Commit c71dd37

Browse files
fix: guard coverage with REPLAY mode check, add coverage_server unit tests
- Skip coverage collection when TUSK_DRIFT_MODE is set to non-REPLAY mode - Add 9 unit tests covering start/stop, mode gating, file filtering, error handling
1 parent 01a0b3a commit c71dd37

2 files changed

Lines changed: 125 additions & 0 deletions

File tree

drift/core/coverage_server.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ def start_coverage_collection() -> bool:
4040
if not os.environ.get("TUSK_COVERAGE"):
4141
return False
4242

43+
# Coverage collection only makes sense in REPLAY mode.
44+
# If TUSK_DRIFT_MODE is not set we still proceed for backwards compatibility.
45+
mode = os.environ.get("TUSK_DRIFT_MODE", "").upper()
46+
if mode and mode != "REPLAY":
47+
logger.debug("Coverage collection skipped: not in REPLAY mode (mode=%s)", mode)
48+
return False
49+
4350
try:
4451
import coverage as coverage_module
4552
except ImportError:

tests/unit/test_coverage_server.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""Tests for coverage_server.py - Coverage collection management."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
7+
import pytest
8+
9+
from drift.core import coverage_server
10+
from drift.core.coverage_server import (
11+
_is_user_file,
12+
start_coverage_collection,
13+
stop_coverage_collection,
14+
take_coverage_snapshot,
15+
)
16+
17+
18+
@pytest.fixture(autouse=True)
19+
def _reset_coverage_state():
20+
"""Reset module-level globals between tests."""
21+
yield
22+
stop_coverage_collection()
23+
# Also make sure _source_root is cleared
24+
coverage_server._source_root = None
25+
26+
27+
class TestStartCoverageCollection:
28+
"""Tests for start_coverage_collection function."""
29+
30+
def test_returns_false_when_tusk_coverage_not_set(self, monkeypatch):
31+
"""Should return False when TUSK_COVERAGE env var is not set."""
32+
monkeypatch.delenv("TUSK_COVERAGE", raising=False)
33+
monkeypatch.delenv("TUSK_DRIFT_MODE", raising=False)
34+
35+
result = start_coverage_collection()
36+
37+
assert result is False
38+
39+
def test_returns_false_when_mode_is_record(self, monkeypatch):
40+
"""Should return False when TUSK_DRIFT_MODE=RECORD even if TUSK_COVERAGE=true."""
41+
monkeypatch.setenv("TUSK_COVERAGE", "true")
42+
monkeypatch.setenv("TUSK_DRIFT_MODE", "RECORD")
43+
44+
result = start_coverage_collection()
45+
46+
assert result is False
47+
48+
def test_returns_true_when_mode_is_replay(self, monkeypatch, mocker):
49+
"""Should return True when TUSK_COVERAGE=true and mode is REPLAY."""
50+
monkeypatch.setenv("TUSK_COVERAGE", "true")
51+
monkeypatch.setenv("TUSK_DRIFT_MODE", "REPLAY")
52+
53+
mock_cov_instance = mocker.MagicMock()
54+
mock_coverage_module = mocker.MagicMock()
55+
mock_coverage_module.Coverage.return_value = mock_cov_instance
56+
mocker.patch.dict("sys.modules", {"coverage": mock_coverage_module})
57+
58+
result = start_coverage_collection()
59+
60+
assert result is True
61+
mock_cov_instance.start.assert_called_once()
62+
63+
def test_returns_true_when_mode_not_set(self, monkeypatch, mocker):
64+
"""Should return True when TUSK_COVERAGE=true and TUSK_DRIFT_MODE is not set (backwards compat)."""
65+
monkeypatch.setenv("TUSK_COVERAGE", "true")
66+
monkeypatch.delenv("TUSK_DRIFT_MODE", raising=False)
67+
68+
mock_cov_instance = mocker.MagicMock()
69+
mock_coverage_module = mocker.MagicMock()
70+
mock_coverage_module.Coverage.return_value = mock_cov_instance
71+
mocker.patch.dict("sys.modules", {"coverage": mock_coverage_module})
72+
73+
result = start_coverage_collection()
74+
75+
assert result is True
76+
mock_cov_instance.start.assert_called_once()
77+
78+
79+
class TestIsUserFile:
80+
"""Tests for _is_user_file function."""
81+
82+
def test_returns_false_for_site_packages(self):
83+
"""Should return False for paths containing site-packages."""
84+
assert _is_user_file("/usr/lib/python3.11/site-packages/requests/api.py") is False
85+
86+
def test_returns_false_for_venv_paths(self):
87+
"""Should return False for paths containing lib/python (venv pattern)."""
88+
assert _is_user_file("/app/venv/lib/python3.11/somepkg/mod.py") is False
89+
90+
def test_returns_true_for_source_root_file(self, monkeypatch):
91+
"""Should return True for files within the source root."""
92+
source_root = os.path.realpath("/tmp/myproject")
93+
monkeypatch.setattr(coverage_server, "_source_root", source_root)
94+
95+
assert _is_user_file(os.path.join(source_root, "app", "main.py")) is True
96+
97+
98+
class TestTakeCoverageSnapshot:
99+
"""Tests for take_coverage_snapshot function."""
100+
101+
def test_raises_runtime_error_when_not_initialized(self):
102+
"""Should raise RuntimeError when coverage is not initialized."""
103+
with pytest.raises(RuntimeError, match="Coverage not initialized"):
104+
take_coverage_snapshot()
105+
106+
107+
class TestStopCoverageCollection:
108+
"""Tests for stop_coverage_collection function."""
109+
110+
def test_cleans_up_state(self, monkeypatch, mocker):
111+
"""Should stop coverage instance and set it to None."""
112+
mock_cov = mocker.MagicMock()
113+
monkeypatch.setattr(coverage_server, "_cov_instance", mock_cov)
114+
115+
stop_coverage_collection()
116+
117+
mock_cov.stop.assert_called_once()
118+
assert coverage_server._cov_instance is None

0 commit comments

Comments
 (0)