fix(cli): exit 1 when check cannot read its input - #1099
Merged
Conversation
`files_dispatched` was bumped before the read was attempted, so the zero-files-matched guard in `run_check` passed on a run whose every file failed to read: `bca check` printed `error processing <path>: Permission denied` and then exited 0 — a CI gate reporting success on a tree it never analysed. The counter now moves only for files that were actually read, and read failures are tallied separately in `Config::read_failures`. Any unreadable input exits 1 (tool error, distinct from 2 = gate breach), because a partially analysed gate is not a passing gate. Like the pre-existing empty-input guard, the check runs before the gate is evaluated and before `--write-baseline`, and is not suppressed by `--no-fail`, which suppresses threshold failures rather than broken input. Both post-walk guards now live in `enforce_usable_input`, mirroring `enforce_explicit_unrecognized` on the analyze side. Fixes #1060
Both helpers took or returned two `Arc<AtomicUsize>` values a call site could transpose, inverting the distinction the tests pin.
`read_file_with_eol` decided "too small to parse" from a bare `stat` and returned `Ok(None)` before opening the file. `stat` succeeds on a file the process cannot read, so an unreadable tiny file was indistinguishable from an empty one and the permission error the function documents never surfaced: `bca check` on a 3-byte mode-000 file exited 0 with no diagnostic at all, not even the per-file `error processing` line the larger-file path emits. The shortcut now confirms readability by opening the file first. The open is skipped for anything that is not a regular file, so the function still never blocks on a FIFO, and behaviour is unchanged for readable files of any size. Fixes #1060
`bca init` scaffolds its baseline through `run_check`, so both post-walk guards fire for it too — and named a subcommand the user never invoked. Prefix the tool instead, and pin `init`'s behaviour: an unreadable file aborts rather than pinning a baseline that would silently under-record the debt in the file it could not read. Also corrects the `read_file_with_eol` rustdoc, which still promised `Ok(None)` for any too-small file after the readability probe landed. Refs #1060
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1099 +/- ##
==========================================
- Coverage 98.08% 98.08% -0.01%
==========================================
Files 272 272
Lines 67791 67795 +4
Branches 67361 67365 +4
==========================================
+ Hits 66493 66496 +3
- Misses 887 888 +1
Partials 411 411
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
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.
Fixes #1060.
The reported bug
validate_and_resolve_filebumpedfiles_dispatchedbeforeattempting the read, so the "no input files matched" guard in
run_checksaw a non-zero count on a run that read nothing:The counter now moves only for files that were actually read, and read
failures are tallied separately in
Config::read_failures. Anyunreadable input exits 1 (tool error), not 2 (gate breach) — a gate
that analysed less than its input has no verdict to report.
The half that wasn't reported
Reordering the counter alone would have left the bug live for files of
three bytes or fewer.
read_file_with_eoldecided "too small toparse" from a bare
fs::metadataand returnedOk(None)beforeFile::open— andstatsucceeds on a file the process cannot read, soa tiny unreadable file was counted as empty:
Note this produced no diagnostic at all — not even the
error processing …line from the original report — so it was strictlyworse than the reported symptom, and it contradicted the function's own
documented contract ("Returns any
std::io::Errorsurfaced byFile::open… lacks read permission"). The shortcut now confirmsreadability by opening the file first; the open is skipped for anything
that is not a regular file, so the function still never blocks on a
FIFO. Behaviour is unchanged for readable files of any size.
Scope notes
bca initshares the guard.scaffold_baselineproduces itsbaseline by driving
run_check, so an unreadable file abortsinitrather than pinning a baseline that silently under-records the debt in
the file it could not read. Both guards' messages are now prefixed
bca:instead ofbca check:, which misattributed aninitfailureto a subcommand the user never ran.
--no-fail, which suppresses thresholdfailures, not broken input — consistent with the pre-existing
empty-input guard. Both guards run before the gate is evaluated and
before
--write-baseline, so a partial run never records a baseline.regular file under
--paths(a stale docker-builttarget/, anode_modulesowned by another uid) now fails until the path isexcluded. That is the intended contract, and it matches what
commands/README.mdandman/bca.1already documented ("unreadableinput" → 1).
bca metricson an unreadablefile still exits 0. Tracked in fix(cli): read failures exit 0 for every subcommand except check #1098, where
bca diff --sinceis thesharper case (a dropped file reads as an added/removed function rather
than an I/O error).
Tests
Every test was verified by perturbing the production line and
confirming it was the only failure in its suite:
dispatch.rs— an unreadable file bumpsread_failuresand notfiles_dispatched; a readable one bumps the opposite; an emptyfile still counts as dispatched, so the reorder did not narrow the
documented semantics.
check_thresholds.rs— all-unreadable, partially-unreadable (twofiles, pinning the plural summary), tiny-unreadable, and
--no-fail-does-not-mask.init.rs—initrefuses to baseline a partially-readable tree.tools_tests.rs— a tiny readable file is stillOk(None); a tinyunreadable one returns
PermissionDenied.The unix-only tests each probe the real capability (
fs::readon amode-000 file) rather than the uid, so a privileged runner skips instead
of failing, and
#[cfg(unix)]sits on thefnrather than an innerblock (lesson #40).
make pre-commitis green..bca-baseline.tomlwas refreshed in thesame change for the one metric that moved
(
read_file_with_eol.nexits6 → 7).