Repository navigation
Run the pytest suite in CI - #16
Merged
Merged
Conversation
Fork CI only ran HACS/hassfest/SonarCloud, so a green PR said nothing about the test suite. Add a tests.yml workflow that installs pinned deps from requirements-test.txt and runs pytest -n 4, keeping pytest.ini's timeout. README and Makefile now install from the same pinned file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FJHinb3pMJEwTKrnMXMcG Signed-off-by: Andrew Brainwood <andrew.brainwood@gmail.com>
Temporary commit to confirm the new pytest workflow fails the PR on a real assertion failure. Reverted in the following commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FJHinb3pMJEwTKrnMXMcG Signed-off-by: Andrew Brainwood <andrew.brainwood@gmail.com>
This reverts commit 1d35c60. Signed-off-by: Andrew Brainwood <andrew.brainwood@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FJHinb3pMJEwTKrnMXMcG
SonarCloud flagged the pytest workflow: pip install without --only-binary :all: (S8541) and dependencies without locked resolved versions (S8544). Resolve requirements-test.txt into requirements-test.lock (full hashes, installed with --only-binary :all:) via `uv pip compile --python-platform x86_64-manylinux_2_28`, targeting the Linux CI runner directly - pip-compile on this dev machine (macOS) evaluates environment markers against the host, which pulled darwin-only extras (pyobjc) into a lock meant for Linux. Four transitive test deps (fnvhash, mock-open, paho-mqtt, pyric) publish no wheel at all on PyPI, so --only-binary can't place them. They're split into requirements-test-sdist.lock and installed separately with --no-deps (none of the four have their own dependencies). scripts/regen_test_lock.py discovers that split by re-resolving with --only-binary :all: and reading off what uv rejects, so a new sdist-only dependency fails the regen loudly instead of silently breaking CI. Runner pinned to ubuntu-24.04 (glibc 2.39) to match the manylinux_2_28 lock target. Workflow gets explicit `permissions: contents: read`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FJHinb3pMJEwTKrnMXMcG Signed-off-by: Andrew Brainwood <andrew.brainwood@gmail.com>
The unquoted second colon in ":all:" was parsed as a new mapping key, so GitHub Actions rejected the whole workflow file. Confirmed with a local YAML parse; caught this only after the first push failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FJHinb3pMJEwTKrnMXMcG Signed-off-by: Andrew Brainwood <andrew.brainwood@gmail.com>
Code review of 7620d8a asked for a simpler design. Replace requirements-test.lock + requirements-test-sdist.lock + scripts/regen_test_lock.py with one requirements-test.lock, generated with `uv pip compile --universal`. --universal preserves environment markers instead of evaluating them against the compiling machine, so it resolves correctly for Linux CI even when regenerated on macOS (darwin-only extras like pyobjc keep their sys_platform marker instead of leaking in unconditionally). The four packages with no wheel at all (fnvhash, mock-open, paho-mqtt, pyric) stay in the one lock, hashed, and get a --no-binary carve-out at install time (Makefile SDIST_ONLY) instead of a separate lock file and a script to split it. setuptools is installed first, hash-pinned from the lock, so the --no-build-isolation build of those four sdists doesn't reach for an unpinned setuptools. Verified with a --no-cache-dir install: every package comes from the lock, no unpinned download. CI now just runs `make test-install`; the workflow carries no rationale comments, that lives in the PR body. actions/checkout and actions/setup-python bumped to v7 (current majors). Job gets timeout-minutes: 15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FJHinb3pMJEwTKrnMXMcG Signed-off-by: Andrew Brainwood <andrew.brainwood@gmail.com>
Re-review of 8edfebe surfaced three issues. pytest.ini: set timeout_method = thread. Proved with a throwaway test (a synchronous sleep scheduled via loop.call_soon, marked @pytest.mark.timeout(1), not committed): under the default signal method the SIGALRM fires inside the asyncio callback but the exception it raises gets swallowed by the event loop's callback dispatcher, so the test falsely passes instead of failing - a hung test in CI would look like nothing happened rather than time out. Under thread the same test correctly fails in ~2s with the real traceback pointing at the stuck callback. Added timeout-minutes: 5 on the "Run pytest" step as a backstop. Makefile test-install: piped the extracted setuptools block straight into `pip install -r /dev/stdin` instead of writing it to a fixed /tmp path, and made the extraction fail loudly (via awk's END block) if requirements-test.lock has no setuptools entry - tested against a lock with that entry stripped out. SHELL/.SHELLFLAGS pin bash with pipefail so a failure in the awk half of the pipe actually fails the target instead of being masked by pip succeeding on empty stdin. Added setuptools==84.0.0 to requirements-test.txt so it's a direct, locked requirement rather than only appearing because acme happens to depend on it; regenerated requirements-test.lock with `make test-lock-regen` and confirmed the autogenerated header stayed clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FJHinb3pMJEwTKrnMXMcG Signed-off-by: Andrew Brainwood <andrew.brainwood@gmail.com>
|
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.



Closes #15
Adds
.github/workflows/tests.ymlrunningpytest -n 4on push to main and on PRs (ubuntu-24.04,timeout-minutes: 15on the job,timeout-minutes: 5on the pytest step), using the pip cache and a single fully hash-pinned lock,requirements-test.lock.Design
requirements-test.lockis generated withuv pip compile --universal --python-version 3.12 --generate-hashes requirements-test.txt(regenerate viamake test-lock-regen).--universalresolves and preserves environment markers instead of evaluating them against the machine doing the compile, so the lock is correct for Linux CI even though it's regenerated on a macOS dev machine (darwin-only extras like pyobjc keep theirsys_platformmarker instead of leaking into the Linux install unconditionally).make test-installinstallssetuptoolsfirst - piped straight from the extracted, hash-pinned block ofrequirements-test.lockintopip install --require-hashes -r /dev/stdin(the extraction fails loudly if the lock has nosetuptoolsentry) - then everything else with--require-hashes --no-build-isolation --only-binary :all: --no-binary $(SDIST_ONLY).setuptoolsis also a direct entry inrequirements-test.txtso it's always locked, not just present becauseacmehappens to depend on it.pytest.inisetstimeout_method = thread. The defaultsignalmethod fires its alarm inside whatever's running when the timer expires - if that's an asyncio event-loop callback, the resulting exception gets swallowed by the loop's callback dispatcher instead of failing the test, so a hung test hangs the run until the job timeout, with no failure output.threadfails it in about a second.Proof
pytestjob fail on the PR, then reverted to green:make test-installverified on macOS (fresh Python 3.12 venv) with a full pytest run: 248 passed.gh cache delete --all) before pushing the final commit. Latest green run, no cache restored (pip cache is not foundin the setup-python log): https://github.com/abrainwood/exo_pool/actions/runs/35025374889 - pytest job took 67s.fnvhash,mock-open,paho-mqttandpyricare built from their hash-pinned sdists, withsetuptoolspinned and--no-build-isolation, as a deliberate exception - they publish no wheel at all on PyPI, so--only-binary :all:can't place them like everything else in the lock.🤖 Generated with Claude Code
https://claude.ai/code/session_019FJHinb3pMJEwTKrnMXMcG