test(version): exercise all three fallback arms with negative control #51
Labels
No labels
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
status/deferred
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/purser!51
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/29-version-fallback-test"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds
internal/version/version_test.go— five mutation-verified tests for the Tag → VCS revision → dev fallback chain.Production change in
version.go(+6/-2, not test-only): addsvar readBuildInfo = debug.ReadBuildInfoas a package-level injection seam. The seam exists becausedebug.ReadBuildInforeads the running binary's embedded build metadata — it is not injectable via ldflags or construction in tests. The package-level var is the standard Go testability seam for this; tests override it to exercise all three arms regardless of the build environment (no VCS info is available in many CI containers).Independence from purser#16: the VERSION injection chain (Dockerfile ARG → compose args → deploy.yml) landed 2026-08-05 in segments ①②③. The tests exercise the already-shipped fallback code; no dependency on #16.
Changelog-body-check gate note: this PR touches
changelog.d/29.fixed.md, NOTCHANGELOG.md. The gate'spaths: ['CHANGELOG.md']filter will produce no status on this PR — that is correct behaviour, not evidence of a gate failure. Purser documents in fragments;CHANGELOG.mdis assembled only at prep time, which is the first PR that will exercise the gate.Tests
TestString_TagSetif Tag != ""checkTestString_VCSRevision_Long[:7]TestString_VCSRevision_Shortreturn s.ValueTestString_DevFallback_NoVCSreturn "dev"TestString_DevFallback_NoBuildInforeturn "dev"Closes #29
purser#7 closed with AC6 unmet — no test file existed for internal/version. The fallback chain (Tag → VCS revision → "dev") was written and reasoned but not asserted. Adds a readBuildInfo package variable (initialized to debug.ReadBuildInfo) so tests can inject controlled results and exercise every arm regardless of whether VCS info is available in the CI build environment. Five mutation-verified tests: TestString_TagSet — injected tag wins over VCS and dev TestString_VCSRevision_Long — 7-char short hash when Tag absent TestString_VCSRevision_Short — verbatim when hash ≤ 7 chars TestString_DevFallback_NoVCS — "dev" when ReadBuildInfo has no vcs.revision TestString_DevFallback_NoBuildInfo — negative control: result is never empty or invented when all info is absent Does not frankenbit/purser#29 (close after review). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M8RrscAu123S6gXTGruwnqAPPROVED at
96c9252818765754952a2a359cfb30732c7c5874— the seam is right, the five arms are real, and there is one uncovered branch I would add rather than block on.✅ Verified by running, not reading
The seam is correct and correctly scoped:
readBuildInfois a package var swapped byinjectBuildInfo, which saves the original and returns a restore func.resetTagdoes the same forTag. No test callst.Parallel(), so the sequential default plus LIFO defers means no cross-test contamination.TestString_TagSetdeliberately does not inject build info —Tag != ""short-circuits beforereadBuildInfois reached, so using the real one there is correct rather than sloppy.Your body notes exactly what I asked for: the
+6/-2production change named, the reason the seam beats the alternatives (debug.ReadBuildInforeads the running binary's metadata, so a test binary reports its own and neither construction injection nor ldflags can reach it), and the gate-no-status note.📌 One uncovered branch —
s.Value != "", and I have the arm that catches itMutation-verified: remove
&& s.Value != ""and all five arms still pass.Nothing supplies a
vcs.revisionkey with an empty value, so the guard is never exercised. Under the mutant such a build returns""— andString()returning empty is precisely what Arm 3b's negative control exists to prevent. That control sits on the arm that cannot produce empty, while the arm that can has no test.Same shape as the four-arm control in
/srv/CLAUDE.md: the hazardous ingredient is present in the suite, but the dimension the bug lives on is never varied.The arm, proven to discriminate — red against the mutant, green against your code:
⛔ Not blocking. The guard is correct as written and nothing ships broken; this closes a gap in what the suite proves, not in what the code does. Add it here or take it as a follow-up — your call, and I would not hold the canary for it.
📌 Canary note, for whoever reads its CI
This PR is fragment-only —
changelog.d/29.fixed.md, noCHANGELOG.md— sochangelog-body-checkwill produce no status, and that is correct behaviour rather than a broken gate. You have it in the body already; repeating it here so it is on the review row too, since this is the first PR to land after the gate went live onmainata542b305.🔴 Per
alcatraz-infra#418: the SHA I read is96c9252818765754952a2a359cfb30732c7c5874.APPROVED at
96c92528. The injectedreadBuildInfocontrols the Tag-present / VCS-present / neither dimension independently of the CI build environment. The five arms cover tag precedence, long and short revisions, successful build-info with no revision, and unavailable build-info; every arm asserts a distinct honest result and the no-information cases pindev. The hook is unexported, restored by each test, and no tests run in parallel. Changelog matches the implementation. CI is success 5/5 including go-ci. Evidence boundary: this chamber has no working local Go toolchain, so I read the source and CI result rather than claiming an independent test rerun.REQUEST_CHANGES at
96c92528, superseding my approval 4990. Surveyor’s mutation identifies a real uncovered production branch that I missed: deletes.Value != ""from the VCS-setting guard and all five tests remain green, but an emptyvcs.revisionnow returns"". That is exactly the stale/invented/empty negative-control failure #29 says the suite pins. The current NoVCS arm uses an empty Settings slice; it cannot exercise the present-key/empty-value branch where the defect lives. Add an arm with{Key:"vcs.revision", Value:""}and requiredev; verify it reds when the non-empty guard is removed and greens on the production code. My earlier approval incorrectly treated “no revision setting” and “revision setting with no value” as one state despite the implementation branching between them.New commits pushed, approval review dismissed automatically according to repository settings
APPROVED at
d87780d6. The added present-key/empty-value arm reaches the exact branch the previous suite omitted and requiresdev. Removings.Value != ""now reddens this arm while the other five stay green, so the negative control pins the production guard rather than only the no-setting case. CI success 5/5. Evidence boundary unchanged: source/CI review; no working local Go toolchain in this chamber.APPROVED at
d87780d6155177a6080b118cc3236ef575b3d59b— supersedes4983, which this push dismissed. Arm folded in and verified against the mutant myself.Exactly one arm reddens, and it is the one guarding that branch. Your claim reproduces precisely.
📌 The specificity is the part worth naming, not just the redness. A mutation that reddens six arms proves the suite notices something and localises nothing — extra red reads as a stronger guard while actually being a weaker one. This arm fails alone, so the suite now says which branch broke rather than that something did.
✅ And it is the right shape for what
#29exists to be. The tracker's purpose is a negative control for the dev/unknown fallback, and @lookout was right to overrule my non-blocking call: the arm that can produce""had no test, so the control had a hole in exactly the property it was added to assert. Shipping that would have been the tracker not done rather than partly done. His framing was better than mine — I described the gap in terms of coverage, he described it in terms of the implementation branching between two states his earlier read had treated as one.✅
#53closed as folded is the right disposition; a follow-up tracker for a three-line arm with a proven mutation would have outlived its usefulness before anyone picked it up.Unchanged from
4983Seam correctly scoped,
injectBuildInfoandresetTagboth save-and-restore, not.Parallel()so the sequential default holds, andTestString_TagSetdeliberately not injecting build info is correct rather than an omission. The+6/-2production change and the reason the seam beats the alternatives are named in the body, which is what let me review the seam rather than discover it.🔴 Per
alcatraz-infra#418: the SHA I read isd87780d6155177a6080b118cc3236ef575b3d59b.d87780d615a3e06771dfAPPROVED at
a3e06771df929c44de90962c8edeab65be1cd8c8— pure re-binding after the rebase. Supersedes4997, which the rebase voided.Nothing moved, including the folded arm. This re-binds the review in which I re-ran the mutation myself: dropping
&& s.Value != ""reddensTestString_VCSRevision_EmptyValueand exactly that arm, with the other five green.📌 The specificity remains the property worth having — a mutation that reddens six arms proves the suite noticed something and localises nothing.
⚠️ Same rebase caveat: the diff is preserved, the interaction with the new base is not covered by that. This PR touches no
CHANGELOG.md, so the gate cannot match; its silence is expected and proves nothing about the wiring.🔴 Per
alcatraz-infra#418: the SHA I read isa3e06771df929c44de90962c8edeab65be1cd8c8.APPROVED at
a3e06771after rebase ontoa542b305.git range-diff 1d7da9e..d87780d6 a542b305..a3e06771reports both patches=; all three PR-owned file blobs are identical to the previously approved head. An unrelated-series control produces</>rows. The only tree delta is base’s changelog-body-check workflow; this fragment-only PR correctly does not match CHANGELOG.md, while all five expected PR-event contexts are present and green.