Skip to content

feat(testdb): fast test database creation for Postgres and Spanner#502

Merged
bpross merged 8 commits into
masterfrom
feature/fast-test-db
Jun 29, 2026
Merged

feat(testdb): fast test database creation for Postgres and Spanner#502
bpross merged 8 commits into
masterfrom
feature/fast-test-db

Conversation

@bpross

@bpross bpross commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Add reusable test database helpers in database/testdb that eliminate per-test migration replay.

Postgres: NewPostgresDatabaseFromTemplate

  • Creates isolated test databases by cloning a content-addressed template database that has already been migrated
  • Template is keyed by SHA256 of migration filenames + contents — auto-invalidates when migrations change
  • Uses pg_advisory_lock for cross-process safety
  • Marks template with IS_TEMPLATE=true, ALLOW_CONNECTIONS=false
  • Per test: CREATE DATABASE test... TEMPLATE tmpl... — full isolation, no migration replay

Spanner: NewSpannerDatabaseFromMigrations / CreateSpannerDatabaseFromMigrations

  • Creates a Spanner test database with all migrations applied in a single batched DDL update via UpdateDatabaseDdl
  • Pre-populates spanner_schema_migrations table with latest version so subsequent RunMigrations calls are no-ops (ErrNoChange)
  • Falls back to normal migration if DDL parsing fails
  • CreateSpannerDatabaseFromMigrations is the error-returning variant for use outside testing.TB contexts (e.g. sync.Once.Do)

Shared helpers (migrations.go)

  • migrationFiles: walks embedded fs.FS, filters by suffix, sorts by filename
  • migrationHash: SHA256 of filenames + contents (content-addressed)
  • migrationVersion: extracts version number from last migration filename

Benchmark results

card-gateway (Postgres template DB, 37 packages):

  • Baseline: 78.5s wall time
  • Template: 60.8s wall time (22.5% faster)

moov-money (Spanner fast DDL, 9 packages):

  • Baseline: 85.3s wall time
  • Fast DDL: 70.7s wall time (17.1% faster)

Test plan

  • go build ./... passes
  • go vet ./database/testdb/... passes
  • CI passes

Postgres: NewPostgresDatabaseFromTemplate creates isolated test databases
by cloning a content-addressed template database that has already been
migrated. The template is created once per unique migration set (SHA256
of migration filenames and contents) and reused across all tests in the
process. Uses pg_advisory_lock for cross-process safety and marks the
template with IS_TEMPLATE=true, ALLOW_CONNECTIONS=false.

Spanner: NewSpannerDatabaseFromMigrations and CreateSpannerDatabaseFromMigrations
create a Spanner test database with all migrations applied in a single
batched DDL update via UpdateDatabaseDdl, instead of running each migration
file individually through golang-migrate. The spanner_schema_migrations
table is pre-populated with the latest version so subsequent RunMigrations
calls are no-ops (ErrNoChange). Falls back to normal migration if DDL
parsing fails.

Shared helpers: migrationFiles, migrationHash, and migrationVersion
utilities for walking embedded migration FS, computing content-addressed
hashes, and extracting version numbers from filenames.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces utilities for accelerating test database setup in PostgreSQL and Google Spanner by utilizing template databases and batched DDL migrations. The feedback focuses on critical correctness and robustness issues: first, the PostgreSQL template creation uses session-level advisory locks on a connection pool, which can cause leaked locks or deadlocks and must be refactored to use a dedicated connection; second, several error paths in the Spanner setup fail to clean up the created database, leading to resource leaks; and finally, defensive nil checks should be added to prevent potential panics when the migrations filesystem is not provided.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread database/testdb/postgres_template.go Outdated
Comment thread database/testdb/spanner_fast.go
Comment thread database/testdb/spanner_fast.go
Comment thread database/testdb/spanner_fast.go
Comment thread database/testdb/spanner_fast.go
Comment thread database/testdb/postgres_template.go
Comment thread database/testdb/spanner_fast.go
bpross added 7 commits June 26, 2026 10:09
Pre-existing govulncheck failure in admin.TestAdmin__AddHandler via
idna.ToASCII. Not related to the testdb changes but blocking CI.
Covers migrationFiles, migrationHash, and migrationVersion with
embedded test fixture migrations. Brings testdb package coverage
above 0% to satisfy CI coverage threshold.
Windows CI reads embedded files with CRLF line endings, causing
exact string equality assertions to fail.
Some tests (e.g. TestEnvironment in card-gateway) call
service.NewEnvironment directly without going through CreateTestDatabase.
The old CreateTestDatabase created the service database as a side effect
via openOrCreateDatabase. NewPostgresDatabaseFromTemplate now preserves
that behavior by creating the service database if it doesn't exist.

Also fix nil pointer panic by guarding testEnv.Shutdown in card-gateway.
Use a dedicated Postgres connection for advisory lock acquisition and
release so the session-level lock is not leaked across pooled connections.

Clean up Spanner databases on post-create failures and guard nil migration
filesystems before reading migrations.
Adds unit coverage for service database creation side effects,
content-addressed lock keys, and nil Spanner migration files so short CI
coverage stays above threshold.
Use the advisory lock only for the template lifecycle: check whether the
content-addressed template exists, create and migrate it if missing, then
mark it as a template before unlocking. The per-test clone happens after
unlocking so clone creation is not unnecessarily serialized.
@bpross bpross marked this pull request as ready for review June 29, 2026 15:02
@bpross bpross merged commit 031cbc1 into master Jun 29, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants