diff --git a/.gitea/workflows/fuzz.yml b/.gitea/workflows/fuzz.yml deleted file mode 100644 index 0802bc5..0000000 --- a/.gitea/workflows/fuzz.yml +++ /dev/null @@ -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 . diff --git a/.gitea/workflows/race.yml b/.gitea/workflows/race.yml deleted file mode 100644 index 4728c47..0000000 --- a/.gitea/workflows/race.yml +++ /dev/null @@ -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 ./... diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 9105297..14965c3 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -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 -# carries the CHANGELOG section as its body and nothing else. The gates still run first, -# in their own job and once, minus the race detector: race never runs on a push path or a -# tag, and the local gate raced this tree before the tag was cut. The write permission -# sits on the release job alone, and the version the binary reports is the one the -# toolchain records from the tag, with nothing injected. +# The pipeline does one thing: it publishes the release from the tag, notes and +# all. No gate runs here. The tagged tree was tested on every push to +# development, the deep suite is the suite workflow's business, and race never +# runs in CI at all. A library ships no assets, so there is no build and +# nothing to upload. # -# Every step is one command, so the step that fails is the gate that failed, and no shell -# option has to be trusted for the run to stop. The scripted steps are Perl, not shell and -# 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, which is why the release body is escaped by hand and curl is the -# transport. +# Every step is one command, and the scripted steps are Perl with builtins +# only. Perl drives curl through a list, so no argument is ever word-split, +# globbed or quoted wrong. name: Release on: push: 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: - gates: + release: 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: - 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 env: VERSION: ${{ gitea.ref_name }} @@ -55,68 +39,12 @@ jobs: 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 env: VERSION: ${{ gitea.ref_name }} run: | - # Each step derives what it needs from the tag, so no value has to travel between - # jobs. The separator after the version is never read, so the heading format's - # separator stays free. + # Each step derives what it needs from the tag, so no value has to travel + # between steps. perl -e ' my $v = $ENV{VERSION} // q{}; $v =~ s{^v}{}; @@ -150,8 +78,8 @@ jobs: open(my $in, q{<:raw}, q{release-body.md}) or die qq{release-body.md: $!}; my $body = do { local $/; <$in> }; close($in); - # Byte-oriented escaping: JSON is UTF-8, so non-ASCII passes through and only the - # characters JSON forbids are rewritten. + # Byte-oriented escaping: JSON is UTF-8, so non-ASCII passes through and + # only the characters JSON forbids are rewritten. $body =~ s/([\\"])/\\$1/g; $body =~ s/\t/\\t/g; $body =~ s/\r//g; @@ -188,5 +116,5 @@ jobs: close($r); $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}; - print qq{release id $1\n}; + print qq{release v$ENV{VERSION} is live (id $1)\n}; ' diff --git a/.gitea/workflows/suite.yml b/.gitea/workflows/suite.yml new file mode 100644 index 0000000..522629b --- /dev/null +++ b/.gitea/workflows/suite.yml @@ -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); + ' diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 353ef50..57fee32 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1,23 +1,19 @@ # 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 -# runner box cannot afford the race detector on every push. Race has its own -# pipeline, dispatched by hand, and the local `just gates` runs it once per -# task. The box is one core and 2 GB beside Gitea, so -# parallelism is bounded on purpose and everything runs in one job. Extra jobs would -# duplicate the checkout, the Go setup and the dependency download three times without -# buying any parallelism. +# The push path owns a two-minute budget end to end, so it carries exactly the +# gates that fit it: the format check, `go vet`, the suite's short layer and the +# coverage floor. There is no build step (`go test` compiles what it runs) and +# no install step: the fedora job image carries git, perl and node (verified on +# the runner, 2026-10-04), and no pipeline ever installs gcc or runs the race +# detector. The modernisation gate (`go fix -diff`) and the full suite live in +# the suite workflow. # -# Every step is one command, so the step that fails is the gate that failed, and no shell -# option has to be trusted for the run to stop. The scripted steps are Perl, not shell and -# 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. +# A test too slow for the push budget marks itself with `testing.Short` and the +# suite workflow runs it; the push path runs what fits its budget. # -# After the gates runs the toml-test compliance suite, which is this library's conformance -# record rather than a gate. It is fixed cases, not exploration, and it fits the box, so it -# is none of the three burdens the push pipeline refuses: no fuzz, no race, no heavy sweep. +# Every step is one command, so the step that fails is the gate that failed, +# and no shell option has to be trusted for the run to stop. The scripted +# steps are Perl with builtins only. name: Test on: @@ -31,10 +27,10 @@ env: GOFLAGS: -p=1 GOMAXPROCS: "2" -# A superseded run of the same ref is cancelled instead of queueing behind one -# that no longer matters. Verified on this Gitea on 2026-09-17: a queued run -# whose ref moved on is cancelled before it ever reaches the runner, while a -# run already dispatched there runs to completion. +# A superseded run of the same ref is cancelled instead of queueing behind one that +# no longer matters. Verified on Gitea 1.27.1 on 2026-09-17: a queued run whose ref +# moved on is cancelled before it ever reaches the runner, while a run already +# dispatched there runs to completion. concurrency: group: ${{ gitea.workflow }}-${{ gitea.ref }} cancel-in-progress: true @@ -42,7 +38,6 @@ concurrency: jobs: test: runs-on: fedora - timeout-minutes: 10 steps: - uses: actions/checkout@v7 @@ -52,11 +47,6 @@ jobs: go-version-file: go.mod 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 run: | perl -e ' @@ -70,17 +60,14 @@ jobs: - 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 - # Scope the pattern to the packages that hold the logic when a thin cmd/ drags the - # total under the floor, and keep it equal to `packages` in the project's justfile. - run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./... + # Equal to `packages` in the project's justfile, so the floor is the same + # number the local gate reports. The short layer is what runs; the + # 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 run: | @@ -93,21 +80,3 @@ jobs: printf qq{Total coverage: %s%%\n}, $total; 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da7f355..18a39e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,11 +53,10 @@ just test 7. Open a pull request against `development`. 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 -with the coverage floor, and publishes the Gitea release with the matching -`CHANGELOG.md` section as its notes. The race detector is not in that set: race -never runs on a push path, and the local `just gates` raced the tree before the -tag was cut. +release workflow validates the tag and publishes the Gitea release with the +matching `CHANGELOG.md` section as its notes. No gate runs at the tag: the +tree was tested on every push to `development`, and race is local to +`just gates`, which raced the tree before the tag was cut. ## Code style @@ -114,13 +113,13 @@ Workflows live in `.gitea/workflows/` and run on the project's own runners: | 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 | -| Race | `workflow_dispatch`, by hand | the suite under the race detector, the same race gate the local `just gates` runs | -| 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, 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 | +| 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 | +| Suite | `workflow_dispatch`, by hand | the complete gate set minus race: build, format, vet, modernisation, the full suite and the coverage floor | +| 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 | -The local equivalent is `just gates`, which is the same set plus the race -detector. +The local equivalent is `just gates`, which is the whole set including race. +Race never runs in CI: it belongs to the machine at the keyboard, once per +task, before the commit. ## Reporting bugs diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 694727d..b6471fb 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -138,5 +138,5 @@ sequenceDiagram 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 -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. diff --git a/docs/CLI.md b/docs/CLI.md index 019f7b3..1905e52 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -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 applications that generate their example configuration from the type. -`--version` prints the binary's version and exits. The release pipeline builds -at the tag, so a released binary prints its own tag; a build from a working -tree prints `(devel)`. +`--version` prints the binary's version and exits. The version is the one the +toolchain records at build time, so a binary built from a tagged tree prints +its tag and a build from a working tree prints `(devel)`. ## Flags diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 1a90036..c590b32 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -29,11 +29,11 @@ prints the same list. | 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 build` | compiles `./cmd/interpres-decode` into `bin/interpres-decode` | -| `just test` | the suite with no cache, then the coverage floor of 80 percent from `coverage.out` | -| `just race` | the same suite under the race detector | -| `just unit ./... TestName` | a fast scoped run for iterating; the second argument is a `-run` pattern, `.*` by default | -| `just fuzz FuzzParse . 30s` | time-boxed fuzzing of one target in exactly one package; `go test -fuzz` rejects `./...`; never a gate | +| `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`, under the memory fence | +| `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, fenced | +| `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 fmt` | `gofmt -w .`, format in place | | `just fmt-check` | `gofmt -l .`, zero diff | @@ -94,22 +94,25 @@ go build -gcflags='-S' ./... # what the compiler generated ## Continuous integration 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 -set of gates, so a green `just gates` locally is the fastest way to a green -pipeline. +They are written by hand rather than through `just`, but between them they +carry the same set of gates, so a green `just gates` locally is the fastest +way to a green pipeline. | 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 | -| `race.yml` | `workflow_dispatch`, by hand | the suite under the race detector; the same race gate `just gates` runs locally | -| `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 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 | +| `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 | +| `suite.yml` | `workflow_dispatch`, by hand | the complete gate set minus race: build, format, vet, modernisation, the full suite and the coverage floor | +| `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 | + +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 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 -and the test suite with the coverage floor, extracts the matching `## [X.Y.Z]` -section from `CHANGELOG.md`, and publishes the release with that section as its -body. A library ships no binaries, so the release carries the notes and nothing -else. +tag drives the release workflow: it validates the tag, extracts the matching +`## [X.Y.Z]` section from `CHANGELOG.md`, and publishes the release with that +section as its body. No gate runs at the tag; a library ships no binaries, so +the release carries the notes and nothing else. diff --git a/justfile b/justfile index ddfd677..20c7505 100644 --- a/justfile +++ b/justfile @@ -11,19 +11,26 @@ package := "./cmd/interpres-decode" # failure, not an empty run. 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") default: @just --list -# Compile. Zero errors, zero warnings. +# Compile. Zero errors, zero warnings; -trimpath and -buildvcs make the binary place-independent and version-stamped. 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: #!/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 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: $!}; @@ -34,21 +41,21 @@ test: printf qq{Total coverage: %s%%\n}, $total; 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: - 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. 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. 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: - go test -run '^$' -bench=. -benchmem -count=5 {{pkgs}} + go test -run '^$' -timeout 0 -bench=. -benchmem -count=5 {{pkgs}} # Format in place. fmt: