Testing Conventions
- Prefer e2e tests for behavior that crosses a process or I/O boundary (CLI flags, config loading, git operations, agent spawning, daemon coordination, stdout/stderr, recorded fixtures); unit-test pure helpers where speed and failure localization matter. Prefer creating real git repos in temp dirs over heavy mocking.
- The e2e suite is behind the
e2ebuild tag;make e2erunsscripts/e2e.sh, which sweeps./internal/e2e/...and./internal/pipeline/steps/..., so keep new step-local e2e tests behind the tag too. - Temporary e2e daemons (
NM_TEST_START_DAEMON=1/ harness) are owned byinternal/e2edaemon: exact inventory, concurrency cap (NM_E2E_DAEMON_MAX, default 2), bounded argv checks, and reapers in harness Cleanup, packageTestMain, andscripts/e2e.shEXIT/INT/TERM. A SIGKILL of the wrapper shell does not run its trap; next-run inventory recovery covers that. External sleep-loop keepalives are out of scope. Never point inventory reaping at the shared~/.no-mistakesservice. Regressions:internal/e2edaemon/*_test.go. - Packages whose tests shell out to git unset
GIT_CONFIG_COUNTinTestMainso ambientGIT_CONFIG_*injection from agent harnesses cannot leak in; a test exercising injected config re-sets it witht.Setenv(seeinternal/git,internal/gate,internal/daemon,internal/pipeline/steps). - Packages whose tests can start a daemon or touch ambient state (
cmd/no-mistakes,internal/cli,internal/update) use a package-wideTestMainthat pointsNM_HOMEandHOMEat fresh temp dirs and disables telemetry/update-check env vars, so a full test run never touches a real~/.no-mistakes. Follow the same pattern in new such packages. paths.New()refuses the default~/.no-mistakesroot undergo test; tests that touch app state must setNM_HOMEto a temp dir, and only the production-default path test may opt in withNO_MISTAKES_ALLOW_DEFAULT_ROOT_IN_TESTS=1.- Isolate filesystem and environment state with
t.TempDir()andt.Setenv(). - The Windows CI leg is process-spawn bound, not compute bound: git-backed packages cost roughly 10x their Linux time (
internal/git5.7s -> 53s,internal/branchsync31s -> 415s). The Windows matrix is split into a git-heavy shard and a core remainder so each job's wall stays insidetimeout-minutes: 40and a hang still surfaces asgo test -timeout(15m) rather than an evidence-free job cancel. Keep long git-heavy packages off the serial critical path (internal/branchsyncrunst.Parallel()for exactly that reason) and keep the Defender scan-exclusion step inci.yml, whose comment owns the rationale. Regressions:TestCIWorkflow_WindowsTestsRunWithScanExclusions,TestCIWorkflow_WindowsHangSurfacesAsGoTimeoutNotJobCancellation. - Go applies an implicit GOOS constraint from a filename suffix, so a test file named
*_windows_test.go(or_linux,_darwin) silently compiles only on that platform. Name platform-agnostic tests about Windows something else. - On macOS a git-heavy package under
-raceintermittently reportsgit <cmd>: signal: segmentation fault. That is not a git or repo bug:~/Library/Logs/DiagnosticReports/*.ipsrecords the crash asprocName: <pkg>.test, parentProc: <pkg>.test, asi: "crashed on child side of fork pre-exec"- the forked child dies beforeexecve. Confirm there before chasing it in Go code; the CI legs are Linux and Windows. The same fork mechanic explains a stray<pkg>.test -test.timeout=...process at high CPU that appears to ignore its own deadline: a pre-exec child inherits the parent's name, argv, and cwd, so it is not a running test binary and no test-side timeout applies to it.internal/procreapreaps those by cwd.