Skip to content

test: add BDD E2E tests with cucumber-rs#69

Merged
rominf merged 84 commits into
mainfrom
test/add-e2e-robot-framework
Jul 15, 2026
Merged

test: add BDD E2E tests with cucumber-rs#69
rominf merged 84 commits into
mainfrom
test/add-e2e-robot-framework

Conversation

@fredespi

@fredespi fredespi commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a behavioral end-to-end test layer using cucumber-rs — standard Gherkin .feature files 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:

  • BDD earns its keep regardless of audience. The value isn't non-engineers reading scenarios — it's tests that describe user-facing behavior independently of implementation. "Short model names are expanded to their full name" tells you what's broken without reading code. assert_cmd/insta couples to output format and breaks on cosmetic changes.
  • Already proven. The 6 bugs at component seams were found by thinking in user journeys, not by reading code. That's what BDD forces you to do.
  • One toolchain. Rust step functions compile with the project. No Python dependency, no separate CI setup. Addresses the concern from EAI-7164 about consolidating on Rust.
  • Not two stacks. The xtask acceptance path handles install/upgrade lifecycle (imperative, infrastructure-level). Cucumber handles user journey scenarios (declarative, behavioral). Different concerns, one toolchain.
  • Real Gherkin. cucumber-rs uses standard Gherkin with Scenario Outline, Background, Rule, and @tags — not Robot Framework's cosmetic prefix stripping. The .feature file is the executable test, not a rewrite of a separate spec.
  • Reporting included. HTML report (comparable to Robot Framework's), JUnit XML for GitHub Actions, and Cucumber JSON — all generated automatically.

What's included

  • 20 BDD scenarios across 4 features: examine, runtime setup, model serving, chat/detection
  • Gherkin .feature files — the spec is the test
  • Rust step functions — organized by feature area (chat_steps.rs, examine_steps.rs, serving_steps.rs, runtime_steps.rs)
  • Mock OpenAI server — axum-based, OS-assigned ports (no port conflicts)
  • HTML report — summary, statistics by tag/feature, expandable scenario details with step-by-step results
  • Environment isolation — each scenario gets isolated config/data/cache directories
  • CI job on hosted runners (every PR)

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.

Tag Scenarios Bug
@expected-failure-EAI-7218 model_serving 5 PyTorch engine dependency resolution
@expected-failure-EAI-7219 model_serving 1, 2, 6 Short model names not expanded
@expected-failure-EAI-7220 model_serving 3, 4; chat 2, 4 Service registry / default port not in detection
@expected-failure-EAI-7221 chat 6 TUI hardcodes model name instead of querying endpoint
@expected-failure-EAI-7223 chat 5 vLLM rejects tool-calling requests

Each Jira ticket has a comment linking back to its test scenario(s).

Running tests

# All scenarios:
bash tests/e2e-cucumber/run.sh

# Via cargo directly:
ROCM_CLI_BINARY=./target/release/rocm cargo test -p e2e-cucumber --test e2e

# Filter by name:
cargo test -p e2e-cucumber --test e2e -- -n "short model"

# Skip known bugs:
cargo test -p e2e-cucumber --test e2e -- -t "not @expected-failure-EAI-7219"

Test plan

  • All 20 scenarios compile and run
  • Mock-tier scenarios pass locally
  • Expected-failure scenarios fail with clear messages
  • HTML report generated with summary, statistics, and step details
  • JUnit XML generated for GitHub Actions
  • CI job passes on this PR

@fredespi fredespi force-pushed the test/add-e2e-robot-framework branch from 9c0732e to a4cc8eb Compare June 30, 2026 11:59
@fredespi fredespi marked this pull request as draft June 30, 2026 12:41
@fredespi fredespi force-pushed the test/add-e2e-robot-framework branch from 68b4c94 to cd83918 Compare June 30, 2026 13:37
@fredespi fredespi marked this pull request as ready for review June 30, 2026 14:28
@fredespi fredespi changed the title test: add Robot Framework E2E test framework test: add BDD E2E tests with cucumber-rs Jul 1, 2026
@fredespi fredespi force-pushed the test/add-e2e-robot-framework branch from 5caff76 to ffab5e2 Compare July 1, 2026 12:50

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the e2e nor e2e-gpu job 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 /models and echoes it back); the rocm binary is never invoked, so it can't fail for a product reason. The mock-based services list scenarios 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 → an xtask subcommand. We already standardize dev/CI tasks in xtask (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 between run.sh and the CI job. A cargo 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 miscounts undefined/ambiguous steps as passed and prints a placeholder -xx-xx date. 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) beats format! 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's Drop remove_dir_alls it out from under the others (risking a fall-back to the real ~/.rocm). Add a per-World unique suffix.
  • reqwest::blocking inside 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 warnings to the e2e job.
  • .feature files aren't in the heavy path filter, so a feature-only change skips the e2e job. Add **/*.feature.

🔵 Nit

  • @expected-failure-EAI-XXXX puts 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.

@fredespi fredespi force-pushed the test/add-e2e-robot-framework branch from e3a2c13 to 6741054 Compare July 8, 2026 15:08
@fredespi

fredespi commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review — all points addressed. Rebased on main and pushed as one commit (6741054). Summary of what changed:

🔴 Blocking

  • Suite can now fail CI. The runner captures the summarized writer and process::exit(1) on failed_steps + parsing_errors + hook_errors (after the HTML report is generated so the artifact still uploads). Verified: the blocking selection exits 0 when green and 1 when a scenario fails.
  • @expected-failure tags are now real. Cucumber tag filters are exact-match (no globbing), so each known-bug scenario carries a bare @expected-failure (for filtering) plus its @expected-failure-EAI-NNNN (for traceability). CI now runs three selections:
    • e2e (blocking): not @gpu and not @expected-failure
    • e2e-known-bugs (non-blocking): @expected-failure and not @gpu
    • e2e-gpu (non-blocking): @gpu
  • Scenarios now exercise rocm. Mock-based scenarios plant a managed-service record (plain JSON matching the on-disk schema, no crate import) into the isolated services dir, so rocm services list and the local chat provider actually discover the mock. The scenarios that were mislabeled EAI-7220 are retagged/untagged — EAI-7220 is a TUI-only wrong-port bug unrelated to services list, and its real surface can't be driven by the non-interactive CLI (noted below). The mock-only "chat model name" scenario that only tested the helper was dropped.

🟠 Fit the project

  • run.shcargo xtask e2e. New xtask subcommand builds the release binary + runs the suite (forwarding cucumber args), used by both CI and local dev. Cross-platform, single-source; run.sh removed.
  • report.rs kept, bugs fixed. Rather than drop it, fixed the two concrete issues: undefined/ambiguous steps now count as failures (not passes), and the -xx-xx placeholder is replaced with a real UTC timestamp. Happy to revisit dropping it in favor of the JUnit/JSON outputs as a follow-up if you'd prefer.

🟡 Should-fix

  • Per-scenario isolation: temp root now has a per-World unique suffix (was PID-only, so scenarios shared one tree and Drop raced).
  • reqwest::blocking → async poll loop in the serving steps (dropped the now-unused blocking feature).
  • Clippy on the crate: added cargo clippy -p e2e-cucumber --all-targets -- -D warnings to the e2e job; fixed the warnings it surfaced.
  • .feature in the heavy path filter so feature-only changes trigger the job.

🔵 Nit

  • Kept the EAI-XXXX IDs for now since it's under discussion — happy to switch to GitHub issue numbers if we land on that.

Also, while rebasing

  • Engine set: main now supports only lemonade/vllm (Limit serving engines to Lemonade and vLLM #79), so I updated the engines-list assertion, dropped the PyTorch scenario (EAI-7218 is Won't Fix and the engine is gone), and compare expansion across the supported engines.
  • Restored the mock/gpu split that the cucumber rewrite had lost: hardware-dependent scenarios (install sdk, real serve, GPU detect) are tagged @gpu so the mock-tier job stays green on ubuntu-latest.

The full workspace test suite and the mock-tier e2e selection pass on Linux.

Comment thread tests/e2e-cucumber/tests/e2e.rs Fixed
Comment thread tests/e2e-cucumber/tests/e2e.rs Fixed
Comment thread tests/e2e-cucumber/tests/e2e.rs Fixed
@fredespi fredespi force-pushed the test/add-e2e-robot-framework branch from 6741054 to 1ea8909 Compare July 8, 2026 16:47
@fredespi fredespi closed this Jul 9, 2026
@fredespi fredespi deleted the test/add-e2e-robot-framework branch July 9, 2026 13:34
@fredespi fredespi restored the test/add-e2e-robot-framework branch July 9, 2026 13:35
@fredespi fredespi reopened this Jul 9, 2026
Comment thread tests/e2e-cucumber/tests/e2e.rs Fixed
Comment thread tests/e2e-cucumber/tests/e2e.rs Fixed
@fredespi fredespi force-pushed the test/add-e2e-robot-framework branch 2 times, most recently from 96b8bbb to 0d5645e Compare July 10, 2026 14:16
Comment thread tests/e2e-cucumber/tests/e2e.rs Fixed

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:142 assert_privacy_notice_accurate has no assertion — only an eprintln! warning. Its scenario chat-privacy-notice-accurate isn't in expectations.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 --managed serve but never checks rc or 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

  • resolutions map is keyed only by @id with 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-report crate doc says "only maud + serde" but it also depends directly on serde_json.
  • journey_theme_picker (dash journeys) asserts only !out.is_empty() rather than the theme_name/modal state transition the sibling unit tests already assert cheaply.
  • normalize_model_id bidirectional substring match (serving_steps.rs:542) is looser than exact match; safe for current fixtures, latent if shorter/overlapping model names are added.
  • commands.jsonl append (e2e.rs:316) relies on incidental POSIX write atomicity rather than a lock; low risk today, compounds with #4.
  • Stale comment at ci.yml:650 says "15-min job cap" but the job's timeout-minutes is 35.
  • Head branch is still named test/add-e2e-robot-framework while 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 neutral KNOWN-BUGS.md.
  • effective_serve_engine in capability.rs deliberately 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.

fredespi added a commit that referenced this pull request Jul 13, 2026
…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>
fredespi added a commit that referenced this pull request Jul 13, 2026
…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)
@fredespi

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough second pass — all findings addressed. Summary (latest commits 9f786da..0e6c80e):

🔴 Blocking

  1. Clippy on the harness + step files — added a CI step cargo clippy -p e2e-cucumber --test e2e -- -D warnings (clippy honours the explicit --test, so it now compiles/lints e2e.rs + all *_steps.rs). It surfaced 9 real lints, all fixed.
  2. Vacuous scenarioschat-privacy-notice-accurate now fails (it's TUI-only, unverifiable black-box) and is tracked xfail in expectations.toml (EAI-7222) instead of a silent green no-op; serve-vllm-default-on-instinct now asserts rc == 0.

🟠 Should-fix
3. Unified the report.json pass predicate — the CI gate (scenario_results_by_id) and the grid (id_pass_map) both route through one scenario_passed (all-steps-passed), so a skipped scenario can no longer fail the job while rendering green.
4. GPU serves are now serial — .max_concurrent_scenarios(1) when a GPU is present (mock keeps default parallelism).
5. Mock e2e job now runs at the job level (not skipped on a non-heavy PR — no more stalled required check); heavy/build-and-test gating moved to the steps, matching the sibling jobs.
6. Removed the unanchored pkill -f 'vulkan/llama-server' — the anchored /tmp/rocm-e2e line already covers the scenario-spawned assistant.
7. scenario_status now treats a failed before/after hook as failed.
8. Default-engine serve uses a model-aware readiness wait + asserts the reply's model id (can't pass against a leaked serve on the shared port).
9. README rewritten to the current @requires-* + expectations.toml + one-job-per-platform model; fixed the expectations.toml path reference.

🟡 Nits — bar_widths skip remainder, e2e-report crate doc (serde_json), duplicate-@id startup assertion, stale 15-min comment.

🔵 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 @nightly tag (skipped per-PR, run in a nightly e2e-gpu-nightly job) so the per-PR GPU run stays fast; and a VRAM-drain wait so a large serve's residual memory can't starve the next scenario.

Re-review welcome.

@fredespi fredespi requested a review from rominf July 13, 2026 17:10
rominf added a commit that referenced this pull request Jul 13, 2026
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>
rominf added a commit that referenced this pull request Jul 13, 2026
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>
rominf added a commit that referenced this pull request Jul 13, 2026
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>
@volen-silo

Copy link
Copy Markdown
Collaborator

🔴 Automated review · pr-review-watcher · 0e6c80e

Summary

Adds 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: e2e-report (31) and e2e-cucumber --lib (23) unit tests pass, both new crates compile clean, no conflict markers; confirmed the blocking e2e mock job is a real gate (xtask e2e bail!s on cucumber non-zero exit) and the consolidated report is informational-only (does not gate CI). Blocking: 1 · Non-blocking: 6.

🚫 Blocking (must fix before merge)

Internal ticket identifiers (EAI-NNNN) shipped on upstream surfaces. AGENTS.md §2 explicitly forbids "internal ticket identifiers" in "PR titles/bodies, ... commit messages, ... code comments, fixtures, and logs," and the review treats a project-instruction violation introduced by the PR as blocking. This PR introduces them broadly:

  • Code / fixtures (52 refs, 8 files): tests/e2e-cucumber/expectations.toml (26), tests/e2e-cucumber/tests/e2e/serving_steps.rs (e.g. serving_steps.rs:432,446), tests/e2e-cucumber/src/expectation.rs, tests/e2e-cucumber/tests/e2e.rs, tests/e2e-cucumber/tests/e2e/chat_steps.rs, tests/e2e-cucumber/features/model_serving.feature, crates/e2e-report/src/lib.rs (test fixtures ~1910/2132/2160/2297), crates/rocm-core/src/lib.rs (test comments).

  • Commit messages: ~20 (e.g. EAI-7052, EAI-7333, EAI-7383/7384 in subjects).

  • PR title/body: EAI-7164/7218/7219/7220/7221/7223.

    Fix: keep the (good) fixed-bug-flips-to-XPASS design, but replace internal IDs with the public issue tracker (ROCm/rocm-cli#NNN) or a neutral bug slug across code, fixtures, commit messages (rebase/reword), and the PR title/body. Run the AGENTS.md §2 leak scan until the diff and non-diff surfaces are clean.

Non-blocking

  • crates/e2e-report/src/lib.rs:825scenario_pass_map uses scenario_status(el) != "failed", counting a skipped scenario as passed, contradicting the file's own single-source-of-truth scenario_passed (line 182). Affects only the command-coverage display table (not the CI gate); route it through scenario_passed.
  • crates/e2e-report/src/lib.rs:193,606 — a truncated/malformed report.json parses as zero scenarios (.ok() swallows the error), so with a valid platform.json every expect-pass reconciles to Missing and the platform renders "PASS"; a lost-results run is indistinguishable from all-pass. Report is non-gating, but distinguish unparseable from empty and surface it as ERROR/UNKNOWN.
  • tests/e2e-cucumber/src/capability.rs:173 — the capability probe ignores rocm examine/engines list exit status and treats spawn failure as empty output, so a broken rocm on a GPU host silently degrades to a GPU-less profile and every @requires-gpu scenario resolves to skip. Check exit status and fail loudly.
  • tests/e2e-cucumber/tests/e2e/chat_steps.rs:32-35,44 — dead step code: send_chat_request (#[when]) is referenced by no feature; user_offered_endpoint is a no-op. Remove.
  • tests/e2e-cucumber/tests/e2e/serving_steps.rs:507assert_service_in_list only checks "127.0.0.1" appears anywhere in services list output, not the row for the model under test; tighten to the specific port (as the sibling assert_endpoint_port does).
  • PR body references bash tests/e2e-cucumber/run.sh (3×) but no run.sh exists — stale from the Robot Framework iteration; the README correctly documents cargo xtask e2e. Update the PR body. (Also: every new-job uses: in ci.yml/nightly.yml is a moving tag vs AGENTS.md §6's SHA-pin rule — but this is a pre-existing repo-wide pattern this PR extends, not a regression; worth a follow-up pinning pass.)

Comment thread tests/e2e-cucumber/tests/e2e.rs Dismissed
fredespi added 22 commits July 15, 2026 14:19
…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>
@fredespi fredespi force-pushed the test/add-e2e-robot-framework branch from 6d309d7 to 204c4e8 Compare July 15, 2026 12:59

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remaining review findings are non-blocking test-infrastructure hardening and can be handled in a focused follow-up.

@rominf rominf added this pull request to the merge queue Jul 15, 2026
Merged via the queue into main with commit 5fab56c Jul 15, 2026
35 of 36 checks passed
@rominf rominf deleted the test/add-e2e-robot-framework branch July 15, 2026 15:49
rominf added a commit that referenced this pull request Jul 15, 2026
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>
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.

4 participants