From e57dd1594206aa8e24ea3c5f2d21b063901a61fb Mon Sep 17 00:00:00 2001 From: Florin Date: Thu, 11 Jun 2026 16:32:41 +0200 Subject: [PATCH 1/3] go testcontainers example --- .../workflows/testing-testcontainers-go.yml | 60 +++++++ testing/testcontainers/go-pgx/README.md | 65 ++++++++ testing/testcontainers/go-pgx/cratedb.go | 105 ++++++++++++ .../go-pgx/function_scope_test.go | 49 ++++++ testing/testcontainers/go-pgx/go.mod | 64 ++++++++ testing/testcontainers/go-pgx/go.sum | 152 ++++++++++++++++++ testing/testcontainers/go-pgx/http_test.go | 62 +++++++ testing/testcontainers/go-pgx/shared_test.go | 106 ++++++++++++ testing/testcontainers/go-pgx/types_test.go | 86 ++++++++++ 9 files changed, 749 insertions(+) create mode 100644 .github/workflows/testing-testcontainers-go.yml create mode 100644 testing/testcontainers/go-pgx/README.md create mode 100644 testing/testcontainers/go-pgx/cratedb.go create mode 100644 testing/testcontainers/go-pgx/function_scope_test.go create mode 100644 testing/testcontainers/go-pgx/go.mod create mode 100644 testing/testcontainers/go-pgx/go.sum create mode 100644 testing/testcontainers/go-pgx/http_test.go create mode 100644 testing/testcontainers/go-pgx/shared_test.go create mode 100644 testing/testcontainers/go-pgx/types_test.go diff --git a/.github/workflows/testing-testcontainers-go.yml b/.github/workflows/testing-testcontainers-go.yml new file mode 100644 index 000000000..ddafc584d --- /dev/null +++ b/.github/workflows/testing-testcontainers-go.yml @@ -0,0 +1,60 @@ +name: Testcontainers for Go + +on: + pull_request: + branches: ~ + paths: + - '.github/workflows/testing-testcontainers-go.yml' + - 'testing/testcontainers/go**' + push: + branches: [ main ] + paths: + - '.github/workflows/testing-testcontainers-go.yml' + - 'testing/testcontainers/go**' + + # Allow job to be triggered manually. + workflow_dispatch: + + # Run job each night after CrateDB nightly has been published. + schedule: + - cron: '0 3 * * *' + +# Cancel in-progress jobs when pushing to the same branch. +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +jobs: + test: + name: " + Go: ${{ matrix.go-version }} + CrateDB: ${{ matrix.cratedb-version }} + on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ 'ubuntu-latest' ] + cratedb-version: [ 'nightly' ] + go-version: + - '1.26.x' + + # Note: No `services: cratedb` block is needed here. Unlike the + # `by-language/go-pgx` example, Testcontainers starts CrateDB itself, using + # the Docker engine available on the runner. + env: + CRATEDB_VERSION: ${{ matrix.cratedb-version }} + + steps: + + - name: Acquire sources + uses: actions/checkout@v6 + + - name: Install Go + uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go-version }} + + - name: Validate testing/testcontainers/go-pgx + working-directory: testing/testcontainers/go-pgx + run: go test -v ./... diff --git a/testing/testcontainers/go-pgx/README.md b/testing/testcontainers/go-pgx/README.md new file mode 100644 index 000000000..34683e6d1 --- /dev/null +++ b/testing/testcontainers/go-pgx/README.md @@ -0,0 +1,65 @@ +# Testcontainers for Go + +*How to run integration tests of Go applications with CrateDB.* + +## About + +[Testcontainers for Go] provides lightweight, throwaway instances of databases +(and anything else that runs in a container) for integration testing. These +examples spin up a single-node [CrateDB] and talk to it over the PostgreSQL +wire protocol using [pgx], the canonical PostgreSQL driver for Go. + +Testcontainers for Go has **no dedicated CrateDB module**, so the helper in +[`cratedb.go`](cratedb.go) drives the core `GenericContainer` with the +[CrateDB OCI image], a single-node command line, and an HTTP wait strategy on +port 4200. + +## What's inside + +- [`cratedb.go`](cratedb.go) — `RunCrateDB(ctx)` plus `ConnectionString` / + `HTTPEndpoint` helpers, and `CRATEDB_VERSION`-driven image selection. +- [`shared_test.go`](shared_test.go) — **shared container**: one CrateDB started + once per package via `TestMain` and reused by all tests. +- [`function_scope_test.go`](function_scope_test.go) — **per-test container**: + each test gets its own throwaway CrateDB, torn down via `t.Cleanup`. +- [`types_test.go`](types_test.go) — **advanced CrateDB types**: `ARRAY`, + `OBJECT(DYNAMIC)`, `GEO_POINT`, and `TIMESTAMP WITH TIME ZONE`. +- [`http_test.go`](http_test.go) — **HTTP endpoint**: query CrateDB's native + `/_sql` REST interface on port 4200, complementing the pgx wire-protocol + tests. + +## Usage + +1. Make sure Go (1.25+) and a Docker engine are available — Testcontainers starts + CrateDB itself, so no CrateDB needs to be running beforehand. + +2. Run the tests: + + ```shell + # Run all tests. + go test -v ./... + + # Run an individual test. + go test -v -run TestAdvancedTypes ./... + + # Select the CrateDB version (image tag) to test against. + # (unset) / nightly -> crate/crate:nightly + # 6.2 / latest / ... -> crate: + export CRATEDB_VERSION=6.2 + go test -v ./... + + # Keep containers around after the run for debugging. + export TESTCONTAINERS_RYUK_DISABLED=true + ``` + +3. From the repository root, the example also runs through the shared test + runner: + + ```shell + ngr test testing/testcontainers/go-pgx + ``` + +[CrateDB]: https://github.com/crate/crate +[CrateDB OCI image]: https://hub.docker.com/_/crate +[pgx]: https://github.com/jackc/pgx +[Testcontainers for Go]: https://golang.testcontainers.org/ diff --git a/testing/testcontainers/go-pgx/cratedb.go b/testing/testcontainers/go-pgx/cratedb.go new file mode 100644 index 000000000..4560c96f2 --- /dev/null +++ b/testing/testcontainers/go-pgx/cratedb.go @@ -0,0 +1,105 @@ +// Package cratedb provides a small helper for running a single-node CrateDB +// instance with Testcontainers for Go, for use in integration tests. +// +// Testcontainers for Go has no dedicated CrateDB module, so this wraps the +// core GenericContainer with the CrateDB OCI image, a single-node command +// line, and an HTTP wait strategy. +package cratedb + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + // httpPort is CrateDB's HTTP endpoint (admin UI, REST/crash). + httpPort = "4200/tcp" + // pgPort is CrateDB's PostgreSQL wire-protocol endpoint. + pgPort = "5432/tcp" + + defaultUser = "crate" + defaultDatabase = "doc" +) + +// imageFromEnv returns the CrateDB OCI image to run, honoring the +// CRATEDB_VERSION environment variable so a single test suite can be run +// against a matrix of CrateDB versions. +func imageFromEnv() string { + return imageFromLabel(os.Getenv("CRATEDB_VERSION")) +} + +func imageFromLabel(label string) string { + switch label { + case "", "nightly": + return "crate/crate:nightly" + default: + return "crate:" + label + } +} + +// CrateDBContainer is a running, single-node CrateDB testcontainer. +type CrateDBContainer struct { + testcontainers.Container +} + +// RunCrateDB starts a single-node CrateDB container and blocks until its HTTP +// endpoint (port 4200) answers with HTTP 200. Always Terminate the returned +// container when done (see TestMain and the per-test examples). +func RunCrateDB(ctx context.Context) (*CrateDBContainer, error) { + req := testcontainers.ContainerRequest{ + Image: imageFromEnv(), + ExposedPorts: []string{httpPort, pgPort}, + Cmd: []string{"-Cdiscovery.type=single-node"}, + WaitingFor: wait.ForHTTP("/"). + WithPort(httpPort). + WithStatusCodeMatcher(func(status int) bool { return status == 200 }). + WithStartupTimeout(180 * time.Second), + } + + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + if err != nil { + return nil, fmt.Errorf("start cratedb container: %w", err) + } + + return &CrateDBContainer{Container: container}, nil +} + +// ConnectionString returns a pgx/libpq-compatible URL for CrateDB's +// PostgreSQL wire-protocol endpoint (port 5432). +func (c *CrateDBContainer) ConnectionString(ctx context.Context) (string, error) { + host, err := c.Host(ctx) + if err != nil { + return "", err + } + + port, err := c.MappedPort(ctx, pgPort) + if err != nil { + return "", err + } + + return fmt.Sprintf("postgres://%s@%s:%s/%s", defaultUser, host, port.Port(), defaultDatabase), nil +} + +// HTTPEndpoint returns the base URL of CrateDB's HTTP endpoint (port 4200), +// e.g. for the REST `/_sql` API or the admin UI. +func (c *CrateDBContainer) HTTPEndpoint(ctx context.Context) (string, error) { + host, err := c.Host(ctx) + if err != nil { + return "", err + } + + port, err := c.MappedPort(ctx, httpPort) + if err != nil { + return "", err + } + + return fmt.Sprintf("http://%s:%s", host, port.Port()), nil +} diff --git a/testing/testcontainers/go-pgx/function_scope_test.go b/testing/testcontainers/go-pgx/function_scope_test.go new file mode 100644 index 000000000..091825583 --- /dev/null +++ b/testing/testcontainers/go-pgx/function_scope_test.go @@ -0,0 +1,49 @@ +package cratedb + +import ( + "context" + "testing" + + "github.com/jackc/pgx/v5" +) + +// Per-test container. +// +// Each invocation gets its own throwaway CrateDB, started inside the test and +// removed via t.Cleanup. Prefer the shared container (see TestMain) for speed; +// reach for a per-test container only when a test needs perfect isolation +// (e.g. cluster-wide settings, different CrateDB versions in one run). +func TestFunctionScope(t *testing.T) { + ctx := context.Background() + + container, err := RunCrateDB(ctx) + if err != nil { + t.Fatalf("start container: %v", err) + } + + t.Cleanup(func() { + if err := container.Terminate(ctx); err != nil { + t.Errorf("terminate container: %v", err) + } + }) + + dsn, err := container.ConnectionString(ctx) + if err != nil { + t.Fatalf("connection string: %v", err) + } + + conn, err := pgx.Connect(ctx, dsn) + if err != nil { + t.Fatalf("connect: %v", err) + } + defer conn.Close(ctx) + + var clusterName string + if err := conn.QueryRow(ctx, "SELECT name FROM sys.cluster").Scan(&clusterName); err != nil { + t.Fatalf("query sys.cluster: %v", err) + } + if clusterName == "" { + t.Fatal("empty cluster name") + } + t.Logf("cluster name: %s", clusterName) +} diff --git a/testing/testcontainers/go-pgx/go.mod b/testing/testcontainers/go-pgx/go.mod new file mode 100644 index 000000000..cf90d7292 --- /dev/null +++ b/testing/testcontainers/go-pgx/go.mod @@ -0,0 +1,64 @@ +module github.com/cratedb-examples/testing/testcontainers/go-pgx + +go 1.26.0 + +require ( + github.com/jackc/pgx/v5 v5.10.0 + github.com/testcontainers/testcontainers-go v0.42.0 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.10.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/klauspost/compress v1.18.6 // indirect + github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/moby/api v1.54.2 // indirect + github.com/moby/moby/client v0.4.1 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/sequential v0.7.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/shirou/gopsutil/v4 v4.26.5 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/tklauser/go-sysconf v0.4.0 // indirect + github.com/tklauser/numcpus v0.12.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/testing/testcontainers/go-pgx/go.sum b/testing/testcontainers/go-pgx/go.sum new file mode 100644 index 000000000..09536c8b9 --- /dev/null +++ b/testing/testcontainers/go-pgx/go.sum @@ -0,0 +1,152 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY= +github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak= +github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8= +github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM= +github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= +github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= +github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= +github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= +github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= +github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/testing/testcontainers/go-pgx/http_test.go b/testing/testcontainers/go-pgx/http_test.go new file mode 100644 index 000000000..645c37e68 --- /dev/null +++ b/testing/testcontainers/go-pgx/http_test.go @@ -0,0 +1,62 @@ +package cratedb + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "testing" +) + +// HTTP endpoint. +// +// CrateDB's native protocol is HTTP: SQL statements are POSTed as JSON to the +// `/_sql` endpoint on port 4200. +func TestHTTPEndpoint(t *testing.T) { + ctx := context.Background() + + endpoint, err := sharedContainer.HTTPEndpoint(ctx) + if err != nil { + t.Fatalf("http endpoint: %v", err) + } + + payload, err := json.Marshal(map[string]string{ + "stmt": "SELECT mountain FROM sys.summits ORDER BY height DESC LIMIT 1", + }) + if err != nil { + t.Fatalf("marshal payload: %v", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint+"/_sql", bytes.NewReader(payload)) + if err != nil { + t.Fatalf("build request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("POST /_sql: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + raw, _ := io.ReadAll(resp.Body) + t.Fatalf("status = %d, body = %s", resp.StatusCode, raw) + } + + var result struct { + Cols []string `json:"cols"` + Rows [][]any `json:"rows"` + } + if err = json.NewDecoder(resp.Body).Decode(&result); err != nil { + t.Fatalf("decode response: %v", err) + } + if len(result.Rows) != 1 || len(result.Rows[0]) != 1 { + t.Fatalf("unexpected result shape: cols=%v rows=%v", result.Cols, result.Rows) + } + + if got, _ := result.Rows[0][0].(string); got != "Mont Blanc" { + t.Errorf("highest summit = %q, want %q", got, "Mont Blanc") + } +} diff --git a/testing/testcontainers/go-pgx/shared_test.go b/testing/testcontainers/go-pgx/shared_test.go new file mode 100644 index 000000000..b7dba5256 --- /dev/null +++ b/testing/testcontainers/go-pgx/shared_test.go @@ -0,0 +1,106 @@ +package cratedb + +import ( + "context" + "fmt" + "os" + "slices" + "testing" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +// Shared, package-scoped container. +// +// One CrateDB container is started once for the whole package via TestMain and +// torn down after all tests have run. Starting a container is expensive, so +// tests that don't need isolation share a single instance and a single +// connection (`sharedConn`). For per-test isolation, see +// function_scope_test.go. `sharedContainer` is exposed so tests can also reach +// the HTTP endpoint (see http_test.go). +var ( + sharedContainer *CrateDBContainer + sharedConn *pgx.Conn +) + +func TestMain(m *testing.M) { + // os.Exit skips deferred functions, so run the real setup/teardown in a + // helper that returns the exit code and lets its defers fire first. + os.Exit(runWithSharedContainer(m)) +} + +func runWithSharedContainer(m *testing.M) int { + ctx := context.Background() + + container, err := RunCrateDB(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "start CrateDB: %v\n", err) + return 1 + } + sharedContainer = container + defer func() { + if err := container.Terminate(ctx); err != nil { + fmt.Fprintf(os.Stderr, "terminate CrateDB: %v\n", err) + } + }() + + dsn, err := container.ConnectionString(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "build connection string: %v\n", err) + return 1 + } + + sharedConn, err = pgx.Connect(ctx, dsn) + if err != nil { + fmt.Fprintf(os.Stderr, "connect to CrateDB: %v\n", err) + return 1 + } + defer sharedConn.Close(ctx) + + return m.Run() +} + +// TestSharedQuerySummits queries CrateDB's built-in `sys.summits` table through +// the shared connection. The table has 9 columns, and the three highest summits +// are Mont Blanc, Monte Rosa and Dom. +func TestSharedQuerySummits(t *testing.T) { + ctx := context.Background() + + rows, err := sharedConn.Query(ctx, + "SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3") + if err != nil { + t.Fatalf("query sys.summits: %v", err) + } + defer rows.Close() + + fields := rows.FieldDescriptions() + if len(fields) != 9 { + t.Errorf("column count = %d, want 9", len(fields)) + } + mountainCol := slices.IndexFunc(fields, func(f pgconn.FieldDescription) bool { + return f.Name == "mountain" + }) + if mountainCol < 0 { + t.Fatal("no 'mountain' column in sys.summits") + } + + var mountains []string + for rows.Next() { + values, err := rows.Values() + if err != nil { + t.Fatalf("read row: %v", err) + } + name, _ := values[mountainCol].(string) + mountains = append(mountains, name) + } + if err := rows.Err(); err != nil { + t.Fatalf("iterate rows: %v", err) + } + + want := []string{"Mont Blanc", "Monte Rosa", "Dom"} + if !slices.Equal(mountains, want) { + t.Errorf("top summits = %v, want %v", mountains, want) + } + t.Logf("top summits: %v", mountains) +} diff --git a/testing/testcontainers/go-pgx/types_test.go b/testing/testcontainers/go-pgx/types_test.go new file mode 100644 index 000000000..8d8692174 --- /dev/null +++ b/testing/testcontainers/go-pgx/types_test.go @@ -0,0 +1,86 @@ +package cratedb + +import ( + "context" + "testing" + "time" +) + +// Advanced CrateDB types. +// +// Exercise CrateDB-specific column types over the PostgreSQL wire protocol: +// ARRAY, OBJECT(DYNAMIC), GEO_POINT and TIMESTAMP WITH TIME ZONE. Composite +// values are written with CrateDB SQL literals and read back through scalar +// CrateDB functions (array_length, object subscripts, longitude/latitude), so +// the test doesn't depend on driver-side decoding of arrays or objects. +func TestAdvancedTypes(t *testing.T) { + ctx := context.Background() + + const ddl = `CREATE TABLE IF NOT EXISTS doc.alltypes ( + name TEXT PRIMARY KEY, + tags ARRAY(INTEGER), + props OBJECT(DYNAMIC) AS (kind TEXT, height INTEGER), + location GEO_POINT, + created_at TIMESTAMP WITH TIME ZONE + ) WITH (number_of_replicas = 0)` + if _, err := sharedConn.Exec(ctx, ddl); err != nil { + t.Fatalf("create table: %v", err) + } + t.Cleanup(func() { + _, _ = sharedConn.Exec(ctx, "DROP TABLE IF EXISTS doc.alltypes") + }) + + const insert = `INSERT INTO doc.alltypes (name, tags, props, location, created_at) VALUES ( + 'mont-blanc', + [1, 2, 3], + {"kind" = 'mountain', "height" = 4808}, + [6.8650, 45.8326], + '2026-06-11T08:30:00+00:00' + )` + if _, err := sharedConn.Exec(ctx, insert); err != nil { + t.Fatalf("insert: %v", err) + } + if _, err := sharedConn.Exec(ctx, "REFRESH TABLE doc.alltypes"); err != nil { + t.Fatalf("refresh: %v", err) + } + + var ( + kind string + height int + tagCount int + lon float64 + lat float64 + created time.Time + ) + err := sharedConn.QueryRow(ctx, `SELECT + props['kind'], + props['height'], + array_length(tags, 1), + longitude(location), + latitude(location), + created_at + FROM doc.alltypes WHERE name = 'mont-blanc'`). + Scan(&kind, &height, &tagCount, &lon, &lat, &created) + if err != nil { + t.Fatalf("select: %v", err) + } + + if kind != "mountain" { + t.Errorf("props['kind'] = %q, want %q", kind, "mountain") + } + if height != 4808 { + t.Errorf("props['height'] = %d, want 4808", height) + } + if tagCount != 3 { + t.Errorf("array_length(tags) = %d, want 3", tagCount) + } + if lon < 6.86 || lon > 6.87 { + t.Errorf("longitude = %v, want ~6.865", lon) + } + if lat < 45.83 || lat > 45.84 { + t.Errorf("latitude = %v, want ~45.8326", lat) + } + if created.Year() != 2026 || created.Month() != time.June { + t.Errorf("created_at = %s, want June 2026", created) + } +} From d1673c4fb18fed3fcb6945d5a5b5865e9ad53601 Mon Sep 17 00:00:00 2001 From: Florin Date: Thu, 11 Jun 2026 18:30:19 +0200 Subject: [PATCH 2/3] add dependabot for go-pgx testcontainers --- .github/dependabot.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8fad25bfa..cd7d2e1b0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -318,6 +318,11 @@ updates: # Testing. + - directory: "/testing/testcontainers/go-pgx" + package-ecosystem: "gomod" + schedule: + interval: "daily" + - directory: "/testing/testcontainers/java" package-ecosystem: "gradle" schedule: From 69a7d817d50fdb6571ced1e26a21f72e4c2d9da3 Mon Sep 17 00:00:00 2001 From: Florin Date: Fri, 12 Jun 2026 09:27:43 +0200 Subject: [PATCH 3/3] document container lifecycle strategies in go-pgx testcontainers example --- testing/testcontainers/go-pgx/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/testing/testcontainers/go-pgx/README.md b/testing/testcontainers/go-pgx/README.md index 34683e6d1..99dc3511a 100644 --- a/testing/testcontainers/go-pgx/README.md +++ b/testing/testcontainers/go-pgx/README.md @@ -28,6 +28,26 @@ port 4200. `/_sql` REST interface on port 4200, complementing the pgx wire-protocol tests. +## Container scope + +Starting a container is expensive (a few seconds each), so the examples show two +strategies for managing CrateDB's lifecycle in a test suite: + +- **Shared, package-scoped** ([`shared_test.go`](shared_test.go)) — one CrateDB + is started once for the whole package in `TestMain` and reused, through a + single `sharedConn`, by every test that doesn't need isolation. This is the + default: it keeps the suite fast. The trade-off is that tests share state, so + they must not depend on a clean database or leak data that confuses one + another. +- **Per-test** ([`function_scope_test.go`](function_scope_test.go)) — each test + starts its own throwaway CrateDB and tears it down via `t.Cleanup`. Reach for + this only when a test genuinely needs a pristine instance — for example when it + changes cluster-wide settings or wants to pin a different CrateDB version — and + accept the extra startup cost per test. + +As a rule of thumb: prefer the shared container, and isolate a single test with +its own container only when sharing would make it flaky. + ## Usage 1. Make sure Go (1.25+) and a Docker engine are available — Testcontainers starts