chore: enable the modernize linter for idiomatic packages #163
Workflow file for this run
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: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # Tier B of the phase 4 gate is far too slow for the push path but it is the only | |
| # thing that runs a full-length RFC vector through one encoder lifetime, so it runs | |
| # nightly. See the gate-long job. | |
| schedule: | |
| - cron: '30 3 * * *' | |
| workflow_dispatch: | |
| env: | |
| GO_VERSION: '1.27' | |
| # Pinned to match the local toolchain so git hooks, `task lint`, and CI all | |
| # run the identical linter version. | |
| GOLANGCI_LINT_VERSION: 'v2.13.2' | |
| permissions: | |
| contents: read | |
| jobs: | |
| test: | |
| name: Test | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-24.04-arm, ubuntu-latest] | |
| include: | |
| # The race detector is arch-agnostic in what it detects (a happens-before | |
| # violation is the same bug on either arch), so it runs once, on the fast | |
| # arm runner. amd64 runs the same suite WITHOUT -race: that still exercises | |
| # amd64 codegen and the SSE2/AVX2 SIMD kernels (now in the tphakala/simd | |
| # library, plus the SIMD-vs-scalar tests) without the race detector's 2-10x | |
| # overhead on the slower runner. | |
| # Coverage rides on the amd64 leg only (no -race there, so -covermode=count | |
| # is enough) and uploads to codecov; the arm leg leaves `cover` empty. This | |
| # keeps the coverage upload on the fast runner instead of a separate slow | |
| # job, while the reftest job stays the real CI long pole either way. | |
| - os: ubuntu-24.04-arm | |
| race: '-race' | |
| cover: '' | |
| - os: ubuntu-latest | |
| race: '' | |
| cover: '-coverpkg=./... -covermode=count -coverprofile=coverage.out' | |
| # The exhaustive SIMD differential sweep in internal/celt (pitch_simd_test.go) | |
| # walks every length 0..600 against 12 adversarial patterns, which is the only | |
| # place the hand-written vector kernels' scalar tail epilogue is checked at every | |
| # remainder class. That breadth is deliberate but too slow to pay on the PR | |
| # feedback loop, so pull_request runs it with -short (a boundary-focused subset | |
| # that still hits every tail remainder). Every other trigger runs the full sweep: | |
| # push to main verifies it on both arches on every merge, and the nightly schedule | |
| # plus manual workflow_dispatch re-run it in full. -short only affects that sweep; | |
| # no other test in this job gates on testing.Short(). See issue #15. | |
| env: | |
| SHORT_FLAG: ${{ github.event_name == 'pull_request' && '-short' || '' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| # No job here pushes, so the GITHUB_TOKEN has no business sitting in | |
| # .git/config for the life of the job. The libopus submodule is a public | |
| # https remote and needs no credential. | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| # The RFC 6716 / RFC 8251 conformance vectors are ~75 MB and are never | |
| # committed (.gitignore), so without this step opus/conformance_test.go SKIPS | |
| # and the RFC gate is not running on any push at all: 12/12 vectors passing | |
| # locally, nothing enforcing it in CI. Only the archive is cached (the | |
| # extracted 121 MB of PCM re-expands from it in a couple of seconds), and the | |
| # cache key is the checksum file itself, so a re-pinned vector set invalidates | |
| # the cache by construction. fetch-vectors.sh sha256-verifies before extracting | |
| # and refuses to extract a mismatching archive. | |
| - name: Cache conformance vectors | |
| uses: actions/cache@v6 | |
| with: | |
| path: testdata/vectors/opus_testvectors-rfc8251.tar.gz | |
| key: opus-vectors-rfc8251-${{ hashFiles('testdata/vectors.sha256') }} | |
| - name: Fetch conformance vectors | |
| run: scripts/fetch-vectors.sh | |
| - name: Download dependencies | |
| run: go mod download | |
| - name: Run tests | |
| run: go test ${{ matrix.race }} ${{ matrix.cover }} $SHORT_FLAG -timeout 20m ./... | |
| - name: Run codec tests with the scratch poison harness | |
| run: go test -tags poison $SHORT_FLAG -count=1 -timeout 15m ./internal/celt/... ./internal/packet/... ./opus/... | |
| # The SIMD differential suites diff each dispatcher against a scalar reference | |
| # that is also the dispatcher's fallback, so a green suite cannot tell a working | |
| # vector kernel from one that never ran (issue #72). The dispatchcount build | |
| # arms a per-kernel counter at the exact library call site; the Dispatch tests | |
| # then assert each dispatcher reaches its vector kernel (and, where an in-tree | |
| # fallback exists, that the fallback shape does not). Runs on both arches. | |
| - name: Verify the SIMD dispatchers reach their vector kernels | |
| run: go test -tags dispatchcount -count=1 -run Dispatch ./internal/celt/ | |
| # Coverage is informational (codecov, fail_ci_if_error: false), never a gate. | |
| # Only the amd64 leg sets `cover`, so it is the only leg with a coverage.out to | |
| # upload; the arm leg skips this step. | |
| - name: Upload coverage | |
| if: matrix.os == 'ubuntu-latest' | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| files: coverage.out | |
| fail_ci_if_error: false | |
| # Forced pure-Go SIMD fallback (#38). The pitch (i16), haar1/band-gain/comb | |
| # (i32), and FFT butterfly (cint) kernels are backed by tphakala/simd, which runs | |
| # hand-written NEON/SSE2/AVX2 on the arm64/amd64 CI hosts and a pure-Go | |
| # fallback on every other GOARCH (riscv64, ppc64le, s390x, loong64, wasm). The | |
| # `test` job therefore only ever exercises the assembly; the Go fallback's | |
| # bit-exactness on those arches would otherwise rest solely on the simd | |
| # library's own suite, not go-opus's differential gate. SIMD_DISABLE=all makes | |
| # the simd cpu package clear every detected feature at init, so every dispatch | |
| # falls to the exact Go functions the exotic arches run, letting the existing | |
| # differential suites (pitch/haar1/band-gain/comb/FFT-cint) cover the fallback on a | |
| # native amd64 runner. -count=1 is mandatory: the outcome depends on an env var | |
| # the go test cache does not track, so a cached SIMD result would defeat the | |
| # leg. TestSIMDFallbackForced asserts the features really were cleared, so a | |
| # green run that quietly kept running SIMD fails instead of passing hollow. | |
| forcego: | |
| name: Test (forced Go SIMD fallback) | |
| runs-on: ubuntu-latest | |
| env: | |
| SIMD_DISABLE: all | |
| GOOPUS_ASSERT_SIMD_DISABLED: '1' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Download dependencies | |
| run: go mod download | |
| # Full sweep (no -short): the exhaustive length 0..600 differential walk is | |
| # the only place the fallback's scalar tail epilogue is checked at every | |
| # remainder class, and it is the whole point of this leg, so it runs in full | |
| # on every trigger. It is a single pure-Go package, so the cost is small. | |
| - name: Run the celt differential suite against the forced Go fallback | |
| run: go test -count=1 -timeout 20m ./internal/celt/... | |
| # Differential round-trip against the pinned libopus oracle (FIXED_POINT + | |
| # DISABLE_FLOAT_API). The cgo harness compiles the vendored libopus sources | |
| # directly, so only a C compiler is needed (no autotools). This job carries the | |
| # phase 4 encoder gate; it runs on amd64 and arm64. | |
| reftest: | |
| name: Reftest (libopus oracle) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| # No job here pushes, so the GITHUB_TOKEN has no business sitting in | |
| # .git/config for the life of the job. The libopus submodule is a public | |
| # https remote and needs no credential. | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| # Persist the cgo-compiled libopus objects across runs. setup-go's built-in | |
| # cache keys on go.sum, which is identical for every job, so the first job to | |
| # finish claims that key and the reftest leg's refc cgo objects (built only | |
| # here, behind CGO_ENABLED=1 + the refc tag) never get saved, forcing a full | |
| # libopus recompile every run. A dedicated key fixes that. Correctness does | |
| # not rest on the key: Go's build cache is content-addressed on the exact C | |
| # sources, cgo flags AND the C compiler binary, so a stale entry is rehashed | |
| # and rebuilt, never linked. The key is still conservative (libopus submodule | |
| # commit, the shim sources and cgo CFLAGS, and the cc version) so a submodule | |
| # bump, a shim edit, or a runner GCC bump misses cleanly; without the cc | |
| # version a compiler bump would let Go rebuild while actions/cache kept the old | |
| # key and never re-saved, stranding the job in a permanent recompile. The | |
| # restore-keys fall back to a partial cache the rebuild reuses. | |
| - name: Resolve reftest cache-key inputs | |
| id: cachekey | |
| run: | | |
| { | |
| echo "libopus=$(git -C internal/reftest/libopus rev-parse HEAD)" | |
| echo "cc=$("$(go env CC)" -dumpfullversion)" | |
| echo "gocache=$(go env GOCACHE)" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Cache libopus cgo build objects | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ steps.cachekey.outputs.gocache }} | |
| key: reftest-cgo-${{ runner.os }}-${{ runner.arch }}-go${{ env.GO_VERSION }}-cc${{ steps.cachekey.outputs.cc }}-libopus${{ steps.cachekey.outputs.libopus }}-${{ hashFiles('internal/reftest/oracle/*.c', 'internal/reftest/oracle/*.h', 'internal/reftest/oracle/oracle_cgo.go') }} | |
| restore-keys: | | |
| reftest-cgo-${{ runner.os }}-${{ runner.arch }}-go${{ env.GO_VERSION }}-cc${{ steps.cachekey.outputs.cc }}-libopus${{ steps.cachekey.outputs.libopus }}- | |
| reftest-cgo-${{ runner.os }}-${{ runner.arch }}-go${{ env.GO_VERSION }}-cc${{ steps.cachekey.outputs.cc }}- | |
| # The vectors unlock the gate's REAL-AUDIO tier (corpus_test.go tier 2 reads | |
| # testvectorNN.dec as 48 kHz PCM) and the packet-inspection differential's RFC | |
| # corpus. Both t.Skip cleanly without them, which is precisely the failure mode | |
| # this step removes: a gate that quietly grades itself on synthetic signal only. | |
| - name: Cache conformance vectors | |
| uses: actions/cache@v6 | |
| with: | |
| path: testdata/vectors/opus_testvectors-rfc8251.tar.gz | |
| key: opus-vectors-rfc8251-${{ hashFiles('testdata/vectors.sha256') }} | |
| - name: Fetch conformance vectors | |
| run: scripts/fetch-vectors.sh | |
| - name: Run differential harness | |
| env: | |
| CGO_ENABLED: '1' | |
| run: go test -tags "refc poison" -timeout 15m ./internal/reftest/... | |
| gate-long: | |
| name: Phase 4 gate, tier B (full-length corpus) | |
| # Tier A of the gate runs on every push, inside the reftest job, over 2 s excerpts. | |
| # Tier B is the only thing that pushes a full-length RFC vector (21 to 32 s) through | |
| # a single encoder lifetime, which is where a one-LSB error in the delay ring, the | |
| # dc_reject memory, the CELT energy history or the VBR reservoir would finally | |
| # accumulate into a divergence. Without this job that tier is dead code: it skips | |
| # unless -gate.long is passed, and nothing passed it. | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| # No job here pushes, so the GITHUB_TOKEN has no business sitting in | |
| # .git/config for the life of the job. The libopus submodule is a public | |
| # https remote and needs no credential. | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Cache conformance vectors | |
| uses: actions/cache@v6 | |
| with: | |
| path: testdata/vectors/opus_testvectors-rfc8251.tar.gz | |
| key: opus-vectors-rfc8251-${{ hashFiles('testdata/vectors.sha256') }} | |
| # Tier B IS the real audio, so a missing corpus is a failure here, not a skip. | |
| - name: Fetch conformance vectors | |
| run: scripts/fetch-vectors.sh | |
| - name: Run tier B | |
| env: | |
| CGO_ENABLED: '1' | |
| run: | | |
| go test -tags refc -count=1 -timeout 150m -v \ | |
| ./internal/reftest/oracle/ -run 'TestOpusencPhase4GateLong' -gate.long | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| # No job here pushes, so the GITHUB_TOKEN has no business sitting in | |
| # .git/config for the life of the job. The libopus submodule is a public | |
| # https remote and needs no credential. | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| # skip-cache because the action's shared analysis cache poisons this job. On | |
| # 2026-09-02 every run, on main and on every PR branch, began failing with | |
| # four SA5011 "possible nil pointer dereference" reports in test files whose | |
| # nil check is a t.Fatalf immediately above the use. The same commit had | |
| # passed hours earlier, reruns failed identically, and every failing run | |
| # logged a hit on the same restored cache key. Running the pinned linter | |
| # (v2.12.2) against the pinned Go (1.26.7) on a clean cache reports zero | |
| # issues for both main and the branch, so the findings come from the cache | |
| # rather than from the code. A lint gate has to reflect the tree; the cache | |
| # saves seconds on a job the reftest dwarfs anyway. | |
| - name: Run golangci-lint | |
| uses: golangci/golangci-lint-action@v9 | |
| with: | |
| version: ${{ env.GOLANGCI_LINT_VERSION }} | |
| skip-cache: true | |
| vet: | |
| name: Vet | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| goarch: [amd64, arm64] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| # No job here pushes, so the GITHUB_TOKEN has no business sitting in | |
| # .git/config for the life of the job. The libopus submodule is a public | |
| # https remote and needs no credential. | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Run go vet | |
| env: | |
| GOARCH: ${{ matrix.goarch }} | |
| run: go vet ./... | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| goos: [linux, darwin, windows] | |
| goarch: [amd64, arm64] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| # No job here pushes, so the GITHUB_TOKEN has no business sitting in | |
| # .git/config for the life of the job. The libopus submodule is a public | |
| # https remote and needs no credential. | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Build | |
| env: | |
| GOOS: ${{ matrix.goos }} | |
| GOARCH: ${{ matrix.goarch }} | |
| run: go build ./... | |
| fuzz: | |
| name: Fuzz smoke | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| # A short bounded run of each decode-path fuzz target on every build. The | |
| # packet and Ogg readers parse untrusted bytes, so this guards against | |
| # panics and unbounded allocation on malformed input; a crash a run finds | |
| # is committed under testdata/fuzz as a regression seed. The last four targets | |
| # fuzz a SIMD kernel against its scalar reference. Smoke runs, not a | |
| # substitute for a long local fuzz session. The libopus submodule is not | |
| # needed: these targets are pure Go, the C reference is build-tagged out. | |
| - name: Fuzz Opus packet parser | |
| run: go test -run='^$' -fuzz='^FuzzParsePacket$' -fuzztime=20s ./internal/packet | |
| - name: Fuzz Opus decode | |
| run: go test -run='^$' -fuzz='^FuzzDecode$' -fuzztime=20s ./opus | |
| - name: Fuzz Ogg page reader | |
| run: go test -run='^$' -fuzz='^FuzzReadPage$' -fuzztime=15s ./oggopus | |
| - name: Fuzz OpusHead parser | |
| run: go test -run='^$' -fuzz='^FuzzParseOpusHead$' -fuzztime=15s ./oggopus | |
| - name: Fuzz OpusTags parser | |
| run: go test -run='^$' -fuzz='^FuzzParseOpusTags$' -fuzztime=15s ./oggopus | |
| - name: Fuzz Ogg container reader | |
| run: go test -run='^$' -fuzz='^FuzzContainerReader$' -fuzztime=15s ./oggopus | |
| - name: Fuzz xcorr SIMD differential | |
| run: go test -run='^$' -fuzz='^FuzzXcorrKernel$' -fuzztime=15s ./internal/celt | |
| - name: Fuzz comb filter SIMD differential | |
| run: go test -run='^$' -fuzz='^FuzzCombFilterConst$' -fuzztime=15s ./internal/celt | |
| - name: Fuzz haar1 SIMD differential | |
| run: go test -run='^$' -fuzz='^FuzzHaar1$' -fuzztime=15s ./internal/celt | |
| - name: Fuzz band-gain SIMD differential | |
| run: go test -run='^$' -fuzz='^FuzzBandGainRequant$' -fuzztime=15s ./internal/celt |