head, tail: keep a + in an invalid count's error, as GNU does - #14300
Closed
arbelonson-source wants to merge 1 commit into
Closed
head, tail: keep a + in an invalid count's error, as GNU does#14300arbelonson-source wants to merge 1 commit into
+ in an invalid count's error, as GNU does#14300arbelonson-source wants to merge 1 commit into
Conversation
`head`/`tail -c`/`-n` share a parser that always dropped the sign from
the value an error names, matching GNU only for `-`:
$ tail -c -5Ki f # GNU and ours agree
tail: invalid number of bytes: '5Ki'
$ tail -c +5Ki f # ours, before
tail: invalid number of bytes: '5Ki'
$ tail -c +5Ki f # GNU
tail: invalid number of bytes: '+5Ki'
GNU keeps a `+` but drops a `-`. The two sign parsers (`parse_signed_num_max`,
and its unused-but-public sibling `parse_signed_num`) now report the
original string when the sign was `+`, and the sign-stripped one otherwise,
matching what was already documented but not implemented for `parse_signed_num`
("returns an error with the raw string").
`tail`'s own `-n` branch had a second, independent bug: it named the
argument straight from `arg.quote()` rather than from the error the parser
already built, so it never picked up the sign handling above at all,
unlike its `-c` sibling next to it, which already went through the error.
Both now go through the error.
Found by a differential sweep across head/tail size suffixes on the most
recently merged multiplier-suffix commit -- this bug was pre-existing,
not introduced by it.
|
GNU testsuite comparison: |
Contributor
|
not sure it matters for the added complexity, closing |
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.
head/tail's-c/-nshare a parser (parse_signed_num_max) that always dropped the sign from the value an error names — matching GNU only for-, not for+:GNU's own rule, worked out by probing every sign/no-sign combination: a
-is dropped from the reported value, a+is kept.headshows this identically for both-cand-n.What changed
parse_signed_num_max(and its unused-but-public siblingparse_signed_num, which has the identical bug and the same doc comment already promising "the raw string") now report the original string when the sign is+, and the sign-stripped one otherwise.tail's own-nbranch inargs.rshad a second, independent bug: it built its message fromarg.quote()directly rather than from the error the parser already constructed, so it never went through the sign-handling above at all — unlike its-csibling two lines above it, which already used the error. Both now use the error, matchinghead, which was already consistent between its two branches.How I found it
A differential sweep across head/tail size suffixes (
K/M/Ki/B/etc.,+/-/unsigned,-c/-n) run against the multiplier-suffix behavior from the most recently merged commit — this bug predates that commit and isn't related to it, the sweep just happened to be checking that area.Getting this right took two passes
My first attempt at a fix used the original string unconditionally, which passed the case above but failed three existing tests (
test_head_invalid_num,test_invalid_count_keeps_its_leading_zeros,test_tail_obsolete_error_cases) — because GNU really does drop the sign for-, so those tests were already correct. Comparing GNU's exact output across-abc/+abc/-5xyz/+5xyz/etc. showed the sign-dependent split described above, which is what's implemented and what the new tests check for both signs together, so a future change can't silently swap back to unconditional.Not covered
Reproducing this exposed a separate, unrelated, pre-existing gap:
head -c "-³"reports'³'where GNU reports the octal-escaped bytes'\302\263'— the same non-ASCII quoting difference already known from other utilities. Unchanged by this PR either way.Testing
head/tail×-c/-n× unsigned/+/-× 23 suffixes (valid and invalid) plus a handful of the exact edge cases from existing tests: 350 match, the 1 remaining mismatch is the pre-existing octal-escaping gap above (confirmed identical onmain).test_head.rsandtest_tail.rscovering+/-on both-cand-n; thetest_tail.rsone verified to fail without theargs.rsfix.parse_signed_num.rsfor the sign-dependent behavior directly.cargo test --features head,tail --test tests -- test_head test_tail: 247 passed, 0 failed (245 pre-existing, 2 new);cargo test -p uucore --features parser-size: 32 passed, 0 failed (1 new).cargo fmt --checkandcargo clippy -p uu_head -p uu_tail -p uucore --all-targets -- -D warnings: clean.Disclosure
Prepared with AI assistance (Claude Opus 5, via Claude Code), per the AI policy in CONTRIBUTING.md. GNU's behaviour was established by running the installed GNU binary as a black box; I did not read GNU coreutils source. All testing was run locally.