-
-
Notifications
You must be signed in to change notification settings - Fork 0
576 lines (536 loc) · 24.8 KB
/
Copy pathci.yml
File metadata and controls
576 lines (536 loc) · 24.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
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.
#
# macOS is the exception and it is the toolkit's, not ours. Measured on
# 2026-08-20, the first run this matrix had since the toolkit arrived:
# with CGO off, vet cannot type check fyne's own internal/widget on
# darwin - subscribeScrollerStyle and three others are undefined, because
# the file defining them is behind a build tag that CGO off removes.
# Nothing of ours is involved and there is nothing of ours to fix, so that
# one runner pays for a C toolchain it already has.
CGO_ENABLED: ${{ matrix.os == 'macos-latest' && '1' || '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)
# LC_ALL=C on every sort here, and on the written list too. Measured on
# 2026-08-20, the first run this job ever had on macOS: both lists
# held the same 68 modules and the comparison still failed, because
# BSD sort and GNU sort disagree about where a hyphen goes. The check
# is a string comparison, so an ordering difference reads exactly like
# a dependency appearing out of nowhere.
expected=$(printf '%s\n' "$expected" | LC_ALL=C sort)
actual=$(go list -m -f '{{.Path}}' all | grep -v '^github.com/donislawdev/TestingFilesGenerator$' | LC_ALL=C 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 | LC_ALL=C 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
# 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.
#
# On Linux and Windows CGO_ENABLED is 0, 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.
#
# On macOS it proves something else, and better - the windowed binary
# actually LINKS. That runner has CGO on because the toolkit cannot be
# type checked on darwin without it, so the build here is the real one.
# Which closes half of the gap this comment used to name: linking was
# done natively and nowhere but on the owner's machine.
#
# The flags below are Windows only. -H windowsgui names a Windows
# subsystem, and handing it to the darwin linker is what turned this
# step red on 2026-08-20 the moment macOS gained a C toolchain.
#
# The linker flags come from the file rather than from this line, so
# that the release build and this one cannot drift apart. The file is
# the single place they live, the same way the coverage threshold is.
# Without them Windows attaches a console window to the program, which
# it decides from one number in the PE header - and that number is what
# the guard reads, out of a binary it builds rather than out of this
# command.
run: |
set -euo pipefail
if [ "$RUNNER_OS" = "Windows" ]; then
go build -ldflags="$(cat .github/gui-ldflags)" ./cmd/tfg-gui
else
go build ./cmd/tfg-gui
fi
shell: bash
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: the headers the toolkit needs
# The window reaches OpenGL through C, so anything that builds or
# analyses the whole tree on Linux compiles GLFW - and no runner carries
# its headers. Measured on 2026-08-20, the first run after the
# repository went public and so the first one this job has ever had
# against the toolkit: "wayland-client-core.h: No such file or
# directory", and the job never reached our code at all.
#
# Taken from the toolkit's own CI rather than from a remembered list.
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends libgl1-mesa-dev libwayland-dev libx11-dev libxkbcommon-dev xorg-dev
shell: bash
- 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: the headers the toolkit needs
# The window reaches OpenGL through C, so anything that builds or
# analyses the whole tree on Linux compiles GLFW - and no runner carries
# its headers. Measured on 2026-08-20, the first run after the
# repository went public and so the first one this job has ever had
# against the toolkit: "wayland-client-core.h: No such file or
# directory", and the job never reached our code at all.
#
# Taken from the toolkit's own CI rather than from a remembered list.
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends libgl1-mesa-dev libwayland-dev libx11-dev libxkbcommon-dev xorg-dev
shell: bash
- 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 ./...
touched:
name: what this push touched
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
concurrency: ${{ steps.look.outputs.concurrency }}
steps:
- uses: actions/checkout@v7
with:
# The comparison needs the earlier commit, and the default checkout
# fetches one.
fetch-depth: 0
- name: did anything concurrent change
id: look
# The three files concurrency is allowed to live in are not a guess.
# They are the map in internal/guard/concurrency_test.go, and a fourth
# file growing a goroutine turns that guard red before it gets here - so
# this list cannot quietly fall behind the tree.
#
# go.mod is watched as well. A toolchain or dependency change can alter
# what the detector sees even when none of our own lines moved.
#
# Anything unclear counts as touched. A first push, a branch with no
# comparable parent, a pull request event with no before - all answer
# true, because the cost of running the detector when it was not needed
# is ten minutes and the cost of skipping it when it was is a data race
# in somebody else's file.
run: |
set -euo pipefail
watched='internal/format/registry.go cmd/tfg/main.go internal/gui/window/run.go go.mod'
before="${{ github.event.before }}"
if [ -z "$before" ] \
|| [ "$before" = "0000000000000000000000000000000000000000" ] \
|| ! git cat-file -e "${before}^{commit}" 2>/dev/null
then
echo "no earlier commit to compare against, so this counts as touched"
echo "concurrency=true" >> "$GITHUB_OUTPUT"
exit 0
fi
changed="$(git diff --name-only "$before" "${{ github.sha }}")"
echo "changed in this push:"
echo "$changed"
hit=false
for f in $watched; do
if printf '%s\n' "$changed" | grep -qx "$f"; then
echo " -> $f is one of the files the race detector is for"
hit=true
fi
done
echo "concurrency=$hit" >> "$GITHUB_OUTPUT"
shell: bash
race:
name: race detector
needs: touched
# Not on every push, decided on 2026-08-20 after the owner asked what it was
# costing. Measured that day: 10m31s on the runner, against about a minute
# for the whole matrix - so it was the longest thing in the run by a factor
# of ten, on every push, including the ones that only touched a document.
#
# What makes that safe to change rather than a corner cut: concurrency in
# this tree is confined to three files by a guard that fails if a fourth
# grows a goroutine. A push that does not touch them cannot introduce a race
# for this to find, so running it there bought nothing.
#
# Three ways in now. A push that touches one of those files, the weekly
# sweep that fuzzing already uses, and by hand. The weekly run is what
# catches a race that arrives through a dependency rather than through us.
if: >-
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
needs.touched.outputs.concurrency == 'true'
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: the headers the toolkit needs
# The window reaches OpenGL through C, so anything that builds or
# analyses the whole tree on Linux compiles GLFW - and no runner carries
# its headers. Measured on 2026-08-20, the first run after the
# repository went public and so the first one this job has ever had
# against the toolkit: "wayland-client-core.h: No such file or
# directory", and the job never reached our code at all.
#
# Taken from the toolkit's own CI rather than from a remembered list.
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends libgl1-mesa-dev libwayland-dev libx11-dev libxkbcommon-dev xorg-dev
shell: bash
- 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@v7
with:
name: fuzz-findings
path: |
internal/guard/testdata/fuzz/**
~/.cache/go-build/fuzz/**
if-no-files-found: warn