test: add BDD E2E tests with cucumber-rs#69
Conversation
9c0732e to
a4cc8eb
Compare
68b4c94 to
cd83918
Compare
5caff76 to
ffab5e2
Compare
rominf
left a comment
There was a problem hiding this comment.
Nice to see a real behavioral layer going in — the feature files read very cleanly. A few things before this is ready, one of which is functional and important.
🔴 Blocking — the suite can't currently fail CI
tests/e2e.rs uses the builder form E2eWorld::cucumber()....run(...).await; and discards the result. In cucumber 0.23 that path records failures into the writers but does not set a non-zero exit code — only run_and_exit() (or World::run()) does. So cargo test -p e2e-cucumber --test e2e exits 0 even when scenarios fail, and the e2e job goes green regardless of results. That undercuts the PR's premise ("as bugs are fixed, more tests pass") — right now the job wouldn't report either way.
Fix: capture the returned writer and std::process::exit(1) on failed_steps() + parsing_errors() + failed_hooks() > 0 (after the HTML report is generated so the artifact still uploads), or switch to run_and_exit().
Related, same theme:
@expected-failure-*tags are inert in CI. They only take effect when a human passes-t "not @expected-failure-*"; neither thee2enore2e-gpujob passes any filter, so tagged scenarios run like any other. Once the exit-code issue above is fixed, every known-bug scenario would turn the job red — there's no xfail/skip mechanism wired up. Needs a real design (dedicated non-blocking job for the tagged set, or genuine skip-on-tag handling).- A couple of scenarios don't exercise
rocm. e.g. "Chat requests use the model name reported by the endpoint" — the assertion checks what the test's own helper sent to the mock (it reads the model from/modelsand echoes it back); therocmbinary is never invoked, so it can't fail for a product reason. The mock-basedservices listscenarios assert the registry contains a mock that's never registered with the CLI, so they fail today for the wrong reason and won't flip to passing when the underlying bug is fixed. Worth routing these through the actual binary or dropping them.
🟠 Fit the project
run.sh→ anxtasksubcommand. We already standardize dev/CI tasks inxtask(signing, manifest, tpn, powershell-lint…). A bash wrapper is off-convention and bash-only in a repo that also builds/tests on Windows, and the build step is now duplicated betweenrun.shand the CI job. Acargo xtask e2e [args]used by both would be cross-platform and single-source.src/report.rs(≈400 lines of hand-built HTML) — do we need it? The crate already emits JUnit XML (rendered natively in the GitHub Actions checks UI) and Cucumber JSON (consumable by standard HTML reporters). The custom generator duplicates that, and it currently miscountsundefined/ambiguoussteps as passed and prints a placeholder-xx-xxdate. I'd lean toward dropping it in favor of the two standard outputs; if we want an HTML artifact in-tree, a compile-checked template (maud/askama) beatsformat!soup.
🟡 Should-fix
- Per-scenario isolation isn't isolated (
e2e.rs): the temp root is keyed only on PID, so all concurrent scenarios share one config/data/cache tree, and the first scenario'sDropremove_dir_alls it out from under the others (risking a fall-back to the real~/.rocm). Add a per-World unique suffix. reqwest::blockinginside async steps (serving_steps.rs) panics under the Tokio runtime ("cannot start a runtime from within a runtime"). GPU-tier only, but it hard-panics instead of waiting — use an async poll loop.- The new crate gets no clippy (excluded from both the clippy job and the pre-commit hook). Add
cargo clippy -p e2e-cucumber --all-targets -- -D warningsto the e2e job. .featurefiles aren't in theheavypath filter, so a feature-only change skips the e2e job. Add**/*.feature.
🔵 Nit
@expected-failure-EAI-XXXXputs internal Jira IDs in files contributors read/type. Non-blocking and I know it's under discussion — flagging only so it's tracked; GitHub issue numbers would keep the same test↔bug link.
e3a2c13 to
6741054
Compare
|
Thanks for the thorough review — all points addressed. Rebased on 🔴 Blocking
🟠 Fit the project
🟡 Should-fix
🔵 Nit
Also, while rebasing
The full workspace test suite and the mock-tier e2e selection pass on Linux. |
6741054 to
1ea8909
Compare
96b8bbb to
0d5645e
Compare
rominf
left a comment
There was a problem hiding this comment.
Reviewed the full diff (33 files, ~50 commits) with a coupling-grouped fan-out and verified findings in source. Overall this is solid, well-tested test infrastructure with no app-runtime changes — but there are a few real defects worth fixing before merge, plus some stale docs.
Verified positives: black-box discipline is fully held (no crate imports in step files); the CodeQL path-injection concern is resolved via tempfile::TempDir; test = false + harness = false correctly isolate the suite from the default workspace build/test; no dependency blast radius; managed-serve teardown covers the real case.
🔴 Blocking
1. ~1,600 lines of new step code + the harness main get zero clippy coverage. tests/e2e-cucumber/Cargo.toml:32 sets test = false on the e2e target, so clippy --workspace --all-targets (ci.yml:359) and the prek hook never lint e2e.rs or the mod-included *_steps.rs — only the lib (capability/expectation/mock_server) is checked. The PR advertises a clippy gate, but it misses the bulk of the added code. Suggest adding a CI step cargo clippy -p e2e-cucumber --test e2e (clippy honors explicit --test the same way the xtask e2e run does); please confirm it actually compiles the step files.
2. Vacuous / silent-pass scenarios that validate nothing but resolve to expect-pass.
chat_steps.rs:142assert_privacy_notice_accuratehas no assertion — only aneprintln!warning. Its scenariochat-privacy-notice-accurateisn't inexpectations.toml, so it resolves to ExpectPass and is guaranteed-green while testing nothing.serving_steps.rs:327(scenario 9,serve-vllm-default-on-instinct) spawns a real--managedserve but never checksrcor readiness; a launch failure after a correct plan-print goes undetected.
Suggest implementing a real check or marking chat-privacy-notice-accurate @skip/xfail with a reason instead of a green no-op, and asserting rc == 0 in scenario 9.
🟠 Should-fix
3. Two divergent reconciliations of the same report.json in crates/e2e-report/src/lib.rs: the CI-gate path (scenario_results_by_id, lib.rs:250, consumed at e2e.rs:580) treats a cucumber "skipped-status" scenario as not passed → exit 1, while the grid/consolidated path (id_pass_map lib.rs:702, tally lib.rs:786) treats it as passed. The same artifact can fail the job while rendering green in the consolidated grid. Suggest one shared predicate.
4. GPU serve jobs assume serial execution but nothing enforces it. There's no @serial tag and no .max_concurrent_scenarios(1) (e2e.rs:508); cucumber-rs defaults to 64 concurrent scenarios, yet serving_steps.rs hardcodes shared ports (SERVE_PORT = 11435, ASSISTANT_PORT = 8001) with kill-by-port logic whose comments assume "one serial GPU box." This does not affect the PR gate — every GPU serve scenario is @requires-gpu and resolves to Skip on the blocking mock job — but it makes the non-blocking GPU jobs flaky. Suggest tagging @requires-gpu scenarios @serial, or capping concurrency to 1 when a GPU is detected.
5. Blocking mock e2e job skips on non-heavy PRs — inconsistent with every sibling required job. e2e (ci.yml:591) gates needs.changes.outputs.heavy == 'true' at the job level, so the job is skipped on a non-heavy PR. Every sibling required job (build-and-test, test, clippy, prek, windows-build-and-test) instead runs if: github.event_name != 'workflow_dispatch' at the job level and gates heavy at the step level — deliberately, per this file's own warnings that a never-produced required check stalls the merge queue. The heavy glob doesn't match expectations.toml, so a PR editing only the xfail matrix (exactly what this suite exists to validate) skips the mock gate on that PR. merge_group forces heavy=true as a backstop, which is why this is Should-fix rather than hard-Blocking. Suggest matching the sibling pattern (job-level if != workflow_dispatch, heavy/build-and-test.result on the steps).
6. pkill -f 'vulkan/llama-server' (ci.yml:668) is unanchored. Unlike its five sibling pkill lines (all scoped to /tmp/rocm-e2e.* / e2e-shared.* / rocm-e2e), this pattern kills any process containing vulkan/llama-server on the shared self-hosted runner — including a legitimate manual-testing serve — contradicting the step's own "never any /workload manual-testing processes" comment. Suggest anchoring it to an e2e marker.
7. Hook/before-failure scenarios can be mis-scored as passing in scenario_status (lib.rs:132): it inspects only el.steps, never el.before/el.after, so a Before-hook failure (steps: []) falls through to "passed". Not a live gate escape (the harness independently checks summary.hook_errors() > 0 at e2e.rs:569 and exits first), but the HTML/consolidated report built from the same report.json would score a hook-failed scenario green. Suggest treating a Failed before/after entry as failed.
8. user_serves_default_engine (serving_steps.rs:323) waits with the model-agnostic wait_for_endpoint while every sibling serve step uses wait_for_model(..., Some(substr), ...) specifically to defend against a leaked prior serve still answering on shared port 11435. Its Then (assert_endpoint_responds) also never checks resp["model"], so scenarios 6/6b can pass against a stale/wrong server. Suggest using the model-substring guard and asserting the model id.
9. README is significantly stale vs. the current design. It documents the abandoned global @expected-failure/@gpu tag model and 3-tier CI scheme that no longer exist (no .feature uses those tags; CI runs one untiered cargo xtask e2e per platform). README.md:59 shows a -t "not @expected-failure-EAI-*" filter that now matches nothing, and the directory-layout diagram omits expectations.toml, src/capability.rs, src/expectation.rs, and two feature files. Separately, expectations.toml:9 references tests/e2e-cucumber/tests/e2e/expectation.rs but the file is at tests/e2e-cucumber/src/expectation.rs.
🟡 Nits
resolutionsmap is keyed only by@idwith no collision detection (e2e.rs:550): a copy-pasted scenario with a forgotten id-tag would silently overwrite an earlier resolution (no live collision today). Consider a startup dedupe assertion.Stats::bar_widths()(lib.rs:99) dumps the integer-division remainder into the skip bar, so a report with 0 skips can render a nonzero grey sliver (counts are correct; bar only is cosmetically off).e2e-reportcrate doc says "only maud + serde" but it also depends directly onserde_json.journey_theme_picker(dash journeys) asserts only!out.is_empty()rather than thetheme_name/modalstate transition the sibling unit tests already assert cheaply.normalize_model_idbidirectional substring match (serving_steps.rs:542) is looser than exact match; safe for current fixtures, latent if shorter/overlapping model names are added.commands.jsonlappend (e2e.rs:316) relies on incidental POSIX write atomicity rather than a lock; low risk today, compounds with #4.- Stale comment at
ci.yml:650says "15-min job cap" but the job'stimeout-minutesis 35. - Head branch is still named
test/add-e2e-robot-frameworkwhile the PR is cucumber-rs (cosmetic).
🔵 Consider
- Internal Jira IDs (EAI-XXXX) pervade public files (
expectations.toml, README, feature files,serving_steps.rs, rocm-core test comments). CONTRIBUTING says work is tracked via GitHub Issues, but there's no documented rule against ticket IDs specifically, so flagging as a Consider rather than a violation: these refs are meaningless to external contributors and leak internal tracker structure into a public repo. Consider mapping them to GitHub issues or a neutralKNOWN-BUGS.md. effective_serve_engineincapability.rsdeliberately duplicates rocm-core's engine rule — verified no divergence for any covered family and the drift is documented with guard tests; fine as-is.
Nice work overall — the per-scenario expectation resolution and reconciled report grid are a real improvement over the global-tag model. The blocking items above (clippy coverage hole + the two vacuous scenarios) are the main things I'd want fixed; #3/#5 are the highest-value should-fixes for CI trustworthiness.
…ning Resolves the review findings on PR #69: Blocking: - Wire clippy for the e2e harness + step files: the `test = false` e2e target is skipped by `clippy --all-targets`, leaving ~1.6k lines unlinted. Add a CI step `cargo clippy -p e2e-cucumber --test e2e -- -D warnings`, and fix the 9 lints it surfaced (map/unwrap_or_else→let-else, collapsible-if, Duration::from_mins, operator-precedence, doc-paragraph, items-after-statements). - Two vacuous scenarios no longer pass while testing nothing: chat-privacy-notice-accurate now fails (TUI-only, unverifiable black-box) and is tracked xfail (EAI-7222); serve-vllm-default-on-instinct asserts rc == 0. Should-fix: - Unify the report.json pass predicate: the CI gate (scenario_results_by_id) and the grid (id_pass_map/tally) disagreed on "skipped" status; both now route through scenario_passed (all-steps-passed), so the same artifact can't fail the job yet render green. - Enforce serial GPU serves: cap cucumber concurrency to 1 when a GPU is present (shared port 11435 / one card); mock keeps default parallelism. - Mock e2e job runs at job level (not skipped on non-heavy PRs, which stalled the merge queue for a required check); heavy/build-and-test gating moved to a step. - Remove the unanchored `pkill -f 'vulkan/llama-server'` (killed manual serves on the shared runner); the anchored /tmp/rocm-e2e line already covers it. - scenario_status treats a failed before/after hook as failed (was scored passed). - Default-engine serve uses a model-aware readiness wait + asserts the reply model id, so it can't pass against a leaked prior serve on the shared port. - Update the stale README (abandoned tag model / 3-tier CI → @requires-* + expectations.toml + one-job-per-platform) and fix the expectations.toml path ref. Nits: bar_widths skip remainder, e2e-report crate doc (serde_json), duplicate-@id assertion, stale 15-min comment. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…ning Resolves the review findings on PR #69: Blocking: - Wire clippy for the e2e harness + step files: the `test = false` e2e target is skipped by `clippy --all-targets`, leaving ~1.6k lines unlinted. Add a CI step `cargo clippy -p e2e-cucumber --test e2e -- -D warnings`, and fix the 9 lints it surfaced (map/unwrap_or_else→let-else, collapsible-if, Duration::from_mins, operator-precedence, doc-paragraph, items-after-statements). - Two vacuous scenarios no longer pass while testing nothing: chat-privacy-notice-accurate now fails (TUI-only, unverifiable black-box) and is tracked xfail (EAI-7222); serve-vllm-default-on-instinct asserts rc == 0. Should-fix: - Unify the report.json pass predicate: the CI gate (scenario_results_by_id) and the grid (id_pass_map/tally) disagreed on "skipped" status; both now route through scenario_passed (all-steps-passed), so the same artifact can't fail the job yet render green. - Enforce serial GPU serves: cap cucumber concurrency to 1 when a GPU is present (shared port 11435 / one card); mock keeps default parallelism. - Mock e2e job runs at job level (not skipped on non-heavy PRs, which stalled the merge queue for a required check); heavy/build-and-test gating moved to a step. - Remove the unanchored `pkill -f 'vulkan/llama-server'` (killed manual serves on the shared runner); the anchored /tmp/rocm-e2e line already covers it. - scenario_status treats a failed before/after hook as failed (was scored passed). - Default-engine serve uses a model-aware readiness wait + asserts the reply model id, so it can't pass against a leaked prior serve on the shared port. - Update the stale README (abandoned tag model / 3-tier CI → @requires-* + expectations.toml + one-job-per-platform) and fix the expectations.toml path ref. Nits: bar_widths skip remainder, e2e-report crate doc (serde_json), duplicate-@id assertion, stale 15-min comment. Signed-off-by: fredespi <fredrik.espinoza@gmail.com> (cherry picked from commit c67cb69)
|
Thanks for the thorough second pass — all findings addressed. Summary (latest commits 🔴 Blocking
🟠 Should-fix 🟡 Nits — bar_widths skip remainder, e2e-report crate doc (serde_json), duplicate- 🔵 Consider (EAI IDs in public files) — noted; leaving as a follow-up decision since it spans many files and isn't a code defect. Also, separately: added large-model (Qwen3.6-27B) vLLM coverage behind a new Re-review welcome. |
Generate terminal demo GIFs for the README from VHS tapes, rendered in CI and published to an orphan `media` branch so no binaries land in the source history. The README embeds them by absolute raw URL. Server-backed demos (chat, services) reuse the e2e harness's mock OpenAI server through a new standalone `rocm-demo-env` binary: it starts the mock, plants a managed-service record into an isolated config, and prints the env that points `rocm` at it — no GPU or real model needed, so renders are deterministic. The service-record schema is factored into a shared `write_service_record` so the cucumber World and the demo runner can't drift. The mock's chat reply is overridable via ROCM_MOCK_CHAT_REPLY for natural-reading screencasts. Rendering runs on workflow_dispatch and on release (never on PRs, where it would be slow and churn the branch); the release tag doubles as an image-cache buster. Stacked on #69 (the e2e harness this reuses); keep as draft until it lands. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
Generate terminal demo GIFs for the README from VHS tapes, rendered in CI and published to an orphan `media` branch so no binaries land in the source history. The README embeds them by absolute raw URL. Server-backed demos (chat, services) reuse the e2e harness's mock OpenAI server through a new standalone `rocm-demo-env` binary: it starts the mock, plants a managed-service record into an isolated config, and prints the env that points `rocm` at it — no GPU or real model needed, so renders are deterministic. The service-record schema is factored into a shared `write_service_record` so the cucumber World and the demo runner can't drift. The mock's chat reply is overridable via ROCM_MOCK_CHAT_REPLY for natural-reading screencasts. Rendering runs on workflow_dispatch and on release (never on PRs, where it would be slow and churn the branch); the release tag doubles as an image-cache buster. Stacked on #69 (the e2e harness this reuses); keep as draft until it lands. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
Generate terminal demo GIFs for the README from VHS tapes, rendered in CI and published to an orphan `media` branch so no binaries land in the source history. The README embeds them by absolute raw URL. Tapes are pure command sequences; all setup (build dir on PATH, isolated config, the mock server) is done by docs/tapes/render.sh BEFORE vhs runs. This is deliberate: VHS types on a fixed clock and never waits for a command to return, so setup done inside a tape races the typing. Server-backed demos (chat, services) reuse the e2e harness's mock OpenAI server via a new standalone `rocm-demo-env` binary: it starts the mock, plants a managed-service record into an isolated config, and prints the env that points `rocm` at it — no GPU or real model needed. The record schema is shared with the cucumber World via `write_service_record` so the two can't drift. The mock ignores SIGINT/SIGHUP (VHS/ttyd emit some during terminal setup) and stops on SIGTERM, so it survives until the tape runs. Its chat reply is overridable via ROCM_MOCK_CHAT_REPLY. Rendering runs on workflow_dispatch and on release (never on PRs, where it would be slow and churn the branch); the release tag doubles as an image-cache buster. Stacked on #69 (the e2e harness this reuses); keep as draft until it lands. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
|
🔴 Automated review · pr-review-watcher · 0e6c80e SummaryAdds a BDD (cucumber-rs) end-to-end test layer — Gherkin features + Rust steps, a mock OpenAI server, per-scenario pass/xfail/skip resolution, an HTML/JUnit/JSON report crate, and CI wiring across mock + self-hosted GPU runners. Verdict: Needs work — the design is sound and the code is high quality, but it ships internal tracker IDs upstream, which the repo's own contribution rules forbid. Verified: 🚫 Blocking (must fix before merge)Internal ticket identifiers (
Non-blocking
|
…ail (#23) `rocm serve <model>` with no --engine is recipe-driven, not platform-driven: it resolves the request to the recipe's preferred model+engine, which may differ from what was requested (e.g. a safetensors request resolves to a GGUF recipe on lemonade). The default-engine scenarios hardcoded the requested model in the readiness wait, so they timed out whenever the recipe resolved to a different model — on a lemonade-default host the safetensors request isn't even servable. Wait on the model the CLI actually resolved (parsed from the serve plan) instead of the requested id, via new resolved_model() + ready_substr_for() helpers; also dedup two existing `resolved model:` parses onto resolved_model(). Re-key the Instinct (effective_engine=vllm) xfail from EAI-7333 to EAI-7052: verified on MI300X that the default serve resolves to a GGUF recipe on lemonade, whose Vulkan backend hangs on Instinct (EAI-7052) — the default path doesn't use vLLM at all here, so EAI-7333 was the wrong bug. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
TEMPORARY: scoped-probe input forwarded to the cucumber harness on the strix-ubuntu job so a dispatch can run just the two serve-default-engine-* scenarios and validate the #23 recipe-aware fix on the lemonade-native Strix path. Empty = full suite. Remove after validation. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…23 xfail - expectation.rs: `#[serde(deny_unknown_fields)]` on `Condition`/`XfailEntry` so a typo'd key can't parse to an all-None (unconditionally matching) xfail. - e2e-report: route `scenario_pass_map` through canonical `scenario_passed`; split reconcile's `Missing` — an expected pass/xfail with NO result is now `Absent` (a problem), so a lost-results run reds the platform instead of passing. - model_serving.feature: gate `serve-vllm-default-on-instinct` with `@requires-engine:vllm` (false failure on lemonade-default hosts). - expectations.toml: xfail both `serve-default-engine-*` on effective_engine= lemonade (EAI-7423: first serve runs a backend install that hides the plan line; endpoint dies pre-inference via lemonade Vulkan instability). - ci.yml/nightly.yml: SHA-pin the newly-added actions/upload-artifact@v4 (v4.6.2) and download-artifact@v4 (v4.3.0) per AGENTS.md §6. - chat_steps.rs / mock_server.rs: remove dead step + never-read received_models. - dash_journeys.rs: assert real transitions (theme changes; chat error surfaced as an Error-role turn), not just a non-empty render. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…odels Removing the never-read `received_models` recording left the `handle_chat` `State(state)` extractor and the `MockServer.state` field unused. CI builds with `-D warnings`, so these tripped `unused-variables` / `field never read` and failed the "Unit tests (e2e-cucumber lib)" step (a plain local `cargo test` without `-D warnings` did not catch it). Drop the state extractor from `handle_chat` and the dead `state` field from `MockServer`; the router keeps its own `with_state` clone for `handle_models`. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…ails to Linux - Report OS/ROCm/vLLM/lemonade versions per platform: the harness collects them from the installed runtime (examine distro, runtimes-list version, vLLM dist-info, lemond --version) into platform.json; the report renders them in each column heading. - Scope the lemonade xfails (serve-default-engine-*, serve-readiness-contract, serve-lemonade-inference, chat-end-to-end-local-model) to os=linux: run 29338475133 showed they PASS on native Windows lemonade and only fail on the Linux llama.cpp Vulkan backend, so the any-OS xfails XPASS'd on Strix-Windows. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…nstall) This is the one GPU scenario that must do a real cold `install sdk` (its precondition is a clean slate, so it can't reuse the shared pre-warmed runtime). The post-install probe unpacks + scans the 8.8GB->13GB devel tree on the runner overlay, which takes ~35min and nearly caps the 90min per-PR GPU job. Move it to @nightly so the fast per-PR GPU run stays ~25min; it still runs in nightly where there's time. Follow-up: make the cold devel install/probe faster or install-once. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…linux Run 29354305288 (Strix-Windows) XPASS'd chat-tool-definitions-accepted: lemonade inference works on native Windows, so like the other lemonade inference scenarios this EAI-7052 xfail applies only on Linux (llama.cpp Vulkan hang). Scope it to os=linux — the last remaining Strix-Windows XPASS. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…versions The report only showed versions on MI300X because collect_versions() was gated entirely on E2E_SHARED_RUNTIMES_DIR (set only by the app-dev-gpu job), so mock and both Strix jobs got no versions — not even OS. Fix: - collect_versions now ALWAYS reads OS (from examine) on every platform, incl. mock; the runtimes dir is an Option, and ROCm/vLLM/lemonade are read only when a persistent runtime dir is available. - ci.yml: set E2E_SHARED_RUNTIMES_DIR on both Strix jobs (Ubuntu + Windows) so their runtime's ROCm/vLLM/lemonade versions are readable at end-of-run (and so serve/chat scenarios install once). Windows engine subpaths differ, so vLLM/ lemonade there degrade to n/a gracefully; OS + ROCm still show. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…sion probe - Expectation grid: render xfail as a grey cross (status-xfail) instead of the word "xfail", with a legend line; fix a latent maud bug that emitted two class attributes so the status colour was dropped. - Command-coverage: render not-run cells as n/a instead of blank, updated legend. - collect_versions: locate site-packages across OS layouts (Windows Lib/, non-pinned python3.x on Unix) and find lemond/lemond.exe, so vLLM/lemonade versions populate on Strix, not just MI300X. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…m/lemonade collect_versions trusted the manifest's install_root, which on Strix is a per-scenario temp dir that no longer exists by report time — so vLLM/lemonade probed a dead path and came back None (only os+rocm populated). Derive the root as <runtimes_dir>/wheel/<runtime_key> from the shared tree we were handed, falling back to the manifest path only when the derived one is absent. On MI300X the two coincide (prewarm installs in place), so this doesn't change it there. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The app-dev-gpu job never set E2E_INCLUDE_NIGHTLY, so the @nightly scenarios (large-model serve, cold install) could only run via the full ~90min nightly workflow. Add an off-by-default boolean dispatch input that sets E2E_INCLUDE_NIGHTLY, so a manual dispatch can confirm a single nightly scenario in-suite (combined with name_filter) without the whole nightly run. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…n't time out serve-large-model-inference failed in-suite (CI run 29369965558): the 27B served and became ready, but the chat POST aborted with "error sending request" — the inference client timeout defaulted to 10s, far too short for a 54 GiB BF16 model's first token in --enforce-eager mode. A manual `curl -m 60` passed, which is why the manual proof missed it. inference_timeout_for(world) now floors the client timeout at the scenario's @serve-timeout override (2400s for the 27B), so a heavy-model scenario gets inference headroom while normal/known-bug scenarios keep the 10s fail-fast the EAI-7052 hang-detection relies on. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
name_filter was only wired into the strix-ubuntu job, so a scoped dispatch targeting app-dev ran the full 25-scenario suite instead of the named scenario. Apply the same --name filter to the app-dev-gpu step so a single scenario (e.g. the 27B nightly) can be probed there without the whole suite. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
The strix-ubuntu job had no pre-warm — it relied on the first "a managed runtime is active" scenario to install into the shared dir. But `install sdk` bakes absolute paths (install_root, python_executable, rocm_sdk.*, .rocm-cli-runtime.json) into the runtime manifest pointing at that scenario's isolated /tmp/rocm-e2e-XXXX data dir, which is deleted when the scenario ends. Every later serve then saw `status=unusable (install root is missing)` and failed — diagnosed directly on the box. Add the same in-place pre-warm the app-dev e2e-gpu job uses: build rocm once, install sdk into $RUNNER_WORKSPACE/e2e-prewarm/data/runtimes with no mv, so the baked paths stay valid for all scenarios and the version probe. No-op after the first run (persists across runs). Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…-shutdown) Box investigation (2026-07-15) disproved the earlier reasons for the lemonade LINUX serve/inference xfails: the serve does NOT hide the engine plan line, and it is NOT the EAI-7052 Vulkan hang. The real cause is a single product bug — the lemonade managed serve reaches ready on :8001 then is shut down ~0.08s later (now tracked in EAI-7423). Re-key the six lemonade+os=linux entries (serve-default-engine-working-endpoint, serve-default-engine-inference, serve-readiness-contract, serve-lemonade-inference, chat-tool-definitions-accepted, chat-end-to-end-local-model) from EAI-7052/old-EAI-7423-reason to EAI-7423 with the corrected ready-then-shutdown reason. The two vLLM/Instinct default-serve entries keep EAI-7052 (a genuinely different failure mode). Scoping unchanged (os=linux only — these pass on native Windows lemonade). Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
Same fix as strix-ubuntu (cf3f9d5), in PowerShell. The strix-windows job had no pre-warm — the first serve scenario installed the runtime, triggering a cold 216MB backend + 4.6GB therock dist download that failed `rocm serve` (serve-readiness-contract scenario 8, run 29357303454). `install sdk` bakes absolute paths into the runtime manifest, so a per-scenario temp install leaves later serves pointing at a deleted dir. Pre-warm in place (build rocm once, install sdk into RUNNER_WORKSPACE\e2e-prewarm\data\runtimes, no move, no-op after first run) + honor name_filter like the other jobs. Needs a CI dispatch to verify on the Windows box (no local console access). Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
Add a GPU-availability preflight to the three GPU E2E jobs (MI300X, strix-ubuntu, strix-windows), right after the reclaim step. It's a bounded poll, not a one-shot check: it retries every 5s up to a ~90s ceiling and succeeds the moment the GPU is both responsive (rocm-smi returns within a 15s timeout — a wedged driver can hang rocm-smi itself) AND has enough free VRAM, so transient contention (VRAM still draining from the reclaim) self-heals. Only a genuinely absent/wedged/held GPU reaches the ceiling and fails, with a per-reason message (never-responsive = wedged/absent; VRAM-never-freed = a leftover serve is holding it). Turns a 90-min hang-to-cap into a ~90s red that says why. Parse note: rocm-smi lines are prefixed "GPU[0]", so the value is taken AFTER the colon (a naive first-number match picks up the "0"). Verified both pass and fail paths on the app-dev MI300X. Windows version is best-effort: if rocm-smi isn't found it warns and continues rather than false-failing a working runner. Floors: 16 GiB (Instinct), 8 GiB (Strix, shared memory); tunable via GPU_PREFLIGHT_MIN_FREE_GIB / GPU_PREFLIGHT_CEILING_SECS. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…MI300X) serve-readiness-contract XPASS'd on MI300X in the full-matrix run 29404668327: it was xfail'd against EAI-7333 (CLI healthcheck reports ready off /v1/models before inference works), but it passes reliably on Instinct vLLM — verified by hand (served Qwen2.5-1.5B, services-list ready at 27s, immediate inference 200). On MI300X (--enforce-eager) the model is genuinely inference-ready by the time the CLI reports ready, so the readiness contract holds. Remove the vLLM entry so the scenario is expect-pass on that path (0 XPASS). EAI-7333 is still OPEN (confirmed Backlog/unresolved) — the early-readiness code smell is real and the serve-vllm-inference EAI-7333 xfail stays; only this scenario stopped exhibiting it. The lemonade/os=linux EAI-7423 entry is unchanged. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…ection Consolidated-report markdown format updates: - Summary matrix: show component versions in the Platform cell (ROCm/vLLM/ lemonade) and the OS cell (distro), wiring PlatformVersions into PlatformReport. - Expectation grid: drop the per-column version subheads (versions now live in the matrix); the Scenario column shows the human scenario name with the @id below as an anchor link. - Add a Scenario reference section (each @id -> its Gherkin name + steps, built from report.json), placed LAST after Command coverage; the grid id links resolve to it. Mock correctly shows no ROCm/lemonade (no installed runtime). Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
clippy map_unwrap_or: `.map(..).unwrap_or("")` on an Option -> `.map_or("", ..)`.
Introduced by the grid Scenario-name change; caught by the PR clippy check (the
local container gate ran tests but not clippy).
Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
clippy too_long_first_doc_paragraph on PlatformVersions + collect_versions docs (introduced with the version-collection feature). Split the first paragraph to a short one-liner. CI clippy is `--workspace --all-targets` (NOT excluding e2e-cucumber), so these harness-src docs are linted. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
…ot fixed Removing this xfail (after a lucky XPASS on run 29404668327) was premature: it FAILED on the next MI300X run (29413321046). EAI-7333 (CLI reports vLLM ready off /v1/models before inference works) is still OPEN, so the scenario is flaky on the readiness->immediate-inference race, not fixed. Restore the vLLM/EAI-7333 xfail so the common (failing) case reconciles as a known bug; an occasional lucky-pass XPASS is expected and harmless. Lemonade/os=linux EAI-7423 entry unchanged. Signed-off-by: fredespi <fredrik.espinoza@gmail.com>
6d309d7 to
204c4e8
Compare
rominf
left a comment
There was a problem hiding this comment.
The remaining review findings are non-blocking test-infrastructure hardening and can be handled in a focused follow-up.
Generate terminal demo GIFs for the README from VHS tapes, rendered in CI and published to an orphan `media` branch so no binaries land in the source history. The README embeds them by absolute raw URL. Tapes are pure command sequences; all setup (build dir on PATH, isolated config, the mock server) is done by docs/tapes/render.sh BEFORE vhs runs. This is deliberate: VHS types on a fixed clock and never waits for a command to return, so setup done inside a tape races the typing. Server-backed demos (chat, services) reuse the e2e harness's mock OpenAI server via a new standalone `rocm-demo-env` binary: it starts the mock, plants a managed-service record into an isolated config, and prints the env that points `rocm` at it — no GPU or real model needed. The record schema is shared with the cucumber World via `write_service_record` so the two can't drift. The mock ignores SIGINT/SIGHUP (VHS/ttyd emit some during terminal setup) and stops on SIGTERM, so it survives until the tape runs. Its chat reply is overridable via ROCM_MOCK_CHAT_REPLY. Rendering runs on workflow_dispatch and on release (never on PRs, where it would be slow and churn the branch); the release tag doubles as an image-cache buster. Stacked on #69 (the e2e harness this reuses); keep as draft until it lands. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
Summary
Adds a behavioral end-to-end test layer using cucumber-rs — standard Gherkin
.featurefiles backed by Rust step functions. Tests exercise the full user journey (install → examine → configure → serve → detect → chat) to catch cross-component integration failures that unit tests and smoke scripts miss.Why cucumber-rs over Robot Framework / plain Rust tests
This PR started with Robot Framework, then switched to cucumber-rs after team discussion. The reasoning:
assert_cmd/instacouples to output format and breaks on cosmetic changes.Scenario Outline,Background,Rule, and@tags— not Robot Framework's cosmetic prefix stripping. The.featurefile is the executable test, not a rewrite of a separate spec.What's included
.featurefiles — the spec is the testchat_steps.rs,examine_steps.rs,serving_steps.rs,runtime_steps.rs)Expected-failure pattern
Scenarios that exercise known bugs are tagged
@expected-failure-EAI-XXXX. On CI without GPU hardware, these scenarios fail and produce clear failure messages. On GPU hardware, they exercise the actual bug. When a bug is fixed, the test passes — signaling the tag can be removed.@expected-failure-EAI-7218@expected-failure-EAI-7219@expected-failure-EAI-7220@expected-failure-EAI-7221@expected-failure-EAI-7223Each Jira ticket has a comment linking back to its test scenario(s).
Running tests
Test plan