diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11c36518..aa72a1d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,18 @@ jobs: with: go-version-file: go.mod + test-race: + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + CGO_ENABLED: "1" # Required by Go's race detector. + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + - uses: open-cli-collective/.github/actions/go-test@b6382514b809d960c6fbdc85745a60c14cde04f1 # shared .github + with: + go-version-file: go.mod + target: test-race + test-no1password-optout: runs-on: ubuntu-latest env: diff --git a/Makefile b/Makefile index 97f52efa..f851f2a1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build test test-cover test-static-smoke test-pi-runtime pi-runtime-version check-pi-runtime-version lint fmt tidy deps check install snapshot package-render-check release clean +.PHONY: all build test test-race test-cover test-static-smoke test-pi-runtime pi-runtime-version check-pi-runtime-version lint fmt tidy deps check install snapshot package-render-check release clean # Standard keyring tags: enable 1Password support, keep passage disabled. GOFLAGS ?= -tags=keyring_nopassage @@ -12,6 +12,11 @@ build: test: go test -v ./... +# Exercise SQLite startup/migration contention repeatedly without racing the +# entire suite. Keep package execution and test scheduling bounded in CI. +test-race: + go test -race -count=25 -cpu=4 -parallel=4 -p=1 -timeout=5m ./internal/ledger/... ./internal/dbmig/... + test-cover: go test -coverprofile=coverage.out ./... diff --git a/docs/development.md b/docs/development.md index fa56b188..40c9a52b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -90,6 +90,7 @@ progress interface rather than importing Cobra or root options. ```bash make build # compile the binary make test # go test ./... +make test-race # repeated race checks for ledger/dbmig make lint # golangci-lint run make tidy # go mod tidy and verify go.mod is unchanged make deps # download and verify Go modules @@ -100,6 +101,27 @@ make test-pi-runtime # installed Pi runtime gate (see Pi Runtime Gate) `make snapshot` runs a local GoReleaser snapshot build without publishing. +### Concurrency checks + +`make test-race` runs all tests in `internal/ledger` and `internal/dbmig` under +the Go race detector with 25 repetitions. This includes concurrent ledger +startup and allocation, the immediate transaction-lock DSN contract, and +migration version re-checks. Repeating the existing contention tests makes +timing-dependent failures more likely to surface without applying race +detection to the entire suite. + +The target sets test `GOMAXPROCS` to four with `-cpu=4` and caps tests using +`t.Parallel` at four with `-parallel=4`. Contention-test worker goroutines +remain bounded by their own fixture counts. Packages run serially, with a +five-minute timeout per package. The target requires +`CGO_ENABLED=1` and a C compiler, even though the SQLite driver itself is +pure Go. CI runs the same target in the separate `test-race` job on pull +requests and pushes to `main`, with a 20-minute job timeout. The job uses the +same pinned shared test action as `test`, including Go version selection from +`go.mod` and the action's default dependency/build caching. Existing required +check names and branch-protection settings are unchanged; requiring the new +check is a separate maintainer decision after it has run successfully. + ## Pi Runtime Gate `make test-pi-runtime` runs the `pi_rpc` adapter against the installed Pi on