mechababs test-cluster: validate a cluster from the campaign - #104
Merged
Conversation
`mechababs test-cluster` (con#98) has to find the e2e scenario wherever mechababs is installed, not only in a source checkout. A suite in `tests/` reaches a campaign today only because bootstrap.sh clones the whole repo into `code/mechababs`; once the code is referenced and locked rather than cloned in — the direction sketched in the study-first redesign (con#101), where `.mechababs/` pins the tools in a `uv.lock` and bootstrap runs them via `uvx` — there is no checkout to reach into and `tests/` is not in a wheel. So move it to `mechababs/testing/e2e/` and ship it as package DATA. Data rather than an importable subpackage because pytest collects from a path, which keeps an `__init__.py` out of a directory of test modules. `mechababs.testing.suite_path()` resolves it through `importlib.resources` and fails loudly on an incomplete install, rather than letting pytest report "no tests collected" much later. Packaging needs care here: setuptools_scm's file finder plus the default include-package-data would ship every git-tracked file under the package, so the `package-data` globs are additive and cannot keep anything out. The dev wrapper scripts are removed with an explicit `exclude-package-data` — they only make sense from a checkout — and a test pins both halves of that. `testpaths` is now required: without it a bare `pytest` also walks `mechababs/`, where the packaged conftest registers `--cluster-config` a second time and collection fails outright. This commit only relocates the suite. `tests/` becomes the unit suite alone: it tests the code and never leaves the repo, so it has no reason to travel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`mechababs test-cluster` runs the packaged scenario with the campaign venv's own pytest, so the campaign has to carry it. That is the one new prerequisite con#98 names beyond the container shim, and it is what removes the `pip install -e '.[test]'` step from validating a cluster. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Validating a cluster config was a repo-dev operation: check mechababs out, `pip
install -e '.[test]'`, export MECHABABS_E2E_WORKDIR and BABS_SPEC, run pytest by
hand. A campaign already carries everything that setup reconstructs — the pinned
babs, an isolated venv, the packaged scenario, and a natural workdir — so validation
belongs on the operate side, next to `iterate` and `status`:
cd my-campaign && source .venv/bin/activate
mechababs test-cluster --cluster ~/config/your-site.yaml
**It does not run in the campaign you point at.** The scenario calls `configure`
(which refuses an existing ledger), `add-dataset`, and `retire-derivative`, so
running it in a campaign holding real work would be destructive. It provisions its
own throwaway campaign from that campaign's pins and works there — which is also
what makes a green run mean "these tools work on this cluster", since the tools under
test are the ones the campaign records.
Three things it does deliberately:
- **Fails loudly.** pytest exits 0 when everything skips, and a missing container
shim is the likeliest reason a first run has nothing to do, so the shim is checked
before pytest starts. Exiting 0 having validated nothing is the worst outcome a
validation command has.
- **Leaves no trace in the campaign.** `-p no:cacheprovider` and a redirected
PYTHONPYCACHEPREFIX, because a `.pytest_cache/` or a `__pycache__/` written into
`code/mechababs` makes `guard.require_clean_pins` refuse every later command.
- **Runs on a campaign that is not configured yet.** It takes the campaign SKELETON
— a datalad dataset with both code pins — not the ledger, which only `configure`
writes. Validating a cluster before committing real data to it is the point, so
gating on the ledger would make the documented order impossible. `configure` and
`test-cluster` now share that check, since both run before a ledger exists.
`--cluster` takes a path, or a name in the campaign's own `clusters/` once
`configure` has copied one in. Deliberately NOT resolved against a vendored clone's
`examples/`: that path disappears with the vendoring. Arguments after a literal `--`
reach pytest; `argparse.REMAINDER` cannot see flag-like tokens without that fence.
The module is `validate.py`, not `test_cluster.py`, so a bare `pytest` does not
collect it as a test module.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `campaign` fixture forked on `--campaign`: with it (what `test-cluster` passes) it provisioned the scenario's campaign from an existing campaign's pins; without it, a dev-only route resolved the checkout above the package, refused a dirty tree, and read BABS_SPEC. So the provisioning code users run was exercised only by users — never by a dev run — which is the failure docs/overview.md's "never a dev-only branch, field, or code path" exists to prevent, and it had already bitten us: a detached-HEAD bug got fixed on one route and left broken on the other. con#98 asks for exactly this ("reworking the e2e's 'I run from a repo checkout' assumption to 'I run from a campaign'"), so collapse to the one path. `--campaign` and `--cluster-config` are now required, as UsageErrors rather than skips — pytest exits 0 when everything skips, and a validation command that passes by doing nothing is worse than one that fails. `repo_under_test`, the ad-hoc `git status` check, the BABS_SPEC env-var read, and `cluster_config`'s `examples/clusters/` fallback are gone, and with them the second bootstrap wrapper and the second cluster-config resolver, since `validate.clone_ref` and `validate.resolve_cluster` already serve both. Dev becomes config rather than a code path: the wrapper scripts bootstrap a DEV CAMPAIGN whose mechababs pin is the checkout, then run `mechababs test-cluster` against it — the same two steps a user runs, with a different value for the pin. `guard.require_clean_pins` then guards provenance on the campaign under test, and each wrapper keeps a dirty-tree refusal where the checkout is actually its input. This is not less code overall (the conftest shrinks, the wrappers grow more) — it is one path, with each guard where it belongs. The two SimBIDS phantom pipelines move to the suite that uses them. They are test fixtures, not starters a site would copy from `examples/`, and the scenario can no longer build a path into `code/mechababs/examples/` anyway. Note this reverses that part of con#100. The podman rung gains a fix for free: it used to run the container's ambient pytest, which since the conftest started importing `mechababs.validate` could not even load it. Now pytest is the dev campaign venv's own, where the pinned mechababs is installed by construction. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tutorial's cluster-validation section described the env-var + checkout path and flagged `test-cluster` as aspirational; it now leads with the campaign route and the aspirational block is gone. Two things it used to require are no longer true and are corrected: a site profile does not have to be committed into `examples/clusters/` to be validated (`--cluster` reads the path you hand it), and the dev invocation is now two steps — bootstrap a dev campaign from the checkout, then run `test-cluster` against it — so the passthrough moved with it and pytest args sit after a literal `--`. installation.md drops the driver venv entirely: the campaign venv bootstrap builds carries pytest and the packaged scenario, so no second env exists to describe. It also stops framing MECHABABS_E2E_WORKDIR as required setup, which is only true when driving the scenario from a checkout — a campaign gets its workdir from its own location, or `--workdir`. reference.md documents the subcommand and states the throwaway-campaign semantics next to it, since that is where someone reading the CLI will look. CONTRIBUTORS.md covers the dev-campaign invocation and why the suite lives in the package rather than `tests/`. README's pointer no longer advertises a driver venv. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Member
|
Without #101 the UX isn't as clean as I'd like it to end up, but this is a move in the right direction. I ran on-cluster and it worked well. Thanks @lobennett!! |
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.
mechababs test-cluster: validate a cluster from the campaign
What this is
mechababs test-cluster --cluster <config>, run from the campaign venv. Validating a cluster config was a repo-dev operation — checkout,pip install -e '.[test]',MECHABABS_E2E_WORKDIR,BABS_SPEC, pytest by hand. The campaign already carries every one of those, so validation moves to the operate side, next toiterateandstatus. The container shim stays the only prerequisite, and it is now checked before pytest starts instead of surfacing as a skip.How it works
--campaignbeside the existing checkout route would have left the user-facing path exercised only by users. I had it that way briefly and it drifted — a detached-HEAD fix landed on one route and not the other. So--campaignis required and the checkout route is gone: a dev run reaches the same code the way a user does, bootstrapping a dev campaign pinned at the checkout (87s of a 40-minute run). The two rungs still differ only in which cluster config and whether a container wraps them.-p no:cacheproviderplus a redirectedPYTHONPYCACHEPREFIX: a.pytest_cache/insidecode/mechababsreads as a dirty pin toguard.require_clean_pins, and one early run left a campaign refusing every later command.configurewrites the ledger, so gating on it made bootstrap → validate → configure impossible. Both commands now share one "campaign enough to run" check.The suite moved into the package
Your plan had
test-clusterread the vendoredcode/mechababs/tests/e2e. This ships it as package data instead, resolved throughimportlib.resources— option 1 from the issue thread. Under #101 the tools arrive viauvx --from git+…against auv.lock, so there is no clone to reach into andtests/is not in a wheel; packaging is the part that survives.Two consequences. The SimBIDS phantom pipelines move out of
examples/pipelines/into the suite that consumes them — this reverses that bit of #100; they are fixtures, and a packaged suite cannot reach a fixture outside the package. Andtestpathsbecomes load-bearing: without it a barepytestalso walksmechababs/, where the packaged conftest registers--cluster-configtwice and collection fails.exclude-package-datakeeps the dev wrappers out of the wheel, sincepackage-dataglobs are additive rather than restrictive once setuptools_scm's file finder is involved.Say the word if you would rather it stay in
tests/until #101 lands. The command only needs some way to find the scenario.Changes
mechababs/validate.py— the command body: cluster resolution, re-clonable ref resolution, the shim precheck, the pytest invocation.mechababs/cli.py— thetest-clustersubcommand; the skeleton and campaign-venv guards extracted soconfigureandtest-clustershare one copy.mechababs/testing/— the e2e scenario, shipped as package data and located throughimportlib.requirements-campaign.txt— pytest in the campaign venv.reference.mddocuments the subcommand,installation.mddrops the driver venv.Five commits, each green on its own.
Verified
3 passed(38m54s) — bootstrap → configure → add-dataset → iterate: scaffold → submit → wait → merge, both pipelines, derivative zips landing in the output RIA.2 passed, 1 failed. The failure is allow full BIDS-acceptable chars as derivative names PennLINC/babs#394 (grep -Ereads the+in a zip name as a metacharacter). The cluster run's third test is green only because it pinned a patched babs, so 2/3 is the honest number on babsmain.58 passed.The podman rung earned its keep: once the conftest imported
mechababs.validateit could no longer load under the container's ambient pytest — invisible on a cluster, where the campaign venv is what runs. It now uses the dev campaign's own pytest.run_in_podman.shis the one change no full run covers. Its${A[@]+"${A[@]}"}fix — an empty array aborts underset -uon bash before 4.4, so the script could not start on a RHEL 7 login node — is verified only in isolation and withbash -n.What #101 will unlearn
The durable pieces are the command surface, the packaged suite, and having one provisioning input. What moves is lookup, each in a single place:
_pins_from_campaignreads a vendored clone's branch and will readuv.lock; the skeleton check tests.datalad+code/<tool>, which a study-first campaign explicitly will not have. The campaign-venv guard is your open 💬 on environment enforcement — this reuses it rather than deciding it.Closes #98