Skip to content

Commit 5341f6e

Browse files
committed
Stage 4
1 parent 4dc1fe9 commit 5341f6e

File tree

5 files changed

+145
-8
lines changed

5 files changed

+145
-8
lines changed

codetracer-python-recorder/tests/python/__init__.py

Whitespace-only changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Shared helpers for Python test suites."""
2+
from __future__ import annotations
3+
4+
from pathlib import Path
5+
6+
__all__ = ["ensure_trace_dir"]
7+
8+
9+
def ensure_trace_dir(root: Path, name: str = "trace_out") -> Path:
10+
"""Return an existing trace directory under ``root``, creating it if needed."""
11+
target = root / name
12+
target.mkdir(exist_ok=True)
13+
return target
14+

codetracer-python-recorder/tests/python/test_monitoring_events.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import codetracer_python_recorder as codetracer
1111

12+
from .support import ensure_trace_dir
13+
1214

1315
@dataclass
1416
class ParsedTrace:
@@ -80,8 +82,7 @@ def _write_script(tmp: Path) -> Path:
8082
def test_py_start_line_and_return_events_are_recorded(tmp_path: Path) -> None:
8183
# Arrange: create a script and start tracing with activation restricted to that file
8284
script = _write_script(tmp_path)
83-
out_dir = tmp_path / "trace_out"
84-
out_dir.mkdir()
85+
out_dir = ensure_trace_dir(tmp_path)
8586

8687
session = codetracer.start(out_dir, format=codetracer.TRACE_JSON, start_on_enter=script)
8788

@@ -132,8 +133,7 @@ def test_py_start_line_and_return_events_are_recorded(tmp_path: Path) -> None:
132133

133134

134135
def test_start_while_active_raises(tmp_path: Path) -> None:
135-
out_dir = tmp_path / "trace_out"
136-
out_dir.mkdir()
136+
out_dir = ensure_trace_dir(tmp_path)
137137
session = codetracer.start(out_dir, format=codetracer.TRACE_JSON)
138138
try:
139139
with pytest.raises(RuntimeError):
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""Unit tests for session helper functions."""
2+
from __future__ import annotations
3+
4+
from pathlib import Path
5+
6+
import pytest
7+
8+
from codetracer_python_recorder import session
9+
10+
11+
def test_coerce_format_accepts_supported_aliases() -> None:
12+
assert session._coerce_format("json") == "json"
13+
assert session._coerce_format("JSON") == "json"
14+
assert session._coerce_format("binary") == "binary"
15+
16+
17+
def test_coerce_format_rejects_unknown_value() -> None:
18+
with pytest.raises(ValueError) as excinfo:
19+
session._coerce_format("yaml")
20+
assert "unsupported trace format" in str(excinfo.value)
21+
22+
23+
def test_validate_trace_path_expands_user(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
24+
home_dir = tmp_path / "home"
25+
home_dir.mkdir()
26+
target = home_dir / "traces"
27+
28+
monkeypatch.setenv("HOME", str(home_dir))
29+
path = session._validate_trace_path(Path("~/traces"))
30+
31+
assert path == target
32+
assert path.parent == home_dir
33+
34+
35+
def test_validate_trace_path_rejects_file(tmp_path: Path) -> None:
36+
file_path = tmp_path / "trace.bin"
37+
file_path.write_text("stub")
38+
with pytest.raises(ValueError):
39+
session._validate_trace_path(file_path)
40+
41+
42+
def test_normalize_activation_path_handles_none() -> None:
43+
assert session._normalize_activation_path(None) is None
44+
45+
46+
def test_normalize_activation_path_expands_user(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
47+
home_dir = tmp_path / "home"
48+
home_dir.mkdir()
49+
script = home_dir / "script.py"
50+
script.write_text("print('hi')\n")
51+
52+
monkeypatch.setenv("HOME", str(home_dir))
53+
result = session._normalize_activation_path("~/script.py")
54+
55+
assert result == str(script)
56+
57+
58+
@pytest.fixture(autouse=True)
59+
def clear_active_session() -> None:
60+
session._active_session = None
61+
yield
62+
session._active_session = None
63+
64+
65+
def test_trace_session_stop_clears_global(monkeypatch: pytest.MonkeyPatch) -> None:
66+
called = {"stop": False, "start": False}
67+
68+
def fake_start(*args, **kwargs) -> None:
69+
called["start"] = True
70+
71+
def fake_stop() -> None:
72+
called["stop"] = True
73+
74+
monkeypatch.setattr(session, "_start_backend", fake_start)
75+
monkeypatch.setattr(session, "_stop_backend", fake_stop)
76+
monkeypatch.setattr(session, "_is_tracing_backend", lambda: session._active_session is not None)
77+
78+
session._active_session = session.TraceSession(path=Path("/tmp"), format="json")
79+
session.stop()
80+
assert session._active_session is None
81+
assert called["stop"] is True
82+
83+
84+
def test_trace_session_flush_noop_when_inactive(monkeypatch: pytest.MonkeyPatch) -> None:
85+
monkeypatch.setattr(session, "_is_tracing_backend", lambda: False)
86+
flushed = []
87+
88+
def fake_flush() -> None:
89+
flushed.append(True)
90+
91+
monkeypatch.setattr(session, "_flush_backend", fake_flush)
92+
session.flush()
93+
assert flushed == []
94+
95+
96+
def test_trace_context_manager_starts_and_stops(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
97+
calls = {"start": [], "stop": []}
98+
99+
trace_state = {"active": False}
100+
101+
def fake_start(path: str, fmt: str, activation: str | None) -> None:
102+
trace_state["active"] = True
103+
calls["start"].append((Path(path), fmt, activation))
104+
105+
def fake_stop() -> None:
106+
trace_state["active"] = False
107+
calls["stop"].append(True)
108+
109+
monkeypatch.setattr(session, "_start_backend", fake_start)
110+
monkeypatch.setattr(session, "_stop_backend", fake_stop)
111+
monkeypatch.setattr(session, "_is_tracing_backend", lambda: trace_state["active"])
112+
monkeypatch.setattr(session, "_flush_backend", lambda: None)
113+
114+
target = tmp_path / "trace"
115+
target.mkdir()
116+
with session.trace(target, format="binary") as handle:
117+
assert isinstance(handle, session.TraceSession)
118+
assert handle.path == target.expanduser()
119+
assert handle.format == "binary"
120+
121+
assert calls["start"] == [(target, "binary", None)]
122+
assert calls["stop"] == [True]

design-docs/test-suite-improvement-plan.status.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
- ✅ Stage 3 – Activation Guard Rails: added unit tests around
1212
`ActivationController` covering activation start, non-matching frames, and
1313
deactivation behaviour; existing runtime integration tests continue to pass.
14-
- ⏳ Stage 4 – Python Unit Coverage: not started.
14+
- ✅ Stage 4 – Python Unit Coverage: added `tests/python/unit/test_session_helpers.py`
15+
for facade utilities and introduced `tests/python/support` for shared
16+
fixtures; updated monitoring tests to use the helper directory builder.
1517
- ⏳ Stage 5 – CI & Coverage Instrumentation: not started.
1618
- ⏳ Stage 6 – Cleanup & Documentation: not started.
1719

1820
## Next Actions
19-
1. Advance Stage 4 by creating Python unit tests for facade helpers and shared
20-
fixtures.
21-
2. Plan Stage 5 CI split once the new unit coverage lands.
21+
1. Plan Stage 5 CI split so Rust/Python harness results appear separately.
22+
2. Draft coverage instrumentation approach before implementation (Stage 5).

0 commit comments

Comments
 (0)