Skip to content

Cover macOS, Linux and WSL in the test suite and CI matrix - #32

Merged
Korijn merged 8 commits into
masterfrom
claude/expand-test-ci-matrix-2qg9lv
Aug 4, 2026
Merged

Cover macOS, Linux and WSL in the test suite and CI matrix#32
Korijn merged 8 commits into
masterfrom
claude/expand-test-ci-matrix-2qg9lv

Conversation

@Korijn

@Korijn Korijn commented Aug 4, 2026

Copy link
Copy Markdown
Owner

The test suite needs an OS keyring that unlocks without user interaction, which is why CI only ran on Windows. Every other platform can provide one too, so CI now tests Linux, macOS and Windows on every supported Python version, plus WSL reaching the Windows credential manager.

Keyring setup per platform

  • Windows: the credential manager is available to the session out of the box, unchanged.
  • macOS: create a throwaway keychain, unlock it, disable auto lock, and make it the default keychain, which is the one the keyring package talks to.
  • Linux: install gnome-keyring and run the tests inside a dbus-run-session with the daemon unlocked, since the secret service is bound to a d-bus session.

The tests that need a keyring skip themselves with a message when none is available, so contributors are not blocked on a headless machine. KEYCMD_REQUIRE_OS_KEYRING=1 turns those skips into failures, and CI sets it so a broken keyring setup cannot quietly gut a run rather than fail it.

WSL

The half of the WSL setup that can actually break is the interop boundary: keycmd installed on Windows, invoked from a shell inside WSL, reading the Windows credential manager. tests/test_wsl.py covers exactly that, in a job of its own that installs WSL, and it passes: keycmd finds its config, reads the credential out of the credential manager, and hands it to the command.

Working inside WSL needs no separate job, since there keycmd is a posix process talking to whichever keyring backend the distro provides, which is what the Linux job covers, daemon and all.

Notes on how that job is set up, both learned the hard way on this branch:

  • Alpine on WSLv1. Provisioning WSLv2 with a store distribution took 44 seconds on one run and blew through a 20 minute timeout on the next. The tests only need a shell on the far side of the boundary, so the job now installs the Alpine root file system on the WSL version that needs no virtual machine. The whole job takes about 30 seconds.
  • Nothing is quoted or backslashed. wsl.exe strips quotes and eats backslashes on its way to the distribution, which mangled the path of the console script and swallowed the $env: prefix of a variable reference. Paths are therefore translated in the test rather than by wslpath (which the Alpine root file system does not ship anyway), passed unquoted, and asserted to be free of spaces. The environment is printed with cmd /c set, which needs no quoting and works whichever shell keycmd detects on the Windows side.

Test suite

Everything platform specific moved into a new tests/conftest.py, which also removes the fixtures that were duplicated across three modules.

  • The suite now exercises every shell of the platform that is installed (sh, bash, zsh and cmd, powershell, pwsh) instead of only the shell that happened to invoke pytest. Shells that are not installed are still covered by asserting on the command line keycmd builds for them, which is what covers the cmd /C branch on posix runners.
  • Fixed the Windows-only assumptions that made the suite fail elsewhere: the exit status for a missing command is per shell (posix shells report 127, not 1), and non-ascii passwords are now exercised everywhere except through the Windows console.
  • New coverage for the posix execvpe path (including exit status propagation and environment inheritance, verified in real subprocesses), the three shell detection fallbacks, --shell, --verbose, the missing command and invalid TOML errors, expose, the missing credential and missing alias errors, and the config walk stopping at the home folder and at the root of the file system.
  • test_find_file no longer creates a file in the real home folder.

Coverage goes from 79% on Linux, with failures, to 100% on Linux, macOS and Windows alike. CI gates at 95%.

Unrelated and pre-existing: on Python 3.14 keycmd/conf.py:43 emits a DeprecationWarning about free-form TOMLDecodeError arguments. Left alone, since it is a source change rather than test or CI scope.

claude added 7 commits August 4, 2026 18:38
The test suite needs an OS keyring that unlocks without user interaction,
which used to limit CI to Windows runners. Both other platforms can
provide one too:

- macOS: create a throwaway keychain and make it the default
- Linux: run the tests in a d-bus session with an unlocked gnome-keyring

CI now tests Linux, macOS and Windows on every supported Python version.
The tests that need a keyring skip themselves with a message when none is
available, so contributors are not blocked; KEYCMD_REQUIRE_OS_KEYRING
turns those skips into failures, and CI sets it so that a broken keyring
setup cannot quietly gut a run.

Everything platform specific now lives in a shared conftest, which also
removes the duplicated fixtures. The suite exercises every shell of the
platform that is installed instead of only the one that happens to have
invoked pytest, and the shells that are not installed are covered by
asserting on the command line keycmd builds for them. New tests cover the
posix process replacement path, the shell detection fallbacks, the
verbose logging, and the error paths of the cli and of get_env, taking
coverage to 100% (CI gates at 95%).

Also stop test_find_file from creating a file in the real home folder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYvtsyJgsswPf278ytw2Zd
WSL needs no job of its own: inside WSL keycmd is a posix process the
Linux job already covers, and calling the Windows install from a WSL
shell runs keycmd as a Windows process the Windows job already covers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYvtsyJgsswPf278ytw2Zd
The half of the WSL story that can actually break is the interop
boundary: keycmd installed on windows, invoked from a shell inside WSL,
reading the windows credential manager. Cover it end to end with a
credential in the credential manager, a shell inside WSL, and the windows
install of keycmd in between, in a CI job that installs WSL.

The tests are opt in through KEYCMD_TEST_WSL, since installing WSL is too
expensive to put in front of every run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYvtsyJgsswPf278ytw2Zd
run_cmd logs the command through pformat, which wraps the list over
several lines when the shell path is long, so a plain repr of the list is
not in the output. Caught by powershell.EXE on the windows runners, whose
path is long enough to wrap where pwsh's is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYvtsyJgsswPf278ytw2Zd
Backslashes do not survive wsl.exe's command line, which turned the path
of the console script into D:akeycmdkeycmd.venvScriptskeycmd.EXE before
wslpath ever saw it. Hand wslpath forward slashes instead, and keep the
script one line so that it crosses the same boundary unharmed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYvtsyJgsswPf278ytw2Zd
The install step took 44 seconds on one run and was still going twenty
minutes later on the next, so drop what makes it heavy: WSLv1 needs no
virtual machine, and the Alpine root file system is a fraction of the
Ubuntu one from the store.

That root file system ships neither bash nor wslpath, so run the scripts
with plain sh and translate windows paths in the test itself, which is
also one less thing to lose backslashes on the way through wsl.exe. The
smoke test now reads a file it created on the windows side, so that the
translation is verified before the tests that depend on it run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYvtsyJgsswPf278ytw2Zd
wsl.exe strips quotes from its command line, so sh expanded the $env:
prefix of the powershell spelling as an unset variable, and keycmd echoed
":KEYCMD_TEST". Ask cmd to print the environment instead: it works
whichever shell keycmd detects on the windows side, needs no quoting, and
lets the test assert on the value of the variable rather than on whatever
the shell echoed.

Also assert paths translated for WSL have no spaces, since quoting them
is not an option.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYvtsyJgsswPf278ytw2Zd
@Korijn Korijn changed the title Cover macOS and Linux in the test suite and CI matrix Cover macOS, Linux and WSL in the test suite and CI matrix Aug 4, 2026
Python 3.14 deprecated the single argument form of TOMLDecodeError, and
the structured one it wants instead does not exist before 3.14, so name
the offending file by rewriting the message of the original error rather
than raising a new one. That keeps the type, the message and the
traceback the cli and the tests already rely on.

Turn warnings into errors while here, so the next deprecation fails the
suite instead of scrolling past in the log.

Trim the matrix to the latest python on all three platforms plus a single
job on the oldest supported one, which is what the type checker is
already pinned to.

Add a CLAUDE.md covering the commands, the shape of the configuration,
credential and shell layers, and the platform traps in the test suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYvtsyJgsswPf278ytw2Zd
@Korijn
Korijn merged commit f5857cf into master Aug 4, 2026
9 checks passed
@Korijn
Korijn deleted the claude/expand-test-ci-matrix-2qg9lv branch August 4, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants