Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing Conventions

Audience: contributors writing tests, regenerating goldens, or trying to figure out which CI gate to run locally before pushing.

From CLAUDE.md, Non-Skippable CI Gates and The Update Demand.

Style

  • Table-driven tests are the default. Put cases in a []struct{...} with a name field, run with t.Run(tc.name, func(t *testing.T)).
  • Hermetic by construction: anything that touches the filesystem uses fs.NewMemMap() so tests don’t depend on disk state.
  • New code lands with tests in the same PR — TDD first, then implementation. A test that passes without the implementation is suspicious; the test is probably wrong.

Running tests

# Full suite
go test ./...

# Single package
go test ./internal/processing/...

# Verbose, specific test
go test ./internal/service/... -v -run TestProcess

# Coverage report
make cover

# Fuzz the .pulse header
go test ./encoding/... -fuzz FuzzPulseFileHeader -fuzztime 30s

Non-skippable CI gates

These tests guard structural invariants. If one of them fails, the underlying conventions (not the test) are what need re-thinking. Their full names appear in CLAUDE.md so the TestClaudeMdMentionsAllNonSkippableGates self-check can find them.

GateGuards
TestPredictNoExecutionImportsinternal/descriptor/predict.go does not import internal/service/ or internal/processing/
TestDescriptorNoFmtSprintfdescriptor/ + internal/descriptor/ never build JSON via fmt.Sprintf
TestGoldensNotHandEditeddescriptor/testdata/* hashes match the generator
TestClaudeMdMentionsFormatVersionCLAUDE.md references the current envelope format_version
TestClaudeMdMentionsAllEnvVarsEvery PULSE_* env var has a CLAUDE.md row
TestClaudeMdMentionsAllNonSkippableGatesThis very table is the source — CLAUDE.md must list every gate by name
TestUpdateDemandTableCoversThe Update Demand table covers every registered component category
TestPerPackageCoverageFloorsPackage directories exist and meet documented coverage floors
TestNoOrbitReferences, TestNoOrbitPrefix, TestNoOrbitPrefixesNo predecessor-project string prefixes leak in
TestSkillsCoverAll*Skill files mention every registered component, error code, distribution, CLI leaf, field type, MCP tool
TestSkillsManifestConsistentEvery skill’s applies_to frontmatter names only real CLI leaves (there is no skills/index.json — the embed.FS walk is the manifest)
TestSkillsFrontmatter_RequiredFieldsEvery skill has name, description, type, applies_to
TestRegistryStreamabilityMatchesTypesAggregator OnlineAggregator capability matches AggregationType.Streamable()
TestPredict_Streamable_MatchesRuntimePredictResult.Streamable mirrors internal/processing’s CanStreamRequest
TestStreamability_*KnownEvery All*Types() entry has a streamability table row
TestCanStreamRequest_RegressionMatrixRegression matrix on the exported CanStreamRequest helper
TestManifest*CompleteManifest enumerates every registered operator, test, distribution, MCP tool, error code
TestManifestStreamableMatchesTypesManifest Streamable flags mirror the type-level methods
TestCodesHaveFixups, TestSkillsErrorCodeFixupsDocumentedEach error code has a fixup template and the skill row to match
TestDefaults_AppliedSmart-default operator-type inference behaves as documented

(See CLAUDE.md “Non-Skippable CI Gates” for the full prose; this table is the quick-reference.)

Running a subset of gates locally

# All descriptor contract gates
go test ./descriptor/ ./internal/descriptor/ -run 'TestPredictNoExecution|TestDescriptorNoFmtSprintf|TestGoldensNotHandEdited'

# Skill coverage gates
go test ./internal/skills/ -run 'TestSkillsCoverAll|TestSkillsManifestConsistent|TestSkillsFrontmatter'

# CLAUDE.md gates
go test . -run 'TestClaudeMd|TestUpdateDemandTable'

# Predecessor-reference scrub
go test . -run TestNoOrbitReferences

Regenerating golden files

Golden files live in descriptor/testdata/. Each ends with a // golden-hash: <sha256> line; TestGoldensNotHandEdited verifies the hash. After a legitimate change to the generator:

go test ./descriptor/ -run 'Test.*Golden' -update
go test ./descriptor/ -run TestGoldensNotHandEdited   # confirms the new hash sticks

Never hand-edit a golden file — the gate will catch you.

Adding a new gate

If your change introduces a structural invariant, add a test for it under the same naming convention (TestX), and add it to the table in CLAUDE.md so TestClaudeMdMentionsAllNonSkippableGates recognises it. The Update Demand lists this as a trigger row.