fix: repair the 10 test files that were failing for unrelated reasons - #196
Merged
Merged
Conversation
main does not build with V 0.5.2, and `make testfmt` is red too, so every
CI job has been failing. This gets both gates back to green without changing
any intended behaviour.
Compile fixes, one per cause:
- `v.mathutil` is gone; `min`/`max` now live in `math`. Affects basenc, cut
and tail.
- `for ; cond; post {}` and `for i := n; i; i-- {}` are no longer accepted:
the condition has to be `bool`. Affects cksum and expand.
- `time.sleep` takes a `time.Duration`, not a number of seconds. sleep now
converts explicitly and saturates instead of overflowing, so `sleep inf`
keeps waiting rather than wrapping around to roughly zero.
- `const` cannot hold the result of a function call, so printenv's NUL
terminator became a function.
- `setup_cp_command`/`setup_mv_command`/`setup_command` (head) were declared
with `?`, but every failure path in them calls the @[noreturn]
`common.exit_with_error_message`, so they never return an error and their
callers had no `err` to look at. They now return plain tuples.
- `C.statvfs` is already declared privately by vlib/builtin/cfns.c.v, so a
local declaration resolved to that one and failed to compile. stat binds a
distinct name to the libc call with #define.
- `struct utmpx` was declared twice, in common/readutmp_nix.c.v and again in
src/users/users.c.v with a different, shorter field list, which V now
rejects as a redeclaration. Declared once, with the full layout.
stat additionally needed more than a compile fix:
- src/stat/stat_to_vlib.v (now src/stat/modes.v) imported v.scanner and
v.pref to decode --printf escapes. Building it pulled the whole V compiler
into the program: `v build` of src/stat never finished, and taking the
machine down with it. The escapes are now decoded directly.
- `<bits/statx.h>` is a glibc internal header, so it is absent on musl based
distributions (issue vlang#145). The kernel uapi `<linux/stat.h>` is used instead.
- `statx()` wrote the kernel's 224 byte struct through a `voidptr` into a
V struct that was 160 bytes, so every call corrupted memory past the end of
the allocation; stat segfaulted on every invocation. The C struct is now
declared and used for real. `struct statvfs` has the same problem and its
V mirror now matches the ABI including the trailing spare.
- `$embed_file` no longer resolves inside a function body, so fstypes.txt is
embedded at module scope.
wc needed a fix of its own:
- `read_chunk` returned `?FileChunk`, but its caller inspected `err`, which
does not exist for an option. os.File.read also reports end of file as an
`os.Eof` error, and reading a zero-length chunk indexed `buffer[-1]`, so
`wc` on an empty file would have panicked. It now returns `!FileChunk` and
distinguishes exhaustion from a real read error.
- `\r` was matched both as "ignore" and as whitespace, which V rejects as a
duplicate case.
- `byte(bool)` is no longer a valid conversion; the single-count check counts
the selected options instead.
Finally, `v fmt -w .` over the 16 files that `v fmt -verify` rejected.
With this, `make` builds all 76 utilities and `make testfmt` passes.
Follow-up to vlang#195, which got the build and the formatting gate green. This brings `make test` from 37/47 to 46/47. The only remaining failure is shuf_test.v, which is issue vlang#189 and gets its own PR. ## common/pwd: group ids came back as garbage getgrouplist() and getgroups() fill an array of gid_t, which is 32 bits wide, but the V declarations handed them a []int, i.e. i64. The kernel wrote the low half of each slot and left the upper half holding the previous entry, so gids arrived as 103079215108 (24 * 2^32 + 4, i.e. adm's 4 with cdrom's 24 above it). The two functions now fill a []u32. getgrgid() was also declared as returning `&C.passwd` rather than `&C.group`. That happened to work because gr_name sits at the same offset as pw_name, but gr_mem/gr_gid do not line up, so there is now a proper struct. groups and id now agree with GNU for the plain, named-user, -nG and -G forms. ## tail: KiB was decimal, and PB/EB used the binary multiplier The suffix table mapped both `kb` and `kib` to 1000, both `mb` and `mib` to 1000^2, and so on, so `tail -c 1KiB` read a third of what it should have. `petabyte` was also `terra * kilo` and `exabyte` `peta * kilo`, i.e. the binary multipliers, instead of continuing the decimal chain. The two chains are now separate and correct, and the test asserts GNU's actual values rather than mirroring the constants. The overflow check was `result == 0 && number != 0`, which cannot catch a wrapped product; it now checks that the division comes back out. `20E` is rejected like GNU rejects it. Z/Y/R/Q stay unsupported, because although GNU's help mentions them, GNU itself rejects all of them with "Value too large for defined data type" (checked against 9.4). Verified end to end against GNU for 2b, k, K, KB, KiB, M, MB, MiB, G, GB, GiB, T, TB, TiB, P, PB, PiB, E, EB and EiB, plus the rejection cases. ## numfmt: 2000.0 and 2e+06 f64.str() always keeps a fractional part and switches to scientific notation above a million, so `--to=none 2000000` printed `2e+06` and `2K` printed `2000.0`. Both now go through a helper based on strlong() that drops a trailing `.0`. ## stat: the default Device: field The default format used `%Dh/%dd`, which printed the device number in hex over the same number in decimal: `830h/2096d` where GNU prints `8,48`. GNU's default is `Device: %Hd,%Ld`, and %H/%L are two character tokens that scan_for_tokens() did not handle; there was already a TODO about it. Both tokens are implemented and the default format matches. ## Three tests were asserting the wrong thing, or crashing * `tail_test` and `fmt_test` captured `&result` for a local in `setup()` and returned the closure. Once setup() returned, the pointer dangled and the closure appended to freed stack memory: `malloc_uninit(0xd0a07408218d1b1f < 0)`. The array header is now heap allocated. (cut_test and paste_test use the same shape but call the closure in scope, so they are fine.) * `string_to_i64_test` used `!` on an option return, which does not compile. * `sum_test` had `const util` next to `import io.util`. The import is aliased. * `truncate_test` computed its expectations as `24 * 1000 * 1000 * 1000`. V's `int` is 32-bit, so the expectation wrapped to -1769803776 while the value under test, a u64, was right all along. The expected values are now u64. ## Testing `v run build.vsh` builds all 76 utilities, `v fmt -verify .` passes, and `v test .` reports 46 of 47 files passing, up from 37.
This was referenced Oct 2, 2026
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.
Follow-up to #195. That got the build and
make testfmtgreen; this bringsmake testfrom 37/47 to 46/47. The one remaining failure isshuf_test.v,which is issue #189 and gets its own PR.
common/pwd: group ids came back as garbage
getgrouplist()andgetgroups()fill an array ofgid_t, which is 32 bitswide, but the V declarations handed them a
[]int, i.e. i64. The kernel wrotethe low half of each slot and left the upper half holding the previous entry, so
gids arrived as
103079215108— that is 24 * 2^32 + 4, i.e. adm's 4 withcdrom's 24 sitting above it:
Both functions now fill a
[]u32.getgrgid()was also declared as returning&C.passwdinstead of&C.group.That only ever worked because
gr_namehappens to sit at the same offset aspw_name;gr_memandgr_giddo not line up. There is now a proper struct.groupsandidnow agree with GNU for the plain, named-user,-nGand-Gforms.
tail: KiB was decimal, and PB/EB used the binary multiplier
The suffix table mapped both
kbandkibto 1000, bothmbandmibto1000^2, and so on — so
tail -c 1KiBread a third of what it should have.petabytewas alsoterra * kiloandexabytepeta * kilo, i.e. the binarymultipliers, instead of continuing the decimal chain. The two chains are now
separate and correct, and the test asserts GNU's actual values instead of
mirroring the constants back at itself.
The overflow check was
result == 0 && number != 0, which cannot catch awrapped product; it now checks the division comes back out, so
20Eis rejectedthe way GNU rejects it. Z/Y/R/Q stay unsupported: although GNU's help mentions
them, GNU itself rejects every one with "Value too large for defined data type"
(checked against 9.4).
Verified end to end against GNU for
2b,k,K,KB,KiB,M,MB,MiB,G,GB,GiB,T,TB,TiB,P,PB,PiB,E,EB,EiB,plus the rejection cases.
numfmt:
2000.0and2e+06f64.str()always keeps a fractional part and switches to scientific notationabove a million, so
--to=none 2000000printed2e+06and2Kprinted2000.0. Both go through a helper based onstrlong()that drops a trailing.0.stat: the default
Device:fieldThe default format used
%Dh/%dd, printing the device number in hex over thesame number in decimal —
830h/2096dwhere GNU prints8,48. GNU's default isDevice: %Hd,%Ld, and%H/%Lare two character tokens thatscan_for_tokens()did not handle; there was already a TODO about it. Both areimplemented and the default format now matches.
Four tests were asserting the wrong thing, or crashing
tail_testandfmt_testcaptured&resultfor a local insetup()andreturned the closure. Once
setup()returned the pointer dangled and theclosure appended to freed stack memory:
The array header is now heap allocated. (
cut_testandpaste_testuse thesame shape but call the closure in scope, so they are fine.)
string_to_i64_testused!on an option return, which does not compile.sum_testhadconst utilnext toimport io.util; the import is aliased.truncate_testcomputed its expectations as24 * 1000 * 1000 * 1000. V'sintis 32-bit, so the expectation wrapped to-1769803776while thevalue under test, a u64, was right all along. The expectations are now u64.
Testing
v run build.vsh— all 76 utilities buildmake testfmt— passesv test .— 46/47 files pass, up from 37/47Verified on V 0.5.2 (0dc6a69, upstream
vlang/vmaster), Ubuntu 24.04, GNUcoreutils 9.4.