ci: add Windows to the test matrix and fix Windows-only test breakage#162
Merged
Conversation
Add a `windows` job (windows-latest, py3.12 + py3.13) running the pytest suite, plus a stable `tests (windows)` aggregator for branch protection. The full scripts/check.sh gate stays Linux-only (it's bash + Go/Homebrew tooling), so the Windows job covers just the test suite — path handling, subprocess/encoding, and POSIX-only assumptions. Fix the two static blockers that would error on a Windows run: - test_hotkey.py imported termios and used os.openpty at module scope, which crashes collection on Windows. Skip the module there via importorskip (keeps it out of the skip/xfail escape-hatch count the Linux gate tracks). - test_init_scaffold.py asserted 0600 permission bits that don't exist on Windows; gate the POSIX-only mode assertions on os.name == "posix" while still verifying the .env rewrite on every platform. https://claude.ai/code/session_01RZW2ga1Phmt4CQpkZp3LqE
The Windows job's first run exposed 147 failures across ~6 root causes, all test-harness / test-expectation issues rather than source bugs (native backslash paths and the Windows "no termios" dictate message are correct platform behavior): - .gitattributes: force LF on checkout so the byte-exact syrupy snapshot goldens (*.ambr) don't fail under Git's Windows autocrlf (~42 failures). - conftest: on Windows, permit loopback sockets (allow_hosts 127.0.0.1/::1) for in-process async tests. The asyncio event loop's self-pipe is an AF_INET socketpair() there, which --disable-socket blocked, breaking every FastAPI TestClient / scaffolded-template test; POSIX uses os.pipe() so this is Windows-only. External network stays blocked. - setup test fixtures: isolate USERPROFILE too — Path.home() reads it (not HOME) on Windows, so skill install/status wrote into the real profile. - test_init_template_contract: read template files with encoding="utf-8" (cp1252 can't decode the UTF-8 assets). - test_transcribe_batch_sources / test_onboard_sections: build expected paths with pathlib / from the fixture path instead of hardcoded "/". - test_dictate_command: accept the unsupported-platform message as an equivalent usage error on Windows. https://claude.ai/code/session_01RZW2ga1Phmt4CQpkZp3LqE
Second Windows CI round surfaced the next layer: - conftest: disable Rich legacy-Windows mode in tests. On the GHA Windows runner Rich detects a legacy console and subtracts 1 from the render width, so COLUMNS=80 became 79 and every byte-exact help snapshot rewrapped (and test_theme's width assertion failed). Modern Windows terminals report non-legacy — what real users get — so pin it. Fixes ~50 snapshot tests. - ci: install ffmpeg on the Windows runner (choco). stream --sample / clip / caption probe for ffmpeg before their mocked work, so its absence failed them at the probe (KeyError 'params', wrong exit codes). - caption: subtitles_filter now normalizes os.sep to "/" before escaping, so the ffmpeg filtergraph gets the portable forward-slash form with an escaped drive colon (C:\a\b.srt -> C\:/a/b.srt) instead of unusable backslashes — a real burn-in bug on Windows. test helper reverses the escaping to recover the on-disk SRT path. - test isolation: USERPROFILE alongside HOME in the coding-agent fixture (Path.home() reads USERPROFILE on Windows). - test expectations: read committed template assets as utf-8 (cp1252 can't decode them); compare the remote-download basename without a hardcoded "/"; use a cross-platform binary + signal (SIGTERM) in the macOS-helper unit tests instead of /bin/echo and SIGTRAP. https://claude.ai/code/session_01RZW2ga1Phmt4CQpkZp3LqE
Last Windows failure: test_synthesize_without_connect_uses_real_client_and_ fails_cleanly relies on socket *creation* being blocked (Linux --disable-socket) to surface a clean CLIError. The suite-wide Windows loopback allowance let the socket be created, then the blocked external connect leaked it, tripping the unraisable-exception warning gate. Pin this test to disable_socket (a no-op on Linux) and have the conftest relaxation skip disable_socket/enable_socket tests. https://claude.ai/code/session_01RZW2ga1Phmt4CQpkZp3LqE
py3.12 went green but py3.13 failed with ffmpeg-missing errors from the same choco command — a transient choco download flake on one matrix cell. Retry the install up to 3x, fix the in-session PATH, and verify `ffmpeg -version` so a real miss fails this step instead of surfacing as confusing test failures downstream. https://claude.ai/code/session_01RZW2ga1Phmt4CQpkZp3LqE
assembly dictate previously raised "not supported on this platform" on Windows because core/hotkey.py is termios-only. Add a Windows backend behind the same TerminalKeys interface: the console is already character-at-a-time, so there's no cbreak mode to enter/restore, and read() polls msvcrt.kbhit()/getwch() (getwch blocks for timeout=None, kbhit polls to a deadline otherwise) — mirroring the POSIX select()+os.read() path. Enter (\r) is already in dictate's TOGGLE_KEYS. Stays stdlib-only (msvcrt is in the stdlib). The win32-only members are loaded via importlib and bound to typed Callable locals so mypy and pyright both accept the module on the POSIX CI host. _on_windows() is an injectable predicate and the backend reads through an injectable console, so the new tests drive the Windows path (and give coverage/mutation) on Linux with a fake msvcrt. https://claude.ai/code/session_01RZW2ga1Phmt4CQpkZp3LqE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds Windows to CI and fixes the static blockers that would break a Windows test run.
CI matrix
windowsjob (windows-latest× py3.12 + py3.13) running the pytest suite viauv run pytest.tests (windows)aggregator job (stable, un-suffixed name) for branch protection, mirroring the existingcheck-resultpattern.scripts/check.shgate stays Linux-only on purpose — it's bash plus Go/Homebrew/shell tooling that can't run on Windows. The Windows job covers the test suite, which is what exercises path handling, subprocess/encoding, and POSIX-only assumptions.sounddevicewheel bundles PortAudio on Windows, and the unit suite mocks every ffmpeg/ffprobe shell-out (thee2e/installsuites that need real binaries are excluded by the default-m 'not e2e and not install').Windows-only test fixes
tests/test_hotkey.pyimportedtermiosand usedos.openptyat module scope, which crashes collection on Windows. Now skipped there viapytest.importorskip("termios")(kept out of the skip/xfail escape-hatch count the Linux gate tracks).tests/test_init_scaffold.pyasserted0o600permission bits that don't exist on Windows. The POSIX-only mode assertions are now gated onos.name == "posix", while the.envrewrite is still verified on every platform.Notes
procs.py'sstart_new_session=Trueis accepted-but-ignored on Windows (not aValueError),stdio.py'sos.dup2(os.devnull…)works on Windows, andstreaming/macos.py/core/hotkey.pyalready guard their POSIX imports inside functions.scripts/check.sh) passes end-to-end on this branch.main), so this PR is the first real exercise of it. Any runtime-only Windows failures it surfaces will be fixed in follow-up commits here.https://claude.ai/code/session_01RZW2ga1Phmt4CQpkZp3LqE
Generated by Claude Code