|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | 5 | import json |
| 6 | +import subprocess |
6 | 7 | import sys |
| 8 | +import time |
7 | 9 | from pathlib import Path |
8 | 10 |
|
9 | 11 | import pytest |
10 | 12 |
|
| 13 | +REPO_ROOT = Path(__file__).resolve().parent.parent |
| 14 | + |
11 | 15 | from utils.export_state_store import ( |
12 | 16 | atomic_write_export_state, |
13 | 17 | export_state_lock, |
@@ -96,14 +100,41 @@ def test_export_state_lock_real_msvcrt_roundtrip(tmp_path: Path) -> None: |
96 | 100 | state_file = tmp_path / "export_state.json" |
97 | 101 | state_file.write_text("{}", encoding="utf-8") |
98 | 102 | payload = { |
99 | | - "sessions": {}, |
100 | | - "lastExportTime": "2026-01-01T00:00:00", |
101 | | - "exportedCount": 0, |
| 103 | + "sessions": {"sess-msvcrt-roundtrip": 1740000123.5}, |
| 104 | + "lastExportTime": "2026-06-04T18:30:00Z", |
| 105 | + "exportedCount": 42, |
102 | 106 | } |
103 | 107 |
|
104 | 108 | with export_state_lock(str(state_file)): |
105 | 109 | atomic_write_export_state(payload, str(state_file)) |
106 | 110 |
|
107 | 111 | loaded = load_export_state_from_disk(str(state_file)) |
108 | | - assert loaded.get("exportedCount") == 0 |
109 | | - assert loaded.get("sessions") == {} |
| 112 | + assert loaded.get("exportedCount") == 42 |
| 113 | + assert loaded.get("sessions") == {"sess-msvcrt-roundtrip": 1740000123.5} |
| 114 | + assert loaded.get("lastExportTime") == "2026-06-04T18:30:00Z" |
| 115 | + |
| 116 | + |
| 117 | +@pytest.mark.skipif(sys.platform != "win32", reason="requires Windows msvcrt") |
| 118 | +def test_export_state_lock_blocks_second_process(tmp_path: Path) -> None: |
| 119 | + """While the parent holds ``msvcrt`` lock, a child process cannot acquire it.""" |
| 120 | + state_file = tmp_path / "export_state.json" |
| 121 | + state_file.write_text("{}", encoding="utf-8") |
| 122 | + marker = tmp_path / "child_acquired.lock" |
| 123 | + child = ( |
| 124 | + "import sys\n" |
| 125 | + "from pathlib import Path\n" |
| 126 | + f"sys.path.insert(0, {str(REPO_ROOT)!r})\n" |
| 127 | + "from utils.export_state_store import export_state_lock\n" |
| 128 | + "path, marker = sys.argv[1], sys.argv[2]\n" |
| 129 | + "with export_state_lock(path):\n" |
| 130 | + " Path(marker).write_text('ok', encoding='utf-8')\n" |
| 131 | + ) |
| 132 | + with export_state_lock(str(state_file)): |
| 133 | + proc = subprocess.Popen( |
| 134 | + [sys.executable, "-c", child, str(state_file), str(marker)], |
| 135 | + cwd=str(REPO_ROOT), |
| 136 | + ) |
| 137 | + time.sleep(0.5) |
| 138 | + assert not marker.is_file(), "child acquired lock while parent still holds it" |
| 139 | + assert proc.wait(timeout=10) == 0 |
| 140 | + assert marker.read_text(encoding="utf-8") == "ok" |
0 commit comments