Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
60 changes: 60 additions & 0 deletions .github/workflows/testing-testcontainers-go.yml
Original file line number Diff line number Diff line change
@@ -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 ./...
65 changes: 65 additions & 0 deletions testing/testcontainers/go-pgx/README.md
Original file line number Diff line number Diff line change
@@ -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:<tag>
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/
105 changes: 105 additions & 0 deletions testing/testcontainers/go-pgx/cratedb.go
Original file line number Diff line number Diff line change
@@ -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
}
49 changes: 49 additions & 0 deletions testing/testcontainers/go-pgx/function_scope_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
64 changes: 64 additions & 0 deletions testing/testcontainers/go-pgx/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
Loading
Loading