diff --git a/db/migration_testcontainers_test.go b/db/migration_testcontainers_test.go index 241962c..988465c 100644 --- a/db/migration_testcontainers_test.go +++ b/db/migration_testcontainers_test.go @@ -13,9 +13,7 @@ import ( _ "github.com/lib/pq" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" - "github.com/testcontainers/testcontainers-go/wait" ) //go:embed migrations_test @@ -30,9 +28,7 @@ func startPostgresForMigrations(t *testing.T) *postgres.PostgresContainer { postgres.WithDatabase("testdb"), postgres.WithUsername("testuser"), postgres.WithPassword("testpass"), - testcontainers.WithWaitStrategy( - wait.ForListeningPort("5432/tcp").WithStartupTimeout(60*time.Second), - ), + postgresReady(), ) require.NoError(t, err, "Failed to start PostgreSQL container") return container diff --git a/db/otel_integration_test.go b/db/otel_integration_test.go index 8b9d4ca..3f23574 100644 --- a/db/otel_integration_test.go +++ b/db/otel_integration_test.go @@ -12,9 +12,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" - "github.com/testcontainers/testcontainers-go/wait" noopl "go.opentelemetry.io/otel/log/noop" noopm "go.opentelemetry.io/otel/metric/noop" sdkmetric "go.opentelemetry.io/otel/sdk/metric" @@ -36,9 +34,7 @@ func TestPostgresPoolWithOTelTracing(t *testing.T) { postgres.WithUsername("testuser"), postgres.WithPassword("testpass"), postgres.WithInitScripts(filepath.Join("..", "scripts", "compose", "pg", "backup", "default.sql")), - testcontainers.WithWaitStrategy( - wait.ForListeningPort("5432/tcp").WithStartupTimeout(60*time.Second), - ), + postgresReady(), ) require.NoError(t, err, "Failed to start PostgreSQL container") defer func() { @@ -209,9 +205,7 @@ func TestPostgresPoolWithOTelMetrics(t *testing.T) { postgres.WithUsername("testuser"), postgres.WithPassword("testpass"), postgres.WithInitScripts(filepath.Join("..", "scripts", "compose", "pg", "backup", "default.sql")), - testcontainers.WithWaitStrategy( - wait.ForListeningPort("5432/tcp").WithStartupTimeout(60*time.Second), - ), + postgresReady(), ) require.NoError(t, err, "Failed to start PostgreSQL container") defer func() { @@ -287,9 +281,7 @@ func TestPostgresPoolMetricsWithoutTracing(t *testing.T) { postgres.WithDatabase("testdb"), postgres.WithUsername("testuser"), postgres.WithPassword("testpass"), - testcontainers.WithWaitStrategy( - wait.ForListeningPort("5432/tcp").WithStartupTimeout(60*time.Second), - ), + postgresReady(), ) require.NoError(t, err, "Failed to start PostgreSQL container") defer func() { @@ -369,9 +361,7 @@ func TestPostgresPoolWithOTelDisabled(t *testing.T) { postgres.WithUsername("testuser"), postgres.WithPassword("testpass"), postgres.WithInitScripts(filepath.Join("..", "scripts", "compose", "pg", "backup", "default.sql")), - testcontainers.WithWaitStrategy( - wait.ForListeningPort("5432/tcp").WithStartupTimeout(60*time.Second), - ), + postgresReady(), ) require.NoError(t, err, "Failed to start PostgreSQL container") defer func() { @@ -539,9 +529,7 @@ func TestOTelCallbacksWithoutContext(t *testing.T) { postgres.WithUsername("testuser"), postgres.WithPassword("testpass"), postgres.WithInitScripts(filepath.Join("..", "scripts", "compose", "pg", "backup", "default.sql")), - testcontainers.WithWaitStrategy( - wait.ForListeningPort("5432/tcp").WithStartupTimeout(60*time.Second), - ), + postgresReady(), ) require.NoError(t, err, "Failed to start PostgreSQL container") defer func() { @@ -682,9 +670,7 @@ func TestOTelCallbacksTableAndRowsAffected(t *testing.T) { postgres.WithUsername("testuser"), postgres.WithPassword("testpass"), postgres.WithInitScripts(filepath.Join("..", "scripts", "compose", "pg", "backup", "default.sql")), - testcontainers.WithWaitStrategy( - wait.ForListeningPort("5432/tcp").WithStartupTimeout(60*time.Second), - ), + postgresReady(), ) require.NoError(t, err, "Failed to start PostgreSQL container") defer func() { diff --git a/db/pool_testcontainers_test.go b/db/pool_testcontainers_test.go index f46e91d..22ce326 100644 --- a/db/pool_testcontainers_test.go +++ b/db/pool_testcontainers_test.go @@ -33,9 +33,7 @@ func setupPostgresContainer(t *testing.T) (*postgres.PostgresContainer, *Connect postgres.WithUsername("testuser"), postgres.WithPassword("testpass"), postgres.WithInitScripts(filepath.Join("..", "scripts", "compose", "pg", "backup", "default.sql")), - testcontainers.WithWaitStrategy( - wait.ForListeningPort("5432/tcp").WithStartupTimeout(60*time.Second), - ), + postgresReady(), ) require.NoError(t, err, "Failed to start PostgreSQL container") diff --git a/db/testcontainers_wait_test.go b/db/testcontainers_wait_test.go new file mode 100644 index 0000000..41c288f --- /dev/null +++ b/db/testcontainers_wait_test.go @@ -0,0 +1,37 @@ +//go:build integration + +package db + +import ( + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// postgresReady is the readiness strategy every Postgres test container must +// use. +// +// Waiting only on the mapped port is not enough. The postgres image runs initdb +// against a temporary server, stops it, then starts the real one — so the +// container passes a port check while the server is on its way down, and the +// connection that follows fails with "connection reset by peer" or "unexpected +// EOF" rather than a timeout. The window is invisible on an idle machine and +// opens up under CI load, which is exactly how it surfaced: two different db +// tests failing on two consecutive runs, each ~10s in, well inside the 60s +// deadline they had. +// +// The occurrence-2 log check is what closes it — the readiness line is printed +// once by the init server and once by the real one. This mirrors +// postgres.BasicWaitStrategies(), whose own source warns these tests "will be +// flaky" on macOS without the port check kept alongside it; we keep both and +// set an explicit deadline with headroom for a loaded runner. +// +// Use this for every Postgres container in the package. MySQL and MSSQL are +// unaffected: they already wait on a real query via wait.ForSQL. +func postgresReady() testcontainers.CustomizeRequestOption { + return testcontainers.WithWaitStrategyAndDeadline(90*time.Second, + wait.ForLog("database system is ready to accept connections").WithOccurrence(2), + wait.ForListeningPort("5432/tcp"), + ) +}