Skip to content

fix(tests): narrow the conftest path filter and make a missing build loud - #53

Merged
akutuva21 merged 2 commits into
mainfrom
fix/conftest-guard-path-filter
Oct 1, 2026
Merged

akutuva21 merged 2 commits into
mainfrom
fix/conftest-guard-path-filter

Conversation

@akutuva21

Copy link
Copy Markdown
Member

Follow-up to #48. The guard that resolves bionetgen to the tree under test filtered sys.path with a substring test on "BioNetGen" — which also strips this repository's build-artifact directory (build/cpp). In any worktree without its own build, the compiled extension becomes unimportable.

Measured both ways, same worktree, same commit, same .so bytes

Only the path STRING differs (measurement from sciStochastic):

PYTHONPATH=python:/tmp/sciStoch/ext              -> 888 passed, 54 skipped,  0 failed
PYTHONPATH=python:build/cpp:/…/BNG3/build/cpp    ->  46 failed, 685 passed, 91 skipped

All 46 failures are AttributeError: 'NoneType' object has no attribute 'parse_file' at python/bionetgen/model.py:650 because _cpp imported as None. None are real regressions.

Worse, and the class this guard exists to prevent: any test file using pytest.importorskip("bionetgen._bionetgen_cpp") reports SKIPPED with exit 0 where its tests should have run. A guard that silences its own tests is passing quietly.

Two changes

  1. Narrow the filter to the entries actually guarded — the editable install's source path and an installed bionetgen package directory — preserving build/cpp. The guard's real work is removing the __editable__ meta_path finder; the substring test was collateral damage.
  2. Make an unimportable extension raise, with the resolved bionetgen.__file__, the surviving sys.path and the underlying error, rather than letting importorskip turn a misconfigured environment into thirteen passing-looking tests.

Also in this PR

Wires tests/validation/test_parity_nfsim_seed.py into parity.yml. All three existing invocations name test_parity_nfsim.py by path, so the new 519-line parity module was collected by no job — the same orphaned-work class as the uncovered Coverage.lean.

Why this was missed

The guard was merged on a genuinely strong three-variant demonstration: pytest_configure placement and module-docstring placement both resolve bionetgen to the wrong tree while leaving the suite green. Nobody tested it against an agent with no build in its worktree, which is the majority case on a 30-agent host. A fix can be rigorously verified against the failure it was designed for and still break an adjacent case nobody checked.

Verified locally: tests/test_ci_contract.py 38 passed, black and ruff clean, parity.yml parses and the new step is present.

akutuva21 and others added 2 commits September 30, 2026 22:42
…loud

The guard that resolves `bionetgen` to the tree under test filtered sys.path
with a substring test on "BioNetGen". That also strips this repository's
build-artifact directory (`build/cpp`), so in any worktree without its own
build the compiled extension becomes unimportable.

Measured both ways in one worktree, same commit, same .so bytes, only the
path STRING differing (reported by sciStochastic):

  PYTHONPATH=python:/tmp/sciStoch/ext             -> 888 passed, 54 skipped, 0 failed
  PYTHONPATH=python:build/cpp:.../BNG3/build/cpp  ->  46 failed, 685 passed, 91 skipped

All 46 are AttributeError: 'NoneType' object has no attribute 'parse_file'
because _cpp imported as None -- none are real regressions. Worse, and the
class this guard exists to prevent: any test file using
pytest.importorskip("bionetgen._bionetgen_cpp") now reports SKIPPED with
exit 0 where its tests should have run.

Two changes:

1. Narrow the filter to the entries actually guarded -- the editable
   install's source path and an installed `bionetgen` package directory --
   preserving `build/cpp`, which is a legitimate sys.path entry. The
   guard's real work is removing the `__editable__` meta_path finder; the
   substring test was collateral.

2. Make an unimportable extension RAISE with the resolved bionetgen path,
   the surviving sys.path and the underlying error, instead of letting
   importorskip convert a misconfigured environment into a quiet pass. A
   guard that silences its own tests is passing quietly, which is the
   opposite of what it was written for.

Also wires tests/validation/test_parity_nfsim_seed.py into parity.yml. All
three existing invocations named test_parity_nfsim.py by path, so the new
519-line parity module was collected by no job -- the same orphaned-work
class as the uncovered Coverage.lean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the previous commit on this branch. sciSignaling identified a
case the first fix re-broke: dropping "entries not under the worktree root"
also drops a legitimate PYTHONPATH pointing at a SHARED build-artifact
directory, which is the workaround most agents use. That trades one silent
failure for another.

The filter now removes the editable install's own source path -- the thing
this guard guards -- identified structurally from the finder's search_paths,
and nothing else. Verified in a worktree with no build of its own and the
extension reachable only via a shared directory:

  pkg:  .../BNG3-fix-segv/python/bionetgen/__init__.py
  ext:  /tmp/guardcheck/ext/_bionetgen_cpp.cpython-314-darwin.so
  guard fired: True        worktree python/ first: True

So the shadowing the guard exists to prevent is still defeated, and the
shared artifact survives. This worktree's own build/cpp is prepended when
present, so a local build takes precedence over a shared one without
displacing it.

Reported by sciSignaling; mechanism confirmed by orchScope independently,
who additionally found the underlying trap: a STALE _bionetgen_cpp*.so
vendored inside python/bionetgen/ in 13 worktrees (Sep 27, 5,764,264 B,
failing to dlopen with "symbol not found ... simulateNfcore2"). Any
sys.path ordering that puts python/ first silently skips every
importorskip-guarded file. Those artifacts are removed separately; the one
legitimate copy in BNG3-parity is its own verified build and is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@akutuva21
akutuva21 merged commit 1c78a72 into main Oct 1, 2026
27 of 30 checks passed
akutuva21 pushed a commit that referenced this pull request Oct 1, 2026
…path filter

PR #53 (1c78a72) landed on main while this branch was open, and it fixes the
defect this harness existed to work around: with it, the canonical command

  PYTHONPATH=python:build/cpp python3 -m pytest tests/python

resolves both the package and the extension to the worktree under test. Measured
after rebasing: 974 passed, 28 skipped, 2 xfailed, and the extension probe
resolves to BNG3-ir-snapshot/build/cpp.

So the harness is no longer a workaround. Kept, and rewritten as documentation
of the probe rather than a path-patcher, because the check costs two seconds
and its absence is invisible. It records the three traps that cost the session
time: the probe must run INSIDE a pytest session (conftest decides extension
reachability), bionetgen._cpp is inert and reads None on every run, and the
sys.modules form works on both the direct and fallback import routes.
akutuva21 pushed a commit that referenced this pull request Oct 1, 2026
…path filter

PR #53 (1c78a72) landed on main while this branch was open, and it fixes the
defect this harness existed to work around: with it, the canonical command

  PYTHONPATH=python:build/cpp python3 -m pytest tests/python

resolves both the package and the extension to the worktree under test. Measured
after rebasing: 974 passed, 28 skipped, 2 xfailed, and the extension probe
resolves to BNG3-ir-snapshot/build/cpp.

So the harness is no longer a workaround. Kept, and rewritten as documentation
of the probe rather than a path-patcher, because the check costs two seconds
and its absence is invisible. It records the three traps that cost the session
time: the probe must run INSIDE a pytest session (conftest decides extension
reachability), bionetgen._cpp is inert and reads None on every run, and the
sys.modules form works on both the direct and fallback import routes.
akutuva21 added a commit that referenced this pull request Oct 1, 2026
…gy structurally (#57)

* snapshot emitter: refuse illegal symbol kinds, serialize barrier energy structurally

Two defects in cpp/bindings/bind_compile_snapshot.cpp, the producer of the
BNGIR v0.2 wire object.

1. symbolKindName could emit a symbol kind outside the format's closed enum.
   It returned "barrier_pattern", "population_type" and "invalid" for
   SymbolKind::BarrierPattern / PopulationType / Count. The schema's
   symbol.kind enum is exactly {parameter, function, observable,
   molecule_type, compartment, reaction_rule, energy_pattern}, so a document
   carrying any of those fails validation. Each now raises a diagnostic naming
   the construct instead. Note: pyBngir corrected my initial report here --
   bngir.py:986-987 DOES map barrier_pattern/population_type to their
   sections, so the defect was schema-invalidity only, not unreadability.

2. A barrier's energy was serialized as parser text. CompiledBarrierFactor
   carries both `energyExpression` (printable BNGL text) and `expression`
   (a ResolvedExpression); the emitter wrote the former. A wire object
   required to hold resolved semantics must not carry BNGL source, and the
   reader walks `expression` as an expression tree, so a bare string is not
   usable there either. Now emits expressionSnapshot(barrier.expression).

Tests: tests/python/test_bngir_snapshot_emitter.py, 16 passed / 2 strict-xfail,
compared semantically (model -> IR -> model -> IR) with a structural differ
that names any dropped key, plus a guard-the-guard test proving the differ
detects a dropped feature.

HONEST CAVEAT, stated because it changes how this PR should be read: both
emitter changes sit on paths that are UNREACHABLE today. barrier_patterns is
always empty on the Python parse path because cpp/bindings/bind_parser.cpp:26-43
omits the finalizeThermodynamicMetadata() call that
cpp/parser/BNGAstVisitor.cpp:2039 makes (thermoParse owns that), so barrier
symbols and population-type symbols can never reach symbolKindName. I verified
this by reverting the emitter to 6889fba and rebuilding: the new tests pass
identically before and after the change. They specify intended behaviour and
will start asserting for real when barriers are lowered; they do not currently
demonstrate it. Two dormant tests are labelled as such rather than presented
as gates.

Reports (not fixed here, owners notified):
- bngir-0.2.schema.json is stale for population_maps; pyBngir ruled the reader
  authoritative, schema rewrite assigned to provenance/schemas' owner.
- bngir.py:1112-1141 _pattern_v02 emits the bond marker AFTER the state, but
  BNGL requires it before, so any site with both regenerates as `A(b~0.)` and
  fails to re-parse. Reported to pyBngir.

Gates: ctest -j4 -> 100% tests passed out of 461. Full pytest suite isolated
to this worktree -> 878 passed, 28 skipped, 2 xfailed. black --check and
ruff check clean.

* rebase onto origin/main; make this file fail loudly when the extension is missing

Two changes, both found by rebasing rather than by review.

1. Rebased onto origin/main (was 22 commits behind; 8d0b9be pyBngir's bngir
   PR and 8c5c315 the conftest guard both landed). Verified with
     git rev-list --count HEAD..origin/main      -> 0
     git diff --name-only origin/main HEAD      -> exactly my 5 files

2. The conftest guard from PR #48 strips this worktree's own build/cpp, so
   the extension became unimportable and this file reported "1 skipped" while
   pytest exited 0. That is precisely the silent-skip mechanism the session
   has been culling, sitting in the file whose entire subject is
   `_cpp._compiled_snapshot`. Replaced
     pytest.importorskip("bionetgen._bionetgen_cpp")
   with a hard import, and the same run now fails loudly:
     before: 1 skipped   (passing, testing nothing)
     after:  16 failed, 2 xfailed, all with
             AttributeError: 'NoneType' object has no attribute 'parse_string'
   which is the correct signal. jsonschema keeps its importorskip because it
   is a genuinely optional validator, not a hard dependency.

Re-verified after the rebase, extension resolved inside a pytest session:
     PKG: .../BNG3-ir-snapshot/python/bionetgen/__init__.py
     EXT: .../BNG3-ir-snapshot/build/cpp/_bionetgen_cpp.cpython-314-darwin.so
  ctest -j4                  -> 100% tests passed out of 473
  pytest tests/python        -> 974 passed, 28 skipped, 2 xfailed

* verify: restate conftest_isolation as a probe now that #53 fixed the path filter

PR #53 (1c78a72) landed on main while this branch was open, and it fixes the
defect this harness existed to work around: with it, the canonical command

  PYTHONPATH=python:build/cpp python3 -m pytest tests/python

resolves both the package and the extension to the worktree under test. Measured
after rebasing: 974 passed, 28 skipped, 2 xfailed, and the extension probe
resolves to BNG3-ir-snapshot/build/cpp.

So the harness is no longer a workaround. Kept, and rewritten as documentation
of the probe rather than a path-patcher, because the check costs two seconds
and its absence is invisible. It records the three traps that cost the session
time: the probe must run INSIDE a pytest session (conftest decides extension
reachability), bionetgen._cpp is inert and reads None on every run, and the
sys.modules form works on both the direct and fallback import routes.

---------

Co-authored-by: irSnapshot <agent@local>
@akutuva21
akutuva21 deleted the fix/conftest-guard-path-filter branch October 4, 2026 02:32
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