Skip to content

Commit 9f3b3c6

Browse files
ci: add windows-latest to test matrix
1 parent 965618b commit 9f3b3c6

4 files changed

Lines changed: 83 additions & 6 deletions

File tree

.github/workflows/ci.yml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ jobs:
4444
PY
4545
4646
pytest:
47-
runs-on: ubuntu-latest
47+
runs-on: ${{ matrix.os }}
48+
strategy:
49+
fail-fast: false
50+
matrix:
51+
os: [ubuntu-latest, windows-latest]
4852
permissions:
4953
contents: read
5054
steps:
@@ -94,7 +98,11 @@ jobs:
9498

9599
integration-tests:
96100
name: API integration tests + coverage
97-
runs-on: ubuntu-latest
101+
runs-on: ${{ matrix.os }}
102+
strategy:
103+
fail-fast: false
104+
matrix:
105+
os: [ubuntu-latest, windows-latest]
98106
permissions:
99107
contents: read
100108
actions: write
@@ -120,12 +128,16 @@ jobs:
120128

121129
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
122130
with:
123-
name: coverage-report
131+
name: coverage-report-${{ matrix.os }}
124132
path: coverage.xml
125133

126134
js-tests:
127135
name: Frontend unit tests (vitest)
128-
runs-on: ubuntu-latest
136+
runs-on: ${{ matrix.os }}
137+
strategy:
138+
fail-fast: false
139+
matrix:
140+
os: [ubuntu-latest, windows-latest]
129141
permissions:
130142
contents: read
131143
steps:

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Thanks for considering a patch. This repo is a small Flask app plus a hash-route
99
- **Python 3.12** (matches CI)
1010
- **Node 20+** (only if you change `static/js/` or run frontend unit tests)
1111

12+
CI runs **`pytest`**, **integration tests**, and **Vitest** on **ubuntu-latest** and **windows-latest** (Python 3.12, Node 20). Type-check (`mypy`) and production install smoke run on Ubuntu only.
13+
1214
### Bootstrap (Windows PowerShell)
1315

1416
```powershell
@@ -105,7 +107,7 @@ npm run test:coverage # optional
105107
- PR checklist:
106108
- [ ] `pytest -q` green locally
107109
- [ ] `npm test` green if JS changed
108-
- [ ] CI jobs green (`pytest`, `integration-tests`, `js-tests`, `prod-install-smoke`)
110+
- [ ] CI jobs green (`pytest`, `integration-tests`, `js-tests` on Ubuntu + Windows; `mypy`, `prod-install-smoke` on Ubuntu)
109111
- [ ] PR description includes a **Test plan** section
110112
- [ ] API changes update [`docs/api-reference.md`](docs/api-reference.md) if behavior or errors change
111113

tests/test_export_state_store.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@
33
from __future__ import annotations
44

55
import json
6+
import sys
67
from pathlib import Path
78

8-
from utils.export_state_store import load_export_state_from_disk
9+
import pytest
10+
11+
from utils.export_state_store import (
12+
atomic_write_export_state,
13+
export_state_lock,
14+
load_export_state_from_disk,
15+
)
916

1017

1118
def test_load_rejects_non_object_json(tmp_path: Path):
@@ -81,3 +88,22 @@ def locking(fd, mode, nbytes):
8188
with mod.export_state_lock(str(state_file)):
8289
assert (FakeMsvcrt.LK_LOCK, 1) in calls
8390
assert calls[-1] == (FakeMsvcrt.LK_UNLCK, 1)
91+
92+
93+
@pytest.mark.skipif(sys.platform != "win32", reason="requires Windows msvcrt")
94+
def test_export_state_lock_real_msvcrt_roundtrip(tmp_path: Path) -> None:
95+
"""Exercise real ``msvcrt.locking`` on a Windows runner (not FakeMsvcrt)."""
96+
state_file = tmp_path / "export_state.json"
97+
state_file.write_text("{}", encoding="utf-8")
98+
payload = {
99+
"sessions": {},
100+
"lastExportTime": "2026-01-01T00:00:00",
101+
"exportedCount": 0,
102+
}
103+
104+
with export_state_lock(str(state_file)):
105+
atomic_write_export_state(payload, str(state_file))
106+
107+
loaded = load_export_state_from_disk(str(state_file))
108+
assert loaded.get("exportedCount") == 0
109+
assert loaded.get("sessions") == {}

tests/test_session_path.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Tests for utils/session_path platform-specific home resolution."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
import sys
7+
from pathlib import Path
8+
9+
import pytest
10+
11+
from utils import session_path
12+
13+
14+
def test_get_claude_projects_dir_uses_userprofile_on_windows(
15+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
16+
) -> None:
17+
profile = tmp_path / "Users" / "chen"
18+
profile.mkdir(parents=True)
19+
monkeypatch.setattr(session_path.platform, "system", lambda: "Windows")
20+
monkeypatch.setenv("USERPROFILE", str(profile))
21+
monkeypatch.delenv("HOME", raising=False)
22+
23+
got = session_path.get_claude_projects_dir()
24+
assert got == os.path.join(str(profile), ".claude", "projects")
25+
26+
27+
@pytest.mark.skipif(sys.platform != "win32", reason="native Windows runner")
28+
def test_get_claude_projects_dir_on_windows_runner(
29+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
30+
) -> None:
31+
profile = tmp_path / "profile"
32+
profile.mkdir()
33+
monkeypatch.setenv("USERPROFILE", str(profile))
34+
35+
got = session_path.get_claude_projects_dir()
36+
assert got.startswith(str(profile))
37+
assert got.endswith(os.path.join(".claude", "projects"))

0 commit comments

Comments
 (0)