Skip to content

go-test

go test -race -coverprofile over the project's packages, with the coverage badge driven by the standard go tool cover -func total line. Runs on go-tools.

An optional go-test-e2e job runs a separate test path under an env-var build flag (default INT_TEST_E2E=1) when enable_e2e: true. When false, the e2e job is not scheduled at all — no skipped-job noise.

An optional go-test-integration job (when enable_integration: true) adds a docker:dind service so testcontainers-go suites can reach a Docker daemon via DOCKER_HOST. It requires a privileged runner (the phpboyscout self-hosted runner already is) and disables testcontainers' Ryuk reaper by default (the dind daemon is ephemeral, so reaping is redundant). Off by default — no dind service or job unless opted in. See the dind spec.

Change-detected by default. Embed projects with a Svelte UI MUST extend changes with their frontend path (e.g. pkg/studio/web/**) so a frontend-only change still runs the Go embed/served-UI tests — see Explanation: the Svelte frontend track. CHANGELOG.md is deliberately not in the default changes: a colophon release MR fast-forwards a changelog-only commit onto a main that every merge already tested, so running test again there re-proves the same tree. It was included from 2026-07-23 to 2026-09-03 for releaser-pleaser's bootstrap case — see spec 0051 and its reversal in spec 0079 D3.

Coverage counts hand-written code by default. exclude_generated (default true) strips files carrying Go's // Code generated … DO NOT EDIT. marker — mockery/protoc/stringer output — out of the coverage profile before the badge is computed, so generated packages (which have ~0% coverage) no longer dilute the figure and paths can stay ./... without per-repo scoping. Only the counted profile is filtered; paths (what gets tested) and the raw cover.out artifact are unchanged. Set exclude_generated: false for the raw figure. See the exclude-generated spec.

The log says what was skipped, and GitLab shows a Tests tab. Each job runs its suite through gotestsum, which the go-tools image bakes. The unit job's log keeps its one line per package and ends with a summary: each skipped test with the reason it gave, each failure with its output, and a count.

=== SKIP: pkg/embed TestCloseIsIdempotent (0.00s)
    embed_integration_test.go:441: INT_TEST_EMBED is not 1

DONE 40 tests, 13 skipped in 2.476s

All three jobs publish junit.xml as a JUnit report, so the pipeline gets a Tests tab (passed, skipped and failed per package) and the merge request widget names failed tests and marks the new ones. The e2e and integration jobs format as go test -v did, so their logs do not change. Nothing new fails a job: a skip is reported, never gated, because only the test can tell a gate nobody set from a dependency that is missing. See Explanation: a skip is not a pass and spec 0101.

With image: set to an image without gotestsum (a stock golang image, say) each job runs plain go test exactly as before, prints one line saying so, and publishes no report.

Jobs

Job What it runs
go-test gotestsum --format pkgname --junitfile junit.xml -- -race -timeout $[[ inputs.timeout ]] -coverprofile=cover.out $[[ inputs.paths ]]; uploads cover.out and the JUnit report as always-on artifacts.
go-test-e2e (only when enable_e2e: true) gotestsum --format standard-verbose --junitfile junit.xml -- $[[ inputs.e2e_paths ]] -timeout $[[ inputs.e2e_timeout ]], with $[[ inputs.e2e_env_var ]]=1; JUnit report.
go-test-integration (only when enable_integration: true) gotestsum --format standard-verbose --junitfile junit.xml -- $[[ inputs.integration_paths ]] -timeout $[[ inputs.integration_timeout ]] (JUnit report), with $[[ inputs.integration_env_var ]]=1, a docker:dind service, and DOCKER_HOST/FF_NETWORK_PER_BUILD/TESTCONTAINERS_RYUK_DISABLED wired.

Caching

On a runner that mounts a shared Go cache at /opt/go-cache (runner1 does, spec 0079 D7), the job points GOMODCACHE, GOCACHE and GOLANGCI_LINT_CACHE there in its before_script and shares one module and build cache with every Go job on the box; the per-project GitLab cache: below then archives empty directories. Without the mount the GitLab cache applies as before. If you override before_script, carry that detection across, or the job falls back to its per-project cache.

Inputs

Input Type Default Description
image string go-tools:v0.5.0 Go toolchain image.
stage string test GitLab CI stage.
paths string "./..." Path expression for go test.
timeout string "20m" Value passed to go test -timeout= for the unit job. Not Go's 10m default, which was only ever survivable on the AWS fleet — switched off since 2026-09-03, so every Go job now lands on runner1, which pins GOMAXPROCS=4 on eight cores with two jobs sharing the box. A -race suite near the ceiling fails with a goroutine dump that reads exactly like a deadlock and is not one. Raising it costs only the extra minutes on a genuine hang: go test still panics at this value, so the project's job timeout is never what fires.
coverage_regex string '/total:\s+\(statements\)\s+(\d+\.\d+)%/' Regex GitLab uses to extract the coverage badge %.
exclude_generated boolean true Strip generated files (first line // Code generated … DO NOT EDIT.) from the coverage profile before the badge — so generated code stops diluting it. Filters only what's counted; paths and the raw cover.out are untouched. false = raw figure.
if string '$CI_PIPELINE_SOURCE == "merge_request_event"' Gating rules:if: for the unit-test job.
changes array ["**/*.go", "**/go.mod", "**/go.sum", ".golangci.*", ".gitlab-ci.yml"] Change-detection paths. CHANGELOG.md is deliberately absent (spec 0079 D3).
enable_e2e boolean false Adds the go-test-e2e job.
e2e_paths string "./test/e2e/..." Path expression for the e2e run.
e2e_timeout string "5m" go test -timeout= value for e2e.
e2e_env_var string "INT_TEST_E2E" Env var set to 1 in the e2e job.
enable_integration boolean false Adds the dind-backed go-test-integration job (testcontainers-go). Requires a privileged runner.
integration_paths string "./test/integration/..." Path expression for the integration run.
integration_timeout string "10m" go test -timeout= value for integration (longer — image pulls + container startup).
integration_env_var string "INT_TEST_INTEGRATION" Env var set to 1 in the integration job.
dind_image string "docker:27-dind" Docker-in-Docker service image (Renovate-tracked).

Usage

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      enable_e2e: true

Testcontainers-go integration suite (needs a privileged runner):

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
    inputs:
      enable_integration: true
      integration_paths: "./test/integration/..."

Your integration tests connect to the daemon via DOCKER_HOST (testcontainers-go reads it automatically) and should skip unless INT_TEST_INTEGRATION=1, e.g. if os.Getenv("INT_TEST_INTEGRATION") != "1" { t.Skip() }.

See also