engdocs/TESTING.md
TESTING.md is the single authority for test commands, test selection, and
test design in this repository. Use the commands here for local work. For the
exact command behind a current CI check, inspect its workflow and corresponding
Makefile target; the CI audit and cleanup plan are dated maintainer context.
Test at the lowest seam that can fail for the user-visible reason. Add a higher tier only when it covers a distinct risk that the lower tier cannot show: integration wiring, a real persistence property, a process boundary, or an external contract.
This keeps feedback fast and failures readable. It does not mean every test is a unit test: use the real boundary when the defect could live there.
| Need | Run | When |
|---|---|---|
| Docs-only validation | git diff --check, go test -tags=gms_pure_go ./test/docsync, and ./scripts/check-doc-freshness.sh | For prose-only changes; add any generated-doc or surface-specific link check the changed paths require. Do not run the full Go suite merely because a Markdown file changed. |
| Focused red/green loop | ./scripts/test.sh -run '^TestExactName$' ./path/to/package/... | While writing or fixing one behavior. |
| Affected-package confidence | ./scripts/test.sh ./path/to/package/... | After the focused test passes; include directly affected neighbors when their contract changed. |
| Final Go baseline | make test | Once after focused work on Go code is green. It applies the normal local build flags, coverage, and local skip handling. |
| Named CI wrapper | make ci-pr-core, make ci-pr-policy, or make ci-pr-lint | Run the wrapper whose risk or surface is affected, or use it to reproduce that CI check. Do not run all three routinely for every edit. |
Do not replace the focused loop with repeated full-suite runs. Run the final
make test once the affected Go tests are green. For docs-only changes, use
the docs, link, and diff checks instead.
./scripts/test.sh is the normal runner. It sources .buildflags, creates an
isolated test environment, applies .test-skip, and defaults to a 25m
per-package Go-test timeout. The timeout is a hang backstop, not a target
runtime. Override it only when diagnosing a legitimate slow path:
TEST_TIMEOUT=30m ./scripts/test.sh ./cmd/bd/...
TEST_VERBOSE=1 ./scripts/test.sh ./cmd/bd/...
TEST_RUN='^TestExactName$' ./scripts/test.sh ./cmd/bd/...
# Equivalent command-line options.
./scripts/test.sh -v -run '^TestExactName$' ./cmd/bd/...
./scripts/test.sh -timeout 30m ./cmd/bd/...
Use the opt-in ICU regex path only when the change requires it:
make test-icu-path
It is maintainer-only and not part of normal validation. make test-full-cgo
and ./scripts/test-cgo.sh remain deprecated compatibility aliases.
Use a named specialized target only when its risk is in scope:
make test-regression
make test-upgrade
make test-cross-version
make test-migration
For a failing GitHub Actions check, follow the current workflow and its
Makefile target when exact reproduction matters. The local runner and CI
intentionally have different contracts in some cases.
The runner isolates HOME, Git configuration, and Dolt state. By default its
test environment adds dolt to BEADS_TEST_SKIP; set
BEADS_TEST_ENV_RUN_DOLT=1 only when deliberately exercising the Dolt path
and its prerequisites are available. Do not make ordinary tests depend on a
developer's database, daemon, global Git configuration, or filesystem state.
To skip an optional service explicitly, use the existing skip mechanism:
BEADS_TEST_SKIP=dolt ./scripts/test.sh ./...
Tests that need a temporary repository or store should use t.TempDir() and
t.Cleanup(). Temporary repositories must set a repository-local hooks path;
do not inherit the developer's global hooks configuration.
For manual CLI experiments, run both initialization and subsequent commands from a disposable working directory:
beads_manual_dir="$(mktemp -d)"
(
set -e
cd "$beads_manual_dir"
bd init --quiet --prefix test --skip-hooks --skip-agents
bd create "Test issue" -p 1
)
rm -rf -- "$beads_manual_dir"
BEADS_DB selects a database for database-opening commands, but it does not by
itself redirect bd init workspace setup. Never run a manual bd init from a
production workspace merely because BEADS_DB points elsewhere.
testing.Short() is for genuine runtime, stress, or large-fixture skips. It
is not a substitute for declaring an integration, end-to-end, API, Docker, or
external-dependency boundary. Keep new uses within the repository policy:
make check-testing-short
Write one scenario at the smallest seam that demonstrates the behavior. Cover the boundary or failure mode that changes the user result; use table-driven subtests when examples share setup. Do not repeat the same scenario through a helper, every caller, and the CLI merely because all are available.
A recording double or fake should be narrow: model only the calls, inputs, outputs, and failures the test needs. It should not recreate a storage engine, process manager, or another subsystem just to make a unit test look realistic.
A behavioral fake is different. If it stands in for a contract shared by multiple production implementations, give it the same semantic-conformance suite as those implementations. That shared suite defines observable behavior; it prevents the fake from teaching callers a contract production code does not honor.
Semantic conformance asks whether an implementation produces the promised results, errors, and state transitions for the same operation. Persistence conformance asks whether the real persistence boundary preserves its required durability, transaction, migration, and recovery properties. They answer different questions. Do not claim backend parity unless a stated contract and its conformance suite establish it.
An integration test belongs above the unit seam only when it covers a distinct boundary that a narrow double cannot prove, such as configuration wiring, a real filesystem or Git interaction, a subprocess protocol, or persistence behavior.
An end-to-end test is admitted only when all of these are true:
Lower-tier coverage of the same user journey does not disqualify the end-to-end test; duplicate coverage of the same boundary risk does. State that risk in the test name or nearby documentation.
Avoid these patterns unless they are the behavior under test:
Sleeps, listeners, and real stores are appropriate when the test is specifically about timing, lifecycle, protocol, or persistence. Keep the setup scoped and make that reason apparent.
.test-skip is a local, temporary exception list. If an unrelated failure is
already listed, report it rather than silently broadening the skip. Before
adding a new skip, record the issue it tracks and remove the skip when the
underlying failure is fixed.
Before opening a PR:
make test.For historical CI inventory and maintainer planning context, consult
CI_TEST_SURFACE_AUDIT.md and
CI_CLEANUP_PLAN.md. For current commands, use the
workflow files and Makefile; these context documents are not a second testing
guide or a live CI inventory.