engine: give one answer about a name on every machine #42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| pull_request: | |
| workflow_dispatch: | |
| schedule: | |
| # Weekly, for the fuzzing job below only. Every other job here runs on | |
| # push, and fuzzing deliberately does not - see that job for why. | |
| - cron: "17 4 * * 1" | |
| permissions: | |
| contents: read | |
| env: | |
| # The exact toolchain used for tests and releases. go.mod declares a | |
| # minimum - this is the pin. Raising it can change generated bytes, so the | |
| # byte stability guard has to be green before it moves. | |
| GO_VERSION: "1.26.5" | |
| jobs: | |
| test: | |
| name: test on ${{ matrix.os }} | |
| # A hung job otherwise holds a runner until the GitHub default of six | |
| # hours. Every number here is well above what the job takes today: the | |
| # matrix runs in about a minute, the race detector took 148 s when it was | |
| # measured, and fuzzing is given 5 minutes a target by its own loop. | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| # The engine, the command line and the tests build without CGO on every | |
| # system. Only the desktop window needs a C compiler, and it is built | |
| # separately. | |
| CGO_ENABLED: "0" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| check-latest: false | |
| # Keyed on go.sum. The one dependency is the YAML parser behind the | |
| # recipe - see docs/STACK.md. | |
| cache: true | |
| - name: formatting | |
| run: | | |
| test -z "$(gofmt -l .)" || { echo "gofmt found unformatted files:"; gofmt -l .; exit 1; } | |
| shell: bash | |
| - name: vet | |
| run: go vet ./... | |
| - name: the dependency list has not grown by accident | |
| # Every dependency is a licence question and a byte stability question. | |
| # A new one arriving as somebody's transitive import has to be visible | |
| # rather than discovered later. | |
| run: | | |
| set -euo pipefail | |
| expected="github.com/goccy/go-yaml" | |
| actual=$(go list -m -f '{{.Path}}' all | grep -v '^github.com/donislawdev/TestingFilesGenerator$' | sort) | |
| if [ "$actual" != "$expected" ]; then | |
| echo "the module list changed." | |
| echo "expected: $expected" | |
| echo "actual : $actual" | |
| exit 1 | |
| fi | |
| echo "dependencies unchanged: $actual" | |
| shell: bash | |
| - name: test | |
| run: go test ./... -count=1 | |
| - name: build the command line binary | |
| run: go build ./cmd/tfg | |
| - name: build the window binary | |
| # Nothing built this, so it could stop compiling and no run would say | |
| # so. The package is a stub today and that is exactly why it is cheap | |
| # to keep honest - it costs a second now and a bisect later. | |
| # | |
| # CGO_ENABLED is 0 for this whole job, which is the point: the window | |
| # binary has to keep building without a C toolchain for as long as the | |
| # toolkit is not wired in. The day it is, this step needs a compiler | |
| # and moves to a job that has one, and finding that out here is better | |
| # than finding it out when the release does not build. | |
| run: go build ./cmd/tfg-gui | |
| govulncheck: | |
| name: known vulnerabilities | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| check-latest: false | |
| cache: true | |
| - name: govulncheck | |
| # The official Go vulnerability scanner. BSD-3-Clause, read from the | |
| # LICENSE file of the pinned version rather than recalled. Run rather | |
| # than imported, so it never enters go.mod. | |
| # | |
| # It reports only what is actually reachable from our code, which is | |
| # what makes it worth having: a scanner that lists every advisory | |
| # touching the module graph produces noise, and noise gets switched off. | |
| # Measured before switching it on, 2026-08-02: no vulnerabilities found. | |
| run: go run golang.org/x/vuln/cmd/govulncheck@v1.1.4 ./... | |
| staticcheck: | |
| name: staticcheck | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| check-latest: false | |
| cache: true | |
| - name: staticcheck | |
| # MIT, pinned. Run rather than imported, so it never enters go.mod and | |
| # the dependency gate above does not see it - measured twice on | |
| # 2026-08-02, go.mod and go.sum both untouched afterwards. | |
| # | |
| # The version is pinned because an unpinned analyser turns somebody | |
| # else's release into a red build on a commit that changed nothing. | |
| # | |
| # Which checks run, and why ST1005 does not, is in staticcheck.conf. | |
| # Measured before switching this on: two findings in the whole tree, | |
| # both of them the word "Pillow" at the start of an error string, which | |
| # is the name of the library that refused the image rather than a | |
| # sentence. Zero findings with the config in place. | |
| run: go run honnef.co/go/tools/cmd/staticcheck@v0.7.0 ./... | |
| race: | |
| name: race detector | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| # The one thing in this project that needs a C toolchain. Linux runners | |
| # ship one, so this job carries the cost and the matrix above stays on | |
| # CGO_ENABLED=0 and stays fast. | |
| CGO_ENABLED: "1" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| check-latest: false | |
| cache: true | |
| - name: test under the race detector | |
| # A data race is the one defect class here that nothing else notices. It | |
| # does not change a size, and on the run that happens to interleave the | |
| # safe way it does not change a byte either - so determinism and the | |
| # pinned values both stay green while the file is wrong once a month on | |
| # somebody else's machine. | |
| # | |
| # Measured on 2026-08-02: 31 s without, 148 s with, and zero races found | |
| # in the tree as it stands. The guard that keeps concurrency confined to | |
| # two files lives in internal/guard, so this and that one answer | |
| # different halves of the same worry. | |
| run: go test ./... -count=1 -race | |
| coverage: | |
| name: coverage gate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| env: | |
| CGO_ENABLED: "0" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| check-latest: false | |
| # Keyed on go.sum. The one dependency is the YAML parser behind the | |
| # recipe - see docs/STACK.md. | |
| cache: true | |
| - name: measure | |
| # -coverpkg is not optional here. The guard tests live in their own | |
| # package, and by default Go credits coverage only to the package | |
| # under test - which reports 0.0% and makes the gate meaningless. | |
| # Measured, not assumed. | |
| run: > | |
| go test ./... -count=1 -covermode=atomic | |
| -coverpkg=./internal/...,./cmd/... | |
| -coverprofile=coverage.out | |
| - name: gate | |
| # The threshold lives in exactly one place, .github/coverage-threshold. | |
| # It rises with coverage and is never lowered to turn a red run green. | |
| # Lowering it is a decision for the owner, not a way to get unblocked. | |
| run: | | |
| set -euo pipefail | |
| threshold=$(tr -d '[:space:]' < .github/coverage-threshold) | |
| actual=$(go tool cover -func=coverage.out | awk '/^total:/ {gsub("%","",$3); print $3}') | |
| echo "coverage ${actual}% - threshold ${threshold}%" | |
| awk -v a="$actual" -v t="$threshold" 'BEGIN { exit (a+0 >= t+0) ? 0 : 1 }' \ | |
| || { echo "coverage ${actual}% is below the threshold ${threshold}%"; exit 1; } | |
| shell: bash | |
| fidelity: | |
| name: reference tools actually installed | |
| # The oracle guards skip when the tool they need is missing, loudly, and a | |
| # skip is not a check. The matrix runners have python and node and nothing | |
| # else, so on an ordinary push most of those guards report a skip and the | |
| # run is green having verified almost nothing about the files themselves. | |
| # | |
| # This job installs Inkscape, 7z, ffmpeg and poppler so the same guards run | |
| # for real. It is separate from the matrix because installing a graphics | |
| # stack takes minutes and a push should not wait for it. | |
| # | |
| # What it does NOT do, said plainly: it does not walk many sizes. The | |
| # guards it runs check the realistic size and the smallest ones, which is | |
| # where the one defect this ever found was hiding - an SVG that rendered to | |
| # a blank canvas at exactly its minimum, 2026-08-03. The wider sweep across | |
| # sizes, seeds and label settings lives in tools/probes/fidelity-sweep.py | |
| # and is run by hand, because tools/ is outside the repository. See O51. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| CGO_ENABLED: "0" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| check-latest: false | |
| cache: true | |
| - name: the reference tools | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| p7zip-full ffmpeg poppler-utils inkscape python3-pil | |
| shell: bash | |
| - name: every format past its reference tool | |
| # -v so that a skip is visible in the log. A tool that failed to install | |
| # would otherwise turn this job back into the green nothing it exists | |
| # to replace. | |
| run: go test ./internal/guard/ -count=1 -run 'ReferenceTool' -v | |
| shell: bash | |
| fuzz: | |
| name: fuzzing with a time budget | |
| # Weekly and by hand, never on a push. Fuzzing searches without end, so a | |
| # budget picked to fit a commit gate would be a gate that says "fuzzing | |
| # passed" after twenty seconds of looking - which is worth less than not | |
| # claiming it. See docs/OBSERVATIONS.md, O34. | |
| # | |
| # What the push jobs do run is the seed corpus in testdata/fuzz, because | |
| # go test executes fuzz targets as ordinary tests. That is regression | |
| # cover for what has already been found, and it is not the same thing as | |
| # searching. | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| # Four targets at five minutes each, plus the build and the baseline pass. | |
| timeout-minutes: 45 | |
| env: | |
| CGO_ENABLED: "0" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| check-latest: false | |
| cache: true | |
| - name: search | |
| # One target at a time, because -fuzz takes exactly one. Five minutes | |
| # each - long enough to be a search rather than a gesture, short enough | |
| # that a weekly run stays cheap. | |
| # | |
| # A finding here is not fixed by this job. It has to be pulled into | |
| # testdata/fuzz and committed, or it disappears with the runner and the | |
| # next search starts from nothing. | |
| run: | | |
| set -euo pipefail | |
| for target in FuzzParseSize FuzzParseRecipe FuzzNameTemplate FuzzCanonicalRecipe; do | |
| echo "=== $target ===" | |
| go test ./internal/guard/ -run "^$" -fuzz "^${target}$" -fuzztime 5m | |
| done | |
| shell: bash | |
| - name: keep what was found | |
| # The corpus a failing run leaves behind is the whole value of the run, | |
| # and it lives in the runner's cache directory rather than the tree. | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: fuzz-findings | |
| path: | | |
| internal/guard/testdata/fuzz/** | |
| ~/.cache/go-build/fuzz/** | |
| if-no-files-found: warn |