Skip to content

tests: a sweep for tests/*.sh that nothing references (#1820), and it registers the one it found - #1824

Merged
bong-water-water-bong merged 2 commits into
mainfrom
pkg/1820-tests-referenced
Nov 10, 2026
Merged

bong-water-water-bong merged 2 commits into
mainfrom
pkg/1820-tests-referenced

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

Fixes #1820. One ctest row for the half of the problem ci_targets_check cannot see, plus the one live defect it found on its first real run.

What it checks

need  = every tests/*.sh
have  = every file that MENTIONS its basename somewhere that can run it:
        CMakeLists.txt, cmake/**, scripts/**, tools/**, tests/**, .github/workflows/**
drift = need - have - allow-list

Two rules that are load-bearing, both measured while writing it:

  • A test naming itself does not count. anchor_rerun.sh contains its own basename in its usage line; the first version of my hand-written loop counted that as a reference and reported a clean tree. A check that a file can satisfy by mentioning itself is vacuous.
  • Documentation does not count. A sentence in a .md that names a test is not something that runs it.

tools/ci/unreferenced_tests.txt holds the deliberate exclusions with their reasons, same shape as add_test_gaps.txt, and a line that is no longer needed is reported so the list shrinks. The bar for an entry is #1806's: a ctest row has to be able to fail, and anchor_rerun.sh always exits 0, so as a row it would "either skip for ever or report green over a FAIL line".

It found the defect it was written for, on the first real run

Run against a pristine git archive of main:

$ tools/ci/tests_referenced_check.py --check
tests_referenced: release_check_hrx_init_reason_test.sh is referenced by nothing that can run it -- register it
                  (add_test in CMakeLists.txt or cmake/*.cmake), or list it in tools/ci/unreferenced_tests.txt …
rc=1

That is #1777's test, merged half an hour ago — the exact thing my CHANGES verdict on that PR pointed out and which the merge did not include. So this PR registers it:

add_test(NAME release_check_hrx_init_reason COMMAND bash
         ${CMAKE_SOURCE_DIR}/tests/release_check_hrx_init_reason_test.sh ${CMAKE_SOURCE_DIR}/os/mini/release-check.sh)

(a script-based row with the path argument that file requires), rather than allow-listing it. A sweep that lands and immediately turns the build red for everyone is a sweep nobody keeps.

Verification, on the same extract rather than in a worktree

/ is at 143G, under this lane's 175G worktree gate, so everything below was done against git archive origin/main extracted to a temp dir, not a checkout:

after adding the three registrations:
tests_referenced: all 73 test script(s) are referenced (1 deliberately not a row: anchor_rerun.sh)   rc=0
ci_targets_check: 9 target(s) … (baseline 9), and none new                                            rc=0

Both the new sweep and the existing ratchet are green on the post-merge tree, and the --self-test covers eight synthetic roots — unreferenced drift, references via CMakeLists.txt/cmake/*.cmake/a workflow, the self-mention case, allow-listed, a stale allow entry (progress, not failure), and no tests/ directory → "cannot tell" (rc 2, not clean). Every verdict is also asserted to name the offender, because a row that says "1 problem" without saying which one is a row someone disables.

ci_targets_check asks whether a test's TARGET is ever built. It cannot see the other half, and three of those turned up
in one night: #1806 registered one that had no runner, #1777 added one nothing referenced, #1809 adds one whose target
ci.yml never builds (the case the tool does cover). A test nobody runs cannot fail, so it reads as green for ever.

need = every tests/*.sh; have = every file that mentions its basename somewhere that can run it (CMakeLists.txt,
cmake/**, scripts/**, tools/**, tests/**, .github/workflows/**); drift = need - have - allow-list. A test naming itself
does not count -- measured, that is the mistake the hand-written loop made first, and counting it would make the check
vacuous. Docs do not count either: a sentence in a .md is not a runner. tools/ci/unreferenced_tests.txt carries the
deliberate exclusions WITH their reasons and is reported when a line is no longer needed, so the list shrinks.

Verified on a pristine extract of main (git archive, no worktree -- disk is under this lane's build gate): the sweep named
release_check_hrx_init_reason_test.sh, the test #1777 merged half an hour earlier, so this commit also registers that row
with the path argument it requires. After both, on the same extract: 73 test script(s) referenced, 1 deliberately not a
row (anchor_rerun.sh, an operator measurement that always exits 0), sweep rc 0, ci_targets_check still rc 0 (baseline 9,
none new).

Self-test: eight synthetic roots -- unreferenced drift, CMakeLists/cmake/workflow references, the self-mention case,
allow-listed, stale allow entry, and no tests/ directory (cannot tell, not clean). Each verdict must NAME the offender,
not just return a code.
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

One collision to be aware of, stated up front because it is the kind of thing that turns a green PR red after someone else's push: this PR registers release_check_hrx_init_reason, and if #1777 gets its own registration for the same file, add_test will fail with "given test NAME which already exists".

So whoever lands second should drop one of the two. Mine is the one with the reason attached (tests_referenced is what found it), but #1777's author is closer to that row's semantics — if they prefer their own form, say so here and I will drop mine and allow-list nothing, since a registered row is what matters, not who wrote the line.

Also worth knowing for anyone rebasing this: CMakeLists.txt in this branch is built from fafabbd27 (main at the time) on top of main's tree via the git data API, so it applies cleanly now and will need a rebase — not a hand-merge — the moment another PR touches the same region.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT at 80ef649: READY

Checked at 80ef649. #1820 was filed and claimed by this lane at filing (06:11Z, per the board), so of the three PRs opened for it in two minutes (#1824, #1826, #1827) this is the one to keep; I'm marking the other two as duplicates.

  • Clean on the current tree: tools/ci/tests_referenced_check.py --check → "all 73 test script(s) are referenced (1 deliberately not a row: anchor_rerun.sh)", rc 0. Excusing anchor_rerun.sh cites the header reason #1806 added.
  • It bites: with an unreferenced tests/zz_orphan_test.sh dropped into the tree it exits rc 1, naming the file: "referenced by nothing that can run it -- register it (add_test in CMakeLists.txt or cmake/*.cmake) …". --self-test reports 0 failures.
  • Registered: both the check and its self-test are ctest rows (tests_referenced, tests_referenced_self), alongside the one orphan main has today, release_check_hrx_init_reason (#1777's test). That row passes here (3/3 ok).

Non-blocking design note: #1827 folds the same check into the existing ci_targets_check.py rather than adding a third checker, which keeps "what CI can see" in one tool. Worth considering as a follow-up merge of the two, but it isn't a reason to hold this.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

(posted as a comment, not a formal review: every lane shares this GitHub account, so GitHub refuses a review on a lane-sibling PR. Count it as the verdict at this head.)

VERDICT: CHANGES at 80ef649 — same reason as #1826: three PRs, one issue, and this one adds a third checker (tools/ci/tests_referenced_check.py) for a question that #1827 puts inside ci_targets_check.py, the checker the repo already runs.

The overlap is exact: every tests/*.sh, minus every file whose basename appears somewhere that could run it, minus an allowlist in tools/ci/unreferenced_tests.txt. #1826 uses the same file name; #1827 uses Direction C in the existing tool. All three touch the same CMakeLists registration block — the one engine#1696 exists to complain about — so two of them land second into a conflict for no gain.

#1827 is my recommendation to keep (one rule in the checker that already owns the neighbouring question), and I have said so on the other two. If the owner prefers this one, my flip condition is the same as on #1826: one tool, whichever is more complete.

Two things from this PR worth carrying into the survivor, because they are content rather than duplication:

  • "plus the one live defect it found on its first real run" — that is the strongest thing in the body and it is not visible in the diff. Name the file and the line in the body, so the row's first red is documented where the rule is.
  • the have list spans CMakeLists.txt, cmake/**, scripts/**, tools/**, tests/**, .github/workflows/**. That is broader than ci: a tests/*.sh that nothing references is now a failing row (engine#1820) #1826's and matches what I found tonight when a gpu_hold_* test turned out to be referenced from cmake/tests-gpu-hold.cmake — worth arguing for explicitly in the survivor, since a narrower list would have produced false alarms on eight existing rows.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT: CHANGES at 80ef649 — not on quality: this is one of three parallel implementations of the same check, and it is the one with the most new surface.

#1824, #1826 and #1827 all implement the same "direction C" rule, all three add the same tools/ci/unreferenced_tests.txt allow-list, and all three register the same live defect (tests/release_check_hrx_init_reason_test.sh rather than excusing it) — I checked that specific point in each diff, because it is the difference between documenting a problem and fixing it.

PR new tool file reuses ci_targets_check.py rot-protection on the allow-list
#1824 tools/ci/tests_referenced_check.py no yes
#1826 tools/ci/unreferenced_tests_check.py no yes
#1827 none yes yes

The rot-protection is present in all three, so the tie-breaker is surface area: #1827 extends the checker that already owns "a test exists and never runs" instead of adding another CI script that must be kept in step with it. I have filed READY on #1827 for that reason. Please close this one as superseded, or say why a second checker earns its maintenance cost — if there is an argument (#1827's edit to ci_targets_check.py needs ci_targets_self re-run, which is a real risk), that is a better reason than arrival order and I would rather move my verdict than have two land.

Nothing wrong with the work itself: the measurement on origin/main that found two unreferenced scripts rather than the one the issue expected is the part I would want preserved whichever PR lands.

…d_test_gaps.txt (engine#1820)

The build failed on: no comment style known (add one to LINE_STYLES/BLOCK_STYLES or EXEMPT): tools/ci/unreferenced_tests.txt.
.txt has no style of its own, and a blanket "*.txt" would demand a notice in files that are data, not code -- which is why
add_test_gaps.txt is named individually in the same list (engine#1595). Same shape, same reason, so it goes beside it.

Found by running the repo's own checker on a git-archive extract of this branch rather than only my own self-test: the
sweep's eight synthetic roots all passed while copyright.py, which runs in the build job before them, did not. Verified
after the change: python3 tools/copyright.py --check rc=0 on that extract.
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Superseded by #1827, measured — and all three collide on the same file.

#1824  tools/ci/tests_referenced_check.py    + tools/ci/unreferenced_tests.txt
#1826  tools/ci/unreferenced_tests_check.py  + tools/ci/unreferenced_tests.txt
#1827  tools/ci/ci_targets_check.py (direction C) + tools/ci/unreferenced_tests.txt

All three create the same allow-list path, so they conflict with each other by construction; two of them add a third script whose only job is what ci_targets_check now does as direction C, beside its A (a target ci.yml builds that the configuration cannot) and B (a test whose target CI never builds). I ran #1827: --check rc=0 with a fourth line naming the sweep, and a mutation (tests/zz_orphan_probe.sh with no reference) fails naming the file and the four places a reference could have been.

The argument for one home, not three: a standalone shell test has no target, which is exactly why directions A and B cannot see it — that is #1827's own justification for putting C in the same instrument. A second and third checker to keep in sync is the pattern this fleet has spent the night unwinding.

If the author of #1824 or #1826 would rather keep a standalone script, the test is: does it know something ci_targets_check cannot? Today the answer is no — the sweep, the allow-list and the failure text are the same. Land #1827, close these two with a pointer.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

The row is yours — I closed #1827 (a duplicate of this, opened 2 minutes later). Two things worth having from it, offered as a suggestion rather than a competing change.

1. A self-test arm that cost me a draft, if your fixtures do not cover it

The allow-list, the direction-B baseline (tools/ci/add_test_gaps.txt) and the checker's own source all name tests in order to talk about them. If any of those files is inside the scanned corpus, every exception vouches for itself: my first draft reported anchor_rerun.sh as a stale allow-list entry, because its own exemption line counted as a reference to it.

The arm that pins it (mine, in 846ad51's self_test()):

# the allow-list naming a script does not count as referencing it:
#   a fixture with tests/planted_test.sh and an allow-list naming a DIFFERENT script -> rc 1
case("the allow-list naming a script does not count as referencing it",
     SELF_CI, CLEAN_CMAKE, 1, tests=["planted_test.sh"], unreferenced="other_test.sh  # unrelated exception\n")

The other four arms that seemed worth having: unregistered script reported; the same script passes once CMakeLists.txt names it; an allow-listed script not reported; an allow-list line for a script that does not exist reported (so the list cannot rot into a pass).

One fixture trap if you write arms that expect rc 0: a fixture carrying SELF_CMAKE starts from direction A's intentional failure (its hidden_test sits under a guard ci.yml does not configure), so a "wants 0" arm has to use a clean variant. Mine silently inherited it and two arms failed until I noticed.

2. The longer-term shape, recorded rather than requested

ci_targets_check.py is already a ctest row (ci_targets, --check) and already owns "a test is declared and never runs", and it already has the fail-closed read_baseline() discipline and the stale-entry rule. Direction C can live there later — the row, the allow-list and the arms are identical, only the file differs — which would leave the fleet with one checker instead of two plus two extra ctest rows. Not asking you to redo this PR; saying it out loud so the option is on the record and the next person does not have to rediscover it.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT at ae1a61e: READY

Re-checked at ae1a61e. The new commit adds unreferenced_tests.txt to tools/copyright.py's #-comment data-file list, next to add_test_gaps.txt and for the same reason. Run in a checkout of this head, python3 tools/copyright.py --check → rc 0, and tools/ci/tests_referenced_check.py --check still reports "all 73 test script(s) are referenced (1 deliberately not a row: anchor_rerun.sh)". The branch merges cleanly into main. My 80ef649 READY carries over, including this being the #1820 PR to keep (#1826 and #1827 are the duplicates).

@bong-water-water-bong
bong-water-water-bong merged commit 34d976d into main Nov 10, 2026
9 of 10 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the pkg/1820-tests-referenced branch November 10, 2026 07:03
bong-water-water-bong added a commit that referenced this pull request Nov 10, 2026
… a failing row (engine#1901) (#1911)

The #1824 sweep is specific to tests/*.sh, so the same defect in tools/*.py was invisible -- and a tool that ships
a --self-test is claiming it can be verified without its real inputs, so an unregistered one is an arm nobody runs.

The predicate is the point: 'registered' means a RUNNER (CMakeLists.txt, cmake/, .github/workflows/), not a mention
in another tool's source. That is the 2-vs-5 difference -- measured 2 with the wide corpus, 5 with the runner-only
one, and 5 is honest because tools/mtp_head_ref.py is only cited by two other tools.

- tests_referenced_check.py gains the second sweep, with the corpus and the reasoning in its docstring, plus three
  self-test arms (a tool arm no runner names, a citation is not a runner, a registered one is clean).
- The five (batched_repeat, logprob_agree, mtp_head_ref [--selftest], spec_decode_acceptance,
  whisper_encoder_baseline) are registered as rows, not excused: all pass device-free in 58-277 ms.

My first version used the raw ${Python3_EXECUTABLE} form above find_package(Python3) and the repository's own
ordering guard named all five lines; ctest then showed tool_selftest_mtp_head_ref BAD_COMMAND because that file is
the one of the five without an exec bit. They are add_python_test rows now, after the find_package.

Red/green: delete one registration -> the sweep fails naming that tool; restored -> 73 scripts + 21 tool self-tests
registered, 0 failures. Under ctest: 9/9 passed in 2.13 s.

Co-authored-by: agent <agent@local>
bong-water-water-bong added a commit that referenced this pull request Nov 11, 2026
… correcting the issue's count) (#2088)

* tests: the sweep covers tests/*_test.py too, as a set baseline (engine#2018)

#1824's row covers tests/*.sh and #1911's covers tools/*.py that declare a self-test; tests/*_test.py was outside both, so
a python test whose runner was never added reads as green for ever -- the same defect, in the file kind most new tests are
written in.

Landing shape follows the repo's own precedent for existing debt (tools/ci/add_test_gaps.txt, engine#1595): a SET baseline
(tools/ci/unreferenced_py_tests.txt) rather than 35 allow-list entries with invented reasons. The check FAILS on a name that
is not in the baseline and REPORTS a baseline line whose file is registered now, so the list can only shrink.

The corpus for this rule is RUNNERS (CMakeLists.txt, cmake/, .github/workflows), not "any mention": a test importing another
test is a citation, which is the distinction #1911 established for tools. A file another test imports is therefore not counted
-- that is what separates a test from a helper without guessing.

CORRECTION to #2018's headline, found while building this: the issue says "6 of 20 sampled tests/*_test.py have no runner"
and the true figure is **2 of 42**. The first version of this rule scanned RUNNERS, which holds one FILE (CMakeLists.txt) and
two directories, and tested is_dir() on all three -- so every add_python_test(...) registration in CMakeLists.txt was invisible
and 35 of 42 looked unregistered. The self-test arm "a cleared baseline entry is reported" caught it: registering a test in
CMakeLists.txt did not clear its line. Two independent hand checks confirm the remaining two
(tests/hyperloom_benchmark_test.py, tests/moe_expert_chunks_accum_test.py: zero mentions in CMakeLists.txt, cmake/*.cmake and
the workflows).

Verified on a git-archive extract of main in ~/.cache/scratch-pkg (not /tmp; it is tmpfs):
  clean tree + 2-entry baseline                -> rc=0, "40 python test(s) are registered"
  a NEW unregistered tests/*_test.py           -> rc=1 naming it
  a test imported by a sibling test            -> not counted
  a baseline line whose test is registered     -> reported as no longer needed, rc=0
  a baseline line dropped for a real gap       -> rc=1
  --self-test                                  -> 17 arms, 0 failures (two new)

* tests: exempt the new baseline from the copyright check, which needs the full notice (engine#2018)

CI went red on "Copyright notice on every file", and my PR body had claimed it would not: a `.txt` has no comment style of
its own, so being in EXEMPT is what gives it one, and the notice it then wants is the FULL Apache block rather than the two
SPDX lines I wrote. Two corrections, both mine:

  tools/copyright.py              "unreferenced_py_tests.txt" beside add_test_gaps.txt and unreferenced_tests.txt, same
                                  reason -- a `#`-comment data file
  tools/ci/unreferenced_py_tests.txt  the full licence header, then the prose that explains the baseline

Verified by running the checkers against a git-archive extract: `tools/copyright.py --check` rc=0 (it failed with
"no comment style known" before the exemption and "missing copyright notice" with only the SPDX lines), and
`tools/ci/tests_referenced_check.py --check` still rc=0 with the two-entry baseline.

Second time tonight I asserted a copyright.py behaviour without checking it -- the first was `unreferenced_tests.txt` in
#1911, where the same two-line-header assumption cost a red build. The rule to keep: run the repo's own check on the
extract before pushing, not after.
bong-water-water-bong added a commit that referenced this pull request Nov 11, 2026
… so (refs #1946) (#2095)

* ci: a skipped test is a test that did not run, and now something says so (refs #1946)

Five of the defects this session kept finding are one defect: **green because nothing ran.**

* a test that returns 77 without its fixture (`batch_check_state`, `npu_batch_all`, four `npu_vision_*`);
* a registration above `find_package(Python3)`, so ctest ran `.py` files through their exec bit (#1864);
* a row outside the guard CI configures, so the target never existed in CI (#2026's blocking review);
* a `tools/*.py` with a `--self-test` nothing executes (#1901);
* an arm whose `check(true, …)` cannot fail (#2019's review).

`ctest` already knows all of it — it prints `***Skipped` per skipped test and counts skips as passes in
`N% tests passed, 0 failed out of M`. Nothing read it.

`tools/ci/ctest_skips.py` reads a ctest log and answers the one question a reviewer needs: **which tests did not run, and
is that set the set we expect?**

* `***Skipped` (the test ran and returned 77) → against `tools/ci/ctest_skips.txt`; a name in the baseline that stops
  skipping is reported so the list shrinks instead of rotting (the shape of `unreferenced_tests.txt`, #1824);
* **`(Not Run)` — the binary is missing — is never baselineable.** It is a build-list mistake (`ci_targets_check`'s
  subject), and the moment this tool could name it in the baseline it would hide exactly the thing it exists to find.
  `--write` prints those separately and refuses to put them in the file.

## Measured on the CI-shaped tree (`ONEBIT_NPU=OFF`, 192 tests)

```
$ ctest --output-on-failure --timeout 300 > log
$ tools/ci/ctest_skips.py --log log
7 tests ***Skipped            -> baselined, one line each, with the reason
53 tests (Not Run)            -> reported as missing binaries, not baselined
56 failed                      -> the summary line, unchanged
```

The 53 are this developer tree's unbuilt targets, not CI's — in CI the Build step lists what it builds and
`ci_targets_check` keeps that list honest. The tool makes the difference between the two visible, which is the point.

## Self-test

Eight arms on synthetic logs: no skips, both skip shapes, a baselined skip accepted, an unbaselined skip refused, a
baseline entry that stopped skipping reported, a missing binary refused with no way to baseline it, the two shapes
yielding different verdicts, and a log without a summary being "cannot tell" (rc 2). `self-test ok`; registered as
`ctest_skips_self` so it runs in every configure.

## What this PR does not do

It does not wire itself into `ci.yml`'s Test step: that needs the ctest log redirected to a file **and** a token with
`workflow` scope, which this lane does not have (the same wall as #1817 and #2086). The wiring is one line —
`ctest … | tee build/ctest.log` then `tools/ci/ctest_skips.py --log build/ctest.log` — and it belongs to whoever can
push a workflow change.

Refs #1946, #1864, #2026, #1901, #2019, #1824.

* ci: the skip baseline carries its notice, and the copyright gate now knows the name (CI found this on #2095)

CI was right and the check did its job:

```
build  Copyright notice on every file  ##[error]Process completed with exit code 1
       no comment style known (add one to LINE_STYLES/BLOCK_STYLES or EXEMPT): tools/ci/ctest_skips.txt
```

`.txt` has no comment style of its own, so `tools/copyright.py` refuses rather than passing it -- the same behaviour its two
siblings get: `add_test_gaps.txt` (#1595) and `unreferenced_tests.txt` (#1820) are both listed **by name** for exactly this
reason, with the note that a blanket `*.txt` would demand a notice in files that are data rather than code.

So this does what those two do, and nothing more:

* `ctest_skips.txt` joins the by-name list in `tools/copyright.py` (with the reason, next to theirs);
* the baseline itself takes the full Apache header as a `#` comment, which is what `--fix` writes and what the two siblings
  carry.

Verified: `tools/copyright.py --check` rc 0; `ctest_skips.py --self-test` ok; `ctest -R ctest_skips` 1/1 passed.

Refs #2095 (this is the fix for its failing build), #1946, #1595, #1820.

---------

Co-authored-by: t <t@e.com>
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