Skip to content

Commit dd06045

Browse files
test(ci): harden msvcrt lock test and document CI job overlap
1 parent a000e30 commit dd06045

3 files changed

Lines changed: 47 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ jobs:
6868
- name: Install dev dependencies (Flask + pytest)
6969
run: pip install -r requirements-dev.txt
7070

71+
# Full suite gate (pyproject addopts include --cov + 60% fail-under).
7172
- name: Run tests
7273
run: pytest --tb=short -q
7374

@@ -123,7 +124,8 @@ jobs:
123124
- name: Install dev dependencies
124125
run: pip install -r requirements-dev.txt
125126

126-
# Subset run: skip fail-under (full suite enforces 60% in pytest job).
127+
# Intentional overlap with pytest job: re-runs API/search subset with --cov
128+
# and uploads coverage.xml per OS (pytest job is the full-suite gate).
127129
- name: Run integration tests with coverage
128130
run: pytest tests/test_api_integration.py tests/test_search.py -v --cov=api --cov=utils --cov-report=xml --cov-fail-under=0
129131

tests/test_export_state_store.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ def _wait_for_file(path: Path, timeout: float = 5.0) -> None:
2121
time.sleep(0.01)
2222
raise AssertionError(f"timed out waiting for {path}")
2323

24+
25+
def _assert_file_stays_absent(path: Path, timeout: float = 0.5) -> None:
26+
"""While parent holds the lock, child must not write *path* (still blocked)."""
27+
deadline = time.monotonic() + timeout
28+
while time.monotonic() < deadline:
29+
if path.is_file():
30+
raise AssertionError(f"{path} appeared while parent still holds the lock")
31+
time.sleep(0.01)
32+
2433
from utils.export_state_store import (
2534
atomic_write_export_state,
2635
export_state_lock,
@@ -128,33 +137,41 @@ def test_export_state_lock_blocks_second_process(tmp_path: Path) -> None:
128137
"""While the parent holds ``msvcrt`` lock, a child process cannot acquire it."""
129138
state_file = tmp_path / "export_state.json"
130139
state_file.write_text("{}", encoding="utf-8")
131-
started_marker = tmp_path / "child_started.lock"
140+
attempting_marker = tmp_path / "child_attempting.lock"
132141
acquired_marker = tmp_path / "child_acquired.lock"
133142
child = (
134143
"import sys\n"
135144
"from pathlib import Path\n"
136145
f"sys.path.insert(0, {str(REPO_ROOT)!r})\n"
137146
"from utils.export_state_store import export_state_lock\n"
138-
"path, started, acquired = sys.argv[1], sys.argv[2], sys.argv[3]\n"
139-
"Path(started).write_text('ok', encoding='utf-8')\n"
147+
"path, attempting, acquired = sys.argv[1], sys.argv[2], sys.argv[3]\n"
148+
"Path(attempting).write_text('ok', encoding='utf-8')\n"
140149
"with export_state_lock(path):\n"
141150
" Path(acquired).write_text('ok', encoding='utf-8')\n"
142151
)
143-
with export_state_lock(str(state_file)):
144-
proc = subprocess.Popen(
145-
[
146-
sys.executable,
147-
"-c",
148-
child,
149-
str(state_file),
150-
str(started_marker),
151-
str(acquired_marker),
152-
],
153-
cwd=str(REPO_ROOT),
154-
)
155-
_wait_for_file(started_marker)
156-
assert not acquired_marker.is_file(), (
157-
"child acquired lock while parent still holds it"
158-
)
159-
assert proc.wait(timeout=10) == 0
160-
assert acquired_marker.read_text(encoding="utf-8") == "ok"
152+
proc = subprocess.Popen(
153+
[
154+
sys.executable,
155+
"-c",
156+
child,
157+
str(state_file),
158+
str(attempting_marker),
159+
str(acquired_marker),
160+
],
161+
cwd=str(REPO_ROOT),
162+
)
163+
try:
164+
with export_state_lock(str(state_file)):
165+
_wait_for_file(attempting_marker)
166+
_assert_file_stays_absent(acquired_marker)
167+
try:
168+
assert proc.wait(timeout=10) == 0
169+
except subprocess.TimeoutExpired:
170+
proc.kill()
171+
proc.wait()
172+
raise
173+
assert acquired_marker.read_text(encoding="utf-8") == "ok"
174+
finally:
175+
if proc.poll() is None:
176+
proc.kill()
177+
proc.wait()

tests/test_session_path.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
def test_get_claude_projects_dir_uses_userprofile_on_windows(
1515
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
1616
) -> None:
17+
"""Linux/Windows CI smoke: patch ``session_path.platform.system`` (needs ``import platform`` in module).
18+
19+
If ``session_path`` ever switches to ``from platform import system``, this patch
20+
no-ops but may still pass on Linux via ``expanduser("~")``. Real Windows behavior
21+
is covered by ``test_get_claude_projects_dir_on_windows_runner`` (win32-only).
22+
"""
1723
profile = tmp_path / "Users" / "testuser"
1824
monkeypatch.setattr(session_path.platform, "system", lambda: "Windows")
1925
monkeypatch.setenv("USERPROFILE", str(profile))

0 commit comments

Comments
 (0)