Skip to content

chore(ci): fail when a test suite is executed by nothing (DEBT-031)#110

Merged
fvadicamo merged 5 commits into
developfrom
chore/debt-031-suite-execution-guard
Jul 14, 2026
Merged

chore(ci): fail when a test suite is executed by nothing (DEBT-031)#110
fvadicamo merged 5 commits into
developfrom
chore/debt-031-suite-execution-guard

Conversation

@fvadicamo

@fvadicamo fvadicamo commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Nothing guaranteed a test suite was ever executed. make test and ci-unit.yml enumerate the packages in two independent hand-maintained lists, and the DEBT-029 guard checks only that a package is isolated, never that anyone runs it. A suite absent from both lists was silently unexecuted and no test went red — the exact shape of the hole BUG-024 fell through, still open one level up.

This adds vektra-shared/tests/test_suite_execution_coverage.py, which reads the runners instead of trusting them.

What the guard does

It parses the test: recipe in the Makefile and every pytest invocation in every workflow, then asserts that each test file would actually be collected by one — honouring the -m filter, not just the paths.

That distinction is the whole thing. A directory-level check (which is what the backlog originally proposed) would have passed: vektra-admin/tests/ is named in both lists. What nobody ran was the integration-marked file inside it, excluded from every unit run by -m "not integration". So the unit path and the integration path are checked independently.

Two design points worth reviewing:

  • YAML is parsed, never grepped. integration.yml carries a commented-out pytest line for test_performance.py. A guard that grepped the text would count it as coverage and certify a suite nobody runs.
  • The test-structure job carries no path filter, deliberately. Every other unit job is gated on dorny/paths-filter, so a PR that only edits the Makefile, only edits a workflow, or adds a brand-new vektra-foo/ matches no filter and skips them all — and those are precisely the three changes this guard exists to catch. A guard a path filter can skip is not a guard, so the DEBT-029 guard (until now gated on vektra-shared/**) runs there too.

Found the moment it was pointed at develop: five suites executed by nothing

Suite Why invisible
tests/test_startup.py ARCH-057 startup validation. Its own docstring says it needs the stack "started by integration.yml" — it does not run there: that workflow names tests/integration/, and nothing ever named tests/. It was watching the precise surface BUG-024 broke, and it was watching nothing.
vektra-admin/tests/test_integration.py integration-marked → excluded from every unit run; the only job running -m integration named vektra-app/tests/ alone
vektra-index/tests/test_integration.py same
vektra-index/tests/test_benchmark.py same
vektra-ingest/tests/test_integration.py same

DEBT-030 wired up vektra-app by hand and left the identical hole open in three neighbouring packages.

Unlike vektra-app's suites in DEBT-030, these four had not rotted — 52 integration tests pass — but that was luck, not a property of the arrangement.

Fixes that make it green

  • app-integrationpackage-integration, targeting vektra-*/tests -m integration. A glob on purpose: the hand-written package list is the thing that has now failed twice, so a package added tomorrow is picked up with no edit.
  • tests/test_startup.py joins the job that already brings the stack up.
  • The five suites under tests/ gain the integration marker they require. Unmarked, they were indistinguishable from unit tests make test had merely forgotten — which is exactly why nobody noticed.
  • tests/nfr/test_performance.py stays deliberately unrun (it needs a live LLM that GitHub Actions does not have) as the single justified entry in the guard's EXPECTED_UNRUN, where a reviewer can see it.

Proof it works — broken five ways, red each time

A guard you have not watched fail is not a guard.

Sabotage Result
Drop vektra-index from the CI workflow RED — test_every_unit_suite_runs_in_ci
Drop vektra-index from the Makefile RED — test_every_unit_suite_runs_in_make_test
Narrow the integration glob back to vektra-app (the DEBT-030 regression) RED — test_every_integration_suite_runs_in_ci
Add a vektra-foo/tests/ nobody wired up RED — both unit assertions
Empty the allowlist RED — correctly refuses to treat the commented-out pytest line as coverage

Each went green again on restore.

Branch protection

The required check names are unchanged: CI gate and Integration tests + NFR gates. The new test-structure job is added to ci-gate's needs; the renamed package-integration is added to integration-gate's. Neither aggregator's name: was touched.

Verification

  • make lint clean
  • make test: 771 passed, 3 skipped, 52 deselected
  • uv run pytest vektra-*/tests -m integration: 52 tests collected across all four packages (duplicate test_integration.py basenames handled by --import-mode=importlib)

Closes DEBT-031.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added CI “structural guard” checks to ensure every test suite is actually discovered and executed by the intended workflows.
  • Bug Fixes

    • Improved startup validation so an empty VEKTRA_LLM_PROVIDER is rejected.
    • Strengthened startup-failure behavior checks (including preventing raw tracebacks).
  • Tests / CI

    • Reworked integration testing to be package-wide, with expanded startup validation during stack startup.
    • Marked relevant suites as integration-only to prevent them from running in unit workflows.
  • Documentation

    • Updated the Unreleased changelog with these CI and test execution improvements.

`make test` and `ci-unit.yml` enumerate the packages in two independent
hand-maintained lists, and the DEBT-029 guard checks only that a package is
isolated, never that anyone runs it. A suite absent from both lists was
silently never executed and nothing went red. That is the hole BUG-024 fell
through, still open one level up.

Add `vektra-shared/tests/test_suite_execution_coverage.py`: it parses the
`test:` recipe and every workflow's pytest invocations and asserts each test
file would actually be *collected* — honouring the `-m` filter, not just the
paths. That distinction is the point: a directory-level check passes, because
`vektra-admin/tests/` is named in both lists; what nobody ran was the
`integration`-marked file inside it, excluded from every unit run by
`-m "not integration"`. Unit and integration paths are checked independently.
YAML is parsed, never grepped, so the commented-out pytest line in
integration.yml grants no coverage.

It runs in a `test-structure` job with no path filter, deliberately: every
other unit job is gated on paths-filter, so a Makefile-only edit, a
workflow-only edit, or a new package skips them all — exactly the three
changes the guard exists to catch. The DEBT-029 guard runs there too.

Pointed at develop, it found five suites executed by nothing:
`tests/test_startup.py` (ARCH-057 startup validation; its docstring assumed
integration.yml ran it, which names tests/integration/ and never tests/), and
the integration suites of vektra-admin, vektra-index (x2) and vektra-ingest,
left behind when DEBT-030 wired up vektra-app by hand. All now run. The four
had not rotted (52 integration tests pass), which was luck, not design.

`app-integration` becomes `package-integration` over a `vektra-*/tests` glob,
so the package list stops being hand-maintained. The five suites under tests/
also gain the `integration` marker they require; `tests/nfr/test_performance.py`
stays deliberately unrun (needs a live LLM) as the one justified entry in the
guard's EXPECTED_UNRUN. Required check names are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fvadicamo fvadicamo added component:shared vektra-shared component ci CI/CD and toolchain changes labels Jul 14, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d311de9b-f185-4560-8cf1-019dc345ddb2

📥 Commits

Reviewing files that changed from the base of the PR and between d44f4c3 and 6b99855.

⛔ Files ignored due to path filters (2)
  • .s2s/BACKLOG.md is excluded by !.s2s/**
  • uv.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (6)
  • .github/workflows/ci-unit.yml
  • CHANGELOG.md
  • tests/test_startup.py
  • vektra-shared/pyproject.toml
  • vektra-shared/src/vektra_shared/config.py
  • vektra-shared/tests/test_suite_execution_coverage.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci-unit.yml

📝 Walkthrough

Walkthrough

CI now marks live-stack suites as integration tests, runs startup and package-wide integration coverage, rejects empty LLM provider configuration, and adds a static guard that verifies test suites are collected by Makefile and GitHub Actions commands.

Changes

CI test coverage enforcement

Layer / File(s) Summary
Suite discovery and runner coverage guard
vektra-shared/tests/test_suite_execution_coverage.py, vektra-shared/pyproject.toml
Discovers unit and integration suites, parses pytest invocations from the Makefile and workflows, validates marker coverage, and declares PyYAML for workflow parsing.
Package integration workflow
.github/workflows/integration.yml, tests/integration/*, tests/nfr/*, tests/test_startup.py
Marks live-stack suites for integration selection, runs startup validation, replaces app-specific integration with package-wide globbed execution, and updates the integration gate.
Startup configuration validation
vektra-shared/src/vektra_shared/config.py, tests/test_startup.py
Rejects empty VEKTRA_LLM_PROVIDER values and updates startup failure coverage, including strict xfail traceback validation.
Required structural CI gate
.github/workflows/ci-unit.yml, CHANGELOG.md
Adds the structural guard job, makes it a required dependency of ci-gate, and documents the CI and configuration fixes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant IntegrationWorkflow
  participant PackageIntegration
  participant Pytest
  participant IntegrationGate
  IntegrationWorkflow->>Pytest: run startup and integration tests
  IntegrationWorkflow->>PackageIntegration: start package-wide integration job
  PackageIntegration->>Pytest: collect vektra-*/tests with -m integration
  IntegrationGate->>IntegrationWorkflow: check integration result
  IntegrationGate->>PackageIntegration: check package-integration result
Loading

Possibly related PRs

  • vektralabs/vektra-stack#56: Changes integration workflow execution for tests/nfr/test_performance.py, which is now covered by the structural guard.
  • vektralabs/vektra-stack#104: Adds the hermetic_env fixture and wiring used by related test-environment isolation coverage.

Suggested labels: bug, dependencies

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is related to the main change: adding a CI guard that fails when a test suite is not actually executed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/debt-031-suite-execution-guard

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request addresses DEBT-031 by introducing a structural guard (test_suite_execution_coverage.py) that ensures all test suites are executed by either local or CI runners, preventing silent test rot. It also marks several previously unrun or unmarked integration test suites with pytest.mark.integration so they are correctly classified and executed. The reviewer identified an issue in the AST parsing logic of the new guard, which fails to detect the integration mark when applied at the class level rather than the module or function level, and provided a code suggestion to resolve this.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread vektra-shared/tests/test_suite_execution_coverage.py Outdated
The classifier read only module-level `pytestmark` and function decorators. A
test class carrying `@pytest.mark.integration` (or a class-body `pytestmark`)
would therefore be classified as unit: the guard would never demand an
integration run for it, and every unit run would deselect it by
`-m "not integration"`. Silently unexecuted — the exact failure this file
exists to prevent, reproduced inside the guard itself.

Class-based tests are already used here (vektra-admin), so the hole was live.
Marks are now inherited module -> class -> function, as pytest applies them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci-unit.yml:
- Around line 209-224: Update the checkout step in the test-structure job to set
persist-credentials to false, matching the credential-isolation behavior of
test-app before running the PR-authored structural tests.

In `@vektra-shared/tests/test_suite_execution_coverage.py`:
- Line 33: Declare PyYAML as a dependency of the vektra-shared package in its
pyproject.toml so the yaml import in the test suite remains available when
syncing only that package’s dev dependencies. Use the package’s existing
dependency configuration and preserve the current test execution flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 53ac895d-21c2-481d-9ea1-71536b2497bb

📥 Commits

Reviewing files that changed from the base of the PR and between 754f18c and d44f4c3.

⛔ Files ignored due to path filters (1)
  • .s2s/BACKLOG.md is excluded by !.s2s/**
📒 Files selected for processing (9)
  • .github/workflows/ci-unit.yml
  • .github/workflows/integration.yml
  • CHANGELOG.md
  • tests/integration/test_chunk_lifecycle.py
  • tests/integration/test_e2e_flow.py
  • tests/nfr/test_nfr_hard.py
  • tests/nfr/test_performance.py
  • tests/test_startup.py
  • vektra-shared/tests/test_suite_execution_coverage.py

Comment thread .github/workflows/ci-unit.yml
Comment thread vektra-shared/tests/test_suite_execution_coverage.py
fvadicamo and others added 3 commits July 14, 2026 22:27
`llm_provider` is required but typed as a bare `str`, and `""` is a valid
`str`, so `VEKTRA_LLM_PROVIDER=` was accepted: pydantic never raised, ARCH-057
step 1 never fired, and the stack booted and served with an empty LLM provider,
deferring the misconfiguration to the first query. That is the exact failure
mode the startup sequence exists to prevent (REQ-011, NFR-009).

`min_length=1` makes an empty value fail at step 1 with the same structured,
remediable error an absent one already produced.

Found by running tests/test_startup.py for the first time (DEBT-031). The test
has asserted this behaviour since it was written, believing `docker run -e VAR=`
*unsets* the variable when it sets it to the empty string — so it was asserting
against a value the product happily accepted, and hung waiting for a container
that had booted fine. Nothing ever ran it, so nobody found out.

Fixed at the source rather than by weakening the test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uctured error

With the empty-provider fix in place, tests/test_startup.py exposes the next
layer: step 1 emits the structured [STARTUP ERROR] block, and then
`raise SystemExit(1)` travels out of the ASGI lifespan into uvicorn, which logs
the exception. The operator who typo'd an env var gets the good message *and* a
Python traceback. NFR-009 asks for the former instead of the latter, not both,
and it applies to all 11 steps, not just config validation.

The fix is to validate before uvicorn.run() rather than inside the lifespan --
the boot path BUG-024 broke, and vektra-app's own tests drive that lifespan
in-process and expect SystemExit. Too much to bolt onto a PR about test
execution, so it is filed as BUG-025 with the analysis and the trap written
down.

The assertion is split into its own test with xfail(strict=True): the defect
stays visible and CI stays honest, and when BUG-025 lands the test XPASSes and
the suite goes red until the marker is removed. A known defect that cannot rot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pyyaml

Two verified review findings from the PR bots:

- test-structure ran PR-authored test code without persist-credentials: false,
  unlike test-app and app-integration which strip the token from .git/config
  for exactly that reason. Same threat model; same fix.

- test_suite_execution_coverage.py imports yaml, but vektra-shared did not
  declare pyyaml — it relied on the root dev group leaking into a
  `uv sync --package vektra-shared`. It works (both test-shared and
  test-structure were green), but an implicit transitive dependency for a guard
  is the precise fragility this PR is about, so declare it where it is used.

A third bot comment (class-level integration marks) was already fixed in
1b2ceef before the comment landed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fvadicamo fvadicamo merged commit 00abb78 into develop Jul 14, 2026
25 checks passed
@fvadicamo fvadicamo deleted the chore/debt-031-suite-execution-guard branch July 14, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD and toolchain changes component:shared vektra-shared component documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant