fix(tests): fail when a chart runs no Helm unit test suites - #3632
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe Helm unit-test script captures and validates test output, rejects incomplete suite execution, and reports failed package directories. The Bats suite covers target handling, suite evidence, failures, successful runs, sweep coverage, and packages without test targets. ChangesHelm test validation
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant PackageDirectory
participant HelmUnitTests as helm-unit-tests.sh
participant Make
participant Helm
PackageDirectory->>HelmUnitTests: discover package test target
HelmUnitTests->>Make: execute make test with captured output
Make->>Helm: run Helm unit suites
Helm-->>HelmUnitTests: return suite output
HelmUnitTests-->>PackageDirectory: report package result and summary
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
IvanHunters
left a comment
There was a problem hiding this comment.
Overview
This tightens hack/helm-unit-tests.sh so a chart whose suite files were deleted/moved/renamed out of tests/*_test.yaml fails the sweep instead of silently reporting success (helm-unittest prints Test Suites: 0 passed, 0 total and exits 0 for "no suites found"). It adds two more checks in the same spirit: a shadowed test Makefile target (make -n test succeeding against a file-backed, non-phony target) and a general "no positive evidence of a passing suite" backstop. hack/helm-unit-tests.bats adds 8 fixture-based tests for the new logic.
Verdict: LGTM on the logic and tests. Marking with notes only because of mergeability (see below), not because of a code defect.
Verification performed (static + isolated dry run)
- Ran the PR's head version of
hack/helm-unit-tests.shfor real (realhelm+helm-unittestv1.0.3, no stubs) against all 76 packages the script currently visits. Result: zeroERRORlines, zeroTest Suites: 0 passed, 0 totaloccurrences, zerois up to dateshadowing hits, script ends withAll Helm unit tests passed.— confirms the PR's core claim that the stricter check introduces no false failures on the current tree. - Ran the new
hack/helm-unit-tests.bats(8 tests) through the repo's ownhack/cozytest.shrunner: all 8 pass. - Independently confirmed the PR-body claim that exactly 76 package directories define a discoverable
testtarget (make -C dir -n testsucceeds) — matches. - Independently confirmed the PR-body claim about
hack/package.mk:2(.PHONY=help show diff apply delete update image, a variable assignment, not a target declaration) — real, pre-existing, out-of-scope-for-this-PR issue as stated (tracked separately as #3353). - Read the two new
grep -qEchecks closely for shell correctness:rc=0; LC_ALL=C make -C "$dir" test > "$OUTPUT_FILE" 2>&1 || rc=$?correctly captures output and status underset -euwithout the "pipeline masks make's exit code" trap the old code would have hit if streamed through a pipe. The backtick/quote character class for the make 3.x/4.x "up to date" message is correctly escaped inside the double-quoted string (verified it survives as a literal character class, not a command substitution).
Non-blocking notes
- Mergeability (CONFLICTING is real, not stale metadata): a 3-way
git merge-treeof base/head/origin/mainshows a genuine both-added conflict onhack/helm-unit-tests.bats— PR #3670 ("close the silent-green gaps in e2e selection and the helm unit sweep", merged) independently created a file at the same path to cover a different gap (thepackages/testsloop omission forcozy-lib-tests). It also touched the same header comment andfor package_dir in ...line inhack/helm-unit-tests.sh, though that part appears to 3-way-merge cleanly on its own. Please rebase onto currentmainand manually reconcile the two.batstest suites (not a mechanical/no-op rebase) before this can merge. - CI "E2E Tests" is currently red on this PR, but the failing subtest is specifically
chainsaw/kubernetes-latestwith a Kamaji guest-control-plane connectivity signature (Unable to connect to the server: ... i/o timeout,no route to host) whilekubernetes-previouspassed in the same run — this matches the already known, currently-undiagnosed intermittent node-join flake, and is unrelated to this PR's diff (which touches onlyhack/helm-unit-tests.sh/.bats). "Unit & controller tests" (which exercises the new.batssuite) passes cleanly. - The PR description already self-documents several deliberate, deferred gaps (the positive-evidence error message names a cause but not a remedy;
docs/agents/overview.mdisn't updated for the new contract; a package that loses both its suite files and itstest:rule simultaneously is still missed). These are reasonable to defer rather than block on. - No actionable items from the existing automated CodeRabbit pass, and no human reviews on record yet.
Closing
The change is well-reasoned, the risky "hard fail on zero suites" behavior was verified empirically (not just asserted) to be safe against the entire current package tree, and the accompanying tests are meaningful (each pinned to its own distinguishing message so they can't stay green for the wrong reason). Please rebase onto main to resolve the conflict with #3670 (reconciling both .bats suites), then this is ready to merge from my side.
abf620e to
c3ea97a
Compare
57ac0c3 to
385a963
Compare
b8a5b9b to
385a963
Compare
helm-unittest is fail-closed on a suite it cannot parse and on one declaring `tests: []`, and fail-open on suites that are not there at all: it prints "Test Suites: 0 passed, 0 total" and exits 0. Measured on plugin 1.1.1 against a throwaway chart, all three forms. hack/helm-unit-tests.sh judges each package by that exit code alone, so a chart whose suites were deleted, moved, or renamed past the tests/*_test.yaml glob reports success having asserted nothing. Moving packages/system/velero/tests aside reproduces it: the script prints "All Helm unit tests passed" and exits 0. Check the captured output for the zero-suite line and fail the package when it appears. Neither `--strict` nor an explicit non-matching `-f` glob does this on its own; both exit 0. Matching that line names the cause where it applies, but the set of ways to run nothing is not closed: a recipe that never invokes helm-unittest, and a `test:` rule make finds nothing to do for, both print no marker at all, so no further match reaches them. So also require the line a real run always emits, a `Test Suites:` count of at least one. Every way of asserting nothing exits 0, and asking what the run asserted is the question that has an answer. All 77 packages the script visits emit that line today. The tradeoff is that a `test` target which legitimately runs no helm-unittest would need renaming or an opt-out, which is the bargain the zero-suite check already struck. That is a contract contributors have to know about, so docs/agents/overview.md states it where it describes the sweep. Because the check matches helm-unittest's own summary wording, a plugin upgrade that reworded it would fail every package at once while blaming each package's Makefile. The error text says so, so the reader of a tree-wide red has the real cause in front of them rather than 77 copies of the wrong one. The discovery gate has the same shape from the other side. `make -C dir -n test` succeeds against a file-backed target, so a path named `test` beside a package Makefile whose rule is not phony would make both the gate and the run exit 0 without the recipe firing. Of the packages the script visits that define a `test` rule, 18 declare it phony, and no such path exists today. Match what make reports rather than looking for the path, because a package that declares the target phony runs its recipe whatever sits next to it, and refusing that would be a false failure. The quoting around the target name differs between make 3.x and 4.x, so the match accepts either opening quote; the trailing quote alone would also match a sub-make reporting another target whose name ends in "test". Running the suite now captures output rather than streaming it, because a pipeline's exit status in POSIX sh reports the last command rather than make. The capture file is allocated once and removed by the EXIT trap that already owns the failure list, and the run is pinned to LC_ALL=C: all three checks match English wording, one on make's and two on helm-unittest's, so a localized make would disarm the first of them silently, which is the failure mode this guard exists to remove. The locale reaches every package's recipe, not only make. Extend hack/helm-unit-tests.bats with eight tests. Three pin the new refusals; two pin what they must not refuse, a phony target with a colliding path and a sub-make reporting an unrelated target up to date; one pins that a chart which does run suites still passes; two pin behaviour that was already there, namely that a failing package is still reported by name and a package with no `test` rule is skipped rather than failed. That last one carries a second package that does run a suite, because with only the target-less package present the script takes its early exit above the failure summary and the assertion holds whatever the script did. The fixtures stub make output instead of invoking helm, so they need no plugin installed. Both tests that assert a package is named in the failure summary match the summary's " - " prefix rather than the bare path. The script announces "Running tests in $dir" before it runs anything, so the bare path is present in the output whatever the summary does; matching it asserted nothing, and the summary could stop naming packages entirely with the suite still green. The failing-package fixture reports a passing suite count alongside its non-zero exit, so that the exit status is the only thing that can decide the outcome. A fixture that fails without printing the marker is refused by the positive-evidence check first, which leaves the exit-status branch unpinned. That shape occurs in the tree: a `test` target that runs helm-unittest and then a second step, a render-parity script or a vendored-version guard or go tests, emits the marker and still fails. The two tests already in that file cover a package reached under packages/tests, and their fixture printed a marker and nothing else. That is the shape the positive-evidence check refuses, so the fixture now also emits the line a real run emits; without it both would fail for a reason unrelated to what they assert. Assisted-By: Claude <[email protected]> Signed-off-by: Aleksei Sviridkin <[email protected]>
385a963 to
94c41d1
Compare
myasnikovdaniil
left a comment
There was a problem hiding this comment.
Approve. Rebase landed and the add/add on hack/helm-unit-tests.bats is resolved the right way: branch is 0 commits behind main, and the merged file keeps both packages/tests tests from main, the sweep runs the suites under packages/tests and a suite failing under packages/tests fails the sweep. That was my only blocker.
Ran the sweep myself on this head with env -u REGISTRY bash hack/helm-unit-tests.sh: 77 packages visited, exit 0, all passed. So nothing in the catalog newly reddens and no allowlist is needed. docs/agents/overview.md is updated too.
One thing stays open and it does not block. Guarded direction has zero live instances in the tree, the mirror direction has three: packages/apps/mongodb (7 suites), packages/system/gpu-operator (1), packages/system/monitoring-agents (1). That is 9 suites and 99 assertions that never run because those packages have no test: target, and every one of them passes when invoked by hand. Three one-line Makefile additions light them up. Either a follow-up, or retitle so "a chart runs no Helm unit test suites" is not claimed for a case that stays green.
What this PR does
helm unittestis fail-closed on a suite it cannot parse and on one declaringtests: [], and fail-open on suites that are not there at all: it printsTest Suites: 0 passed, 0 totaland exits 0.hack/helm-unit-tests.shjudges each package by that exit code alone, so a chart whose suites were deleted, moved, or renamed past thetests/*_test.yamlglob reports success having asserted nothing.That is measurable in both directions rather than argued. Move
packages/system/velero/tests/velero_test.yamlaside and run the script as it exists on main today:The run ends with
All Helm unit tests passed.and exit 0. The chart passed, and the count of things it checked was zero. With this PR the same tree exits 1 and names the package and the reason. Neither--strictnor an explicit non-matching-fglob changes this on its own; all three forms exit 0, checked against plugin 1.1.1.So the script now reads the captured output and fails the package when the zero-suite line appears.
Matching that line names a cause and a remedy where it applies, but the set of ways to run nothing is not closed. A recipe that never invokes
helm unittest, and atest:rule that make finds nothing to do for, print no marker of any kind and exit 0, so no additional match reaches them; enumerating absences leaves a fresh hole every time a new shape turns up. So the script also requires the line a real run always emits, aTest Suites:count of at least one. That turns the question round: every way of asserting nothing exits 0, and "what did this run assert" is the question that has an answer. All 76 packages the script visits emit that line today. The tradeoff is that atesttarget which legitimately runs nohelm unittestwould need renaming or an opt-out, which is the same bargain the zero-suite check already strikes.The discovery gate has the same shape from the other side.
make -C dir -n testsucceeds against a file-backed target, so a path namedtestbeside a package Makefile whose rule is not phony would make both the gate and the run exit 0 without the recipe ever firing. The check keys on what make reports rather than on the path existing, because a package that declares the target phony runs its recipe whatever sits next to it, and refusing that would be a false failure. The quoting around the target name differs between make 3.x and 4.x, so it accepts either opening quote; anchoring on the trailing quote alone would also match a sub-make reporting some other target whose name ends intest.That second check is guarding a live gap rather than a hypothetical one.
hack/package.mkline 2 reads.PHONY=help show diff apply delete update image, which is a variable assignment and not a target declaration, so it makes nothing phony. Of the packages the script visits that define atestrule, 18 declare it phony in their own Makefile; the rest are unprotected if a path of that name ever appears. No such path exists today. Fixing the shared include is #3353 and is not this PR.One deliberate non-change, stated so it is not rediscovered as an oversight. Running the suite now captures output instead of streaming it, because a pipeline's exit status in POSIX
shreports the last command rather thanmake. Streaming reads better and loses the status, so it stays buffered.The run is pinned to
LC_ALL=C, since both checks match English wording and a localizedmakewould disarm one of them silently, which is the failure mode the whole change exists to remove.The guard is all-or-nothing, which is worth stating plainly. A chart that loses four of its five suites still reports
Test Suites: 1 passedand passes; only total disappearance is caught. Tying the expected count to what each chart actually has would be a per-chart number to maintain, and a number maintained in one place while the suites move in another is the failure this repo has been paying for elsewhere, so the cheap check that catches the total loss is the one worth having.One behaviour worth stating because the message does not: a suite in which every test carries
skip:reportsTest Suites: 0 passed, 1 skipped, 1 totaland exits 0, and the positive-evidence check refuses it. That refusal is intended, since a wholly skipped suite asserted nothing, but the message talks about suites expected undertests/, which in that case are present and skipped on purpose. No package is in that state today. Partial skips are unaffected:2 passed, 1 skipped, 3 totalsatisfies the check.Three known gaps in what the change ships, none of them a wrong statement. The positive-evidence message explains its cause but stops short of naming a remedy, where the other two messages both end in an action; the remedy for the legitimate case, renaming the target or giving it an opt-out, currently lives only in the code comment. That same message enumerates two ways a run reports nothing, and the skipped-suite case above is a third, so it reads as a diagnosis where it is really a list of the common causes. And no document in the tree states the convention this tightens:
docs/agents/overview.mdstill describes the script accurately as running over every package that defines atesttarget, but the contract is now that such a package must also report at least one passing suite. All three are improvements to make on the next touch of these files rather than reasons to hold the change.One adjacent gap stays open, and it is worth naming rather than leaving someone to assume otherwise. This catches a chart whose suite files vanished; it does not catch a package that loses its
test:rule along with them. Nothing ties the presence oftests/*_test.yamlto the presence of atesttarget, so a change that removes both is skipped in silence, and the script only complains when no package in the tree has atesttarget at all. The test nameda package with no test target is skipped, not failedpins that permissiveness deliberately, because the many packages that legitimately define notestrule would otherwise turn every run red. Closing it needs an instrument keyed on the suite files rather than on the Makefile target, which is a separate change.hack/helm-unit-tests.batsadds eight tests, picked up automatically by thehack/*.batsglob inmake unit-tests. Three pin the new refusals, and each matches its own message so none can stay green on a refusal that came from another. Two pin what must not be refused: a phony target with a colliding path, and a sub-make reporting an unrelated target up to date. One pins that a chart which does run suites still passes, which is what stops the refusals being satisfied by rejecting everything. Two pin behaviour that was already there, namely that a failing package is still reported by name and that a package with notestrule is skipped rather than failed. That last one builds a tree carrying a second package that does run a suite: with only the target-less package present, the script takes an early exit above the failure summary and the assertion would hold whatever the script had done. The fixtures stubmakeoutput instead of invoking helm, so they need no plugin installed.Relates to #3453.
Screenshots
Downstream repositories
Release note
Summary by CodeRabbit
Bug Fixes
Tests
Documentation