Skip to content

test: adopt table-driven t.Run subtests as house convention#15

Merged
drogers0 merged 3 commits into
mainfrom
table-driven-tests
Jun 1, 2026
Merged

test: adopt table-driven t.Run subtests as house convention#15
drogers0 merged 3 commits into
mainfrom
table-driven-tests

Conversation

@drogers0

@drogers0 drogers0 commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Adopts table-driven tests with t.Run subtests as the house test convention (per issues/table-driven-test-convention.md) and documents it in a new TESTING.md (linked from CLAUDE.md).

What changed

Churn-only — no production code or behavior changed. Every assertion and verbatim error-string contract is preserved byte-for-byte; executed-case (true-leaf) counts are unchanged across all 12 packages.

Three commits, each independently green:

  1. Migrate — collapse standalone func TestXxx_Case into table-driven parents (data tables for homogeneous clusters, closure tables for heterogeneous families). Top-level test funcs 555 → 146; executed cases 524 → 552 (the +28 are pre-existing bare-loop tables that gained per-case t.Run names).
  2. Reduce boilerplate — shared testutil helpers (MemStore, CountingServer, RejectServer) + per-package assertion helpers + a runFetch(fetchOpts{}) fixture defaulting the usage/profile/refresh stub triplet; cmd/aistat wantExit/wantOut/wantErrOut.
  3. Dedupe + externalize — shared testutil.WantNoErr/WantErrIs/WantErrContains; move large exact-equality golden output to testdata/*.golden via LoadFixture.

Footprint

Stage non-blank test LOC
baseline (main) 12,040
after migration 12,822
final 12,037 (−3, +~40 lines testdata)

Net flat on lines while gaining full t.Run coverage, 552 named subtest cases, and one source of test helpers.

Convention doc

New TESTING.md documents the idiom (slice tests, loop tt, name field, t.Run), the data-vs-closure-table rules, "cluster don't over-merge", and the verification approach.

Verification

go test ./..., -race, go vet, staticcheck — all green on darwin and linux. Cross-GOOS go test -c compiles for the build-tagged packages, FreeBSD gate for the !darwin && !linux files, -tags=fake (test + smoke) and -tags=live compile. Faithfulness verified via per-package leaf-count parity, string-literal contract audits, and multiple reviewer passes.

Notes for reviewers

  • internal/accounts keeps its inline helpers: its white-box package accounts tests can't import testutil (which imports accounts) without an import cycle.
  • Failure-mode normalizations (no pass/fail impact): cmd/aistat stdout/stderr Contains checks use Errorf via wantOut/wantErrOut (a few were Fatalf); ~16 errors.Is sentinel checks in claude/codex use Fatalf via WantErrIs (a strictness escalation that aborts before a wrong-error-type cascade).

drogers0 added 3 commits June 1, 2026 14:47
Adopt table-driven tests with t.Run subtests as the house convention.
Collapse standalone `func TestXxx_Case` functions into table-driven
parents: data tables for homogeneous clusters, closure tables for
heterogeneous families. Coverage is unchanged — every case is preserved
as a subtest row (top-level test funcs 555 -> 146; executed cases
524 -> 552, the +28 from pre-existing bare-loop tables that gained
per-case t.Run names).

No production code or behavior changed. Asserted values and verbatim
error-string contracts are preserved byte-for-byte. Setup stays inside
each subtest; no t.Parallel added.

Document the convention in TESTING.md and link it from CLAUDE.md.
Reduce the test footprint introduced by the table-driven migration by
factoring out repeated scaffolding. No behavior change; every assertion,
expected value, and error-string contract is preserved (leaf counts
unchanged: claude 93, codex 107, copilot 33, cmd/aistat 89).

- testutil: add MemStore, CountingServer, RejectServer (hoisted from the
  byte-identical per-package stub helpers, now deleted).
- claude/codex: replace NewMemoryStore()+Upsert loops with MemStore;
  add wantNoErr/wantErrIs/wantAccounts (+wantWarn) assertion helpers;
  add a runFetch(fetchOpts{}) fixture that defaults the usual usage/
  profile/refresh stub triplet so rows set only what differs.
- cmd/aistat: add wantExit (Fatalf, matches prior) / wantOut / wantErrOut
  CLI assertion helpers; adopt MemStore.
- copilot: wantNoErr.

Net -568 lines vs the migration commit; the suite is now +192 lines over
the pre-migration baseline while fully on the t.Run convention.
Second footprint pass on top of the table-driven migration:

- testutil: add WantNoErr/WantErrIs/WantErrContains (shared error
  assertions) and adopt across cred (-96), httpx, claude, codex,
  copilot, usagecache, orchestrate, multiaccount, cmd/aistat; delete the
  duplicated per-package wantNoErr/wantErrIs. accounts is left alone (its
  white-box `package accounts` tests can't import testutil — cycle).
- render/orchestrate: externalize the large exact-equality golden output
  blocks to testdata/*.golden, read via testutil.LoadFixture. Byte-exact
  (verified) and every comparison preserved.

No behavior change: leaf counts unchanged across all packages; asserted
values/error-string contracts preserved byte-for-byte. Failure-mode note:
~16 errors.Is sentinel checks in claude/codex move t.Errorf -> t.Fatalf
(via the shared WantErrIs), a strictness escalation that aborts before a
wrong-error-type cascade; no pass/fail outcome changes.
@drogers0 drogers0 merged commit 484c005 into main Jun 1, 2026
2 checks passed
@drogers0 drogers0 deleted the table-driven-tests branch June 1, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant