diff --git a/.gitea/workflows/freebsd.yml b/.gitea/workflows/freebsd.yml new file mode 100644 index 0000000..4645377 --- /dev/null +++ b/.gitea/workflows/freebsd.yml @@ -0,0 +1,42 @@ +# FreeBSD compile gates. Dispatched by hand, never on a push. +# +# The debugger's ptrace surface and the JIT substrate are the two +# FreeBSD-portable layers the tree carries; the forge has no FreeBSD runner, +# so they can only be compile-gated, and three foreign-GOOS builds of the +# whole module are minutes of one-core work the push pipeline's budget cannot +# carry. The push pipeline stays fast and light; this workflow is the +# deliberate run, before a release or after touching the ported layers. +# Running the ptrace suite itself needs real FreeBSD hardware. +# +# A dispatched workflow takes no concurrency block: it is one deliberate run. +name: FreeBSD build + +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: + build: + runs-on: fedora + timeout-minutes: 10 + 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: FreeBSD build (amd64) + run: GOOS=freebsd GOARCH=amd64 go build ./... + + - name: FreeBSD build (arm64) + run: GOOS=freebsd GOARCH=arm64 go build ./... + + - name: FreeBSD build (riscv64) + run: GOOS=freebsd GOARCH=riscv64 go build ./... diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 33968ba..dd7c8ad 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -6,6 +6,11 @@ # 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 budget is part of the contract: a push run is fast and light, about two minutes, +# and nothing that cannot run natively on the runner belongs here. The FreeBSD compile +# gates live in freebsd.yml behind workflow_dispatch for that reason; the GOOBJ link +# parity campaign is an opt-in local verification (just link-parity). +# # 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 @@ -56,28 +61,6 @@ jobs: - name: Build run: go build ./... - - name: FreeBSD build (amd64) - # The debugger's ptrace surface and the JIT substrate are the two - # FreeBSD-portable layers the tree carries; the forge has no FreeBSD - # runner, so a push can only compile-gate them. Running the ptrace - # suite needs real FreeBSD hardware. - env: - GOOS: freebsd - GOARCH: amd64 - run: go build ./... - - - name: FreeBSD build (arm64) - env: - GOOS: freebsd - GOARCH: arm64 - run: go build ./... - - - name: FreeBSD build (riscv64) - env: - GOOS: freebsd - GOARCH: riscv64 - run: go build ./... - - name: Format run: | perl -e ' @@ -104,7 +87,11 @@ jobs: # command, so the floor is the same number everywhere. ./verify/... carries the # live oracle-parity comparison against `go tool asm` (the TestGroundTruth # suites); the runner's Go setup provides both the tool and GOROOT. - run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/... + # -short skips the deliberate-run categories inside the suites (the live + # ptrace sessions above all): they need the machine to themselves and a + # starved single-core runner turns each into a timeout the budget cannot + # carry. The local `just test` gate runs everything, in full. + run: go test -short -count=1 -timeout 10m -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/... - name: Tests outside the coverage set # The CLI and the debugger sit outside `packages` because a thin main and a @@ -112,12 +99,12 @@ jobs: # shipped surfaces: the command exit codes, the manual pages against the # binary's own help, and the debugger's architecture-neutral units. They run # here so the floor stays a product measure and nothing is left untested. - run: go test -count=1 -timeout 10m ./cmd/... ./debug/... - - - name: Oracle parity - # Re-run the live go-tool-asm comparison as its own step so that a parity - # regression names the gate that failed instead of hiding inside the suite. - run: go test -count=1 -timeout 10m -run 'TestGroundTruth' ./verify/... + # -short skips the debugger's live ptrace sessions, the deliberate-run + # category the runner cannot starve-proof. The live go-tool-asm oracle + # comparison (TestGroundTruth in ./verify/...) runs inside the coverage + # sweep above; it is not re-run as its own step, because every second on + # this box is budget. + run: go test -short -count=1 -timeout 10m ./cmd/... ./debug/... - name: Coverage floor run: | diff --git a/debug/ptrace_integration_test.go b/debug/ptrace_integration_test.go index f0288b1..994a907 100644 --- a/debug/ptrace_integration_test.go +++ b/debug/ptrace_integration_test.go @@ -18,8 +18,17 @@ import ( ) // buildGasm produces the gasm binary the debugger spawns as its debuggee. +// Every live ptrace test funnels through here, so this is also where the +// deliberate-run boundary sits: under -short (the push pipeline's mode) the +// live sessions skip, because a real debuggee's launch handshake needs the +// machine to itself and a starved single-core runner turns each one into a +// timeout that burns the step's whole budget. The local test gate and the +// dispatched workflows run them in full. func buildGasm(t *testing.T) string { t.Helper() + if testing.Short() { + t.Skip("live ptrace session: skipped in -short mode") + } if p := os.Getenv("GASM_TEST_BIN"); p != "" { return p }