Documentation
¶
Overview ¶
Package containers provides shared helpers for starting testcontainers with uniform retry behavior. It exists so every container builder in the repo can opt into the same backoff policy instead of each rolling its own.
Container startup flakes for many non-deterministic reasons — Docker daemon cold starts, port conflicts, image pull stalls, transient network blips — and a single attempt is too brittle for a large integration test suite.
Index ¶
- Constants
- Variables
- func DefaultRetryConfig() retry.Config
- func PingUntilReady(tb testing.TB, ctx context.Context, ping func(context.Context) error)
- func Run[C Terminable](tb testing.TB, start func(ctx context.Context) (C, error), ...)
- func SkipIfNotRunning(tb testing.TB)
- func StartWithRetry[C any](ctx context.Context, start func(context.Context) (C, error)) (C, error)
- type Terminable
Constants ¶
const ( // DefaultShutdownTimeout bounds how long Run waits for a container to // terminate. Termination happens on a fresh context, so a test that has // already blown its own deadline still gets its container reaped. DefaultShutdownTimeout = 30 * time.Second )
Variables ¶
var RunningTests = strings.TrimSpace(strings.ToLower(os.Getenv("RUN_CONTAINER_TESTS"))) == "true"
RunningTests reports whether RUN_CONTAINER_TESTS=true is set in the environment. Container-backed tests across the repo should gate on this (typically via `if !containers.RunningTests { t.SkipNow() }`) so a default `go test ./...` does not require a Docker daemon. The variable is read once at package init.
Functions ¶
func DefaultRetryConfig ¶
DefaultRetryConfig returns the retry.Config used by StartWithRetry. Callers that need bespoke retry behavior can start from this and tweak individual fields before calling retry.NewExponentialBackoffPolicy themselves.
func PingUntilReady ¶
PingUntilReady calls ping until it succeeds, failing tb if it never does.
A container's readiness log is not the same event as its server accepting connections. Both the postgres and MySQL entrypoints run an init pass against a temporary server and then restart, and MySQL's real server logs a readiness line from the X plugin before the one it logs for port 3306 — so a wait strategy counting log occurrences can release the test at any of several moments, one of which is a socket that is about to close. What that looks like downstream is a "bad connection" or "unexpected EOF" from the very first statement, on a container that is perfectly healthy a second later.
Retrying the first ping is the fix that does not depend on reading logs: whichever occurrence the wait strategy matched, the pool is not handed to a test until a query has actually round-tripped. database/sql discards a connection that failed this way, so each attempt dials anew.
func Run ¶
func Run[C Terminable](tb testing.TB, start func(ctx context.Context) (C, error), fn func(ctx context.Context, container C))
Run starts a container and hands it to fn, owning everything around the closure so the test body only has to say what it wants done with a live container. It is to container-backed tests what database.RunInTransaction is to transactions: the caller supplies the work, the helper supplies the lifecycle.
Everything a container-backed test in this repo has to remember is handled here:
- the RUN_CONTAINER_TESTS gate, so a bare `go test ./...` skips instead of demanding a Docker daemon.
- startup via StartWithRetry, so the shared backoff policy applies, and a startup failure fails the test rather than yielding a nil container.
- termination, once, whatever fn does — return, t.Fatal, or panic.
fn receives the container itself along with tb.Context(); it is not handed a shutdown closure, because it does not own shutdown.
Termination is registered with tb.Cleanup rather than deferred until fn returns. That distinction is load-bearing: a closure that registers parallel subtests returns *before* those subtests execute, and a deferred Terminate would pull the container out from under them.
The flip side is that the container lives until the end of tb, not the end of fn, so call Run from the narrowest test that needs the container rather than hoisting it up to a parent that runs unrelated work afterwards.
func SkipIfNotRunning ¶
SkipIfNotRunning skips the current test or benchmark (via SkipNow) when RunningTests is false. It is the one-line equivalent of `if !containers.RunningTests { tb.SkipNow() }` that every container-backed test and benchmark in the repo needs. It accepts testing.TB so both *testing.T and *testing.B can use it.
func StartWithRetry ¶
StartWithRetry invokes start with exponential backoff retry on failure. It is a thin wrapper over the retry package so that every container builder in the repo gets the same backoff policy for free.
The callback receives the same ctx that was passed in, and is expected to return the concrete container type from its module's Run function (e.g. *postgres.PostgresContainer, *redis.RedisContainer). Callers handle the error themselves — typically via must.NoError(t, err) — so that this helper stays decoupled from the testing package.
Types ¶
type Terminable ¶
type Terminable interface {
Terminate(ctx context.Context, opts ...testcontainers.TerminateOption) error
}
Terminable is the teardown half of the testcontainers container API — the only thing Run needs in order to own a container's lifecycle. Every module container type (*postgres.PostgresContainer, *redis.RedisContainer, …) satisfies it, as does testcontainers.Container itself.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package mysqltest provides the MySQL testcontainer setup that every MySQL-backed suite in this repo would otherwise hand-roll: start the container with the shared retry policy and wait strategy, open a go-sql-driver pool against it, ping it, and tear all of it down afterwards.
|
Package mysqltest provides the MySQL testcontainer setup that every MySQL-backed suite in this repo would otherwise hand-roll: start the container with the shared retry policy and wait strategy, open a go-sql-driver pool against it, ping it, and tear all of it down afterwards. |
|
Package pgtest provides the postgres testcontainer setup that every postgres-backed suite in this repo would otherwise hand-roll: start the container with the shared retry policy and wait strategy, open a pgx-backed *sql.DB against it, ping it, and tear all of it down afterwards.
|
Package pgtest provides the postgres testcontainer setup that every postgres-backed suite in this repo would otherwise hand-roll: start the container with the shared retry policy and wait strategy, open a pgx-backed *sql.DB against it, ping it, and tear all of it down afterwards. |
|
Package redistest provides a single source of truth for the redis testcontainer setup that the redis-backed test suites in this repo all duplicate.
|
Package redistest provides a single source of truth for the redis testcontainer setup that the redis-backed test suites in this repo all duplicate. |