test(e2e): remove 7 latent false @e2e anchors — proven by a +7/-7 control - #182
Merged
Conversation
…actured coverage the moment anyone un-skipped them Seven `@e2e` anchors sat on `test.skip`ed tests whose bodies do not assert the scenario they name. gate-19 does not credit a skipped test, so none of them moves a number today. That is precisely why they are dangerous: un-skipping is the obvious way to burn gate-19 down, and doing it would have scored seven scenarios as covered without one new assertion being written. MEASURED, expectation computed BEFORE the run (hydra-gates @ 81c8c97, 1735-line checker), openbuild@development ff25c1b: as shipped {scenarios: 767, covered: 98, excluded: 509, uncovered: 160} the 7 tests un-skipped, nothing else changed {scenarios: 767, covered: 105, excluded: 509, uncovered: 153} restored {scenarios: 767, covered: 98, excluded: 509, uncovered: 160} +7 exactly, which is the count of anchors, naming those seven scenarios. WHAT IS WRONG WITH EACH, read from the body, not the title: openbuild-rbac two anchors about the VIEWER and the EDITOR sit on tests titled for the OWNER and the ADMIN, both driving the shared admin session. The second one's only assertion is `expect(count).toBeGreaterThanOrEqual(0)` on a locator count — a count is never negative, so the assertion CANNOT FAIL. application-detail-overview a requirement naming six rows IN DOM ORDER plus four sidebar tabs, anchored to a test that asserts `main` is visible and the title matches /openbuild/i. Its sibling computes `iconCount` and never asserts on it. app-icon-management "uploads a light icon" uploads nothing; "removes the dark icon" removes nothing and both of its branches pass without the feature; "non-SVG is rejected" puts its rejection assertion under `if (fileInputCount > 0)` and then `if (errorCount > 0)`, so the product failing to reject is exactly the case where nothing is asserted. That body already says so: "the test passes vacuously because the UI is not built". THE NUMBER DOES NOT MOVE, and that is the point: after removal {scenarios: 767, covered: 98, excluded: 509, uncovered: 160} No test was skipped, un-skipped, deleted or loosened; no exclusion was written; no timeout was widened. What changed is the count of uncovered scenarios carrying a dead anchor: 35 -> 28 (the gate's own `collect_ref_status`). The anchors are DELETED rather than commented out. Per ConductionNL/.github#358 both gate-19 and gate-26 parse prose, so an anchor quoted in an explanatory comment is re-read as a live anchor — the replacement notes name no slug. SCOPE, stated because it is weak: this change touches three TEST files and no spec file, so `changed_spec_files()` returns the empty set and gate-19's own cell on this PR is EMPTY SCOPE (exit 3, NOT APPLICABLE) — it corroborates nothing. The full-scope figures above and the +7/-7 control are the only evidence, and the control is the load-bearing half. Refs #178, ConductionNL/.github#343, ConductionNL/.github#358
Contributor
Quality Report — ConductionNL/openbuild @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| composer | ✅ | ✅ 101/101 | |||
| npm | ✅ | ✅ 654/654 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-11 19:09 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
test(e2e): remove 7 latent false @e2e anchors — they would have manufactured coverage the moment anyone un-skipped them
Seven
@e2eanchors sat ontest.skiped tests whose bodies do not assert thescenario they name. gate-19 does not credit a skipped test, so none of them
moves a number today. That is precisely why they are dangerous: un-skipping is
the obvious way to burn gate-19 down, and doing it would have scored seven
scenarios as covered without one new assertion being written.
MEASURED, expectation computed BEFORE the run (hydra-gates @ 81c8c97,
1735-line checker), openbuild@development ff25c1b:
as shipped {scenarios: 767, covered: 98, excluded: 509, uncovered: 160}
the 7 tests un-skipped, nothing
else changed {scenarios: 767, covered: 105, excluded: 509, uncovered: 153}
restored {scenarios: 767, covered: 98, excluded: 509, uncovered: 160}
+7 exactly, which is the count of anchors, naming those seven scenarios.
WHAT IS WRONG WITH EACH, read from the body, not the title:
openbuild-rbac two anchors about the VIEWER and the EDITOR sit
on tests titled for the OWNER and the ADMIN, both
driving the shared admin session. The second one's
only assertion is
expect(count).toBeGreaterThanOrEqual(0)on alocator count — a count is never negative, so the
assertion CANNOT FAIL.
application-detail-overview a requirement naming six rows IN DOM ORDER plus
four sidebar tabs, anchored to a test that asserts
mainis visible and the title matches/openbuild/i. Its sibling computes
iconCountandnever asserts on it.
app-icon-management "uploads a light icon" uploads nothing; "removes
the dark icon" removes nothing and both of its
branches pass without the feature; "non-SVG is
rejected" puts its rejection assertion under
if (fileInputCount > 0)and thenif (errorCount > 0), so the product failing toreject is exactly the case where nothing is
asserted. That body already says so: "the test
passes vacuously because the UI is not built".
THE NUMBER DOES NOT MOVE, and that is the point:
after removal {scenarios: 767, covered: 98, excluded: 509, uncovered: 160}
No test was skipped, un-skipped, deleted or loosened; no exclusion was written;
no timeout was widened. What changed is the count of uncovered scenarios
carrying a dead anchor: 35 -> 28 (the gate's own
collect_ref_status).The anchors are DELETED rather than commented out. Per ConductionNL/.github#358
both gate-19 and gate-26 parse prose, so an anchor quoted in an explanatory
comment is re-read as a live anchor — the replacement notes name no slug.
SCOPE, stated because it is weak: this change touches three TEST files and no
spec file, so
changed_spec_files()returns the empty set and gate-19's owncell on this PR is EMPTY SCOPE (exit 3, NOT APPLICABLE) — it corroborates
nothing. The full-scope figures above and the +7/-7 control are the only
evidence, and the control is the load-bearing half.
Refs #178, ConductionNL/.github#343, ConductionNL/.github#358
Also measured in this session (no code change here)
gate-16 is dead on openbuild's
--fullruns —.github#361reproduces exactlyopenbuild's fleet table has never carried a gate-16 line. One tree
(
ff25c1be5=origin/development), only the base changed. Every number isnamed with its base, because the same checker prints PASS or 445 depending on
nothing but this input:
origin/development(ff25c1be5— the unguarded CI--fulldefault)# count=0→ PASSorigin/beta(5ab286fba)origin/main(e9c22311e)--mode report, whole treemainand report mode agree to within 6, which is the corroboration; the PASSis the outlier.
Positive control, committed and then reverted (an uncommitted plant reports
PASS for an unrelated reason —
check_spec_coverage.py:157derives the changedset from committed history while reading
@specfrom disk). Apublic function plantedGateSixteenProbe(): JSONResponsewith no@specwas added tolib/Controller/SettingsController.phpand committed:origin/developmentcount=1—lib/Controller/SettingsController.php::plantedGateSixteenProbe — missing @specHEADcount=0The second row is the whole finding: the gate reports PASS over a defect it had
just named, because base == HEAD makes the diff empty. Plant reverted.
Mechanism, confirmed in the canonical package (
ConductionNL/.github@ 81c8c97):
bin/hydra-gatesbuilds--scope-to-diff --base "${BASE_REF}"inside
if [ "${SCOPE_DIFF}" = "1" ], so on--fullno base is forwarded andrun-hydra-gates.sh:204'sBASE_REF="origin/development"survives toHYDRA_GATE_BASE_REF="${BASE_REF}"at line 3034. The base-IS-HEAD rescue(
resolve-push-base.sh) also lives underSCOPE_DIFF=1, so it never fires on--full. Not a checker bug — the checker honours an explicit base..github#356: the split, with theexclude_reasonkey — refining round 1Round 1 reported 80 distinct reasons, 42 covering more than one scenario, 471
of 509 (92.5%) on a shared reason, largest bucket 32. Grouped by
(spec, reason)— its actual key — every one of those reproduces exactly,and its headline (one reason covering 32) is a whole-spec marker and stands.
got 58/21/472, and read that as "round 1's method was an artifact". It was my
key that differed. Reproduce a predecessor's number under their method before
correcting it — an unreproduced number looks exactly like a wrong one.
What does need refining:
_make_scenario_entrywrites the literal string"<inherited from requirement>"as theexclude_reasonof everyrequirement-level exclusion — 238 of the 509 here. So for nearly half the
corpus the grouping key is a sentinel, and requirement-level exclusions
collapse together within a spec regardless of what their requirements say.
Recovering the real requirement text, same key, same tree:
Full layer split, from the same instrumented run (positive control asserted
first: 509 exclusions parsed, 509 with a non-empty reason, 509/509 layers
assigned — and the sample value read, not just counted):
Bare exclusions: 0. 14 spec files are blanket-excluded by a whole-spec
marker, the largest covering 32, 29, 26, 25 and 20 scenarios.
openbuild's profile is not nldesign's (61.5% whole-spec / 31.6% scenario /
6.8% requirement). Here the
#356requirement-inheritance shape is the singlelargest bucket. Measure each app.
How many of the 160 are genuinely uncovered: all 160
Inheritance can only move scenarios OUT of
uncoveredand INTOexcluded,never the reverse — so it cannot be inflating this number. Proven rather than
asserted, by re-running report mode with both inherited layers disabled and
nothing else changed:
455 scenarios are excluded by an inherited marker only; 453 of them carry no
anchor at all and would land straight in
uncovered, and 2 would becomecovered. So 160 is the floor and 613 is the honest ceiling — the reported
figure understates openbuild's e2e debt by 3.8×.
What the 160 actually consist of
@e2eanchor inside atest.skip— gate-19 correctlyrefuses them. 7 of those are the false anchors this PR removes; the other
28 are honest, quarantined behind a named blocker (openbuild#41,
vitest-covered) or documenting real product defects.
Two of the 28 have a rotted reason and are the cheapest real coverage left:
openbuild-runtime's empty-list-for-a-roleless-caller andeditor-sees-Save-but-not-Publish both skip saying they need fixture users that
do not exist — but
tests/e2e/global-setup.ts:325provisionsrbac-owner,rbac-editor,rbac-viewerandrbac-outsiderand mints a storage state foreach. Those reasons name a state of the world, and it has changed. They are
not written here because they need a rig to prove they can fail, and no
single-owner rig was available (host at 97% disk).
Filed / extended alongside this PR:
actually asserts, and the
collect_ref_status()method that finds the class.openbuild-runtimescenarios whose skip reason hasrotted;
globalSetupalready provisions the users they say do not exist.positive control, plus confirmation that the
--fullpath has no basehandling at all (the base-IS-HEAD rescue is also under
SCOPE_DIFF=1).per-reason report grouped on
exclude_reasonas stored would bucket 46.8% ofopenbuild's exclusions under the sentinel
"<inherited from requirement>".Not touched, deliberately: #177 and #173 are review-only and left for Ruben.
I added one factual comment to #177 recording that its remaining red E2E cell is
the shared
builder-hostfailure, not its own.