Adopt the mandated test target pattern (closes #2) #9
Reference in New Issue
Block a user
Delete Branch "make-test-policy-pattern"
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?
Closes #2.
What changed
Makefile— thetest:target was a barego test $(GO_PKGS). It nowimplements the mandated pattern:
This closes all four divergences named in the issue:
-timeout 30s,-race,-cover, and the conditional verbose rerun. The repo's existing$(GO_PKGS)variable is kept rather than hardcoding
./..., per definition-of-done item 1.The recipe is
@-prefixed so the rerun banner is the only added noise onsuccess, and the rerun branch ends in
exit 1so the target still fails if aflaky test happens to pass on the second attempt.
TODO.md— Completed Steps entry, in the same commit as the work.Nothing else is touched.
.golangci.ymlis byte-identical to canonical(verified
sha256 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcbafter the change). No Dockerfile, CI config, or
script/entrypoints wereadded — this repo is exempt per
TODO.mdFuture Steps note 3. No Go sourcechanged, so game behavior cannot have changed; this is a build-tooling commit
only.
Verification
All verification was through
maketargets only — no directgo test,go build,go vet, orgolangci-lintinvocations, per the issue'simplementation requirements.
make checkis fully green (fmt-check,lint,test),golangci-lintreporting 0 issues.
Timing, against the 20-second policy budget:
make testcold, including the-racebuildmake testuncached, warm build (5 consecutive runs)make checkend to end (fmt-check + lint + test)Comfortably inside the budget; no flag had to be dropped. For reference the
old bare target was 1.0s cold, so
-racecosts roughly 4s of build on a coldcache and about 1.3s of runtime.
The race detector is clean. This is the first time this suite has run
under
-race, and it was the part of the issue flagged as risky — the repohas
os.Exit-path tests and a tcell terminal layer. I ran the suite fiveconsecutive times with the cache defeated (
GOFLAGS=-count=1) and thedetector reported nothing on any run,
TestDeepPlaythroughandTestTurnLoopCrashSweepincluded. Nothing was papered over: no flag dropped,no
//nolintadded, no test skipped.The failure path was exercised, not just eyeballed. I temporarily added a
deliberately failing test, ran
make test, and confirmed the first run failsquietly, the
--- Rerunning with -v for details ---banner fires, the rerunproduces full verbose output (33
=== RUNlines), andmakeexits non-zero.The throwaway test was deleted before the commit; the diff is
MakefileandTODO.mdonly.Note for the reviewer
On the
TODO.mdrotation: I added a Completed Steps entry and deliberatelyleft Next Step unchanged. The documented workflow rotates Next Step into
Completed, but the current Next Step is "broaden unit test coverage", which is
not this work — rotating it would falsely mark it done and would promote a
deferred Future Step into Next. This follows the precedent set by the
golangci-v2.12.2work in63d1e79, which handled out-of-band policy work thesame way. Flagging it explicitly in case you read the rotation rule more
strictly.
The test: target was a bare `go test $(GO_PKGS)`, diverging from the mandated shape in four ways: no -timeout 30s, no -race, no -cover, and no conditional verbose rerun. It now runs go test -timeout 30s -race -cover $(GO_PKGS) and, on failure, reruns with -v and then exits 1 — so the build still fails even if a flaky test happens to pass on the second attempt. The repo's existing $(GO_PKGS) variable is kept rather than hardcoding ./..., and the recipe is @-prefixed so the rerun banner is the only noise. The substance here is -race, not the Makefile edit: this is the first time the suite has run under the race detector. It is clean, across five consecutive uncached runs, including the tcell terminal layer and the os.Exit-path playthrough tests that were the suspected risk. Timing against the 20-second budget: 5.1s cold (including the race build), ~2.3s warm. The failure path was exercised with a throwaway failing test to confirm the verbose rerun fires and make exits non-zero. Build tooling only; no game behavior change. .golangci.yml is untouched.Build and verification summary
Commit
e376b2b, two files, +19/-2. No Go source in the diff.What I changed
Makefile— one target.test:went fromgo test $(GO_PKGS)to themandated pattern with
-timeout 30s -race -cover, plus the conditionalverbose rerun terminated by
exit 1.$(GO_PKGS)retained, not replaced witha hardcoded
./.... The recipe is@-prefixed. The comment above the targetwas extended to say why the
exit 1is there, since that line looksremovable to anyone who does not know the policy reasoning.
TODO.md— one Completed Steps entry, same commit as the work, formattedby
make fmt(prettier reported it unchanged, so the wrapping was alreadycompliant).
How I verified it
Everything through
maketargets. No directgo test/go build/go vet/golangci-lintinvocation at any point, including mid-task checks.make checkgreen —fmt-checkclean,golangci-lint0 issues,testpassing. 8.6s end to end.Timing.
make testcold, including building the race-instrumentedbinary: 5.1s. Uncached with a warm build cache, five consecutive runs:
2.2s, 2.2s, 2.5s, 2.3s, 2.5s. Against the 20s budget this has ample
headroom. The old bare target was 1.0s cold, so
-racecosts about 4s ofone-time build and roughly 1.3s of added runtime.
Race detector clean. This was the flagged risk and the only part of the
change with real uncertainty, so I did not settle for a single green run. I
defeated the test cache with
GOFLAGS=-count=1and ran the suite fivetimes; the detector reported nothing on any run. That includes
TestDeepPlaythroughandTestTurnLoopCrashSweep, which drive the realturn loop, and the
os.Exit-path and tcell terminal-layer tests that werethe suspected trouble spots. No race was found, so nothing needed
suppressing — no flag dropped, no
//nolint, no skipped test.Failure path actually exercised. A Makefile rerun branch that has never
run is an untested branch, so I temporarily added a deliberately failing
test and ran
make test. Confirmed: first run fails without-vnoise, the--- Rerunning with -v for details ---banner fires, the rerun emits fullverbose output (33
=== RUNlines), andmakeexits non-zero(
make: *** [Makefile:37: test] Error 1). The throwaway test was deletedbefore staging;
git statusconfirmed the working tree was clean apartfrom
MakefileandTODO.md.Constraints honored
.golangci.ymluntouched —sha256sumafter the change still reads021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb, matchingcanonical.
script/entrypoints added (exempt perTODO.mdFuture Steps note 3).origin/main, not the sharedcheckout; worktree removed after pushing.
One judgment call worth a reviewer's eye
I left the
TODO.mdNext Step alone instead of rotating it. The workflow atthe top of
TODO.mdsays to move Next Step into Completed Steps, but NextStep is currently "broaden unit test coverage" — not this work. Rotating it
would record an unfinished step as done and would pull a deliberately deferred
Future Step ("tag a release", "full-terminal-size support") into Next. The
golangci-v2.12.2commit63d1e79handled out-of-band policy work the sameway. If you read the rotation rule strictly, say so and I will change it.
Review: PR #9 — verdict PASS
Independent adversarial review of
e376b2bagainst issue #2 andREPO_POLICIES.md. Everything below was re-run by the reviewer in a throwawaygit worktree detached at the PR head; nothing was edited, committed, or pushed,
and the shared clone was left untouched.
Verification performed
1.
make checkgreen from a clean worktree of the PR head. Confirmed.fmt-checkclean (gofmt plus prettier, "All matched files use Prettier codestyle"),
golangci-lint0 issues, tests ok. 10.4s end to end. Note thereviewer host has
golangci-lint2.10.1 rather than the 2.12.2 the authorused; still 0 issues, so the result is not version-contingent in either
direction that matters here.
2.
make testunder 20 seconds. Confirmed, and the author's numbersreproduce. Five consecutive runs with
GOFLAGS=-count=1: 2.27s, 2.24s, 2.31s,2.14s, 2.46s — against the author's reported 2.2s to 2.5s. See finding O-1 for
the cold-cache number, which is worse than the PR body implies but still
inside budget.
3.
-racegenuinely in effect, not silently dropped or shadowed. Proven,not assumed.
make -n testexpands the recipe togo test -timeout 30s -race -cover ./...with-racepresent in both the first run and the rerun. Toprove the flag is actually reaching a race-instrumented binary rather than
merely appearing on the command line, the reviewer injected a deliberate
data-race test into a throwaway package inside the disposable worktree: the
detector fired with
WARNING: DATA RACE(3 reports) and the target failed. Theprobe was then deleted and
git status --porcelainconfirmed empty.The real suite is clean under
-race: noDATA RACEin any of the fiveuncached runs above, nor in the cold run,
TestDeepPlaythroughandTestTurnLoopCrashSweepincluded. The author's claim holds.4. The conditional rerun branch and the trailing
exit 1. Tested by thereviewer, and tested in the form that actually matters. A test that merely
fails twice does not exercise
exit 1at all —makewould fail on the secondrun regardless. The discriminating case is a first-run failure followed by a
rerun that passes, which is precisely the flaky-pass scenario the
exit 1exists to defeat. The reviewer used a probe test that fails on its first
invocation and passes on every later one. Result: the first run failed quietly,
the
--- Rerunning with -v for details ---banner fired, the verbose rerunemitted 33
=== RUNlines and passed completely (every packageok), andmakestill failed withmake: *** [Makefile:37: test] Error 1. Theexit 1does its job. Probe removed afterward.
5. Pattern conformance to
REPO_POLICIES.md. The recipe is byte-equivalentto the policy's Go form (policy lines 210 to 215) with
./...replaced by$(GO_PKGS), as definition-of-done item 1 requires. The@prefix is presenton the first line only, as in the policy. The line continuations and the
{ echo ...; <cmd>; exit 1; }brace group match.$(GO_PKGS)is used in boththe first run and the rerun — confirmed by expansion, not by eyeballing the
diff.
6.
.golangci.ymluntouched.sha256sumat the PR head reads021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb. Matches.The file is not in the diff at all.
7. Scope. Diff is
MakefileandTODO.mdonly, +19/-2. Zero Go source. NoDockerfile, no CI config, no
script/entrypoints, no new tracked dotfiles.Consistent with the exemption in
TODO.mdFuture Steps note 3. The onlyaddition beyond the target itself is a three-line comment above
test:explaining why the
exit 1is there, which is warranted rather than scopecreep: that line looks removable to anyone who does not know the policy
reasoning.
8. No Claude or Anthropic references, no attribution trailers. A
case-insensitive scan of the entire worktree (excluding
.git), the commitmessage, the author and committer identity, and the PR body and comments
returns nothing for
claude,anthropic,co-authored, orclaude.ai.Commit author is
sneak <sneak@sneak.berlin>. Clean.9. Commit title.
build: adopt the mandated test target pattern (closes #2)— ends with
(closes #2). Correct.10. Markdown formatting.
make fmt-checkpasses; prettier reports allmatched files,
TODO.mdincluded, already conformant.CI. The repo has no Actions workflows (
list_workflowsreturns 0) and isexplicitly exempt from CI config, so the head commit has no status to be red.
needs-checksdoes not apply.Mergeability.
e376b2bis a direct descendant oforigin/main(
d6cd418); it fast-forwards with no conflicts.needs-rebasedoes not apply.Ruling on the TODO.md rotation deviation
The author did not rotate Next Step and flagged this explicitly. The
deviation is correct and the reviewer endorses it. Rotating would have been
the defect, not the fix.
The workflow block at the top of
TODO.mdreads "do the work in Next Step /move Next Step to the top of Completed Steps / move the top item of Future
Steps into Next Step". Every clause of that rotation is predicated on the first
one: the work done was the Next Step. This work was not. It came in out of band
from issue #2, and the current Next Step is "broaden unit test coverage where
playtesting finds thin spots (rings, sticks, wizard commands)", which remains
untouched and unfinished. Rotating it would have written a false record into
Completed Steps.
The second-order damage would have been worse. The top Future Step is "tag a
release once a full game (Amulet retrieval and score entry) completes without
defects" — a step gated on a condition that has not been met. Promoting it into
Next Step would have queued work that cannot be started, and would have
displaced the coverage step that is genuinely next.
The precedent cited holds up on inspection.
63d1e79("chore(lint): adoptcanonical golangci-lint config"), which landed on
mainvia PR #1, touchedTODO.mdby adding exactly one Completed Steps entry and left Next Step alone— the same shape as this change. Issue #2's definition-of-done item 5 says
"rotate the step per the repo's documented workflow", and the documented
workflow, read as a whole rather than as three detached bullets, does not
prescribe rotation for work that was not the queued step. Item 5's real
requirement —
TODO.mdupdated in the same commit as the work — is satisfied.Assessment of the flagged risks
-raceagainstos.Exitpaths and the tcell layer: appropriate. Theconcern is reasonable a priori and the author was right to treat it as the
substantive part of the change rather than the Makefile edit. In practice the
detector found nothing across six independent runs here. The
os.Exitpaths inthis repo unwind through the
gameEndpanic recovered inRun(perMEMORY.md) rather than through a realos.Exitthat would truncate thedetector's report, so the exposure is smaller than it first appears. Policy
mandates
-raceregardless, and the correct response to a detector hit wouldhave been to report it, which the author committed to and did not need to do.
Flakiness risk in local runs: low, and structurally bounded. The suite is
deterministic (seeded RNG, scripted sessions, golden files) and the added
runtime under
-raceis roughly 1.3s against a 30s per-package timeout, so thetimeout is nowhere near binding. If a race does surface later it will be a real
finding, not noise. One genuine consequence worth naming:
-racerequires cgoand a host C toolchain, so
make testnow hard-fails on a machine without onewhere it previously worked. That is inherent to the policy and not a defect in
this PR, but it is a new hard dependency.
-covernoise: not a problem. Three summary lines on success (0.0%forcmd/rogueandterm,46.0%forgame). The@prefix already suppressesrecipe echo, so this is the entirety of the added output.
Observations (non-blocking, no rework required)
O-1. The PR body's "cold" timing is not the true worst case. The body and
TODO.mdboth report 5.1s cold "including the-racebuild". That figure is acold test cache with a warm build cache. With a genuinely empty build cache
(
GOCACHEpointed at an empty directory, so the race-instrumented stdlib andtcell are rebuilt from scratch)
make testtakes 16.2s here. That is stillinside the 20s policy budget, which is what the definition of done requires, so
this is not a defect. But the headroom in that scenario is under 4s rather than
the roughly 15s the PR body implies, and a future reader sizing the budget
against "5.1s cold" would be working from a number that does not describe the
empty-cache case. Practical impact is small: the repo has no CI and no
Dockerfile, so a from-scratch build cache is rare. Recording it here so the
figure is on the record.
O-2.
README.mdline 75 still documentsgo test ./game/directly. Notintroduced by this PR, not in scope for issue #2, and not worth a rework cycle.
Noted only so it is not mistaken for something this change should have caught.
Verdict
PASS. The change implements the mandated pattern exactly, every claim in
the PR body was independently reproduced rather than taken on faith, the
failure path and the
exit 1were tested in the discriminating flaky-pass formrather than the trivial one,
-racewas proven live by injected race ratherthan inferred from the command line, scope is tight, and the one deliberate
deviation from the definition of done is the correct call and was disclosed
rather than buried.
Manager notes (review results are in their own comment above).
Verdict accepted: PASS. Labeling
merge-readyand merging directly —mainis unprotected in this repo, so this does not go tosneak.I weighted the review as credible because it verified rather than trusted the
two things that are easiest to fake green:
-racewas proven live by injecting a deliberate data race and observingWARNING: DATA RACE, not by observing that the suite passed.exit 1guard was probed with a fail-then-pass test. A probe thatfails twice would never have discriminated — the target would fail either
way. This is the specific defect I was most worried about and it is now
genuinely ruled out.
I concur with the reviewer's ruling on the TODO.md rotation. The
implementer was right not to rotate, and I want that on the record as a
precedent:
TODO.md's rotation rule presumes the work came from Next Step.Work arriving out of band via an issue adds a Completed Steps entry and leaves
Next Step alone. Rotating here would have recorded the unfinished coverage
step as done and promoted a release step whose precondition is unmet. Issue
#2's definition of done said "rotate the step" — that instruction was mine and
it was wrong for this case; the implementer was correct to flag it rather than
comply silently, and correct to cite
63d1e79.One correction to the review, non-blocking and not a defect in this PR.
The reviewer wrote that the
os.Exitpaths "unwind through thegameEndpanic recovered in
Run". That is not true any more — refactor step 8 removedthe
gameEndunwind;myExitnow callsos.Exit(0)andRun()neverreturns. The reviewer picked this up from
MEMORY.md, which still asserts it.This does not change the verdict (the race-detector evidence was empirical, not
derived from that belief), but it is a nice demonstration of why #3 exists: the
stale
MEMORY.mdclaim misled an otherwise careful reviewer within an hour ofbeing filed. Bumping #3's priority accordingly.
Follow-ups recorded, not silently dropped:
GOCACHEmake testis 16.2s, not 5.1s — insidethe 20s budget but with under 4s of headroom) is noted. Not acting on it:
the repo has no CI or Dockerfile, so a truly cold cache is a rare local
case. If a pin lands via #4 this is worth revisiting.
README.mdline 75 still documents runninggo test ./game/directly) is real and out of scope here. It is the sameclass of problem as #3 — docs telling agents to bypass the make targets,
which now matters more because a raw
go testskips-race. Folding itinto #3 rather than filing a new issue.
-racerequires cgo and a host C toolchainfor
make test. Acceptable for a dev-only Makefile in an exempt repo, butworth knowing.