Add CI workflow with lint + headless test subset across 3 OS - #678
Open
mxschmitt wants to merge 3 commits into
Open
Add CI workflow with lint + headless test subset across 3 OS#678mxschmitt wants to merge 3 commits into
mxschmitt wants to merge 3 commits into
Conversation
The ruff formatting commit (eeb7eab) only reformatted lib/pynput/. This applies the same formatter (ruff format with quote-style=preserve and line-length=80) to tests/ so the entire repo can be checked with a single ruff format --check invocation. The u-prefix on the __author__ string in lib/pynput/_info.py is a Python 2 leftover with no semantic effect under Python 3 and was the only diff left in lib/ with the same formatter settings.
v1.8.0 added a trailing 'injected' boolean to all listener callbacks. The arity-clipping in pynput._util._wrap protects callbacks passed directly via Listener(on_X=...), but tests/__init__.py:assert_event forwards args through a *args wrapper that bypasses _wrap, so callbacks in those paths must accept the new arity. Affects 11 callbacks across two files: - mouse_controller_tests.py: on_click (2), on_move (4), on_scroll (2), and a nested def on_click in test_click (1) - keyboard_controller_tests.py: on_press (1), on_release (1) Confirmed locally on Windows: test_left/right/up/down previously raised 'TypeError: lambda takes 2 positional arguments but 3 were given' and now run cleanly.
Adds .github/workflows/ci.yml with two jobs: - lint: ruff format --check on lib/pynput and tests/ with the same options that produce the formatting in commit eeb7eab (quote-style=preserve, line-length=80). The flags are inline so the PR doesn't need to add a project-level ruff config file. - test: matrixed across {ubuntu, macos, windows} x Python {3.10..3.13}. Runs an explicit eight-test subset — keyboard_hotkey parser/state machine tests, mouse_controller and keyboard_controller enum integrity tests, and the InvalidKey validation tests. These are headless-safe across all three OSes: they don't synthesize OS input via Controller methods, and don't poll for human input via self.confirm() or assert_stop(). Tests outside this subset fall into two buckets that aren't feasible on shared CI runners: those that synthesize input (macOS Accessibility permission can't be granted on hosted runners, shared Windows runners would flake) and listener tests that wait for human input. They remain manual, per moses-palmer#660. Linux runs under xvfb-run -a so Display() can open, and pip-installs evdev + python-xlib.
mxschmitt
force-pushed
the
ci/add-github-actions
branch
from
May 13, 2026 22:21
ef81f5b to
28b4a43
Compare
Author
|
cc @moses-palmer some initial CI pipeline to run the existing tests for changes on the master branch. |
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.
Context
This addresses the gap discussed in #660: the repo has no CI. The hard part of testing pynput is input simulation, which needs real OS events and a graphical session — that's still manual. This PR lands the easy half: linting and the small subset of tests that don't need real input.
What's added
Workflow (
.github/workflows/ci.yml):lintjob — runsruff format --checkonlib/pynputandtests/withquote-style=preserveandline-length=80. These are the same options that produce the formatting in commiteeb7eab("Apply ruff formatting"), so the check is green on master after the test reformat below. No project-level config file is needed; the options are passed via CLI flags so this PR doesn't have to make a project-wide tooling decision.testjob — matrixed across{ubuntu-latest, macos-latest, windows-latest} × Python {3.10, 3.11, 3.12, 3.13}(12 cells). Runs an explicit eight-test subset chosen because they don't synthesize OS input and don't wait onself.confirm():keyboard_hotkey_tests:test_parse_valid,test_parse_invalid,test_activate_single,test_activate_combo— pure parser + state machinemouse_controller_tests::test_buttonsandkeyboard_controller_tests::test_keys— verify each platform's backend exposes every Button/Key in the base enumkeyboard_controller_tests::test_press_invalidandtest_release_invalid— exception-path tests that never reach the OSLinux runs under
xvfb-run -aand pip-installsevdev/python-xlibso the Xorg backend can instantiateDisplay().Pre-requisites also in this PR
To make the lint job green and the (now-running) tests pass, two small fixes:
Format
tests/with the same formatter aslib/pynput/(commit "Apply ruff formatting to tests/ and drop u-string prefix in _info"). The May 12 ruff commit only touchedlib/pynput/. Running the sameruff format --config 'format.quote-style=preserve' --config 'line-length=80'ontests/produces the formatting in this commit. Also drops au'...'prefix inlib/pynput/_info.pythat's a Python-2 leftover and was the only remaining diff inlib/.Update test callbacks for the v1.8.0
injectedparameter (commit "Update test callbacks for the v1.8.0 injected parameter"). v1.8.0 added a trailinginjectedboolean to all listener callbacks. The arity-clipping inpynput._util._wrapprotects callbacks passed directly viaListener(on_X=...), buttests/__init__.py:assert_eventforwards args through a*argswrapper that bypasses_wrap, so any callback going through it must accept the new arity. Eleven callbacks acrossmouse_controller_tests.pyandkeyboard_controller_tests.pywere stale; fixed mechanically by addinginjectedto their signatures. Confirmed locally on Windows:test_left/right/up/downpreviously raisedTypeError: lambda takes 2 positional arguments but 3 were givenand now run cleanly.What's intentionally out of scope
Controller.press/click/scroll/type/move. macOS GitHub runners can't grant Accessibility permission, so they can never run there cleanly; shared Windows runners would also flake. These remain manual, per Collaborate on testing #660.self.notify('Move mouse...')andself.confirm(...)and require a human at the keyboard.pylint. The existingpylintrcproduces a-3.37/10rating against currentmasterout of the box; making it green requires either many code edits or many# pylint: disable=annotations, both larger scope decisions than belong in this PR.pyproject.toml/ruff.toml. The CLI flags inline in the workflow are deliberately minimal; if you'd prefer a config file I'm happy to add one in a follow-up.Verification
ruff format --check lib/pynput testsis green locally with the options above.mouse_controller_testsmove tests (test_left/right/up/down) now run withoutTypeError— outside the CI subset, but the lambda fix unblocks anyone running the suite manually.