Skip to content

LCORE-2343: implement behave step definitions for unified-mode feature files - #2448

Open
max-svistunov wants to merge 7 commits into
lightspeed-core:mainfrom
max-svistunov:lcore-2343-unified-mode-step-definitions
Open

LCORE-2343: implement behave step definitions for unified-mode feature files#2448
max-svistunov wants to merge 7 commits into
lightspeed-core:mainfrom
max-svistunov:lcore-2343-unified-mode-step-definitions

Conversation

@max-svistunov

Copy link
Copy Markdown
Contributor

Description

Implements LCORE-2343: behave step definitions, fixtures, and gating for the five unified-mode-*.feature files authored spec-first in LCORE-2341 (PR #2020), and unskips them.

  • Step definitions (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-config CLI 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's native_override and additionally assert the baseline differed, so replacements cannot 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.
  • Mode-aware startup-log step: in server mode the synthesis evidence is emitted by the llama-stack container (entrypoint + CLI), not the lightspeed-stack container the Gherkin names — the step inspects the container that actually synthesizes, with the rationale documented in the step (agreed deviation; scenario intent is R10 "path is logged at startup").
  • Fixtures (tests/e2e/configuration/unified-mode/{library-mode,server-mode}/, 20 files + README): every fixture validated against the real Configuration model — 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.
  • Legacy-fidelity fix (agreed in planning): LCORE-2342 migrated the standard library baseline to unified mode, silently removing R2's library-mode legacy boot coverage — a dedicated lightspeed-stack-legacy.yaml fixture restores it, with a one-line Given edit in unified-mode-legacy.feature (the single deliberate Gherkin edit; rationale in a Gherkin comment and the commit message).
  • Gating: all five features tagged @skip-in-prow (Docker/subprocess dependencies); the two openai-hardcoded boot scenarios tagged @openai-only with a before_scenario skip keyed on E2E_DEFAULT_PROVIDER_OVERRIDE, so the providers matrix (azure/watsonx/bedrock full-list runs) skips them instead of failing.
  • Harness fix surfaced by these scenarios: restart_container waited on docker health, which can report healthy before uvicorn binds the port (the documented race wait_for_lightspeed_stack_http_ready exists 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.
  • Unskip: @skip removed from the five features. Rebased over the LCORE-3537 e2e rework: the features carry main's @cfg_unified affinity tag (plus @skip-in-prow), so the existing cfg_unified CI shard picks them up on unskip. (Two mid-branch commit messages predate the rework and mention the old @e2e_group_2 sharding; 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

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement
  • Benchmarks improvement

Tools used to create PR

Identify any AI code assistants used in this PR (for transparency and review context)

  • Assisted-by: Claude Opus 4.8
  • Generated by: Claude Opus 4.8

Related Tickets & Documents

  • Related Issue # LCORE-2341, LCORE-2338, LCORE-2872, LCORE-2342, LCORE-3370, LCORE-3537
  • Closes # LCORE-2343

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

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:

  1. Step-pattern completeness (no stack needed):
    uv run behave --dry-run tests/e2e/features/unified-mode-*.feature
    Actual (on the rebased head): 24 scenarios, 200 steps, zero undefined.
  2. Library mode: start the library compose stack, then
    E2E_DEPLOYMENT_MODE=library uv run behave tests/e2e/features/unified-mode-*.feature
    Actual: 17 scenarios passed, 0 failed, 7 skipped (server-only variants) — includes migrate→synthesize round-trip, the custom --synthesized-config-output service subprocess, and the startup-log check.
  3. Server mode: start the full compose stack, then the same run with E2E_DEPLOYMENT_MODE=server and E2E_LLAMA_HOSTNAME=llama-stack exported (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.
  4. uv run make format — clean on the rebased head. Full make verify deferred to CI (local machine constraint); the last full local verify on this branch was clean except the 14 pre-existing mypy errors in tests/unit/utils/test_models_dumper.py.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@max-svistunov, you've reached your PR review limit, so we couldn't start this review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4f9ca75d-6a15-48cb-9ecd-15244fca75ee

📥 Commits

Reviewing files that changed from the base of the PR and between da64733 and ba9f5eb.

📒 Files selected for processing (31)
  • .gitignore
  • tests/e2e/configuration/library-mode/lightspeed-stack-legacy.yaml
  • tests/e2e/configuration/unified-mode/README.md
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-invalid-config-and-legacy.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-invalid-providers-and-legacy.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-invalid-version-legacy-unified-body.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-legacy-for-migration.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-absolute-profile.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-config-only.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-native-override-list.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-native-override-scalar.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-providers.yaml
  • tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-relative-profile.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-invalid-config-and-legacy.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-invalid-providers-and-legacy.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-invalid-version-legacy-unified-body.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-legacy-for-migration.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-absolute-profile.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-config-only.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-native-override-list.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-native-override-scalar.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-providers.yaml
  • tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-relative-profile.yaml
  • tests/e2e/features/environment.py
  • tests/e2e/features/steps/unified_mode.py
  • tests/e2e/features/unified-mode-boot.feature
  • tests/e2e/features/unified-mode-legacy.feature
  • tests/e2e/features/unified-mode-migration.feature
  • tests/e2e/features/unified-mode-synthesis.feature
  • tests/e2e/features/unified-mode-validation.feature
  • tests/e2e/utils/utils.py

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.
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.

1 participant