Skip to content

oracle: an independent library reads every Office package back #93

oracle: an independent library reads every Office package back

oracle: an independent library reads every Office package back #93

Workflow file for this run

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.6"
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.
#
# Two questions rather than one, since the graphics toolkit arrived
# on 2026-08-05. The graph below is 68 modules and most of them are
# Fyne's. Their licences were measured before it was added and every
# one in the linked set is permissive and one way compatible with
# GPL-3.0: 13 BSD-3, 10 MIT, 1 Apache-2.0, 1 BSD-2, 1 ISC.
#
# Changed once since, on 2026-08-05, when the window gained a
# folder picker. github.com/FyshOS/fancyfs was already named here
# because the toolkit requires it - importing the dialog package
# moved it from named to downloaded, checksummed and compiled in.
# Checked before it was accepted: BSD-3, 129 lines, written by the
# toolkit's own author, and reached from one line of the dialog
# package where it decorates folder icons. Tidying after it pruned
# github.com/kr/text from go.mod, which took github.com/creack/pty
# out of the graph, taking the count from 69 to 68.
#
# The second question protects what ships to most people. The
# command line binary links exactly two external modules and the
# toolkit is not among them, so a build for a server carries no
# window, no OpenGL and - see internal/guard - no socket.
run: |
set -euo pipefail
# Built with printf rather than written across several lines. A
# continuation starting in column one reads as the end of the jobs
# block to anything scanning this file by indentation, and the
# guard on job timeouts does exactly that - it counted one job
# instead of seven and went green whatever it was given.
expected=$(printf '%s\n' \
fyne.io/fyne/v2 \
fyne.io/systray \
github.com/BurntSushi/toml \
github.com/FyshOS/fancyfs \
github.com/akavel/rsrc \
github.com/anthonynsimon/bild \
github.com/clipperhouse/uax29/v2 \
github.com/cpuguy83/go-md2man/v2 \
github.com/davecgh/go-spew \
github.com/felixge/fgprof \
github.com/fogleman/gg \
github.com/fredbi/uri \
github.com/fsnotify/fsnotify \
github.com/fyne-io/gl-js \
github.com/fyne-io/glfw-js \
github.com/fyne-io/image \
github.com/fyne-io/oksvg \
github.com/go-gl/gl \
github.com/go-gl/glfw/v3.4/glfw \
github.com/go-ole/go-ole \
github.com/go-text/render \
github.com/go-text/typesetting \
github.com/go-text/typesetting-utils \
github.com/goccy/go-yaml \
github.com/godbus/dbus/v5 \
github.com/golang/freetype \
github.com/google/pprof \
github.com/hack-pad/go-indexeddb \
github.com/hack-pad/safejs \
github.com/inconshreveable/mousetrap \
github.com/jackmordaunt/icns/v2 \
github.com/jeandeaual/go-locale \
github.com/josephspurrier/goversioninfo \
github.com/jsummers/gobmp \
github.com/kr/text \
github.com/lucor/goinfo \
github.com/mattn/go-runewidth \
github.com/mcuadros/go-version \
github.com/natefinch/atomic \
github.com/nfnt/resize \
github.com/nicksnyder/go-i18n/v2 \
github.com/niemeyer/pretty \
github.com/pkg/profile \
github.com/pmezard/go-difflib \
github.com/russross/blackfriday/v2 \
github.com/rymdport/portal \
github.com/spf13/cobra \
github.com/spf13/pflag \
github.com/srwiley/oksvg \
github.com/srwiley/rasterx \
github.com/stretchr/objx \
github.com/stretchr/testify \
github.com/urfave/cli/v2 \
github.com/xrash/smetrics \
github.com/yuin/goldmark \
golang.org/x/crypto \
golang.org/x/image \
golang.org/x/mobile \
golang.org/x/mod \
golang.org/x/net \
golang.org/x/sync \
golang.org/x/sys \
golang.org/x/term \
golang.org/x/text \
golang.org/x/tools \
golang.org/x/tools/go/vcs \
gopkg.in/check.v1 \
gopkg.in/yaml.v3)
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, $(echo "$expected" | wc -l) modules"
linked=$(go list -deps -f '{{if .Module}}{{.Module.Path}}{{end}}' ./cmd/tfg | sort -u | grep -v '^github.com/donislawdev/TestingFilesGenerator$' | grep .)
wanted=$(printf '%s\n' github.com/goccy/go-yaml golang.org/x/text)
if [ "$linked" != "$wanted" ]; then
echo "the command line binary links a different set of modules."
echo "expected: $wanted"
echo "actual : $linked"
exit 1
fi
echo "the command line binary links only: $linked"
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 without a C toolchain
# The toolkit arrived on 2026-08-05 and this step still passes, which
# is the point of how it was wired in. Reaching the toolkit's app
# package needs CGO and, on Linux, X11 development headers no runner
# carries by default - so that one file sits behind a cgo build tag and
# everything building a widget tree sits outside it.
#
# CGO_ENABLED is 0 for this job, so this proves the tree still compiles
# where there is no compiler and no graphics. The tests go further:
# they render a screen to an image and read it, also with CGO off.
#
# What it does NOT prove is that the windowed binary links, because
# that build needs a C compiler per platform and is done natively -
# docs/STACK.md section 4.4. Naming the gap beats implying it is
# covered.
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