ci: fence the test recipes and rebuild the pipelines around the gate set
Test / test (push) Failing after 24s

Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
petrbalvin committed 2026-10-04 21:15:21 +02:00
1 parent e3dda5e115
commit 9a1b922712
10 files changed
+165 -263

No files matched your search

-36
View File
@@ -1,36 +0,0 @@
# Fuzz smoke, Go. Dispatched by hand when a change asks for it.
#
# Fuzzing is exploration, so it never belongs to the push pipeline; a 30 second
# smoke per target on a hand dispatch checks a change without holding the
# shared box. The targets run the seeds and whatever the corpus has gathered; a
# failure leaves its crashing input in testdata/fuzz, which the ordinary suite
# then reproduces on every push.
#
# Every step is one command, so the step that fails is the gate that failed.
name: Fuzz
on:
workflow_dispatch:
env:
# One core: parallelism buys no speed here and costs memory the box does not have.
GOFLAGS: -p=1
GOMAXPROCS: "2"
jobs:
fuzz:
runs-on: fedora
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Fuzz the parser
run: go test -run '^$' -fuzz FuzzParse -fuzztime=30s -timeout 10m .
- name: Fuzz the encoder
run: go test -run '^$' -fuzz FuzzMarshal -fuzztime=30s -timeout 10m .
-37
View File
@@ -1,37 +0,0 @@
# Race, Go. Dispatched by hand.
#
# The race detector roughly doubles both time and memory, which the shared runner box
# cannot afford on every push. Locally it belongs to `just gates`, which runs it once
# per task; here it is an explicit decision rather than a routine, a hand dispatch
# when a change asks for one. Development carries its race gate on every push through
# that local gate.
#
# Every step is one command, so the step that fails is the gate that failed.
name: Race
on:
workflow_dispatch:
env:
# One core: parallelism buys no speed here and costs memory the box does not have.
GOFLAGS: -p=1
GOMAXPROCS: "2"
jobs:
race:
runs-on: fedora
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Install gcc
# The race detector needs cgo and the runner image carries no C compiler.
run: dnf install -y gcc
- name: Race
run: go test -race -count=1 -timeout 10m ./...
+22 -94
View File
@@ -1,49 +1,33 @@
# Release, Go library. Runs on version tags (v1.2.3) pushed to main. # Release, Go library. Runs on a version tag (v1.2.3) pushed to main.
# #
# A library ships no binaries, so there is no build matrix and no smoke test: the release # The pipeline does one thing: it publishes the release from the tag, notes and
# carries the CHANGELOG section as its body and nothing else. The gates still run first, # all. No gate runs here. The tagged tree was tested on every push to
# in their own job and once, minus the race detector: race never runs on a push path or a # development, the deep suite is the suite workflow's business, and race never
# tag, and the local gate raced this tree before the tag was cut. The write permission # runs in CI at all. A library ships no assets, so there is no build and
# sits on the release job alone, and the version the binary reports is the one the # nothing to upload.
# toolchain records from the tag, with nothing injected.
# #
# Every step is one command, so the step that fails is the gate that failed, and no shell # Every step is one command, and the scripted steps are Perl with builtins
# option has to be trusted for the run to stop. The scripted steps are Perl, not shell and # only. Perl drives curl through a list, so no argument is ever word-split,
# not Python: Perl behaves the same on both runner images, there is no bashism to trip over # globbed or quoted wrong.
# on ash, and it is one language instead of two. The Perl uses builtins only, because
# Fedora packages the Perl modules separately and nothing beyond `perl` itself may be
# assumed present, which is why the release body is escaped by hand and curl is the
# transport.
name: Release name: Release
on: on:
push: push:
tags: ["v*"] tags: ["v*"]
env:
# The box is shared with the forge, so parallelism is bounded on purpose. The gates job
# needs it most, since it runs the suite.
GOFLAGS: -p=1
GOMAXPROCS: "2"
jobs: jobs:
gates: release:
runs-on: fedora runs-on: fedora
timeout-minutes: 10 permissions:
# contents: read is required for the checkout: a job that declares any
# permissions gets a token scoped to exactly those, and releases: write
# alone leaves the fetch with no read access, which Gitea answers with
# a 404 "Repository not found". Verified on the instance 2026-09-16.
contents: read
releases: write
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
# The module is the source of truth for the version, so it cannot drift.
go-version-file: go.mod
cache: true
- name: Install Perl
# Perl for the steps below. The install is a no-op where the package
# is already present.
run: dnf install -y perl
- name: Validate the tag - name: Validate the tag
env: env:
VERSION: ${{ gitea.ref_name }} VERSION: ${{ gitea.ref_name }}
@@ -55,68 +39,12 @@ jobs:
print qq{tag $v\n}; print qq{tag $v\n};
' '
- name: Format
run: |
perl -e '
open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
my @bad = <$g>;
close($g);
print @bad;
exit(@bad ? 1 : 0);
'
- name: Vet
run: go vet ./...
- name: Modernise
# Exits non-zero when it has something to rewrite, so it needs no output capture.
run: go fix -diff ./...
- name: Build
run: go build ./...
- name: Tests
# Keep the pattern equal to `packages` in the project's justfile.
run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./...
- name: Coverage floor
run: |
perl -e '
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
my $total;
while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
close($c);
die qq{no total line in coverage.out\n} unless defined $total;
printf qq{Total coverage: %s%%\n}, $total;
exit($total < 80 ? 1 : 0);
'
release:
runs-on: fedora
timeout-minutes: 15
needs: gates
permissions:
# contents: read is required for the checkout: a job that declares any
# permissions gets a token scoped to exactly those, and releases: write
# alone leaves the fetch with no read access, which Gitea answers with
# a 404 "Repository not found". Verified on the instance 2026-09-16.
contents: read
releases: write
steps:
- uses: actions/checkout@v7
- name: Install Perl
# The runner images are minimal and Perl is not guaranteed. The install is a
# no-op where it is already present; drop this step once verified on the box.
run: dnf install -y perl
- name: Extract the CHANGELOG section - name: Extract the CHANGELOG section
env: env:
VERSION: ${{ gitea.ref_name }} VERSION: ${{ gitea.ref_name }}
run: | run: |
# Each step derives what it needs from the tag, so no value has to travel between # Each step derives what it needs from the tag, so no value has to travel
# jobs. The separator after the version is never read, so the heading format's # between steps.
# separator stays free.
perl -e ' perl -e '
my $v = $ENV{VERSION} // q{}; my $v = $ENV{VERSION} // q{};
$v =~ s{^v}{}; $v =~ s{^v}{};
@@ -150,8 +78,8 @@ jobs:
open(my $in, q{<:raw}, q{release-body.md}) or die qq{release-body.md: $!}; open(my $in, q{<:raw}, q{release-body.md}) or die qq{release-body.md: $!};
my $body = do { local $/; <$in> }; my $body = do { local $/; <$in> };
close($in); close($in);
# Byte-oriented escaping: JSON is UTF-8, so non-ASCII passes through and only the # Byte-oriented escaping: JSON is UTF-8, so non-ASCII passes through and
# characters JSON forbids are rewritten. # only the characters JSON forbids are rewritten.
$body =~ s/([\\"])/\\$1/g; $body =~ s/([\\"])/\\$1/g;
$body =~ s/\t/\\t/g; $body =~ s/\t/\\t/g;
$body =~ s/\r//g; $body =~ s/\r//g;
@@ -188,5 +116,5 @@ jobs:
close($r); close($r);
$code eq q{201} or die qq{ERROR: the release was not created, HTTP $code: $body\n}; $code eq q{201} or die qq{ERROR: the release was not created, HTTP $code: $body\n};
$body =~ m{"id"\s*:\s*([0-9]+)} or die qq{ERROR: no release id in the response: $body\n}; $body =~ m{"id"\s*:\s*([0-9]+)} or die qq{ERROR: no release id in the response: $body\n};
print qq{release id $1\n}; print qq{release v$ENV{VERSION} is live (id $1)\n};
' '
+69
View File
@@ -0,0 +1,69 @@
# Suite, Go. Dispatched by hand, on development. Never a push gate.
#
# The complete gate set minus race: the build, both static gates, the full
# suite with every short-layer skip unskipped, and the coverage floor. It is
# the pipeline form of the local gate, for the moments when the tree must be
# proven end to end and nobody is at the keyboard.
#
# Race never runs in CI. It roughly doubles time and memory on a box shared
# with the forge, and the local `just gates` races the tree on the machine at
# the keyboard, which is where that gate belongs.
name: Suite
on:
workflow_dispatch:
env:
# One core: parallelism buys no speed here and costs memory the box does not have.
GOFLAGS: -p=1
GOMAXPROCS: "2"
jobs:
suite:
runs-on: fedora
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
# The module is the source of truth for the version, so it cannot drift.
go-version-file: go.mod
cache: true
- name: Build
# The examples are main programs; the build is what compiles them.
run: go build ./...
- name: Format
run: |
perl -e '
open(my $g, q{-|}, q{gofmt}, q{-l}, q{.}) or die qq{gofmt: $!};
my @bad = <$g>;
close($g);
print @bad;
exit(@bad ? 1 : 0);
'
- name: Vet
run: go vet ./...
- name: Modernise
# Exits non-zero when it has something to rewrite, so it needs no output capture.
run: go fix -diff ./...
- name: Tests
# The full suite, every skip layer lifted, with the coverage profile.
# No timeout: a run that does not finish is a defect to find.
run: go test -count=1 -timeout 0 -coverprofile=coverage.out ./...
- name: Coverage floor
run: |
perl -e '
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
my $total;
while (my $l = <$c>) { $total = $1 if $l =~ m{^total:\s+\S+\s+([0-9.]+)%} }
close($c);
die qq{no total line in coverage.out\n} unless defined $total;
printf qq{Total coverage: %s%%\n}, $total;
exit($total < 80 ? 1 : 0);
'
+23 -54
View File
@@ -1,23 +1,19 @@
# Test, Go. Push and pull request to development. Never on main. # Test, Go. Push and pull request to development. Never on main.
# #
# The gates are the ones the justfile's `gates` recipe runs, minus race: the shared # The push path owns a two-minute budget end to end, so it carries exactly the
# runner box cannot afford the race detector on every push. Race has its own # gates that fit it: the format check, `go vet`, the suite's short layer and the
# pipeline, dispatched by hand, and the local `just gates` runs it once per # coverage floor. There is no build step (`go test` compiles what it runs) and
# task. The box is one core and 2 GB beside Gitea, so # no install step: the fedora job image carries git, perl and node (verified on
# parallelism is bounded on purpose and everything runs in one job. Extra jobs would # the runner, 2026-10-04), and no pipeline ever installs gcc or runs the race
# duplicate the checkout, the Go setup and the dependency download three times without # detector. The modernisation gate (`go fix -diff`) and the full suite live in
# buying any parallelism. # the suite workflow.
# #
# Every step is one command, so the step that fails is the gate that failed, and no shell # A test too slow for the push budget marks itself with `testing.Short` and the
# option has to be trusted for the run to stop. The scripted steps are Perl, not shell and # suite workflow runs it; the push path runs what fits its budget.
# not Python: Perl behaves the same on both runner images, there is no bashism to trip over
# on ash, and it is one language instead of two. The Perl uses builtins only, because
# Fedora packages the Perl modules separately and nothing beyond `perl` itself may be
# assumed present.
# #
# After the gates runs the toml-test compliance suite, which is this library's conformance # Every step is one command, so the step that fails is the gate that failed,
# record rather than a gate. It is fixed cases, not exploration, and it fits the box, so it # and no shell option has to be trusted for the run to stop. The scripted
# is none of the three burdens the push pipeline refuses: no fuzz, no race, no heavy sweep. # steps are Perl with builtins only.
name: Test name: Test
on: on:
@@ -31,10 +27,10 @@ env:
GOFLAGS: -p=1 GOFLAGS: -p=1
GOMAXPROCS: "2" GOMAXPROCS: "2"
# A superseded run of the same ref is cancelled instead of queueing behind one # A superseded run of the same ref is cancelled instead of queueing behind one that
# that no longer matters. Verified on this Gitea on 2026-09-17: a queued run # no longer matters. Verified on Gitea 1.27.1 on 2026-09-17: a queued run whose ref
# whose ref moved on is cancelled before it ever reaches the runner, while a # moved on is cancelled before it ever reaches the runner, while a run already
# run already dispatched there runs to completion. # dispatched there runs to completion.
concurrency: concurrency:
group: ${{ gitea.workflow }}-${{ gitea.ref }} group: ${{ gitea.workflow }}-${{ gitea.ref }}
cancel-in-progress: true cancel-in-progress: true
@@ -42,7 +38,6 @@ concurrency:
jobs: jobs:
test: test:
runs-on: fedora runs-on: fedora
timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
@@ -52,11 +47,6 @@ jobs:
go-version-file: go.mod go-version-file: go.mod
cache: true cache: true
- name: Install Perl
# The runner images are minimal and Perl is not guaranteed. The install is a
# no-op where it is already present; drop this step once verified on the box.
run: dnf install -y perl
- name: Format - name: Format
run: | run: |
perl -e ' perl -e '
@@ -70,17 +60,14 @@ jobs:
- name: Vet - name: Vet
run: go vet ./... run: go vet ./...
- name: Modernise
# Exits non-zero when it has something to rewrite, so it needs no output capture.
run: go fix -diff ./...
- name: Build
run: go build ./...
- name: Tests - name: Tests
# Scope the pattern to the packages that hold the logic when a thin cmd/ drags the # Equal to `packages` in the project's justfile, so the floor is the same
# total under the floor, and keep it equal to `packages` in the project's justfile. # number the local gate reports. The short layer is what runs; the
run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./... # persistent build cache on the runner makes a warm run seconds, not
# minutes. No timeout anywhere: `-timeout 0` disables go test's own
# ten-minute default, because a run that does not finish is a defect to
# find and a timeout only hides it.
run: go test -short -count=1 -timeout 0 -coverprofile=coverage.out ./...
- name: Coverage floor - name: Coverage floor
run: | run: |
@@ -93,21 +80,3 @@ jobs:
printf qq{Total coverage: %s%%\n}, $total; printf qq{Total coverage: %s%%\n}, $total;
exit($total < 80 ? 1 : 0); exit($total < 80 ? 1 : 0);
' '
- name: Install toml-test
# A dev and CI tool only; it is not a module dependency of interpres. GOBIN pins
# the destination, so the later step invokes the tool by path and no command
# output has to be captured into a variable.
env:
GOBIN: ${{ gitea.workspace }}/bin
run: go install github.com/toml-lang/toml-test/v2/cmd/toml-test@v2.2.0
- name: Build the decoder
run: go build -o bin/interpres-decode ./cmd/interpres-decode
- name: Compliance suite
# interpres implements TOML 1.1, and the suite runs both directions: the decoder
# on the valid and invalid corpora, the encoder on the tagged JSON of the valid
# one. The mode is pinned so an upstream default change cannot silently move the
# corpus.
run: bin/toml-test test -decoder=bin/interpres-decode -encoder='bin/interpres-decode -encode' -toml=1.1
+10 -11
View File
@@ -53,11 +53,10 @@ just test
7. Open a pull request against `development`. 7. Open a pull request against `development`.
Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The
release workflow validates the tag, runs the static gates and the test suite release workflow validates the tag and publishes the Gitea release with the
with the coverage floor, and publishes the Gitea release with the matching matching `CHANGELOG.md` section as its notes. No gate runs at the tag: the
`CHANGELOG.md` section as its notes. The race detector is not in that set: race tree was tested on every push to `development`, and race is local to
never runs on a push path, and the local `just gates` raced the tree before the `just gates`, which raced the tree before the tag was cut.
tag was cut.
## Code style ## Code style
@@ -114,13 +113,13 @@ Workflows live in `.gitea/workflows/` and run on the project's own runners:
| Workflow | Trigger | What it does | | Workflow | Trigger | What it does |
|---|---|---| |---|---|---|
| Test | push or pull request to `development` | format check, vet, modernisation, build, the test suite with the coverage floor, the toml-test compliance suite | | Test | push or pull request to `development` | format check, vet, and the suite's short layer with the coverage floor, inside the two-minute push budget |
| Race | `workflow_dispatch`, by hand | the suite under the race detector, the same race gate the local `just gates` runs | | Suite | `workflow_dispatch`, by hand | the complete gate set minus race: build, format, vet, modernisation, the full suite and the coverage floor |
| Fuzz | `workflow_dispatch`, by hand | a 30 second fuzz smoke per target over the seeds and the gathered corpus | | Release | a `v*` tag | tag validation, then the Gitea release created from the `CHANGELOG.md` section and nothing else; no gate runs at the tag |
| Release | a `v*` tag | tag validation, format, vet, modernisation, build and the test suite with the coverage floor, then the Gitea release created from the `CHANGELOG.md` section; no race detector |
The local equivalent is `just gates`, which is the same set plus the race The local equivalent is `just gates`, which is the whole set including race.
detector. Race never runs in CI: it belongs to the machine at the keyboard, once per
task, before the commit.
## Reporting bugs ## Reporting bugs
+1 -1
View File
@@ -138,5 +138,5 @@ sequenceDiagram
None. `go.mod` declares the module and the Go version and carries no requires; None. `go.mod` declares the module and the Go version and carries no requires;
the library imports the standard library only, which is the point of the the library imports the standard library only, which is the point of the
project. The `toml-test` binary is a development and CI tool, never a module project. The `toml-test` binary is a development tool, never a module
dependency. dependency.
+3 -3
View File
@@ -59,9 +59,9 @@ option printed as a comment above it, and the `default=` option as the value
where one is set. It is the inverse of `--struct`, for config-driven where one is set. It is the inverse of `--struct`, for config-driven
applications that generate their example configuration from the type. applications that generate their example configuration from the type.
`--version` prints the binary's version and exits. The release pipeline builds `--version` prints the binary's version and exits. The version is the one the
at the tag, so a released binary prints its own tag; a build from a working toolchain records at build time, so a binary built from a tagged tree prints
tree prints `(devel)`. its tag and a build from a working tree prints `(devel)`.
## Flags ## Flags
+20 -17
View File
@@ -29,11 +29,11 @@ prints the same list.
| Recipe | What it does | | Recipe | What it does |
|---|---| |---|---|
| `just gates` | the definition of done: build, format check, vet, the test suite with the coverage floor, and the race detector | | `just gates` | the definition of done: build, format check, vet, the test suite with the coverage floor, and the race detector |
| `just build` | compiles `./cmd/interpres-decode` into `bin/interpres-decode` | | `just build` | compiles `./cmd/interpres-decode` into `bin/interpres-decode`, with `-trimpath -buildvcs=true` |
| `just test` | the suite with no cache, then the coverage floor of 80 percent from `coverage.out` | | `just test` | the suite with no cache, then the coverage floor of 80 percent from `coverage.out`, under the memory fence |
| `just race` | the same suite under the race detector | | `just race` | the same suite under the race detector, fenced too |
| `just unit ./... TestName` | a fast scoped run for iterating; the second argument is a `-run` pattern, `.*` by default | | `just unit ./... TestName` | a fast scoped run for iterating; the second argument is a `-run` pattern, `.*` by default, fenced |
| `just fuzz FuzzParse . 30s` | time-boxed fuzzing of one target in exactly one package; `go test -fuzz` rejects `./...`; never a gate | | `just fuzz FuzzParse . 30s` | time-boxed fuzzing of one target in exactly one package; `go test -fuzz` rejects `./...`; never a gate, fenced |
| `just bench` | benchmarks, `-benchmem -count=5`, on an idle machine only | | `just bench` | benchmarks, `-benchmem -count=5`, on an idle machine only |
| `just fmt` | `gofmt -w .`, format in place | | `just fmt` | `gofmt -w .`, format in place |
| `just fmt-check` | `gofmt -l .`, zero diff | | `just fmt-check` | `gofmt -l .`, zero diff |
@@ -94,22 +94,25 @@ go build -gcflags='-S' ./... # what the compiler generated
## Continuous integration ## Continuous integration
Workflows live in `.gitea/workflows/` and run on the project's own runners. Workflows live in `.gitea/workflows/` and run on the project's own runners.
They are written by hand rather than through `just`, but they enforce the same They are written by hand rather than through `just`, but between them they
set of gates, so a green `just gates` locally is the fastest way to a green carry the same set of gates, so a green `just gates` locally is the fastest
pipeline. way to a green pipeline.
| Workflow | Trigger | What it does | | Workflow | Trigger | What it does |
|---|---|---| |---|---|---|
| `test.yml` | push or pull request to `development` | format check, vet, modernisation, build, the test suite with the 80 percent coverage floor, then the toml-test compliance suite | | `test.yml` | push or pull request to `development` | format check, vet, and the suite's short layer with the 80 percent coverage floor, inside the two-minute push budget |
| `race.yml` | `workflow_dispatch`, by hand | the suite under the race detector; the same race gate `just gates` runs locally | | `suite.yml` | `workflow_dispatch`, by hand | the complete gate set minus race: build, format, vet, modernisation, the full suite and the coverage floor |
| `fuzz.yml` | `workflow_dispatch`, by hand | a 30 second fuzz smoke per target over the seeds and the gathered corpus | | `release.yml` | a `v*` tag | tag validation, then the Gitea release from the CHANGELOG section and nothing else. No gate runs at the tag: the tree was tested on every push, and a library ships no assets |
| `release.yml` | a `v*` tag | tag validation, then format, vet, modernisation, build and the test suite with the coverage floor, then the Gitea release from the CHANGELOG section. No race detector: race never runs on a push path, and the local `just gates` raced the tree before the tag was cut |
Race never runs in CI: it belongs to the local `just gates`, which races the
tree on the machine at the keyboard before the commit. The toml-test
compliance suite is a local recipe (`just toml-test`) and a development record
rather than a push gate.
## Releases ## Releases
Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The
tag drives the release workflow: it validates the tag, runs the static gates tag drives the release workflow: it validates the tag, extracts the matching
and the test suite with the coverage floor, extracts the matching `## [X.Y.Z]` `## [X.Y.Z]` section from `CHANGELOG.md`, and publishes the release with that
section from `CHANGELOG.md`, and publishes the release with that section as its section as its body. No gate runs at the tag; a library ships no binaries, so
body. A library ships no binaries, so the release carries the notes and nothing the release carries the notes and nothing else.
else.
+17 -10
View File
@@ -11,19 +11,26 @@ package := "./cmd/interpres-decode"
# failure, not an empty run. # failure, not an empty run.
packages := "./..." packages := "./..."
# The memory fence for the test recipes: a cgroup ceiling with swap off, so a
# runaway run dies as a failed run and never eats the machine. 4G is the
# default; raise it only with a reason recorded here.
memlimit := "4G"
bindir := env_var_or_default("BINDIR", env_var("HOME") / ".local" / "bin") bindir := env_var_or_default("BINDIR", env_var("HOME") / ".local" / "bin")
default: default:
@just --list @just --list
# Compile. Zero errors, zero warnings. # Compile. Zero errors, zero warnings; -trimpath and -buildvcs make the binary place-independent and version-stamped.
build: build:
CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/{{binary}} {{package}} CGO_ENABLED=0 go build -trimpath -buildvcs=true -ldflags "-s -w" -o bin/{{binary}} {{package}}
# The test gate: the suite, no cache, the coverage floor. # The test gate: the suite, no cache, the coverage floor, under the memory fence.
test: test:
#!/usr/bin/env perl #!/usr/bin/env perl
system(q{go}, q{test}, q{-count=1}, q{-timeout}, q{30m}, my @fence = (q{systemd-run}, q{--user}, q{--scope},
q{-p}, q{MemoryMax={{memlimit}}}, q{-p}, q{MemorySwapMax=0});
system(@fence, q{go}, q{test}, q{-count=1}, q{-timeout}, q{0},
q{-coverprofile}, q{coverage.out}, qw({{packages}})) == 0 q{-coverprofile}, q{coverage.out}, qw({{packages}})) == 0
or die qq{the test suite failed\n}; or die qq{the test suite failed\n};
open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!}; open(my $c, q{-|}, q{go}, q{tool}, q{cover}, q{-func=coverage.out}) or die qq{cover: $!};
@@ -34,21 +41,21 @@ test:
printf qq{Total coverage: %s%%\n}, $total; printf qq{Total coverage: %s%%\n}, $total;
exit($total < 80 ? 1 : 0); exit($total < 80 ? 1 : 0);
# The same suite under the race detector. The expensive one. # The same suite under the race detector. The expensive one, still fenced.
race: race:
go test -race -count=1 -timeout 30m {{packages}} systemd-run --user --scope -p MemoryMax={{memlimit}} -p MemorySwapMax=0 go test -race -count=1 -timeout 0 {{packages}}
# Fast scoped run for iterating. This is the one that runs after every edit. # Fast scoped run for iterating. This is the one that runs after every edit.
unit pkgs=packages run=".*": unit pkgs=packages run=".*":
go test {{pkgs}} -run '{{run}}' systemd-run --user --scope -p MemoryMax={{memlimit}} -p MemorySwapMax=0 go test -timeout 0 {{pkgs}} -run '{{run}}'
# Time-boxed fuzz of one target in one package. The package is required; never a gate. # Time-boxed fuzz of one target in one package. The package is required; never a gate.
fuzz target pkg fuzztime="60s": fuzz target pkg fuzztime="60s":
go test -run '^$' -fuzz '{{target}}' -fuzztime={{fuzztime}} {{pkg}} systemd-run --user --scope -p MemoryMax={{memlimit}} -p MemorySwapMax=0 go test -run '^$' -timeout 0 -fuzz '{{target}}' -fuzztime={{fuzztime}} {{pkg}}
# Benchmarks. On an idle machine only. # Benchmarks. On an idle machine only, deliberately unfenced: a ceiling would distort the measurement.
bench pkgs=packages: bench pkgs=packages:
go test -run '^$' -bench=. -benchmem -count=5 {{pkgs}} go test -run '^$' -timeout 0 -bench=. -benchmem -count=5 {{pkgs}}
# Format in place. # Format in place.
fmt: fmt: