6.9 KiB
| title | description |
|---|---|
| Development Guide | How to build, test, benchmark, and contribute to go-store. |
Development Guide
Prerequisites
- Go 1.26 or later
- No CGO required (
modernc.org/sqliteis a pure-Go SQLite implementation) - No external tools beyond the Go toolchain
Build and Test
The package is a standard Go module. All standard go commands apply.
# Run all tests
go test ./...
# Run with the race detector (required before any commit touching concurrency)
go test -race ./...
# Run a single test by name
go test -v -run TestEvents_Watch_Good_SpecificKey ./...
# Run tests with coverage
go test -cover ./...
# Generate a coverage profile and view it in the browser
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Run benchmarks
go test -bench=. -benchmem ./...
# Run a specific benchmark
go test -bench=BenchmarkSet -benchmem ./...
Alternatively, if the core CLI is available:
core go test
core go cov --open # generates and opens coverage HTML
core go qa # fmt + vet + lint + test
Coverage target: 95%. The remaining uncovered lines are defensive error paths (scan errors, rows iteration errors on corrupted databases) covered by coverage_test.go. Do not remove these checks to chase coverage -- they protect against driver and OS-level failures that are unreachable through integration tests against a healthy SQLite database.
Test Patterns
Tests follow the Test<File>_<Function>_<Good|Bad|Ugly> convention used across the Core Go ecosystem:
_Good-- happy-path behaviour, including edge cases that should succeed_Bad-- expected error conditions (closed store, invalid input, quota exceeded)_Ugly-- not currently used in this package (reserved for panic/edge cases)
Tests are grouped into sections by the method under test, marked with comment banners:
// ---------------------------------------------------------------------------
// Watch -- specific key
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_SpecificKey(t *testing.T) { ... }
func TestEvents_Watch_Good_WildcardKey(t *testing.T) { ... }
In-Memory vs File-Backed Stores
Use New(":memory:") for all tests that do not require persistence. In-memory stores are faster and leave no filesystem artefacts.
Use core.Path(t.TempDir(), "name.db") for tests that verify WAL mode, persistence across open/close cycles, or concurrent writes. t.TempDir() is cleaned up automatically at the end of the test.
TTL Tests
TTL expiry tests require short sleeps. Use the minimum duration that reliably demonstrates expiry (typically 1ms TTL + 5ms sleep). Do not use sleeps as synchronisation barriers for goroutines -- use sync.WaitGroup instead.
Assertion Library
The test suite uses github.com/stretchr/testify/assert and github.com/stretchr/testify/require. Use require for preconditions (test should abort on failure) and assert for verifications (test should continue and report all failures).
Race Detector
Run go test -race ./... before marking any concurrency work as complete. The test suite must be clean under the race detector.
Benchmarks
The benchmark suite covers the major operations. Reference results (Apple M-series, in-memory store):
BenchmarkSet-32 119280 10290 ns/op 328 B/op 12 allocs/op
BenchmarkGet-32 335707 3589 ns/op 576 B/op 21 allocs/op
BenchmarkGetAll-32 (10K keys) 258 4741451 ns/op 2268787 B/op 80095 allocs/op
BenchmarkSet_FileBacked-32 4525 265868 ns/op 327 B/op 12 allocs/op
Derived throughput:
| Operation | Approximate ops/sec | Notes |
|---|---|---|
In-memory Set |
97,000 | |
In-memory Get |
279,000 | |
File-backed Set |
3,800 | Dominated by fsync |
GetAll (10K keys) |
211 | ~2.3 MB allocated per call |
Additional benchmarks in bench_test.go cover:
GetAllwith varying group sizes (10, 100, 1,000, 10,000 keys)- Parallel
Set/Getthroughput (b.RunParallel) Counton a 10,000-key groupDeletethroughputSetWithTTLthroughputRenderwith 50 keys and a 3-variable template
GetAll allocations scale linearly with the number of keys (one map entry per row). Applications fetching very large groups should consider pagination at a higher layer or restructuring data into multiple smaller groups.
Coding Standards
Language
Use UK English throughout all documentation, comments, and error messages. This applies to spelling (colour, organisation, behaviour, serialise, initialise) and terminology. American spellings are not acceptable.
Code Style
gofmtformatting is mandatory. Rungo fmt ./...before committing.go vet ./...must report no warnings.- All error strings begin with the package function context:
"store.Method: what failed". This convention makes errors self-identifying in log output without requiring a stack trace. - Exported identifiers must have Go doc comments.
- Internal helpers (unexported) should have comments explaining non-obvious behaviour.
Dependencies
go-store is intentionally minimal. Before adding any new dependency:
- Verify it cannot be replaced with a standard library alternative.
- Verify it is pure Go (no CGO) to preserve cross-compilation.
- Verify it has a compatible open-source licence (EUPL-1.2 compatible).
The only permitted runtime dependency is modernc.org/sqlite. Test-only dependencies (github.com/stretchr/testify) are acceptable.
Adding a New Method
- Implement the method on
*Storeinstore.go(orscope.goif it is namespace-scoped). - If it is a mutating operation, call
s.notify(Event{...})after the successful database write. - Add a corresponding delegation method to
ScopedStoreinscope.gothat prefixes the group. - Write tests covering the happy path, error conditions, and closed-store behaviour.
- Update quota checks in
checkQuotaif the operation affects key or group counts. - Run
go test -race ./...andgo vet ./.... - Update
docs/architecture.mdif the method introduces a new concept or changes an existing one.
Commit Guidelines
Use conventional commit format:
type(scope): description
Common types: feat, fix, test, docs, refactor, perf, chore.
Examples:
feat(store): add PurgeExpired public method
fix(events): prevent deadlock when callback calls store methods
test(scope): add quota enforcement for new groups
docs(architecture): document WAL single-connection constraint
perf(store): replace linear watcher scan with index lookup
Every commit must include the co-author trailer:
Co-Authored-By: Virgil <virgil@lethean.io>
All tests must pass before committing:
go test -race ./...
go vet ./...
Licence
go-store is licensed under the European Union Public Licence 1.2 (EUPL-1.2). All contributions are made under the same licence.