From be755f20967199f58a9e8d1fd8b17dd40ae642ed Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sat, 31 Oct 2026 21:48:28 +0100 Subject: [PATCH] =?UTF-8?q?conformance:=20derive=20CUR=5FVERSION=20from=20?= =?UTF-8?q?RUBY=5FVERSION=20=E2=80=94=20one=20fact,=20one=20place?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the last item of #799: the version the gate resolves `ruby_version_is` guards at stops being a second hardcoded copy of what the runtime already reports. -CUR_VERSION = "3.4.1" +CUR_VERSION = RUBY_VERSION ## Why this is worth a PR of its own `CUR_VERSION` selects the **population** the whole conformance figure is counted over. At 3.4.1 every example behind a `ruby_version_is "4.0"` guard is removed from the suite before anything runs — 61 block guards and 5 inline sites at that boundary, and 41 guards asserting what 4.0 changed away from (#836's census). The two copies agreed, so nothing was wrong on any given day. The defect #799 names is that **half a version change could land**: `rubyVersion` bumped in internal/vm/kernel_introspect.go, this file forgotten, and the gate goes on measuring the 3.4 population while every page claims the new version. Both halves look correct read on their own, which is exactly why nothing would have said so. This lands **before** the move to 4.0.x rather than with it, so the move is a one-line change to one place whose effect the ratchet can then attribute cleanly. ## Measured: a true no-op today `RUBY_VERSION` is `"3.4.1"`, the same string the literal held, so the population does not move. Measured rather than asserted — the derived shim run against `main`'s own binary (3a3fb818, so nothing else of mine is in the reading), on a private two-file corpus: $ rbgo core/builtin_constants/builtin_constants_spec.rb RBGO_RESULT pass=17 fail=0 error=0 skip=0 `BASELINE` records `17` for that file. **A green ratchet on this PR is therefore itself the proof of equivalence**, across all 2,206 files rather than the one I could run here. ## Upstream behaviour, not a local convenience mspec reads RUBY_VERSION for this too. The chain, at mspec b79c65e: guards/version.rb:67 VersionGuard.new(VersionGuard::FULL_RUBY_VERSION, …) guards/version.rb:6 FULL_RUBY_VERSION = SpecVersion.new SpecGuard.ruby_version(:full) guards/guard.rb:41 def self.ruby_version(which = :minor) guards/guard.rb:51 RUBY_VERSION.split('.')[0,n].join('.') # n = 3 for :full i.e. RUBY_VERSION itself for a three-part version. A consequence worth having: run under a different interpreter, the shim now measures at THAT interpreter's version. ## Two tests, and what each one cannot see `TestShimVersionIsTheRuntimesNotASecondCopy` asserts the equality through the **real shim** rather than by matching the file's text, because what matters is not how the line is spelled but what the constant holds at run time. `TestShimVersionGuardsResolveAtThatVersion` proves CUR_VERSION actually feeds `_version_in_range`: a guard at the running version keeps its example, one above it removes its example (removes, not skips — a block guard below the threshold deletes rather than counting, so the deliberately-failing example inside it contributes to no counter). ⛔ **The control, planted:** with the shim set back to a literal `CUR_VERSION = "9.9.9"`, the first test fails and names the disagreement (`expected "9.9.9" to == "3.4.1"`) — while **the second still passes**, because a guard at `>= RUBY_VERSION` is satisfied by any higher value. So the second test proves the wiring and the first proves the value; reading the second alone as the guard against a hardcode would be wrong, and the comment says so at the site. ## Not in this PR - The move to 4.0.x itself. #799 asks for it with the baseline **not** regenerated, so the ratchet names every file that shifts in both directions; that reading is only clean once missing capabilities have landed separately (#844 is the first). - `PLATFORMS = [:darwin, :bsd, :unix]`, three lines below and the same shape of defect — a hardcoded copy of something `RUBY_PLATFORM` already knows (#837). It is left alone deliberately: unlike this change it is **not** a no-op, it moves the population on the Linux lane, and it needs its own measurement. The documentation side is go-embedded-ruby/docs#41, which makes the stated version a checked claim and already recognises this derived spelling — so that guard does not break on the day this merges. Co-Authored-By: Claude Opus 5 --- internal/vm/rubyspec_shim_test.go | 61 +++++++++++++++++++++ scripts/conformance/rubyspec/spec_helper.rb | 26 ++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/internal/vm/rubyspec_shim_test.go b/internal/vm/rubyspec_shim_test.go index 1fad5249..12fcdc35 100644 --- a/internal/vm/rubyspec_shim_test.go +++ b/internal/vm/rubyspec_shim_test.go @@ -246,3 +246,64 @@ describe "TIME_TOLERANCE (mspec constant)" do end` assertShimResult(t, runShim(t, "", src), "pass=2 fail=0 error=0 skip=0") } + +// ⛔ CUR_VERSION selects the POPULATION the whole conformance figure is counted +// over, and it used to be a second hardcoded copy of what +// kernel_introspect.go's `rubyVersion` already says (#799). They agreed, so +// nothing was wrong on any given day; the defect was that half a version change +// could land — the runtime bumped, the shim forgotten — and the gate would go on +// measuring the old population while every page claimed the new version. Both +// halves look right on their own, which is why nothing would have said so. +// +// This is the test that makes that state impossible to commit. It asserts the +// equality through the REAL shim rather than by matching the file's text, +// because the thing that matters is not how the line is spelled but what the +// constant holds at run time. +func TestShimVersionIsTheRuntimesNotASecondCopy(t *testing.T) { + src := ` +describe "CUR_VERSION" do + it "is the version this interpreter reports, not a second copy of it" do + CUR_VERSION.should == RUBY_VERSION + end +end` + assertShimResult(t, runShim(t, "", src), "pass=1 fail=0 error=0 skip=0") +} + +// And the constant has to actually GOVERN the guard: an equality that nothing +// reads would satisfy the test above while the guards resolved against +// something else entirely. mspec's chain is +// guards/version.rb -> SpecGuard.ruby_version(:full) -> RUBY_VERSION +// (guard.rb:41-52, at mspec b79c65e); here it is CUR_VERSION, and this proves +// _version_in_range is fed by it. +// +// A guard AT the running version must keep its examples; one above it must +// remove them — note "remove", not "skip": a block guard below the threshold +// deletes the examples rather than counting them as skipped, so the failing +// example inside the second block contributes nothing to any counter. If it +// ever did run, `1.should == 2` makes that visible as fail=1 instead of a +// silently absent example. +// +// ⛔ WHAT THIS ONE CANNOT SEE, measured by planting it: with the shim set back +// to a literal `CUR_VERSION = "9.9.9"`, this test still PASSES — a guard at +// `>= RUBY_VERSION` is satisfied by any higher value and `"99.0"` still removes +// its block. Only the test above catches that, and it did, naming the +// disagreement (`expected "9.9.9" to == "3.4.1"`). So this test proves the +// WIRING and the one above proves the VALUE; neither substitutes for the other, +// and reading this one alone as the guard against a hardcode would be wrong. +func TestShimVersionGuardsResolveAtThatVersion(t *testing.T) { + src := ` +describe "version guards" do + ruby_version_is RUBY_VERSION do + it "keeps an example guarded at exactly the running version" do + 1.should == 1 + end + end + + ruby_version_is "99.0" do + it "removes an example guarded above it" do + 1.should == 2 + end + end +end` + assertShimResult(t, runShim(t, "", src), "pass=1 fail=0 error=0 skip=0") +} diff --git a/scripts/conformance/rubyspec/spec_helper.rb b/scripts/conformance/rubyspec/spec_helper.rb index be05671b..ecef98ce 100644 --- a/scripts/conformance/rubyspec/spec_helper.rb +++ b/scripts/conformance/rubyspec/spec_helper.rb @@ -19,7 +19,31 @@ class SpecSkip < Exception; end $mock_registry = nil $mock_installs = nil -CUR_VERSION = "3.4.1" +# ⛔ DERIVED, never typed again (#799). This selects the POPULATION the whole +# conformance figure is counted over: at 3.4.1 every example behind a +# `ruby_version_is "4.0"` guard is removed from the suite before anything runs +# -- 61 block guards and 5 inline sites at that boundary, and 41 guards +# asserting what 4.0 changed away from (censused by cmd/guards). +# +# It used to read `"3.4.1"`, a second hardcoded copy of what +# internal/vm/kernel_introspect.go's `rubyVersion` already says. They agreed, so +# nothing was wrong today; the defect was that half a version change could land +# -- the runtime bumped, this file forgotten -- and the gate would go on +# measuring the old population while every page claimed the new version. Both +# halves look correct on their own, which is why nothing would have said so. +# +# Reading RUBY_VERSION is also what mspec itself does, so this is the upstream +# behaviour rather than a local convenience. The chain, at mspec b79c65e: +# +# guards/version.rb:67 VersionGuard.new(VersionGuard::FULL_RUBY_VERSION, …) +# guards/version.rb:6 FULL_RUBY_VERSION = SpecVersion.new SpecGuard.ruby_version(:full) +# guards/guard.rb:41 def self.ruby_version(which = :minor) +# guards/guard.rb:51 RUBY_VERSION.split('.')[0,n].join('.') # n = 3 for :full +# +# i.e. RUBY_VERSION itself for a three-part version. Run under a different +# interpreter, the shim then measures at THAT interpreter's version, which is the +# only answer that is ever right. +CUR_VERSION = RUBY_VERSION PLATFORMS = [:darwin, :bsd, :unix] WORDSIZE = 64 ENDIAN = :little