Repository navigation
Array.new's size conversion, and ~ expansion's HOME rule (closes #901) - #908
Conversation
Both items of #901, plus a third divergence the second one exposed. ## 1. Array.new(2**63) raises RangeError, not ArgumentError Array.new(10**40) # MRI: RangeError: bignum too big to convert into 'long' # rbgo: ArgumentError: array size too big rb_ary_new2 takes a long, so NUM2LONG runs BEFORE any size test; ary_new's ArgumentError is only for a value that converted and is still out of range. The class matters: `rescue RangeError` could not catch it. The boundary is exactly LONG_MAX, measured on 4.0.7: 2**62 ArgumentError: array size too big 2**63-1 ArgumentError: array size too big 2**63 RangeError: bignum too big to convert into 'long' 10**40 RangeError Array#first already had the identical *object.Bignum check with the right class 200 lines below (builtins.go:3331). One idiom, applied in one of the two places that needed it. ⛔ An existing row PINNED the divergence: array_residuals_wave10_test.go asserted ArgumentError for Array.new(10**40), under a comment reading "Verified against MRI Ruby 4.0". It is moved with its citation, and both sides of the boundary are now asserted so the row says WHERE the line is. ## 2. expand_path("~") answers when HOME is unset ENV.delete("HOME"); File.expand_path("~") # MRI: "/Users/…" (the passwd entry for the current uid) # rbgo: ArgumentError: couldn't find HOME environment -- expanding '~' dirHomeStr (dir.go) ALREADY encoded MRI's rule for Dir.home — $HOME, then getpwuid, raise only when neither answers. expand_path kept its own HOME-only copy, so Dir.home was right and expand_path was wrong for the same input. The fix is a call to the existing helper. ## 3. And HOME="" is a VALUE, not an absence (found by fixing 2) The first version of this change sent an EMPTY HOME to the passwd database too, and the repository's own TestFileExpandPathHomeGuards caught it. MRI's rb_default_home_dir branches on `if (!dir)` — getenv returning NULL — so only an ABSENT HOME reaches the fallback. Measured on 4.0.7: HOME Dir.home File.expand_path("~") "" "" ArgumentError: non-absolute home "relative/home" "relative/home" ArgumentError: non-absolute home unset passwd home passwd home So Dir.home answers getenv VERBATIM — empty or relative, unchecked — and expand_path is the one that demands an absolute path. ⛔ The seam could not express that rule: osUserHomeDir wrapped os.UserHomeDir, which reports an error for unset AND empty alike. It is now osLookupHome, with LookupEnv's (value, set) shape. The two seam tests moved with it rather than being relaxed, and a third row asserts that a SET HOME never reaches the passwd stub. ⛔ That also fixes a divergence nobody had filed: Dir.home with HOME="" answered the passwd home where MRI answers "". It is the same helper and the same distinction as item 2, so it could not be separated out. ⛔ TestFileExpandPathHomeGuards passed BEFORE this change for the wrong reason — right class, wrong message ("couldn't find HOME environment" where MRI says "non-absolute home"). The new test asserts the messages. ## Measured cmd/oracle against /opt/homebrew/opt/ruby@4.0/bin/ruby (ruby 4.0.7, +PRISM): six probes, all "2 engines AGREED, byte for byte". ⛔ The new test mutates the PROCESS environment (ENV["HOME"] in Ruby is setenv, and runSrc builds a VM in this process), so it pins and restores HOME with t.Setenv: a row leaving HOME empty would hand that to every later test in the package. Closes #901 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The corpus sweep: 0 moved, and that is the expected answerClean on every channel: Why
|
covered by core,language? |
|
|---|---|
Array.new(2**63) → RangeError |
no — nothing in core/array/ mentions either message |
expand_path("~") with unset HOME |
no |
expand_path("~") with empty HOME |
no |
expand_path("~") with a relative HOME |
yes — core/file/expand_path_spec.rb:260 |
Both defects were found by probing MRI with rubydiff/cmd/oracle, not by a failing example, so no example can move when they are fixed. The sweep's job here was the other one — proving the blast radius is zero for a helper (dirHomeStr) that Dir.home, Dir.chdir and every ~ expansion go through.
⭐ And the corpus did supply one control I did not have to write
core/file/expand_path_spec.rb control: pass=27 fail=0 treatment: pass=27 fail=0
That file asserts ENV["HOME"] = "non-absolute" → ArgumentError, 'non-absolute home' — the one branch of the three that this change deliberately leaves alone. It passed before and passes after, which is exactly the row I would have had to invent to show the relative-HOME path is untouched.
⭐ Incidentally it also guards the hazard this PR's own test had to handle: its after :each restores ENV["HOME"]. Upstream treats a spec that mutates HOME as something to clean up after, for the same reason t.Setenv is in the new test — a leaked HOME is handed to everything that runs next.
🤖 Generated with Claude Code
…able
CI caught two things in the previous commit, and only the first was the test.
## 1. The regression, which darwin cannot see
var osLookupHome = func() (string, bool) { return os.LookupEnv("HOME") }
On Windows HOME is usually not set at all: MRI consults HOME, then
HOMEDRIVE+HOMEPATH, then USERPROFILE (file.c rb_default_home_dir), and
os.UserHomeDir is exactly that chain per platform. Reading HOME alone would
send a Windows user with no HOME to the PASSWD lookup instead of USERPROFILE.
On darwin the two agree, so nothing local could have shown it.
os.UserHomeDir is back for the absent case. LookupEnv is kept only for the one
thing it adds and the reason it was introduced: telling HOME SET-BUT-EMPTY from
HOME ABSENT, which os.UserHomeDir reports identically.
So the chain is now: HOME if set (verbatim, even "") -> os.UserHomeDir ->
passwd -> raise.
## 2. The test asserted a POSIX concept on every platform
expand_path_home_test.go:99: run: ArgumentError: can't find user for -1
Process.uid is -1 on Windows and Etc.getpwuid(-1) raises. The row asserted
WHERE an absent HOME resolves to, which is not assertable cross-platform
because MRI's chain differs there.
Split in two, because the two halves are not equally portable:
an unset HOME does not RAISE -> every platform
an unset HOME resolves to the passwd home -> POSIX only, skipped on Windows
file_path_internal_test.go already marks its ~user row the same way, with the
same reason in a comment.
## Not ours: compile on every target (openbsd)
That job also failed, and it is not this change. The "Vet (or build) every
GOARCH of openbsd" step has conclusion NULL — it never completed — the job ran
63 minutes (21:23:38 -> 22:26:39) against ~18m37s for the same job on an
earlier run, and its log blob is gone, which is what a cancellation leaves
rather than a compile error. Runner acquired, the three preceding steps green.
go test ./... green locally.
Refs #901
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI went red on two checks. One was mine, and it was worse than the test row.
|
step Vet (or build) every GOARCH of openbsd |
conclusion: null — it never completed |
| job duration | 63 min (21:23:38 → 22:26:39) vs ~18m37s for the same job on an earlier run |
| log blob | gone (BlobNotFound) — what a cancellation leaves, not a compile error |
| runner | acquired; the three preceding steps all green |
A compile failure would have concluded the step and left a log naming the file. This stalled. The push above starts a fresh run that re-runs it: if it fails the same way twice that becomes a finding rather than a stall, and I will say so here either way rather than let a re-run quietly stand in for a diagnosis.
🤖 Generated with Claude Code
Closes #901 — both items — plus a third divergence that fixing the second one exposed.
1.
Array.new(2**63)raisesRangeError, notArgumentErrorrb_ary_new2takes along, soNUM2LONGruns before any size test;ary_new'sArgumentErroris only for a value that converted and is still out of range. The class is the point —rescue RangeErrorcould not catch it.The boundary is exactly
LONG_MAX, measured on 4.0.7 rather than reasoned from the width of along:2**62ArgumentError: array size too big2**63-1ArgumentError: array size too big2**63RangeError10**40RangeErrorArray#firstalready had the identical*object.Bignumcheck with the right class 200 lines below (builtins.go:3331). One idiom, applied in one of the two places that needed it.⛔ An existing row pinned the divergence.
array_residuals_wave10_test.goassertedArgumentErrorforArray.new(10**40)— under a comment reading "Verified against MRI Ruby 4.0". I found it by measuring the boundary, not by trusting the test. It is moved with its citation, and both sides of the boundary are now asserted so the row says where the line is.2.
expand_path("~")answers whenHOMEis unsetdirHomeStr(dir.go) already encoded MRI'srb_default_home_dirforDir.home—$HOME, thengetpwuid, raise only when neither answers.expand_pathkept its ownHOME-only copy, soDir.homewas right andexpand_pathwas wrong for the same input. The fix is a call to the existing helper.#901 said the passwd fallback was a guess and had to be established first. It is:
Etc.getpwuid(Process.uid).diris byte-identical to what MRI returns, andHOMEis re-read on every call ("/first/home"then"/second/home"answer differently in one process), which is the control that makes the fallback attributable rather than a value captured at startup.3. And
HOME=""is a VALUE, not an absence — found by fixing 2My first version sent an empty
HOMEto the passwd database as well, and the repository's ownTestFileExpandPathHomeGuardscaught it. MRI branches onif (!dir)—getenvreturning NULL — so only an absentHOMEreaches the fallback:HOMEDir.homeFile.expand_path("~")""""ArgumentError: non-absolute home"relative/home""relative/home"ArgumentError: non-absolute homeDir.homeanswersgetenvverbatim — empty or relative, unchecked — andexpand_pathis the one that demands an absolute path.⛔ The seam could not express that rule.
osUserHomeDirwrappedos.UserHomeDir, which reports an error for unset and empty alike, so the two were folded together. It is nowosLookupHome, withLookupEnv's(value, set)shape. The two seam tests moved with it rather than being relaxed, and a third row asserts that a setHOMEnever reaches the passwd stub.⛔ This also fixes a divergence nobody had filed:
Dir.homewithHOME=""answered the passwd home where MRI answers"". Same helper, same distinction as item 2 — it could not be separated out, so it is here and said out loud rather than slipped in.⛔
TestFileExpandPathHomeGuardspassed before this change for the wrong reason — right class, wrong message (couldn't find HOME environmentwhere MRI saysnon-absolute home). It only checked the class. The new test asserts the messages.Measured
cmd/oracleagainst/opt/homebrew/opt/ruby@4.0/bin/ruby(ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25]): six probes, every one2 engines AGREED, byte for byte.⛔ The new test mutates the process environment.
ENV["HOME"] = …in Ruby issetenv, andrunSrcbuilds a VM inside the test process, so a row leavingHOMEempty or deleted would hand that to every test running after it in the same package. It pins and restoresHOMEwitht.Setenv, which also makes the test refuse to run in parallel — correct for something that writes a global.Closes #901
🤖 Generated with Claude Code