Skip to content

Add GitHub Actions CI (lint + tests + manual integration)#395

Merged
wshlavacek merged 5 commits into
masterfrom
ci/github-actions
May 26, 2026
Merged

Add GitHub Actions CI (lint + tests + manual integration)#395
wshlavacek merged 5 commits into
masterfrom
ci/github-actions

Conversation

@wshlavacek
Copy link
Copy Markdown
Collaborator

Summary

Adds remote CI to PyBNF. Until now the only automated check was a local pre-push hook scoped to the bngsim test files, leaving the ~750 pure-Python tests uncovered on push/PR.

Three workflows + a shared composite action:

  • lint.yml (push to master + all PRs): uvx ruff@0.15.14 check . (pinned for a reproducible rule set; [tool.ruff] in pyproject.toml pins the rules).
  • tests.yml (push to master + all PRs): matrix py3.10 / py3.12 on ubuntu-latest, PYBNF_NO_BNGSIM=1 pytest -m "not slow" -n auto. ~3.5 min with xdist.
  • integration.yml (workflow_dispatch): pytest -m slow -n auto — the 4 analytical recovery tests.
  • .github/actions/setup-pybnf: installs BioNetGen 2.9.3 + pybnf's public deps into a uv venv, omitting the private bngsim wheel.

Key finding: BNG2.pl is required even bngsim-less

The original plan assumed ~750 "pure-Python" tests need nothing but a bngsim-less install. That's not true. Configuration._load_simulators (config.py:611-641) execs BNG2.pl -v for any BNGLModel regardless of bngsim. On a clean runner (no BNGPATH/BNG2.pl) the bngsim-less suite is 98 failed + 30 errors, not 29 — the affected tests are the core optimizer/sampler suites.

Fix: the composite action fetches the free, open-source BioNetGen 2.9.3 Linux release onto the ephemeral runner and sets BNGPATH. It is not vendored into the repo and not added as a pybnf dependency — it mirrors the existing developer prerequisite. import roadrunner is also top-level in config.py/pset.py, so libroadrunner is installed too.

The bngsim simulation suites (true NFsim/RuleMonkey/SBML runs) stay out of hosted CI — they need the private, macOS-only bngsim wheel — and remain covered by the local pre-push hook until bngsim ships Linux wheels.

Commits (kept separate per concern)

  1. Mark bngsim-dependent bridge tests — 25 unmarked functions (29 cases) in test_bngsim_bridge.py got @pytest.mark.bngsim so they skip without bngsim (they failed before). Param-level marks on maps_methods avoid over-skipping its net/pla/unknown cases. Real correctness fix to the suite.
  2. Make TestJob teardown xdist-safeteardown_class did rmtree('pybnf_output') on relative paths; flaky single-core (order-dependent) and unreliable under -n auto. Now ignore_errors=True.
  3. Ruff config + lint fixes — pin default E,F; autofix safe findings (unused/duplicate imports, an f-string, one-line imports); remove one line of unreachable dead code (the lone F821); ignore legacy style (E402/E712/E721/E722/E741/F841) and per-file-ignore tests' semicolons + the context.py re-export shim.
  4. Workflows (this + the composite action) — additive.

Validation (local, no act)

  • uvx ruff@0.15.14 check . → exit 0.
  • Full pytest -m "not slow" with bngsim → 811 passed (no over-marking, ruff autofixes broke nothing).
  • Replicated the exact CI install in a fresh bngsim-less venv (py3.12, real BNG2.pl): PYBNF_NO_BNGSIM=1 pytest -m "not slow" -n auto747 passed, 64 skipped, 0 failed in 3:21.

Notes

  • mypy intentionally omitted from v1: the codebase is essentially unannotated, so non-strict mypy is a near no-op (everything Any). Worth revisiting once annotations exist.
  • ruff format deferred: a repo-wide reformat is a large, behavior-adjacent diff for its own PR.
  • A nightly schedule: cron for integration.yml is left out pending your decision.

⚠️ Please review before merging — this touches CI config.

tests/test_bngsim_bridge.py had 25 functions (29 parametrized cases) that
call into bngsim (classify_actions_for_bngsim, _normalize_nf_action_method,
_initialize_models with BNGSIM_AVAILABLE patched, etc.). These were unmarked,
so under PYBNF_NO_BNGSIM=1 they failed instead of skipping via conftest's
dependency-aware collection hook.

Add @pytest.mark.bngsim to those functions. For the parametrized
test_classify_action_method_backend_maps_methods, mark only the nf/nf_reject/
nfsim params (which route through bngsim.normalize_method); the ode/ssa/psa/
pla/unknown cases short-circuit before the BNGSIM_AVAILABLE check and still
run without bngsim, so they are left unmarked.

Verified: PYBNF_NO_BNGSIM=1 pytest -m 'not slow' has 0 failures (these skip),
and the same tests still pass with bngsim present (no over-marking).
teardown_class removed pybnf_output/sim_* with bare rmtree() on relative
paths. Those dirs only exist if a test in the class created them in the
worker's cwd, so teardown raised FileNotFoundError when a dir was absent --
flaky single-core (order-dependent) and unreliable under pytest-xdist's -n
auto, where the worker running this class need not have produced every dir.

Use rmtree(d, ignore_errors=True) so cleanup is best-effort.
Pin ruff's default rule set (pycodestyle E4/E7/E9 + pyflakes F) in
[tool.ruff] so local and CI agree, then get to green:

- Auto-fix the safe findings repo-wide (ruff check --fix): unused imports
  (F401), duplicate imports (F811: BNGLModel in algorithms.py, NegBinLikelihood
  in config.py), one-line multi-import (E401), and an f-string without
  placeholders (F541).
- Remove one line of unreachable dead code: 'return session' after two
  unconditional returns in bngsim_model._create_nf_session (was the lone F821).
- Ignore pre-existing legacy style that is cosmetic or behavioral-to-fix:
  E402, E712, E721, E722 (ROB-4), E741, F841 (several are CQ-5 dead code).
- per-file-ignores: tests E702 (semicolons) / F402; and F401 on
  tests/context.py, which is a re-export shim (test modules do
  'from .context import data').

Verified: 'uvx ruff check .' exits 0; full 'pytest -m "not slow"' with
bngsim still passes (811 passed), so the autofixes broke nothing.
Three workflows plus a shared composite action (.github/actions/setup-pybnf)
that installs BioNetGen 2.9.3 (BNG2.pl) and pybnf's public deps into a uv
venv, omitting the private macOS-only bngsim wheel. Tests run under
PYBNF_NO_BNGSIM=1 so bngsim-marked tests skip via conftest.

- lint.yml (push to master + PRs): uvx ruff@0.15.14 check . (pinned for a
  reproducible rule set). ruff format is deferred to a future dedicated PR.
- tests.yml (push to master + PRs): matrix py3.10/py3.12 on ubuntu-latest;
  PYBNF_NO_BNGSIM=1 pytest -m 'not slow' -n auto (uv run --no-sync so uv does
  not re-resolve bngsim). ~3.5 min with xdist.
- integration.yml (workflow_dispatch): pytest -m slow -n auto, the 4 analytical
  recovery tests. A nightly cron is left out pending a maintainer decision.

BNG2.pl is required even bngsim-less: Configuration validation execs
'BNG2.pl -v' for any BNGLModel regardless of bngsim. BioNetGen is fetched onto
the ephemeral runner (not vendored, not a pybnf dependency). The bngsim
simulation suites stay on the local pre-push hook until bngsim ships Linux
wheels.

Validated locally by replicating the exact install in a fresh bngsim-less venv:
ruff check green; PYBNF_NO_BNGSIM=1 pytest -m 'not slow' -n auto = 747 passed,
64 skipped, 0 failed.
…n py3.10

The first CI run passed on py3.12 (747 passed) but failed collection on py3.10
with 'ImportError: libpython3.10.so.1.0: cannot open shared object file'.
libroadrunner is a C++ extension that dlopen's libpython, and uv's managed
(python-build-standalone) Python 3.10 does not expose libpython3.10.so.1.0 on
the loader path -- though its 3.12 build does, which is why only the 3.10 leg
broke.

Provision the interpreter with actions/setup-python (built --enable-shared,
libpython discoverable) and point 'uv venv --python' at its python-path, so
roadrunner imports on every matrix leg. Also key the setup-uv cache on
pyproject.toml (no uv.lock exists) to silence the never-invalidates warning.
@wshlavacek
Copy link
Copy Markdown
Collaborator Author

CI is green on real GitHub infra ✅

Validated this branch end-to-end on hosted runners (not just locally):

job result
lint ✅ success (uvx ruff@0.15.14 check .)
tests / py3.10 747 passed, 64 skipped, 0 failed (5m35s)
tests / py3.12 747 passed, 64 skipped, 0 failed (4m40s)
integration (-m slow) command validated bngsim-less + xdist: 4 passed (3m35s). Dispatchable only after merge — workflow_dispatch workflows must live on the default branch.

The first run exposed one Linux-only issue the local macOS replica couldn't: uv's standalone Python 3.10 doesn't expose libpython3.10.so.1.0, which libroadrunner (a C++ ext) dlopen's — so 3.10 failed collection while 3.12 passed. Fixed by provisioning the interpreter via actions/setup-python (built --enable-shared) and pointing uv venv at it. Both legs green since.

Heads-up (non-blocking): runner annotations warn that actions/checkout@v4, actions/setup-python@v5, astral-sh/setup-uv@v5 run on Node 20, which GitHub forces to Node 24 on 2026-06-02. Worth a routine action-version bump later.

@wshlavacek wshlavacek merged commit fd421ae into master May 26, 2026
3 checks passed
@wshlavacek wshlavacek deleted the ci/github-actions branch May 26, 2026 06:33
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.

1 participant