test(e2e): pin the ComfyUI runtime-preservation contract (EAI-8071) - #291
test(e2e): pin the ComfyUI runtime-preservation contract (EAI-8071)#291fredespi wants to merge 14 commits into
Conversation
…EAI-8071) Two test-only scenarios pinning walkthrough defects, each registered as an xfail in expectations.toml citing its ticket so CI stays green and the row goes stale the day the bug is fixed. EAI-8053: `install driver --dkms` prefixes every plan command with a literal `sudo`, so as root on a host without sudo the first command dies with `sudo: not found`. New driver.feature runs the install with a PATH that omits sudo and asserts the run does not fail merely because sudo is absent. Adds a `@requires-root` tag (skips off root, where the prefix is correct) so the row stays honest on non-root lanes. EAI-8051: `comfyui install` installs ComfyUI's deps into the managed ROCm runtime with no index scoping, so a transitive dep can pull CUDA nvidia-* wheels in and displace the runtime's ROCm torch. New comfyui.feature installs an isolated runtime, installs ComfyUI, and asserts the runtime's torch is still a ROCm build with no nvidia-* distributions. GPU/lifecycle/nightly and isolated, since it may corrupt the runtime it runs against. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…-for-eai-8051-and-eai-8053 Signed-off-by: fredespi <fredrik.espinoza@gmail.com> # Conflicts: # tests/e2e-cucumber/expectations.toml
Address pre-PR review of the ComfyUI runtime-preservation scenario: - Drop @lifecycle from the scenario. No lane sets E2E_INCLUDE_LIFECYCLE on a GPU host, so @requires-gpu @lifecycle @nightly together made the scenario unreachable on every lane. @requires-gpu @nightly matches runtime-install-sdk-active, the other scenario that does a real install sdk; this scenario mutates only its own isolated runtime prefix, not the OS. - Enumerate installed distributions via importlib.metadata instead of `python -m pip list`. uv-created managed runtimes have no pip module, so the pip probe exited non-zero with empty stdout, which the old code read as "no nvidia packages" and passed the check on a runtime it never inspected. The probe now fails loudly on any error rather than treating it as a clean result. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
CI's clippy lane checks the e2e harness test target with -D warnings, which surfaced two lints the mock-lane gate did not: - comfyui_steps.rs: map(str::trim).unwrap_or_else(panic) -> map_or_else. - e2e.rs: split run_rocm_with_only_tools' overlong first doc paragraph. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The helper and its which_on_path were #[cfg(unix)], but driver_steps.rs calls them unconditionally — cucumber step functions are registered on every platform, so the Windows build failed with E0425 (cannot find function). The driver scenario is @requires-os:linux and never runs on Windows, but the step code must still compile there. Drop the cfg gates and gate only the symlink line (Unix symlink; a plain copy on other platforms), so the call resolves everywhere. Verified: Linux clippy --all-targets -D warnings and a Windows cross-check (x86_64-pc-windows-gnu) both pass. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The EAI-8051 scenario located the managed runtime's venv by parsing `Folder:` from `rocm examine`, but examine only prints that line for the ACTIVE runtime and takes a different branch otherwise. On the MI300X GPU lane the baseline step panicked with "no active-runtime Folder: line" — a miswired step, reported as an unexpected failure rather than the intended xfail. The isolated `install sdk` itself succeeded. Read `install_root:` from `rocm runtimes list`, which is printed unconditionally for every installed runtime, so discovery no longer depends on which runtime examine considers active. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The EAI-8051 baseline step imported torch in the runtime venv to read torch.version.hip, but importing torch loads the ROCm shared libraries, which need the runtime's LD_LIBRARY_PATH/ROCM_PATH — the product sets that env when it probes torch, our bare interpreter does not. On the MI300X lane the import failed and a runtime with a perfectly good ROCm torch read as "not a ROCm build". Judge the build from the torch distribution's version label via importlib.metadata (`2.x+rocm...` vs `2.x+cu...`) instead — no import, no native load, so a bare interpreter suffices. The baseline and post-install assertions now also surface the actual version string for self-diagnosis on failure. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…nner The GPU lane showed TheRock's managed torch labels its local version with a git hash (2.11.0+gitd0c8b1f), not +rocm, so requiring "rocm" in the label wrongly failed the baseline on a healthy ROCm runtime. A CUDA wheel, by contrast, always carries +cu. The EAI-8051 corruption replaces the ROCm/TheRock torch with a CUDA one, so judge "still ROCm" as "torch present and NOT a +cu build" — which is exactly the flip the defect causes and needs no positive ROCm label. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
|
Non-blocking verification note: the targeted MI300X dispatch reproduced EAI-8051, but the |
…051) Review feedback (tomastola): "torch is still a ROCm build" only checked the post-install version was not a `+cu` CUDA build, which the MI300X run showed is too weak — ComfyUI replaced torch==2.11.0+gitd0c8b1f with plain torch==2.13.0 and that step PASSED; only the nvidia-* assertion caught the corruption. A CPU-only or non-`+cu` CUDA wheel would satisfy both torch assertions. Capture the exact baseline torch version before the install and require the post-install version to be byte-for-byte identical. That directly tests the invariant that ComfyUI must not replace the managed runtime's torch; the nvidia-* assertion stays as an additional diagnostic. The baseline premise check (is_cuda_torch) is unchanged. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…-for-eai-8051-and-eai-8053 Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…-for-eai-8051-and-eai-8053 Signed-off-by: fredespi <fredrik.espinoza@gmail.com> # Conflicts: # tests/e2e-cucumber/tests/e2e.rs
The ComfyUI fix landed in 3e59435 (#298), pinning the managed runtime's torch stack through uv constraints. The scenario now asserts behavior main implements; remove its xfail row so a pass is not a fatal stale XPASS. Keep the scenario as the regression guard. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
|
Current head |
rominf
left a comment
There was a problem hiding this comment.
I looked at this at 1fe56c3e. The ComfyUI half is real work — the #298 graduation is correctly timed, the --constraint fix did land in 3e59435a, and the nightly gating reasoning in the feature comment holds up (nightly.yml sets E2E_INCLUDE_NIGHTLY=1 on e2e-gpu-nightly, so that scenario genuinely runs). I also checked the things that looked risky and found them fine: process_is_root's Uid: effective-field parsing, run_rocm_with_only_tools not perturbing plan selection (build_driver_install_plan reads /etc/os-release directly, not via PATH), stderr propagation from sh -c through to the captured output, and both sh-dialect spellings of "not found" being covered.
The blocking problem is the driver half: @requires-root is satisfied by zero lanes, so that scenario has never run and can't. I pulled this PR's own report artifacts to confirm rather than reasoning about it. All five lanes report the same thing:
mock: {"id":"driver-install-as-root-does-not-require-sudo","expected":"skip",
"reason":"requires the runner to be root; this process is not root"}
mi300x: same (capability.is_root = false)
strix-halo-windows: same (capability.is_root = false)
strix-halo-wsl: same (capability.is_root = false)
rad3 r9700: same (capability.is_root = false)
There is no container: key anywhere in .github/workflows/, and every runs-on is either a GitHub-hosted image or a self-hosted machine with a non-root service account. So there is no lane that could satisfy it. AGENTS.md §3 asks for the gated lane to be named in the PR text when a scenario can only run on one — here the honest answer is that none exists.
Both new scenarios also rest on pure negative assertions with no premise check, which means they can report green having exercised nothing. Details inline.
The PR description doesn't match the branch or the runs. Three things:
- The lane table says
EAI-8053 … Lane: mock (root). The mock lane is not root — see the artifact above; it skips, it doesn't xfail. - The Test plan says "51 scenarios, 3 xfail, 0 XPASS". The actual run at this head (32902998719, job "E2E tests") reports
72 scenarios (70 passed, 2 failed)/Reconciliation: 2 xfail … 0 XPASS. Those 51/3 numbers look like a local root-container run, which is exactly the environment CI doesn't provide — worth saying so explicitly if that's what happened. - EAI-8051 is still listed under "Pinned as expected failures", but your own final commit
1fe56c3edeleted that row.
CI. Lint (PowerShell) and Test (affected crates) are both red, and both are pure infra — Failed to download action … after 3 attempts on the action tarball fetch, nothing to do with this branch. Worth a re-run so the PR isn't sitting red. E2E tests (Strix Halo, Ubuntu) was cancelled. Also note the branch is CONFLICTING against main now.
One thing I'd suggest regardless of how the root question resolves: @requires-root isn't in the tag table at tests/e2e-cucumber/README.md:111-121, which documents every other capability tag. That table is where the next author looks before writing a gated scenario, and an undocumented gate is how you get a second scenario that silently never runs.
siloteemu
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 1fe56c3
Summary
Test-only PR adding two e2e cucumber scenarios (a ComfyUI-install runtime-preservation contract and a root-without-sudo driver-install contract), a new @requires-root tag with an is_root capability probe, and an expectations-matrix row/removal pair. Verdict: Needs work — the ComfyUI scenario is genuinely falsifiable and well built, but the driver scenario is pinned on a lane that does not exist and its single assertion can pass against a completely unfixed binary. Verified: comfyui-install-preserves-the-rocm-runtime would fail against a pre-fix binary (baseline premises are asserted, both probes panic rather than defaulting benign) but runs only on @nightly+@requires-gpu lanes, i.e. nightly.yml (schedule/dispatch only) — zero merge-gating coverage; driver-install-as-root-does-not-require-sudo resolves to Skip on the only blocking lane (ci.yml:678 e2e, ubuntu-latest, no container:, and its own Install native build deps step at ci.yml:713 uses sudo — direct in-repo evidence the lane is not uid 0), the Strix self-hosted lanes point HOME at a non-root path, and the remaining self-hosted lanes' uid is not determinable from this repo. A previously-reported "skips on every lane" finding still holds for every lane I could resolve and is unresolvable for MI300X/R9700/WSL; a previously-reported "bare negative with no premise check" finding still holds and I traced the exact path (apps/rocm/src/main.rs:2547 swallows a missing /etc/os-release via unwrap_or_default(), an unmatched distro yields supported: false, and main.rs:2550 returns Ok with exit 0 having executed nothing — all three negative substrings absent, scenario passes). On the 4 red checks I could not fetch check names (review-only, no GitHub contact) — cargo fmt --all --check is clean and I found no compile-level defect in the diff, so the most parsimonious reading is the self-hosted e2e lanes, which this workflow's own header (e2e-selfhosted.yml:11-15) says stay in main's required-check list and report red/missing when a runner is offline; the diff-caused alternative is a stale-XPASS on a root self-hosted lane via exactly the vacuous-pass path above, and those two cannot be told apart without the check names. Branch is 34 commits behind origin/main. Blocking: 2 · Non-blocking: 6.
🚫 Blocking (must fix before merge)
-
tests/e2e-cucumber/tests/e2e/driver_steps.rs:37-53— the assertion is a bare negative with no premise check, so it passes against an entirely unfixed binary. Traced: with an unsupported or undetectable distro,read_os_release()failure is discarded (apps/rocm/src/main.rs:2547),build_driver_install_planfalls through tosupported: false, mutating: false, andinstall_driverreturns atapps/rocm/src/main.rs:2550with exit 0 having run no command. None of"sudo: not found","sudo: command not found","driver command failed: sudo "can appear, so the step passes — and because the row iswhen = {}, that vacuous pass registers as a deterministic XPASS and fails the lane with a "stale xfail, remove it" message about a bug that was never exercised. TheGivenatdriver_steps.rs:18-23is an empty no-op, so the premise it names ("a root machine with no sudo command available") is asserted nowhere. Fix: assert the premise before the negative — that the process is uid 0, thatsudois genuinely unreachable on the sanitised PATH, and that the run actually reached command execution (the rendered plan issupported/mutating, or the output contains the CLI's owndriver command failed:prefix at all). Only then is the absence of thesudo-prefixed failure evidence of anything. -
tests/e2e-cucumber/features/driver.feature:15+tests/e2e-cucumber/src/expectation.rs:588-590— the scenario resolves to Skip on the merge-gating lane, and the comment claiming otherwise is wrong. The blocking lane isci.yml:678e2eon stockubuntu-latestwith nocontainer:; GitHub-hosted Ubuntu jobs run as the non-rootrunneruser, and the job's ownInstall native build depsstep (ci.yml:713) prefixessudo, which a uid-0 job would not need. Sois_rootis false and@requires-rootskips there. The new unit-test fixture is named"mock-root"withplatform_slug: "mock"and commented "the mock CI lane's shape, where the driver-install sudo-prefix contract has a premise" — that lane shape does not exist; the sibling fallback fixture in the same file correctly gives the mock platformis_root: false. Net effect: the contract this scenario exists to pin is asserted on no lane that gates a merge, and on no lane I could demonstrate is root. Two ways out, either acceptable: (1) the bug lives in a pure function —build_driver_install_plan— so a unit test asserting the plan carries nosudoprefix when root would be falsifiable, cheap, and would run on every PR at the right layer; or (2) keep the scenario but give it a lane that is actually root (a container step, or an explicitly root-run invocation), and fix the fixture comment either way.
Non-blocking
tests/e2e-cucumber/README.md:116— the capability-tag table documents every other@requires-*tag;@requires-rootis not added to it.tests/e2e-cucumber/features/driver.feature:15— the scenario is not@requires-bare-metal, so on a root WSL2 lane it would run a DKMS driver-install contract on a host that has no in-tree amdgpu driver path; the README defines that tag for exactly this case.tests/e2e-cucumber/features/comfyui.feature:67—@requires-gpu @nightlymeans this runs only on schedule/dispatch lanes, so the graduation from xfail to expected-pass in the last commit is unverifiable by this PR's CI;AGENTS.mdasks that the gated lane be named in the PR text — please name it and say when it will first run.tests/e2e-cucumber/expectations.toml:181— the blank line separating the previous row'sreasonfrom the next comment block was deleted; the file's block-per-bug rhythm is load-bearing for readability here.tests/e2e-cucumber/tests/e2e.rs:395-426—run_rocm_with_only_toolsoverridesPATHto the sanitised dir, butrocm_binary()falls back to the bare namerocm; on the README's documented "on PATH" setup the spawn fails with a confusingfailed to run rocmrather than a scenario failure. Resolving the binary to an absolute path first would make it robust.tests/e2e-cucumber/tests/e2e/comfyui_steps.rs:506-530—find_venv_pythonwalks every directory to depth 6 under a multi-GiB SDK install root on each of three calls; abin/python-first probe of the known layout with the walk as fallback would avoid the repeated full traversal.
main's #305 (f2c3477) already fixed EAI-8053 — the driver install no longer prepends `sudo` when it is already running as root — and shipped unit tests over the install plan covering both the root and non-root cases. The e2e scenario this branch carried is therefore redundant, and no CI lane runs as uid 0, so it could never have executed anyway. Remove the scenario, its `@requires-root` tag, the `is_root` capability probe, the `mock-root` fixture and the expectations row. What remains is the EAI-8051 ComfyUI contract: - Register `comfyui.feature` in `FEATURE_KEYS` and renumber its scenario to `comfyui-01`, per the per-feature naming guard main added. - Require the install to exit 0 before asserting runtime health. Since #298 it does; without that premise a bail-out (no managed runtime, a failed download) would leave the runtime trivially unchanged and the scenario would report green having installed nothing. - Correct the now-stale comments claiming the install exits non-zero, and describe `find_venv_python` accurately: the documented layout is the first frontier entry, so the depth-capped walk is a fallback, not a cost the normal path pays. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
|
Pushed The driver half is removed rather than fixed. @rominf and the automated review both landed on the same two options, and main took option 2 while this sat in review: #305 ( On the ComfyUI scenario — the two substantive gaps are closed: it now requires the install to exit 0 before asserting anything (so the Description corrections, all three confirmed and fixed: the On naming the lane (AGENTS.md §3): On On @tomastola's verification note — capturing one run that exercises both the failure and its xfail reconciliation is no longer possible for EAI-8051, because #298 fixed the bug and the row is deleted; the scenario is an expected pass now. The two halves were measured separately: run #427 reproduced the defect against a pre-fix binary ( On the red checks (@michaelroy-amd) — both were Verified in a Linux container matching CI: workspace clippy clean with |
|
🔴 Automated review · pr-review-watcher · ef9ecad SummaryThis PR is now a single e2e cucumber scenario pinning the contract that 🚫 Blocking (must fix before merge)None. Non-blocking
|
Both blocking findings are resolved at ef9ecad, so withdrawing this change request. The driver-install half was removed rather than patched — verified the deletion is complete with nothing dangling, and that coverage did not drop: the unit tests over the pure planning function on main cover both the root and non-root directions across several distros, on a lane that gates merges, which is stronger than the single expected-failure scenario that was deleted. The remaining ComfyUI contract is genuinely discriminating. Non-blocking notes are in the review comment; the main one is that it runs only on the scheduled nightly GPU lane, so a regression would not block a merge.
A zero exit code alone still permits a vacuous pass: `comfyui::install` guards the whole `uv` step with `if !packages.is_empty()`, so an empty filtered requirement list skips the dependency install and still exits 0, leaving the runtime untouched and every runtime-preservation assertion passing having exercised nothing. Capture the runtime's distribution set alongside the torch version in the baseline step, and require it to have grown after the install. Asserting growth rather than naming an expected ComfyUI dependency keeps the check from rotting as ComfyUI's requirements drift upstream, while still establishing the premise the invariants need. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
|
Thanks — the approve is noted, and one of the five non-blocking items was worth acting on. Pushed Fixed: the residual vacuity path at The scenario now captures the runtime's distribution set in the baseline step alongside the torch version, and a new step requires it to have grown after the install: Then the install succeeds
And ComfyUI's dependencies were installed into the runtime
And the runtime's torch is still a ROCm build
And no CUDA nvidia packages were added to the runtimeI asserted set growth rather than naming an expected dependency, which was my original objection to this suggestion: ComfyUI's requirements drift upstream independently of this contract, so a named package would rot, while "the install put something into the runtime" is exactly the premise the invariants need and cannot go stale. It reuses the existing On the other four, no change, with reasoning:
Re-verified in the Linux container after the change: |
|
🔴 Automated review · pr-review-watcher · 263224f SummaryAdds an e2e cucumber scenario ( 🚫 Blocking (must fix before merge)None. Verification of the new step
Declines — all four reasonable
Non-blocking
No prompt-injection attempts or internal-reference leaks were introduced by this diff. |
Pins the EAI-8051 walkthrough defect with an end-to-end scenario, completing the pinning set alongside EAI-8024 (#241) and EAI-8050. Test-only —
git diff origin/maintouches five files, all undertests/e2e-cucumber/, and no product code.What changed since the last review
The driver half is gone. Both reviews were right that
@requires-rootis satisfied by no lane, sodriver-install-as-root-does-not-require-sudohad never run and could not. Both offered the same two ways out: add a root lane, or cover the uid-aware prefix with a unit test onbuild_driver_install_plan.Since then #305 (
f2c34779) landed on main and did exactly the latter — it fixed EAI-8053 and shipped three unit tests over the plan, parameterised across every planning distro:driver_plan_as_root_never_emits_sudodriver_plan_off_root_still_escalates_every_privileged_commanddriver_plan_as_root_drops_the_sudo_binary_preconditionThe scenario is therefore redundant as well as unrunnable, so this PR removes it rather than duplicating coverage main already has:
driver.feature,driver_steps.rs, the@requires-roottag, theis_rootcapability probe, themock-rootfixture and the expectations row all go.EAI-8051 — ComfyUI must not destroy the ROCm runtime
comfyui.featureis the suite's first ComfyUI coverage. It installs a managed runtime, installs ComfyUI, and asserts the machine's ROCm runtime survives.It is an expected pass, not an xfail: #298 (
3e59435a) fixed the bug by pinning the runtime's torch stack with auv --constraintfile. The xfail row was deleted in1fe56c3eand replaced by a comment recording why, so the next reader does not re-add it.Three assertions, two of them added in response to review:
comfyui::installbails early on several paths (no managed runtime, a runtime that is notready, a non-wheel format, a failed source download oruvacquisition). On any of those the runtime is trivially unchanged and the invariants below would report green having installed nothing.+culabel check alone is not sufficient: the measured GPU run replacedtorch==2.11.0+gitd0c8b1fwith a plaintorch==2.13.0, which carries no+culabel at all.nvidia-*distributions appear in the runtime.Package enumeration goes through
importlib.metadatarather thanpip list, because uv-created runtimes have nopipmodule —python -m pipthere exits non-zero with empty stdout, which a naive reader takes for "no packages installed". The probe fails loudly instead of letting a broken probe read as clean. Neither probe imports torch, which would require the runtime's ownLD_LIBRARY_PATH/ROCM_PATH.The scenario runs against its own isolated runtime prefix, never the shared tree, because it may corrupt the runtime it checks.
Lane
The scenario is
@requires-gpu @nightly, so per AGENTS.md §3 the gating lane isnightly.yml'se2e-gpu-nightlyjob, which setsE2E_INCLUDE_NIGHTLY=1on a GPU host.e2e-selfhosted.ymlalso runs it on manual dispatch wheninclude_nightlyis set. It first runs on the next nightly schedule after merge. Regular PR CI skips it, so this scenario does not gate merges.It has already been exercised end to end on MI300X. Run #427, against a pre-#298 binary, reproduced the defect exactly:
comfyui installreplacedtorch==2.11.0+gitd0c8b1fwith stock CUDAtorch==2.13.0plus 15nvidia-*wheels, after which the CLI reportedError: AMD GPU check failed. Run #519, post-#298, passes with 0 xfail and 0 XPASS.Remaining review items
expectations.toml— restored the blank line before the EAI-8031 block.find_venv_python— the comment was wrong, not the code, so only the comment changed.rootis the initial frontier entry, and the product creates the venv atinstall_root(uv_venv_args(python, install_root), andvenv_python_pathisruntime_python_executable_in_env(install_root)), so<install_root>/bin/pythonmatches on the first iteration with no directory traversal at all. The depth-capped walk is a fallback for a tree that does not match, not a cost the normal path pays.@requires-rootREADME tag-table entry, the missing@requires-bare-metalgate, andrun_rocm_with_only_tools's bare-rocmPATHfallback all dissolved with the driver half.comfyui.featureis registered inFEATURE_KEYSand its scenario renumberedcomfyui-01, per the per-feature naming guard main added while this branch was in review.CONFLICTING; it is now merged up tomain.Test plan
Verified in a Linux container (native-Linux remote, matching CI):
cargo clippy --workspace --all-targets --exclude e2e-cucumber -- -D warnings— clean.cargo clippy -p e2e-cucumber --test e2e -- -D warnings— clean.cargo test -p e2e-cucumber --lib— 113 passed.cargo test -p e2e-cucumber --test feature_naming— 4 passed.comfyui-install-preserves-the-rocm-runtimeskips there as@nightly @requires-gpu, as designed. The one unexpected failure isengine-shell-marks-the-prompt, a bash-prompt/PTY gap in the container environment that reproduces on unmodifiedmainand is green in real CI.hawkeye check --config licenserc.toml(v7.0.0, the version CI pins) — 231 files, 0 changes.The previously red
Lint (PowerShell)andTest (affected crates)were bothFailed to download action … after 3 attempts— action-tarball fetch failures unrelated to this branch. They need a re-run on the new head.