Uh oh!
There was an error while loading. Please reload this page.
benchmarks: settle the argv contract for the whole suites/ tree (#489) - #491
Conversation
#488 fixed the two runners the orchestrator can reach. This finishes the sweep over the seven scripts it cannot, and the finding is that the hazard was demonstrated rather than argued: * jacobian/probe_attach.py read `--help` as a model id, failed to find a model by that name, and wrote the one-row failure over the COMMITTED results/attach_probe.json -- 46 lines of measurement replaced by two, by a command typed to find out what the script does. Reproduced against the file in this repository. * ode_engines_s4_sbml/check_sbml_engine_agreement.py ran bngsim, RoadRunner and COPASI over the Table S4 models, then overwrote its committed report. * ode_fullnet/recover_s4_points.py reached furthest: regenerate two networks through BNG2.pl, rewrite the characterization under parity_checks/, then copy both files into a DIFFERENT repository's latex/generated/, one of them committed there. * ssa_table5/{convert_all,emit_ssa_table,merge_jobout}.py reconverted the corpus or rewrote the Table 5 result set. * jacobian/diagnose_divergence.py read `--help` as a model id (and stopped there, the one benign case). All seven now build an ArgumentParser: `--help` exits 0 with usage, an unrecognized flag exits 2, a bare invocation is unchanged. Where a probe needs somewhere harmless to write, it gets a flag -- `--out` on probe_attach.py and check_sbml_engine_agreement.py, `--paper-dir` / `--no-copy` on recover_s4_points.py, which also skips rather than crashes when the paper checkout is absent. diagnose_divergence.py re-enters itself as `--worker` on an argv it composes; that half is matched before the parser and left exactly as it was. Two scripts are exempt, at the paper side's explicit ask on the issue: ssa_table5/_ssa_cell.py is spawned per (engine, model) by run_ssa_timing.py, which is Table 5's measurement path, and bngsim_wrapper.py is invoked by the vendored SBML test-suite runner on the fixed `%d %n %o %l %v` template. A parser in either changes a caller's contract to buy a usage line for an invocation nobody makes. Each names its caller instead, and the wrapper already refused a hand invocation with usage + exit 2. test_benchmark_runner_help.py now discovers its family by structure -- every suites/ script with a `__main__` guard, minus that exemption list -- rather than from run_all.py's registry plus a hand-kept companion list. 127 cases, 15 s. A script added to any suite inherits the contract without an edit here, and the exemptions are themselves tested: no parser, and the worker must still name its caller, so the omission reads as intent. Two further tests assert `--help` leaves each committed artifact byte-identical. Verified with libsbml, scipy, roadrunner, amici, COPASI and antimony blocked at import: only the two scripts that import an optional engine at module scope are affected, and both of those deps (python-libsbml core, scipy via the test extra) are present wherever this test runs. No measured value changes.
CI was red on 19 cases, all in the new test file, all one defect it had
just been widened enough to see.
Eight scripts resolved their repo root as
BNGSIM = Path(os.environ.get("BNGSIM_ROOT", Path.home() / "Code" / "bngsim"))
and then put <root>/parity_checks and <root>/parity_checks/bng_parity on
sys.path. So `import _bng_common` at module scope resolved only on a
machine whose clone happens to sit at ~/Code/bngsim, and raised
ModuleNotFoundError in any other checkout -- a worktree, a fresh clone,
and every CI runner. `--help` on those eight could not answer because the
module could not import:
ode_engines_s3/run_s3_timing.py (and patch_amici_split.py, which
imports it)
ode_engines_s4_sbml/run_s4_timing.py
ode_engines_s4_sbml/check_sbml_engine_agreement.py
ode_fullnet/{run_timing,run_forced,gen_networks,probe_growth,
recover_s4_points}.py
The default is now Path(__file__).resolve().parents[3] -- the checkout the
file lives in -- which is the idiom biomodels/emit.py already uses
(`BNGSIM_ROOT = BENCH_DIR.parents[2]`). BNGSIM_ROOT still overrides, so
the case the env var was written for is untouched: a different venv run
against a canonical checkout. Where the two already coincided, the
resolved path is identical and nothing moves.
The test could not have caught this, because the author's $HOME made the
wrong default look right. Every probe now runs with HOME (and USERPROFILE)
pointed at an empty session-scoped directory, so "anyone can run --help"
means anyone. Verified by stashing one script's fix: the two cases for it
fail locally with the same ModuleNotFoundError CI reported.
Also verified with amici, COPASI, gillespy2, diffrax, basico, pybnf,
psutil, matplotlib and bioservices blocked at import and BNGPATH /
SBML_TEST_SUITE_DIR cleared, alongside the scrubbed HOME: all 59 scripts
answer --help.wshlavacek
commented
Aug 27, 2026
CI was red — 19 cases, all in the new test file, all one defect it had just been widened enough to see. Fixed in c031852. Eight scripts resolved their repo root as The default is now The test could not have caught it, because the author's Worth noting this is the same shape as the issue itself: a script that only works where its author's files happen to be. It just showed up in |
Closes#489. Follows #490, which fixed
the two runners
run_all.pycan reach.The paper side's comment on the issue is followed exactly:
_ssa_cell.pyandbngsim_wrapper.pyget no parser, that exemption is now endorsed by a testrather than left to the next implementer, and the freedom granted over
recover_s4_points.pyis used.The hazard was demonstrated, not argued
Two of the seven wrote committed files on a bare invocation. Reproduced
against this repository, then restored from git:
46 lines of measurement replaced by
[{"model": "--help", "error": "missing"}]—by the command typed to find out what the script does. That file is the record of
which models genuinely attach the analytical Jacobian, which is what decides
membership in the suite's speedup table.
--helpused to dojacobian/probe_attach.py--help; overwrote the committedresults/attach_probe.jsonode_engines_s4_sbml/check_sbml_engine_agreement.pyode_fullnet/recover_s4_points.pyparity_checks/, then copied both into another repository'slatex/generated/ssa_table5/convert_all.pyresults/converted/+conversion_log.jsonssa_table5/emit_ssa_table.pyssa_timing_ballpark.jsonin place and re-rendered the.mdssa_table5/merge_jobout.pyssa_timing_ballpark.jsonfrom_jobout/jacobian/diagnose_divergence.py--helpas a model id and stopped — the one benign caseThe fix
All seven build an
ArgumentParser:--helpexits 0 with usage, an unrecognizedflag exits 2, a bare invocation is unchanged.
Where a probe needs somewhere harmless to write, it now has a flag — the same
shape
--outtook on the PSA companion in #490:--outonprobe_attach.pyandcheck_sbml_engine_agreement.py;--paper-dir/--no-copyonrecover_s4_points.py. That one also skipsrather than crashes when the paper checkout is absent: the merge has already
happened by then, so a
FileNotFoundErrorat the copy left the work done andthe run marked failed. Its stale
/Users/wish/Code/PyBNF/...follow-up line isgone too, per your note that it was never a contract.
diagnose_divergence.pyis both driver and worker — it re-enters itself as--workeron an argv it composes in_spawn(). That branch is matched beforethe parser and left byte-identical; only the half a person types is parsed.
The exemption, endorsed
ssa_table5/_ssa_cell.py(spawned per(engine, model)byrun_ssa_timing.py,Table 5's measurement path) and
sbml_test_suite/testrunner/bngsim_wrapper.py(invoked by the vendored runner on
%d %n %o %l %v) get no parser. Each nownames its caller in a comment saying why there is no parser.
Worth noting:
bngsim_wrapper.pywas already correct. Its argc check answers--helpwith a usage line and exit 2 — the issue's table overstated it asignoring argv. It needed the comment, not a change.
The endorsement is a test, not a convention:
So the next argv sweep cannot quietly "fix" these two, and cannot read the
missing parser as an oversight either.
The test now discovers its family
test_benchmark_runner_help.pyno longer derives fromrun_all.py's registryplus a hand-kept companion list. It globs every
suites/script with a__main__guard and subtractsSPAWNED_WORKERS— 59 scripts, 127 cases,15 s. A script added to any suite inherits the contract without an edit here.
A separate test asserts every registry-named script lands inside that family, so
the orchestrator link stays explicit.
Two tests assert
--helpleaves each committed artifact byte-identical, whichstates the actual harm rather than only "exit 0".
Verification
--helpwithlibsbml,scipy,roadrunner,amici,COPASI,antimony,gillespy2,diffrax,jax,pandas,matplotlibandpsutilblocked at import. Only two scripts import an optional engine at modulescope —
biomodels/filter.py(libsbml) andshowcase/run_ode_trf_fit_from_net.py(scipy) — and both deps are guaranteed present wherever this test runs
(
python-libsbmlis a core dependency;scipycomes in via thetestextra,which
devincludes and CI syncs).diagnose_divergence.py --workerverified to still reachworker()and failinside it on a bogus path, proving the spawn argv unpack is intact.
Risk
None to any measured value. Every script behaves identically when invoked as it
was before, and the two scripts on a measurement path are the two that were not
touched functionally.