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¶
go-lint,go-security,go-singleuse- Explanation: a skip is not a pass, the Svelte frontend track, change-detection