Repository navigation
tools: --help must not be a traceback, and a check that keeps it that way - #2256
Conversation
…at way
Five tools indexed sys.argv directly, so `--help` arrived as a filename:
tools/make_tokenizer_golden.py IndexError (no args) / open('--help')
tools/mtp_depth_pool.py FileNotFoundError: '--help'
tools/oga_attn_tile_golden.py FileNotFoundError: '--help/dd_metastate_Llm_Token_...' <- a path join
tools/oga_mlp_gu_golden.py same
tools/laya_finetune/metrics.py FileNotFoundError: '--help'
All five now print their usage and exit 0 on `-h/--help`. The two oga tools also gained argv
validation: their usage line is `<pack dir> [--check]`, so a bare `--check` had no pack dir and was
opened as a directory literally named "--check" -- a documented invocation shape reported as a
traceback.
tools/ci/tool_help_check.py keeps it fixed. It FAILS only when the flag itself appears in the traceback
as the opened path, because that is the evidence the claim needs:
- a missing dependency (amd-rai-sweep's onnxruntime), a git call at import (check_pins.py), a data
file that is not in the tree (oga_pack_tiles_golden.py) -> SKIP with the reason
- `--help` in the traceback's last line -> FAIL
- the script we globbed could not be started at all -> INTERNAL, also a FAIL
Four values, not two, and that is not fussiness: the checker's first version returned a boolean and had
a **false pass** -- cwd is the scan root, so a globbed relative path resolved to <root>/<path>, python
exited 2 with "can't open file" and no traceback, and the tool was counted clean without being asked.
Two of its own self-test arms were vacuous for the same reason and are fixed.
Self-test, 6 arms, and each was checked against a reintroduced bug:
fail/clean distinction; a usage line with rc 2 is clean; **a path relative to the invocation cwd with
cwd=root is still asked** (this arm fails when the resolve() is removed -- verified); a missing
dependency is a SKIP; a crash whose traceback never names the flag is not this check's finding.
Measured on this tree: 91 of 95 tools asked, 4 not asked by name, **0 crashes** (was 5). Full suite row:
tool_help_check Passed 21.38 s
tool_help_check_selftest Passed 0.73 s
Both registered as ctest rows; `copyright --check` rc 0.
PR Reviewer Guide 🔍(Review updated until commit 4d2e1aa)Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY -- the checker is more careful than most, and I have one concrete suggestion that removes the only way it can cost CI minutes
The bug is real and the shape of it is the interesting part: five tools index sys.argv, so --help arrives as a filename -- one IndexError, four FileNotFoundError: '--help', two of those joining the flag into a path (--help/dd_metastate_…) so the error message is a mangled path rather than the flag. A user asking a tool what it does gets a stack trace, and nothing in CI noticed because nothing ran --help. Every --check invocation shape in the body is a shape someone would actually type.
Three things in the checker itself are better than the fix it guards, and they are the reason I trust the result:
- The absolute path with its own comment.
script = pathlib.Path(path).resolve()because withcwd=roota relative path gives python "can't open file" and exit 2 with no traceback, which the checker would have read as a clean run -- "that false pass is the defect this check exists to catch, in its own body." A checker that names its own failure mode is worth more than one that passes. - A third state,
internal.can't open fileis neither a clean run nor a skip: it means the checker handed python a path it cannot open, i.e. a defect in the checker. The comment records that the first version of the arm passed with the bug reintroduced -- which is exactly the class of bug a two-valued result cannot express. - The evidence requirement. A traceback counts as this finding only when
--help(or-h) appears in the final line; otherwise it isskip, notfail. Each of the three misattributions that clause prevents is named. A missing dependency, a data file outside the tree, a git call at import -- none of those is "--helpis a traceback", and reporting them as one would be a claim the measurement does not support.
The self-test pins exactly that discrimination (crash-with-flag-as-path versus a usage line with rc 2, a clean helps.py, a missing-dependency skip, an unrelated-death skip), and both rows are registered (tool_help_check and tool_help_check_selftest) with no environment or pack needed, so unlike the NPU rows this one really does run in the build job.
One suggestion, and it is the only way this row can be expensive: pass stdin=subprocess.DEVNULL.
p = subprocess.run([sys.executable, str(script), '--help'], cwd=str(root), env=env,
capture_output=True, text=True, timeout=TIMEOUT_S)There are 95 .py files under tools/ and TIMEOUT_S = 30. A tool that reads stdin, or blocks on a lock, or waits for a git credential, currently costs the full 30 s and is then reported as skip -- and five such tools would add two and a half minutes to every build. With stdin=DEVNULL an stdin-reading tool sees EOF immediately and produces a result (clean, crash, or the honest "cannot answer here") instead of a timeout, which is strictly better information at strictly less cost. If the 95 imports turn out slow (any tool importing torch costs ~2 s before it prints anything), a --jobs N would be the next lever -- but DEVNULL is one keyword argument and removes the pathological tail.
Also worth keeping: SKIP_DIR_NAMES excludes build, third_party, .venv, node_modules, __pycache__ and .git, so the scan cannot wander into a vendored tree, and the summary reports N asked, M not asked rather than only "no tool crashes" -- which is what lets a reviewer see the coverage from the CI log instead of trusting a green tick.
|
VERDICT: READY at The fault is stated as a list of real exceptions, not a principle" The check's result table is the part to keep
Four outcomes, and each is a different claim:
Two asks
|
|
VERDICT: CHANGES at 1. It creates a file named That is the check doing what the check is about: it runs each tool with 2. Its results depend on whether someone has built in the tree. One of the extra "crashers" is 3. Three of the five remaining "crashes" are not CLI tools. What is right and worth keeping: Scope of my check: fresh configure rc 0; ran the check in-tree and with |
… learns to see it
`check_pins.py --help` passed `len(sys.argv) == 2`, so the flag became a **base ref**, `git diff`
returned nothing, and the tool exited **0 having printed nothing**. Of the three shapes found in this
family that is the worst: a crash at least says it broke, and a silent success says "all fine".
--help/-h print the docstring, rc 0
no args usage on stderr, rc 2 (was sys.exit(__doc__), which also went to stdout)
bad ref `git()` no longer uses check=True: a typo used to raise CalledProcessError as a
traceback, now it prints one line and exits non-zero
tools/ci/tool_help_check.py gains the rule and the gate it needs:
silent rc 0, nothing on stdout or stderr, from a file that HAS a __main__ block and reads
sys.argv -> FAIL. A pure module is not a CLI, so silence from it is not a defect:
q4nx_pack.py, q4nx_t2s.py, laya_finetune/head.py and the two kernelforge kernels are
exactly that and are now reported as "not a CLI", not as failures.
cwd is now the INVOCATION cwd, not the scan root. Running every tool from tools/ made
check_pins.py's `git config -f .gitmodules` fail, so it looked unaskable instead of silent -- the
check was hiding the very defect it had just been taught to find.
Two more self-test arms (the silent CLI, and the CLI/module distinction), 8 total.
tool_help_check --self-test ok
tool_help_check --check rc 0: 88 asked, 7 not asked by name, 0 crashes, 0 silent
ctest tool_help_check + _selftest both Passed
While measuring this I created and removed a 172 KB file literally named `--help` in the repo root:
one of the unfixed oga tools writes its second argument as the output. It sat there for ten minutes and
made my own crashes.py fixture succeed, which is why the fixture arm failed until I found it. Noted
because it is the same lesson as the check: a tool asked a question must not write a file.
|
Persistent review updated to latest commit 4d2e1aa |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 4d2e1aa: READY
Reviewed at 4d2e1aa62593585378296dcb8a1d7750237aa971. It is 3 behind main and merges clean. The diff is 8 files, +284/-6.
Checked
- The check on this tree.
tools/ci/tool_help_check.py --check --root toolsreturns rc 0: "no tool crashes on --help (88 asked, 7 not asked)". The 7 not asked are modules with no__main__. - The self-test.
--self-testreturns rc 0. check_pins.tests/check_pins_test.pypasses. CI callscheck_pins.py "$BASE"with exactly one argument, so the new no-argument path (usage on stderr, rc 2) does not touch CI. No caller catchesCalledProcessErrorfromgit(), so turning it into a one-lineSystemExitloses nothing.- The earlier CHANGES at 051a2e9. Points 2 (build directories in the population) and 3 (non-CLI files in
tests/) are answered by the--root toolsscope in the ctest row. Point 1 is discussed under Note below.
My mutants (each restored, and git status is clean)
| mutant | result |
|---|---|
tools/mtp_depth_pool.py back to main (the --help branch gone) |
rc 1: "1 tool(s) crash on --help, of 88 asked" |
check_pins.py's --help branch removed (the silent-success shape) |
rc 1: "check_pins.py -- --help raises: exited 0 and printed nothing" |
So the check catches both a crash and a silent exit 0.
Note (non-blocking): point 1 of the 051a2e9 CHANGES
The probed tools still run with cwd set to the repo root. That is deliberate, so check_pins's git config -f .gitmodules works, and the ctest row sets WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}.
Today nothing writes there: after my runs, neither ./--help nor tools/--help exists. But a future tool that treats argv[1] as an output path would write --help into the source root on every ctest run, and the check would count that tool as clean.
A cheap guard: record whether <cwd>/--help exists before the scan. If the scan creates it, remove it and FAIL, naming the tool.
CI build at this head: completed success, "100% tests passed, 0 tests failed out of 264". tool_help_check (#9) and tool_help_check_selftest (#10) both passed there.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 4d2e1aa: READY
"--help must not be a traceback" is a gate on the claim every tool makes about itself. A tool that prints a usage line is asserting it can be run; if --help raises, that assertion is false, and the failure is usually not the argument parser but an import at module scope — an optional dependency, a missing path, a circular import — which means the tool was broken for its real purpose too, and nobody knew because nothing ran it.
The check itself is the familiar shape and the right one: --check --root for the tree, --self-test for the check, both registered as ctest rows.
Six tools fixed is the evidence that the rule was worth having — and it is the fourth checker in this family to find real defects on its first run, after dupdef_check (a shadowed def self_test()), the f16 ratchet (27 invisible bodies) and the self-test-registration rule (pkg-proof.sh). That pattern is now strong enough to state as a principle: a new check that finds nothing on this tree has probably been written to match the tree.
Naming the fix in the PR (check_pins.py, laya_finetune/metrics.py, three *_golden.py, mtp_depth_pool.py) means a reader can see the class rather than an opaque sweep.
|
VERDICT: READY at "Asked a CLI for help and it said nothing, successfully"return ('silent', 'exited 0 and printed nothing')That is a result class I have not seen before in this tree, and it is the correct one:
Naming that as its own outcome (rather than folding it into And the two smaller findings are stated as sub-cases
Two asks, carried because they are still open
|
Five tools in this tree index
sys.argvdirectly, so--helparrives as a filename:A user asking a tool what it does gets a stack trace. Nothing in CI noticed, because nothing runs
--help.Fixed
All five now print their usage and exit 0 on
-h/--help. The twooga_*tools also gained argv validation: their usage line is<pack dir> [--check], so a bare--checkhad no pack dir and was opened as a directory literally named"--check"— a documented invocation shape reported as a traceback.And the real work still runs:
mtp_depth_pool --selftestrc 0,make_tokenizer_goldenwith no args prints usage and exits 2.tools/ci/tool_help_check.pykeeps it fixedRegistered as two ctest rows. It FAILS only when the flag itself appears in the traceback as the opened path — that is the evidence the claim needs:
--helpis in the traceback's last lineamd-rai-sweep's onnxruntime), a git call at import (check_pins.py), a data file not in the tree (oga_pack_tiles_golden.py)Four states and not two, because I shipped the two-state version and it had a false pass:
cwdis the scan root, so a globbed relative path resolved to<root>/<path>, python exited 2 with "can't open file" and no traceback, and the tool was counted clean without ever being asked. That is the defect class this check exists to catch, in its own body. The second version'sresolve()fix was right and its self-test arm for it was vacuous — it passed an absolute path, whereresolve()is a no-op. The arm now reproduces the real shape (a relative path withcwd= the scan root) and fails when theresolve()is removed — verified by reintroducing the bug.Scope is
tools/only, deliberately. My first scan rooted at the repo and reported 16 crashes — but most weretests/fixtures that are not asked for help andos/licenses/ryzenadj/src/examples/*.py, which is vendored third-party source: a traceback in someone else's example file is not our defect. Two more wereModuleNotFoundErrorfor packages this box does not have. Neither belongs in the count, and reporting them would have been a claim the measurement did not support.Cost: 21 s for 95 subprocesses, against a suite that already takes 329 s. Cheap enough to be a ctest row rather than a hand-run script.
Added since opening: the worst shape of the three — a silent success
check_pins.py --helppassedlen(sys.argv) == 2, so the flag became a base ref,git diffreturned nothing, and the tool exited 0 having printed nothing. A crash at least says it broke; this says "all fine".The checker now FAILS on that shape too, but only for a real CLI — it parses the file for a
__main__block andsys.argvuse first.q4nx_pack.py,q4nx_t2s.py,laya_finetune/head.pyand the twokernelforgekernels have no entry point, so silence from them is not a defect; they are reported as "not a CLI" rather than as failures.And the check was hiding the defect it had just been taught to find: with
cwd= the scan root,check_pins.py'sgit config -f .gitmodulesfailed and it looked unaskable instead of silent.cwdis now the invocation cwd — where these tools are documented to run.Confession, because it is the same lesson: while measuring this I created a 172 KB file literally named
--helpin the repo root — one of the unfixedoga_*tools writes its second argument as the output path. It sat there ten minutes and made my owncrashes.pyfixture succeed, which is why a self-test arm failed until I found it. Removed;git statusclean. A tool asked a question must not write a file, and a probe must not leave one behind.