LCORE-2343: implement behave step definitions for unified-mode feature files - #2448
Conversation
|
Warning Review limit reached
Next review available in: 40 minutes Limit details: You’ve used all 1 included review currently available under your plan. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (31)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Create tests/e2e/configuration/unified-mode/ with library-mode/server-mode
variants (same two-subdir layout configure_service resolves), covering the
five unified-mode feature files:
- unified-providers: minimal unified config driven only by top-level
inference.providers over the default baseline (R1/S5); openai-specific.
- unified-config-only / unified-relative-profile: profile: run.yaml — the
CI-materialized repo-root run.yaml as baseline, provider-agnostic (R1/R8;
two files because the features pin the intents separately).
- unified-absolute-profile: container-absolute profile paths, differing per
mode subdir (/app-root vs /opt/app-root mounts).
- unified-native-override-{scalar,list}: R5 replacement semantics fixtures,
synthesis-only, never booted.
- invalid-{providers,config}-and-legacy: mutual-exclusion validation
fixtures (R3); invalid-version-legacy-unified-body: R11 marker mismatch
(needs LCORE-2872's cross-validation to fail for the right reason).
- legacy-for-migration: legacy half of the migration fixture pair, kept
free of enrichment sections so migrate-then-synthesize round-trips
losslessly (LCORE-3370).
Every fixture is validated against the real Configuration model: bootable
ones load, invalid ones fail with the intended error. The test-generated
lightspeed-stack-unified-migrated.yaml is gitignored, and the directory
README documents each fixture's purpose.
…d fixture LCORE-2342 migrated the standard library-mode baseline to unified mode (config.profile: run.yaml), which silently changed what unified-mode-legacy.feature's library scenario exercises: it now boots the unified baseline, not the deprecated two-file path, so R2's library-mode legacy coverage was gone. Add lightspeed-stack-legacy.yaml — identical to the baseline except its llama_stack block uses the true legacy shape (use_as_library_client + library_client_config_path: run.yaml, no synthesis input) — and point the library scenario's Given at it. This is the one deliberate Gherkin edit in LCORE-2343, agreed with Maxim in planning; the server-mode scenario is untouched since container-side enrichment there is genuinely legacy.
Add tests/e2e/features/steps/unified_mode.py — the 16 step patterns the validation, migration, and synthesis features need (boot and legacy resolve entirely through existing generic steps). Per the planning decisions: - All artifact steps operate on the ON-DISK configuration (the repo-root lightspeed-stack.yaml copy configure_service applied), never the live service. - Validation runs the service CLI (--dump-configuration) as a black-box subprocess from the repo root and asserts a non-zero exit, so the error-contains assertions can never pass against a healthy load. - Migration runs the real --migrate-config CLI; the output lands in the active mode subdir under the gitignored name later Gherkin references, and is cleaned up per scenario. - Synthesis runs the config CLI exactly as the server entrypoint does (unified auto-detection -> synthesize_to_file, giving the 0600 mode the permissions scenario asserts). Round-trip and override assertions parse YAML and compare data, never bytes; override assertions are self-referential against the fixture's native_override and additionally assert the baseline differed, so replacements can't pass vacuously. - The --synthesized-config-output scenario launches a short-lived local service from the library-mode fixture variant on a rewritten port and polls for the custom output file (the flag is library-mode-only by design; running containers cannot be restarted with new CLI args). - The startup-log step is mode-aware: in server mode the synthesis evidence is emitted by the llama-stack container (entrypoint + CLI), not the lightspeed-stack container the Gherkin names — asserted against the synthesizing container with the rationale documented in the step. behave --dry-run over the five features: 24 scenarios, 200 steps, zero undefined.
Tag all five unified-mode features @skip-in-prow: the new steps rely on Docker containers and local subprocesses, neither of which exists in the Prow environment (existing convention, handled in before_scenario). Add an @openai-only tag on the two inference.providers boot scenarios and a matching before_scenario skip keyed on E2E_DEFAULT_PROVIDER_OVERRIDE: the providers workflow runs the full unsharded test list against azure/watsonx/bedrock matrices, and the unified-providers fixture hardcodes an openai provider that cannot serve those models' queries. Profile-based fixtures stay provider-agnostic (they consume the CI-materialized run.yaml) and need no gating.
Remove the @Skip placeholder tag from the five unified-mode features: the step definitions and fixtures they need now exist. The features keep @skip-in-prow (Docker/subprocess dependencies) and stay in test_list.txt under @e2e_group_2, so CI shards pick them up via 'not @Skip and @e2e_group_2' and local runs via --tags=-skip.
restart_container waits on docker health, but docker can report healthy before uvicorn binds the published port — the exact race wait_for_lightspeed_stack_http_ready documents and was, until now, only closed in the proxy steps. The unified-mode boot scenarios are the slowest restarts in the suite (first unified/default-baseline boots) and hit that window reliably: the restart step passed while the following readiness GET got connection-refused. Call the existing HTTP-ready wait from restart_container for the lightspeed-stack container, closing the race for every restarting scenario; when the port is already accepting, the first poll returns immediately.
--migrate-config writes its output 0600 (R10: migrated files may carry lifted secrets), but the boot scenarios copy that file to the repo root for the container to consume, and the container user cannot read a host-owned 0600 file — the migrated-config boot scenario died on config read. Relax the harness copy to 0644 after a successful migration; the fixture pair is env-reference-only by design, so no secret can leak.
ecc4b5b to
ba9f5eb
Compare
Description
Implements LCORE-2343: behave step definitions, fixtures, and gating for the five
unified-mode-*.featurefiles authored spec-first in LCORE-2341 (PR #2020), and unskips them.tests/e2e/features/steps/unified_mode.py, 16 new patterns — boot/legacy resolve entirely through existing generic steps): validation runs the service CLI (--dump-configuration) as a black-box subprocess against the on-disk active configuration and asserts a non-zero exit; migration runs the real--migrate-configCLI with output landing in the active mode subdir (gitignored, cleaned per scenario, relaxed to 0644 so the container user can boot the copy); synthesis runs the config CLI exactly as the server entrypoint does (unified auto-detect →synthesize_to_file, giving the 0600 mode the permissions scenario asserts). All comparisons are YAML-data equality, never bytes; override assertions are self-referential against the fixture'snative_overrideand additionally assert the baseline differed, so replacements cannot pass vacuously. The--synthesized-config-outputscenario launches a short-lived local service from the library-mode fixture variant on a rewritten port and polls for the custom output.tests/e2e/configuration/unified-mode/{library-mode,server-mode}/, 20 files + README): every fixture validated against the realConfigurationmodel — bootable ones load in both modes, the three invalid ones fail with exactly the intended errors. Profile-based fixtures consume the CI-materialized./run.yaml, staying provider-agnostic across the providers matrix.lightspeed-stack-legacy.yamlfixture restores it, with a one-line Given edit inunified-mode-legacy.feature(the single deliberate Gherkin edit; rationale in a Gherkin comment and the commit message).@skip-in-prow(Docker/subprocess dependencies); the two openai-hardcoded boot scenarios tagged@openai-onlywith abefore_scenarioskip keyed onE2E_DEFAULT_PROVIDER_OVERRIDE, so the providers matrix (azure/watsonx/bedrock full-list runs) skips them instead of failing.restart_containerwaited on docker health, which can report healthy before uvicorn binds the port (the documented racewait_for_lightspeed_stack_http_readyexists for, previously closed only in proxy steps) — the unified first boots are the slowest restarts in the suite and hit it reliably; the HTTP-ready wait is now wired into every lightspeed-stack restart.@skipremoved from the five features. Rebased over the LCORE-3537 e2e rework: the features carry main's@cfg_unifiedaffinity tag (plus@skip-in-prow), so the existingcfg_unifiedCI shard picks them up on unskip. (Two mid-branch commit messages predate the rework and mention the old@e2e_group_2sharding; the final state is@cfg_unified.)Depends on: LCORE-2338 (PR #2319 — server-mode synthesis path; still open). LCORE-2872 (config_format_version) has merged, so the validation scenario now fails/passes for the right reason.
Type of change
Tools used to create PR
Identify any AI code assistants used in this PR (for transparency and review context)
Related Tickets & Documents
Checklist before requesting a review
Testing
Verified against a local merge of this branch with PR #2317 and PR #2319, docker-compose stacks in both modes, live OpenAI key (pre-rebase, 2026-08-04); step-matching re-verified after rebasing onto the LCORE-3537 e2e rework:
uv run behave --dry-run tests/e2e/features/unified-mode-*.featureActual (on the rebased head): 24 scenarios, 200 steps, zero undefined.
E2E_DEPLOYMENT_MODE=library uv run behave tests/e2e/features/unified-mode-*.featureActual: 17 scenarios passed, 0 failed, 7 skipped (server-only variants) — includes migrate→synthesize round-trip, the custom
--synthesized-config-outputservice subprocess, and the startup-log check.E2E_DEPLOYMENT_MODE=serverandE2E_LLAMA_HOSTNAME=llama-stackexported (the validation subprocess resolves the fixture's${env.E2E_LLAMA_HOSTNAME}; CI sets it at job level).Actual: 17 scenarios passed, 0 failed, 7 skipped (library-only variants) — all validation, migration, and synthesis scenarios green, mode-aware log step verified against the llama-stack container.
uv run make format— clean on the rebased head. Fullmake verifydeferred to CI (local machine constraint); the last full local verify on this branch was clean except the 14 pre-existing mypy errors intests/unit/utils/test_models_dumper.py.