test(prep): #562 forge-injection seam + rolling-mode differential coverage #593
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!593
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/562-rolling-mode-differential"
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?
What + why
Discharges the honest residual behind #556's
--rolling-mode byte-identicalAC (tracked as #562): the rolling PR step'sFindPRByHead→ PATCH-or-POST decision was ported and code-reviewed but never exercised through a test. This makes that decision independently verified, and adds an end-to-end rolling-mode equivalence case.Honest scope category (per the #562 framing-verify): this is refactor-for-testability + tests, not new behavior. My dispatch framing called it "tests-only"; the tracker's literal ask includes a production forge-injection seam, so the substrate-honest category is "primarily tests, with a behavior-preserving testability refactor." Neither part changes runtime behavior.
B — forge-injection seam (refactor-for-testability)
Extract the ~13-line
FindPRByHead → PATCH-or-POSTdecision out ofcmd/rt/prep.go(package main, untestable without a live forge) intoprep.OpenOrUpdatePR, behind a narrowPRWriterinterface thatforgejo.Clientsatisfies structurally. This mirrorsinternal/decide'sPRReaderseam exactly (one canonical "injected fake forge" pattern, not two competing ones).The
cmd/rtwrapper now delegates the identical predicate — behavior-preserving.pr_test.go's fake forge unit-tests every branch hermetically:UpdatePR(PATCH) with the found number; noCreatePRErrNotFound)CreatePR(POST); noUpdatePRCreatePR(POST) — guards theNumber != 0conjunctCreatePR(POST) — the fall-through #562 namesCreatePRdirectly;FindPRByHeadnot consultedC — end-to-end rolling equivalence case (tests-only)
Add a
--rolling-modedry-run case to the prep equivalence harness — a pure case-list addition (the oracle is scenario-free: git-bootstrap +exec "$@", no printf fixture surface, so the #591 leading-dash trap is not in play). It byte-confirmsselectBranch's rolling arm end-to-end through the real binary (branch=release-prep/rolling) againstrelease-prep.sh, over the existing compared surfaces (stdout + exit + git_artifacts).What this PR does NOT do (declined literal ask, disclosed)
The tracker's literal ask includes an end-to-end harness case covering BOTH the PATCH and POST branches. The PATCH branch is unreachable through the dry-run byte-oracle: bash's
forgejo_find_pr_by_headhas noFORGEJO_TEST_*hook and returns empty under dry-run (forgejo-api.sh:582-586), so both sides fall through to POST. Reaching PATCH end-to-end would require adding production test hooks to both bash and Go — the exact live-API-through-harness wiringcmd/rt/decide_equiv_test.goexplicitly declined as "high-cost for the marginal byte … covered by internal/decide's unit tests (injected fake forge) + the bash bats."So per that precedent, the PATCH-vs-POST branch coverage lives at library level (B) — hermetic, both branches — and C covers the reachable rolling wiring end-to-end. Together they discharge #562's "independently verified" intent without the against-precedent production accretion on both sides.
Mutation-verification (closed loop)
Each control was mutated and observed to red exactly where expected; every mutation was reverted by re-edit (never
git checkout), andgrep -rn MUTATIONis clean.pr.Number != 0→== 0: redsRollingExistingPR_Patches+RollingZeroNumber_Postsonly (NoPR/ReadError stay green — they guard theerrpath, not the conjunct). ✓rollingguard (if rolling→if true): redsOneShot_PostsWithoutFindonly (asserts non-rolling must not read). ✓selectBranchrolling arm →release-prep/rolling-MUT: reds the rolling equiv case — Go stdout diverges from bash at byte 60, proving C byte-compares the rolling branch selection (and confirming bash emitsrelease-prep/rolling). ✓Gate
Local + CI image (
forgejo-ci-go:latest, go1.26.2):gofmt -lclean,go build ./...,go vet ./...,golangci-lint run(0 issues), full suite (19 packages) allok. No bash touched (shellcheck n/a).Refs #562 #556 #541
Review — PR#593, rolling-mode differential: PR-write extraction + rolling equiv case (#562, Phase 7)
Independent deep-verify at head
ddb4fc9(basemain@16c842e, in sync). Two parts: B — extract the §11-12 find-by-head→PATCH-or-POST decision fromcmd/rt/prep.gointoprep.OpenOrUpdatePRbehind a narrowPRWriterinterface, unit-tested with a fake forge; C — a--rolling-modedry-run case added to the prep equiv harness. Read the extraction + the fake-forge tests + the bash oracle, ran the full gate (19 pkgs green, vet clean), verified behavior-preservation by both reading and mutation, verified the declined literal ask is a sound falsifiable record, and mutation-confirmed case C is non-vacuous.No findings. Clean refactor-for-testability, honest declined-skip, non-vacuous test add.
B — behavior-preservation, byte-identical + mutation-pinned
The extracted predicate is byte-for-byte the old inline logic under a field rename (
req.Head=branch,req.Base=baseBranch,req.Title/Body=title/body,rolling=o.rollingMode,repo=r):The removed
cfg config.Configparam was dead in the old body (never referenced) — dropping it is a correct bonus cleanup, not a behavior change. The fake-forge unit tests cover every branch — PATCH (existing #42 → UpdatePR w/ that number + title/body, no CreatePR), POST on ErrNotFound / zero-number / read-error (each with UpdatePR-not-called), and one-shot never reads (FindPRByHead 0 times) — withassertOneCreatepinning the exact CreatePR fields. My own mutations on the predicate each red the right test:pr.Number != 0RollingZeroNumber_Postsif rolling→if trueOneShot_PostsWithoutFindHead→BaseassertOneCreateThe declined literal ask (PATCH-through-oracle) — a sound, verified falsifiable record
The decline: the dry-run byte-oracle cannot reach the PATCH branch, so it's covered at library level (B) rather than end-to-end. I verified each leg at source rather than taking the rationale:
forgejo_find_pr_by_head(forgejo-api.sh:577-582) has no test hook: underFORGEJO_API_DRY_RUNitreturn 0with empty stdout (comment: "emit nothing so callers fall through to POST behavior in dry-run"); release-prep.sh:770 captures empty →[[ -n ]]false →forgejo_create_pr. The PATCH branch is structurally unreachable through the oracle, not merely untested.decide_equiv_test.go:29-30carries the exact "high-cost for the marginal byte… covered by internal/decide's unit tests (injected fake)" note. This decline mirrors it.pr_test.go'sRollingExistingPR_Patchesdrives it hermetically with the injected fake.So the skip is not a coverage hole — it relocates an oracle-unreachable branch to the library seam, exactly per precedent. Correct call.
C — the rolling equiv case is non-vacuous
{"rolling-mode dry-run", ["--dry-run","--rolling-mode","--target-version","0.2.0"]}byte-compares bash vs rt through the rolling arm. I proved it actually exercisesselectBranch's rolling branch by mutating the Go-sidereturn "release-prep/rolling"literal (line 283) → onlyrolling-mode_dry-runreds (the other cases stay green — they don't hit that branch). The case genuinely pinsbranch=release-prep/rollingend-to-end. (The comment is honest about what it does not reach — the PATCH branch — pointing at B.)Self-note: my first attempt at that mutation used
replace(...,1)and landed on the comment on line 276, not the return — it printed green (cached), and I nearly took it. The inert-mutation trap (assert the mutant behaves differently); caught by re-running unfiltered when the result came back ambiguously empty, then targeting the return statement.Verdict
APPROVED, head-pinned at
ddb4fc9. The extraction preserves behavior byte-for-byte (read + 3 mutations pinning the guard / rolling-gate / request fields), the deadcfgparam is correctly dropped, the fake-forge covers all five branches including one-shot-never-reads, the declined PATCH-through-oracle skip is a verified falsifiable record (bash structurally emits empty in dry-run; decide precedent real; PATCH covered at library level), and case C is mutation-confirmed non-vacuous. Full suite green (19 pkgs), CI 8/8, no bash touched. A textbook refactor-for-testability. Yours to land.— Surveyor