rastrillo / fiesta Public

Browser roles run only the browser tests: every test in one role, 48 shards

A browser role ran its whole package under the tag, plain tests
included, so the gallery's, www's and check's plain tests ran twice: in
root or their test roles, and again in the browser slices. The fleet
run of the 52-shard suite was CPU-bound, and that repeat was about two
minutes of CPU-heavy rendering per run. hack/gotest.sh now runs, in a
browser role, only the tests the browser build adds (`go test -tags
browser -list` less plain `go test -list`), whole package or slice.

ci_test.go's TestEveryTestRunsInExactlyOneRole makes that a checked
property: it reads each package's tests from the files each build
includes, without compiling, and requires every test to land in
exactly one role (plain tests in root or the package's test roles,
browser-only tests in its browser roles), so a package with a browser
test outside BROWSER_PKGS, or a browser package with none, fails.

The browser weights lose their plain tests, and the lighter browser
packages take fewer slices: check@13, internal/gallery@18,
internal/www@4. The suite is 48 roles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 195862d0f96ff60b5958f9597b92b4f3295a631f parent b8ddd3f
5 files changed, +217 −45
  • .amadan/ci.d/{50-suite@52 => 50-suite@48} +0 −0
  • Makefile +6 −3
  • ci_test.go +157 −0
  • hack/gotest.sh +49 −18
  • hack/test-weights.txt +5 −24

CI passed — run details

CI log
=== gofmt ===
=== no-rastrillo ===
mkdir -p "/var/lib/amadan-runner/w4/repos/rastrillo__fiesta/wt/gallery/.build/tmp"
go test -run '^(TestFiestaNeverImportsRastrillo|TestThePublicPackagesLinkNoBrowser)$' -count=1 .
go: downloading go1.26.10 (linux/arm64)
ok  	amadan.net/rastrillo/fiesta	1.188s
=== size ===
go test ./internal/build -run '^TestTheOneLinkIsUnder25KiBGzipped$' -count=1 -v
=== RUN   TestTheOneLinkIsUnder25KiBGzipped
    build_test.go:138: fiesta-plain.css: 152856 bytes, 22303 bytes gzipped (21.8 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta-signal.css: 152951 bytes, 22414 bytes gzipped (21.9 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta.css: 155770 bytes, 23018 bytes gzipped (22.5 KiB of the 25.0 KiB limit)
--- PASS: TestTheOneLinkIsUnder25KiBGzipped (0.03s)
PASS
ok  	amadan.net/rastrillo/fiesta/internal/build	0.032s
=== history ===
history: a shallow clone; fetching the whole history and the release tags
history: 26 commits; release tags: v1.0.0
=== suite ===
--- shard 0 of 48 ---
==> shard 0 of 48: make root
go build ./...
go vet ./...
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh root 'internal/gallery@3 cmd/fiesta@2 internal/www@2'
ok  	amadan.net/rastrillo/fiesta	7.607s
ok  	amadan.net/rastrillo/fiesta/check	11.552s
ok  	amadan.net/rastrillo/fiesta/example	0.424s
?   	amadan.net/rastrillo/fiesta/hack/seed/english	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/axe	0.204s
ok  	amadan.net/rastrillo/fiesta/internal/build	3.220s
ok  	amadan.net/rastrillo/fiesta/internal/codeview	0.123s
?   	amadan.net/rastrillo/fiesta/internal/harness	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/markup	0.034s
ok  	amadan.net/rastrillo/fiesta/internal/nodetest	0.874s
ok  	amadan.net/rastrillo/fiesta/internal/site	6.115s
ok  	amadan.net/rastrillo/fiesta/internal/tokens	0.014s
suite: root took 61s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m20.660000s 0m4.410000s
--- shard 1 of 48 ---
==> shard 1 of 48: make test.internal.gallery~1of3
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.gallery~1of3'
gotest: slice 1 of 3 of ./internal/gallery/ runs 33 of 102 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	107.903s
suite: test.internal.gallery~1of3 took 138s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m55.850000s 0m2.300000s
--- shard 2 of 48 ---
==> shard 2 of 48: make test.internal.gallery~2of3
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.gallery~2of3'
gotest: slice 2 of 3 of ./internal/gallery/ runs 35 of 102 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	101.314s
suite: test.internal.gallery~2of3 took 131s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m46.900000s 0m1.760000s
--- shard 3 of 48 ---
==> shard 3 of 48: make test.internal.gallery~3of3
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.gallery~3of3'
gotest: slice 3 of 3 of ./internal/gallery/ runs 34 of 102 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	84.952s
suite: test.internal.gallery~3of3 took 117s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m37.550000s 0m1.870000s
--- shard 4 of 48 ---
==> shard 4 of 48: make test.cmd.fiesta~1of2
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'cmd.fiesta~1of2'
gotest: slice 1 of 2 of ./cmd/fiesta/ runs 7 of 12 test tests
ok  	amadan.net/rastrillo/fiesta/cmd/fiesta	91.448s
suite: test.cmd.fiesta~1of2 took 122s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m36.170000s 0m2.900000s
--- shard 5 of 48 ---
==> shard 5 of 48: make test.cmd.fiesta~2of2
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'cmd.fiesta~2of2'
gotest: slice 2 of 2 of ./cmd/fiesta/ runs 5 of 12 test tests
ok  	amadan.net/rastrillo/fiesta/cmd/fiesta	94.380s
suite: test.cmd.fiesta~2of2 took 124s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m38.980000s 0m3.050000s
--- shard 6 of 48 ---
==> shard 6 of 48: make test.internal.www~1of2
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.www~1of2'
gotest: slice 1 of 2 of ./internal/www/ runs 8 of 18 test tests
ok  	amadan.net/rastrillo/fiesta/internal/www	72.676s
suite: test.internal.www~1of2 took 120s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m45.700000s 0m3.020000s
--- shard 7 of 48 ---
==> shard 7 of 48: make test.internal.www~2of2
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 FIESTA_TEST_REQUIRE_TAGS=1 ./hack/gotest.sh test 'internal.www~2of2'
gotest: slice 2 of 2 of ./internal/www/ runs 10 of 18 test tests
ok  	amadan.net/rastrillo/fiesta/internal/www	68.411s
suite: test.internal.www~2of2 took 114s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m41.090000s 0m2.840000s
--- shard 8 of 48 ---
==> shard 8 of 48: make staticcheck
go run honnef.co/go/tools/cmd/staticcheck@v0.8.2 -tags browser ./...
go: downloading honnef.co/go/tools v0.8.2
go: downloading golang.org/x/tools v0.51.0
go: downloading golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678
go: downloading github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
go: downloading golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
suite: staticcheck took 69s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m36.280000s 0m5.530000s
--- shard 9 of 48 ---
==> shard 9 of 48: make govulncheck
go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 ./...
go: downloading golang.org/x/vuln v1.8.0
go: downloading golang.org/x/telemetry v0.0.0-20260908163034-4bcc4b2ee518
go: downloading golang.org/x/tools v0.50.0
go: downloading golang.org/x/mod v0.41.0
go: downloading golang.org/x/sync v0.23.0
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 2 vulnerabilities in packages you import and 5
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
suite: govulncheck took 55s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.810000s 0m2.700000s
--- shard 10 of 48 ---
==> shard 10 of 48: make gitleaks
go run github.com/zricethezav/gitleaks/v8@v8.30.1 git --no-banner --redact --log-opts='--full-history --diff-filter=tuxdb HEAD' .
go: downloading github.com/zricethezav/gitleaks/v8 v8.30.1
go: downloading github.com/rs/zerolog v1.33.0
go: downloading github.com/spf13/cobra v1.9.1
go: downloading github.com/spf13/viper v1.19.0
go: downloading github.com/Masterminds/sprig/v3 v3.3.0
go: downloading github.com/charmbracelet/lipgloss v0.5.0
go: downloading github.com/fatih/semgroup v1.2.0
go: downloading github.com/gitleaks/go-gitdiff v0.9.1
go: downloading github.com/h2non/filetype v1.1.3
go: downloading github.com/BobuSumisu/aho-corasick v1.0.3
go: downloading golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
go: downloading github.com/hashicorp/go-version v1.7.0
go: downloading github.com/mholt/archives v0.1.2
go: downloading golang.org/x/sync v0.11.0
go: downloading github.com/muesli/termenv v0.15.1
go: downloading github.com/lucasb-eyer/go-colorful v1.2.0
go: downloading github.com/mattn/go-runewidth v0.0.14
go: downloading github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68
go: downloading github.com/spf13/pflag v1.0.6
go: downloading dario.cat/mergo v1.0.1
go: downloading github.com/Masterminds/goutils v1.1.1
go: downloading github.com/Masterminds/semver/v3 v3.3.0
go: downloading github.com/google/uuid v1.6.0
go: downloading github.com/huandu/xstrings v1.5.0
go: downloading github.com/mitchellh/copystructure v1.2.0
go: downloading github.com/shopspring/decimal v1.4.0
go: downloading github.com/spf13/cast v1.7.1
go: downloading golang.org/x/crypto v0.35.0
go: downloading github.com/mattn/go-colorable v0.1.14
go: downloading github.com/klauspost/compress v1.17.11
go: downloading github.com/STARRY-S/zip v0.2.1
go: downloading github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3
go: downloading github.com/bodgit/sevenzip v1.6.0
go: downloading github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
go: downloading github.com/klauspost/pgzip v1.2.6
go: downloading github.com/minio/minlz v1.0.0
go: downloading github.com/nwaples/rardecode/v2 v2.1.0
go: downloading github.com/pierrec/lz4/v4 v4.1.21
go: downloading github.com/sorairolake/lzip-go v0.3.5
go: downloading github.com/therootcompany/xz v1.0.1
go: downloading github.com/ulikunitz/xz v0.5.12
go: downloading golang.org/x/text v0.22.0
go: downloading github.com/mitchellh/reflectwalk v1.0.2
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading github.com/rivo/uniseg v0.2.0
go: downloading github.com/sagikazarmark/slog-shim v0.1.0
go: downloading github.com/fsnotify/fsnotify v1.8.0
go: downloading github.com/mitchellh/mapstructure v1.5.0
go: downloading github.com/spf13/afero v1.12.0
go: downloading github.com/subosito/gotenv v1.6.0
go: downloading github.com/hashicorp/hcl v1.0.0
go: downloading gopkg.in/ini.v1 v1.67.0
go: downloading github.com/magiconair/properties v1.8.9
go: downloading github.com/pelletier/go-toml/v2 v2.2.3
go: downloading gopkg.in/yaml.v3 v3.0.1
go: downloading github.com/aymanbagabas/go-osc52/v2 v2.0.1
go: downloading golang.org/x/sys v0.30.0
go: downloading github.com/bodgit/plumbing v1.3.0
go: downloading github.com/bodgit/windows v1.0.1
go: downloading github.com/hashicorp/go-multierror v1.1.1
go: downloading go4.org v0.0.0-20230225012048-214862532bf5
go: downloading github.com/hashicorp/golang-lru/v2 v2.0.7
go: downloading github.com/hashicorp/errwrap v1.1.0
7:29PM INF Unknown SCM platform. Use --platform to include links in findings. host=amadan.net
7:29PM INF 26 commits scanned.
7:29PM INF scanned ~7744239 bytes (7.74 MB) in 5.57s
7:29PM INF no leaks found
suite: gitleaks took 62s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m30.640000s 0m3.960000s
--- shard 11 of 48 ---
==> shard 11 of 48: make browser.internal.harness
./hack/gotest.sh browser 'internal.harness'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: ./internal/harness/ runs its 10 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/harness	9.004s
suite: browser.internal.harness took 59s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.850000s 0m4.540000s
--- shard 12 of 48 ---
==> shard 12 of 48: make browser.check~1of13
./hack/gotest.sh browser 'check~1of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 1 of 13 of ./check/ runs 5 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	95.855s
suite: browser.check~1of13 took 155s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m57.250000s 0m10.760000s
--- shard 13 of 48 ---
==> shard 13 of 48: make browser.check~2of13
./hack/gotest.sh browser 'check~2of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 2 of 13 of ./check/ runs 9 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	88.666s
suite: browser.check~2of13 took 146s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m40.340000s 0m9.700000s
--- shard 14 of 48 ---
==> shard 14 of 48: make browser.check~3of13
./hack/gotest.sh browser 'check~3of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 3 of 13 of ./check/ runs 10 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	93.792s
suite: browser.check~3of13 took 153s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m54.730000s 0m11.760000s
--- shard 15 of 48 ---
==> shard 15 of 48: make browser.check~4of13
./hack/gotest.sh browser 'check~4of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 4 of 13 of ./check/ runs 11 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	96.359s
suite: browser.check~4of13 took 155s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m58.270000s 0m13.960000s
--- shard 16 of 48 ---
==> shard 16 of 48: make browser.check~5of13
./hack/gotest.sh browser 'check~5of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 5 of 13 of ./check/ runs 16 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	68.039s
suite: browser.check~5of13 took 125s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m31.920000s 0m8.290000s
--- shard 17 of 48 ---
==> shard 17 of 48: make browser.check~6of13
./hack/gotest.sh browser 'check~6of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 6 of 13 of ./check/ runs 16 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	78.098s
suite: browser.check~6of13 took 137s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m41.400000s 0m11.430000s
--- shard 18 of 48 ---
==> shard 18 of 48: make browser.check~7of13
./hack/gotest.sh browser 'check~7of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 7 of 13 of ./check/ runs 16 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	77.211s
suite: browser.check~7of13 took 136s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m39.280000s 0m9.500000s
--- shard 19 of 48 ---
==> shard 19 of 48: make browser.check~8of13
./hack/gotest.sh browser 'check~8of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 8 of 13 of ./check/ runs 17 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	72.807s
suite: browser.check~8of13 took 131s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m37.540000s 0m10.760000s
--- shard 20 of 48 ---
==> shard 20 of 48: make browser.check~9of13
./hack/gotest.sh browser 'check~9of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 9 of 13 of ./check/ runs 18 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	70.400s
suite: browser.check~9of13 took 130s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m38.090000s 0m12.440000s
--- shard 21 of 48 ---
==> shard 21 of 48: make browser.check~10of13
./hack/gotest.sh browser 'check~10of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 10 of 13 of ./check/ runs 17 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	84.736s
suite: browser.check~10of13 took 144s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m47.750000s 0m13.460000s
--- shard 22 of 48 ---
==> shard 22 of 48: make browser.check~11of13
./hack/gotest.sh browser 'check~11of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 11 of 13 of ./check/ runs 18 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	67.807s
suite: browser.check~11of13 took 127s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m35.760000s 0m9.900000s
--- shard 23 of 48 ---
==> shard 23 of 48: make browser.check~12of13
./hack/gotest.sh browser 'check~12of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 12 of 13 of ./check/ runs 18 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	78.973s
suite: browser.check~12of13 took 138s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m40.290000s 0m12.040000s
--- shard 24 of 48 ---
==> shard 24 of 48: make browser.check~13of13
./hack/gotest.sh browser 'check~13of13'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 13 of 13 of ./check/ runs 18 of 189 browser tests
ok  	amadan.net/rastrillo/fiesta/check	69.376s
suite: browser.check~13of13 took 128s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m36.740000s 0m10.910000s
--- shard 25 of 48 ---
==> shard 25 of 48: make browser.internal.gallery~1of18
./hack/gotest.sh browser 'internal.gallery~1of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 1 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	78.090s
suite: browser.internal.gallery~1of18 took 136s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m34.160000s 0m7.060000s
--- shard 26 of 48 ---
==> shard 26 of 48: make browser.internal.gallery~2of18
./hack/gotest.sh browser 'internal.gallery~2of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 2 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	75.345s
suite: browser.internal.gallery~2of18 took 132s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m30.670000s 0m6.100000s
--- shard 27 of 48 ---
==> shard 27 of 48: make browser.internal.gallery~3of18
./hack/gotest.sh browser 'internal.gallery~3of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 3 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	73.841s
suite: browser.internal.gallery~3of18 took 132s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m29.950000s 0m5.320000s
--- shard 28 of 48 ---
==> shard 28 of 48: make browser.internal.gallery~4of18
./hack/gotest.sh browser 'internal.gallery~4of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 4 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	71.943s
suite: browser.internal.gallery~4of18 took 129s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m30.960000s 0m5.110000s
--- shard 29 of 48 ---
==> shard 29 of 48: make browser.internal.gallery~5of18
./hack/gotest.sh browser 'internal.gallery~5of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 5 of 18 of ./internal/gallery/ runs 6 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	80.930s
suite: browser.internal.gallery~5of18 took 136s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m32.900000s 0m7.480000s
--- shard 30 of 48 ---
==> shard 30 of 48: make browser.internal.gallery~6of18
./hack/gotest.sh browser 'internal.gallery~6of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 6 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	92.301s
suite: browser.internal.gallery~6of18 took 148s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m33.990000s 0m7.920000s
--- shard 31 of 48 ---
==> shard 31 of 48: make browser.internal.gallery~7of18
./hack/gotest.sh browser 'internal.gallery~7of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 7 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	78.169s
suite: browser.internal.gallery~7of18 took 134s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m34.240000s 0m6.890000s
--- shard 32 of 48 ---
==> shard 32 of 48: make browser.internal.gallery~8of18
./hack/gotest.sh browser 'internal.gallery~8of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 8 of 18 of ./internal/gallery/ runs 6 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	85.221s
suite: browser.internal.gallery~8of18 took 143s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m36.400000s 0m9.100000s
--- shard 33 of 48 ---
==> shard 33 of 48: make browser.internal.gallery~9of18
./hack/gotest.sh browser 'internal.gallery~9of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 9 of 18 of ./internal/gallery/ runs 2 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	93.641s
suite: browser.internal.gallery~9of18 took 150s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m35.610000s 0m7.400000s
--- shard 34 of 48 ---
==> shard 34 of 48: make browser.internal.gallery~10of18
./hack/gotest.sh browser 'internal.gallery~10of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 10 of 18 of ./internal/gallery/ runs 3 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	91.925s
suite: browser.internal.gallery~10of18 took 152s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m35.820000s 0m7.780000s
--- shard 35 of 48 ---
==> shard 35 of 48: make browser.internal.gallery~11of18
./hack/gotest.sh browser 'internal.gallery~11of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 11 of 18 of ./internal/gallery/ runs 4 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	95.151s
suite: browser.internal.gallery~11of18 took 151s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m33.700000s 0m7.590000s
--- shard 36 of 48 ---
==> shard 36 of 48: make browser.internal.gallery~12of18
./hack/gotest.sh browser 'internal.gallery~12of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 12 of 18 of ./internal/gallery/ runs 4 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	95.502s
suite: browser.internal.gallery~12of18 took 152s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m34.860000s 0m7.850000s
--- shard 37 of 48 ---
==> shard 37 of 48: make browser.internal.gallery~13of18
./hack/gotest.sh browser 'internal.gallery~13of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 13 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	77.785s
suite: browser.internal.gallery~13of18 took 138s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m32.510000s 0m6.180000s
--- shard 38 of 48 ---
==> shard 38 of 48: make browser.internal.gallery~14of18
./hack/gotest.sh browser 'internal.gallery~14of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 14 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	77.429s
suite: browser.internal.gallery~14of18 took 135s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m31.280000s 0m6.040000s
--- shard 39 of 48 ---
==> shard 39 of 48: make browser.internal.gallery~15of18
./hack/gotest.sh browser 'internal.gallery~15of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 15 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	82.416s
suite: browser.internal.gallery~15of18 took 139s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m32.290000s 0m6.550000s
--- shard 40 of 48 ---
==> shard 40 of 48: make browser.internal.gallery~16of18
./hack/gotest.sh browser 'internal.gallery~16of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 16 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	79.284s
suite: browser.internal.gallery~16of18 took 136s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m31.050000s 0m6.180000s
--- shard 41 of 48 ---
==> shard 41 of 48: make browser.internal.gallery~17of18
./hack/gotest.sh browser 'internal.gallery~17of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 17 of 18 of ./internal/gallery/ runs 5 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	81.260s
suite: browser.internal.gallery~17of18 took 139s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m35.070000s 0m7.390000s
--- shard 42 of 48 ---
==> shard 42 of 48: make browser.internal.gallery~18of18
./hack/gotest.sh browser 'internal.gallery~18of18'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 18 of 18 of ./internal/gallery/ runs 2 of 82 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	77.175s
suite: browser.internal.gallery~18of18 took 134s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m28.750000s 0m4.700000s
--- shard 43 of 48 ---
==> shard 43 of 48: make browser.internal.gallery.history
./hack/gotest.sh browser 'internal.gallery.history'
gotest: on ip-172-31-28-52, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: ./internal/gallery/history/ runs its 3 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery/history	64.501s
suite: browser.internal.gallery.history took 110s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m37.040000s 0m4.380000s
--- shard 44 of 48 ---
==> shard 44 of 48: make browser.internal.www~1of4
./hack/gotest.sh browser 'internal.www~1of4'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 1 of 4 of ./internal/www/ runs 4 of 16 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	80.179s
suite: browser.internal.www~1of4 took 134s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m48.950000s 0m4.700000s
--- shard 45 of 48 ---
==> shard 45 of 48: make browser.internal.www~2of4
./hack/gotest.sh browser 'internal.www~2of4'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 2 of 4 of ./internal/www/ runs 4 of 16 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	77.788s
suite: browser.internal.www~2of4 took 131s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m55.180000s 0m5.020000s
--- shard 46 of 48 ---
==> shard 46 of 48: make browser.internal.www~3of4
./hack/gotest.sh browser 'internal.www~3of4'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 3 of 4 of ./internal/www/ runs 4 of 16 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	78.830s
suite: browser.internal.www~3of4 took 132s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m47.560000s 0m4.560000s
--- shard 47 of 48 ---
==> shard 47 of 48: make browser.internal.www~4of4
./hack/gotest.sh browser 'internal.www~4of4'
gotest: on ip-172-31-28-52, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 4 of 4 of ./internal/www/ runs 4 of 16 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	72.823s
suite: browser.internal.www~4of4 took 127s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m48.190000s 0m4.860000s
diff --git a/.amadan/ci.d/50-suite@52 b/.amadan/ci.d/50-suite@48
similarity index 100%
rename from .amadan/ci.d/50-suite@52
rename to .amadan/ci.d/50-suite@48
diff --git a/Makefile b/Makefile
index b7c9091..53690ab 100644
--- a/Makefile
+++ b/Makefile
@@ -51,8 +51,11 @@ ci: gofmt no-rastrillo size history suite
#
# The slowest role is how long the suite takes, so the slow packages
# are roles of their own, sliced until every role runs in under a
-# minute on a runner. The browser packages are a role each, and
-# ROOT_SPLIT's packages leave root's go test ./... for test.<pkg> roles.
+# minute on a runner. The browser packages are a role each, running
+# only the tests the browser build adds (a package's plain tests run in
+# root, or in its test.<pkg> roles), and ROOT_SPLIT's packages leave
+# root's go test ./... for test.<pkg> roles. ci_test.go checks that
+# every test runs in exactly one role.
# A package written pkg@n is n roles, each a slice of its tests, dealt
# by hack/gotest.sh by the seconds in hack/test-weights.txt. In a
# role's name a / in the path is a . so internal/gallery@2 becomes
@@ -61,7 +64,7 @@ ci: gofmt no-rastrillo size history suite
# the two axe sweeps, the HTML tab's renders, the floors drive). The
# tasks that add a browser package append here and rename 50-suite@N
# to match.
-BROWSER_PKGS := internal/harness check@14 internal/gallery@20 internal/gallery/history internal/www@5
+BROWSER_PKGS := internal/harness check@13 internal/gallery@18 internal/gallery/history internal/www@4
ROOT_SPLIT := internal/gallery@3 cmd/fiesta@2 internal/www@2
go-roles = $(foreach p,$(2),$(if $(findstring @,$(p)),$(call go-slices,$(1),$(subst /,.,$(word 1,$(subst @, ,$(p)))),$(word 2,$(subst @, ,$(p)))),$(1).$(subst /,.,$(p))))
go-slices = $(foreach k,$(shell seq 1 $(3)),$(1).$(2)~$(k)of$(3))
diff --git a/ci_test.go b/ci_test.go
index f9b33eb..6248b4d 100644
--- a/ci_test.go
+++ b/ci_test.go
@@ -1,6 +1,10 @@
package fiesta
import (
+ "encoding/json"
+ "go/ast"
+ "go/parser"
+ "go/token"
"os"
"os/exec"
"path/filepath"
@@ -9,6 +13,8 @@ import (
"strconv"
"strings"
"testing"
+ "unicode"
+ "unicode/utf8"
)
// This file is rastrillo's amadan_ci_test.go, aimed at this repo's own
@@ -334,3 +340,154 @@ func TestSlicesPartitionTheirPackages(t *testing.T) {
}
}
}
+
+// testNames reads the tests, examples and fuzz targets go test would
+// find in each package of ./..., built with tags (none, or browser):
+// import path to the set of names. It parses the files go list says
+// the build includes rather than compiling anything, so it costs a
+// second, not a build of every test binary.
+func testNames(t *testing.T, tags string) map[string]map[string]bool {
+ t.Helper()
+ args := []string{"list", "-e", "-json"}
+ if tags != "" {
+ args = append(args, "-tags", tags)
+ }
+ out, err := exec.Command("go", append(args, "./...")...).Output()
+ if err != nil {
+ t.Fatalf("go %s ./...: %v", strings.Join(args, " "), err)
+ }
+ names := map[string]map[string]bool{}
+ dec := json.NewDecoder(strings.NewReader(string(out)))
+ for dec.More() {
+ var pkg struct {
+ ImportPath, Dir string
+ TestGoFiles, XTestGoFiles []string
+ Error *struct{ Err string }
+ }
+ if err := dec.Decode(&pkg); err != nil {
+ t.Fatal(err)
+ }
+ // A package whose files the tags exclude entirely is not a
+ // package of this build; any other error is a broken tree.
+ if pkg.Error != nil {
+ if strings.Contains(pkg.Error.Err, "build constraints exclude all Go files") {
+ continue
+ }
+ t.Fatalf("go list %s: %s", pkg.ImportPath, pkg.Error.Err)
+ }
+ set := map[string]bool{}
+ for _, f := range append(pkg.TestGoFiles, pkg.XTestGoFiles...) {
+ file, err := parser.ParseFile(token.NewFileSet(), filepath.Join(pkg.Dir, f), nil, parser.SkipObjectResolution)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, d := range file.Decls {
+ fn, ok := d.(*ast.FuncDecl)
+ if !ok || fn.Recv != nil {
+ continue
+ }
+ for _, prefix := range []string{"Test", "Example", "Fuzz"} {
+ rest, ok := strings.CutPrefix(fn.Name.Name, prefix)
+ if r, _ := utf8.DecodeRuneInString(rest); ok && (rest == "" || !unicode.IsLower(r)) && fn.Name.Name != "TestMain" {
+ set[fn.Name.Name] = true
+ }
+ }
+ }
+ }
+ names[pkg.ImportPath] = set
+ }
+ return names
+}
+
+// TestEveryTestRunsInExactlyOneRole holds the suite's roles to running
+// each test once. A package's plain tests (those the untagged build
+// has) run in root, or in its test.<pkg> roles where ROOT_SPLIT names
+// it; its browser tests (those only the browser build has) run in its
+// browser roles, which is hack/gotest.sh's rule for a browser role. So
+// every package with a browser test must be a browser package, every
+// browser package must have one, every split package must exist, and
+// root must be a role. That a sliced package's slices partition it is
+// TestSlicesPartitionTheirPackages.
+func TestEveryTestRunsInExactlyOneRole(t *testing.T) {
+ if _, err := exec.LookPath("make"); err != nil {
+ t.Skip("make is not installed; the gate itself runs under make")
+ }
+ out, err := exec.Command("make", "-s", "--no-print-directory", "suite-roles").Output()
+ if err != nil {
+ t.Fatalf("make suite-roles: %v", err)
+ }
+ modPath := "amadan.net/rastrillo/fiesta"
+ pathOf := func(role, mode string) string {
+ rel := strings.TrimPrefix(role, mode+".")
+ rel, _, _ = strings.Cut(rel, "~")
+ return modPath + "/" + strings.ReplaceAll(rel, ".", "/")
+ }
+ root := false
+ browserRoles, testRoles := map[string]bool{}, map[string]bool{}
+ for _, r := range strings.Fields(string(out)) {
+ switch {
+ case r == "root":
+ root = true
+ case strings.HasPrefix(r, "browser."):
+ browserRoles[pathOf(r, "browser")] = true
+ case strings.HasPrefix(r, "test."):
+ testRoles[pathOf(r, "test")] = true
+ }
+ }
+ if !root {
+ t.Fatal("the suite has no root role, so the plain tests of every unsplit package run nowhere")
+ }
+
+ plain, tagged := testNames(t, ""), testNames(t, "browser")
+ for pkg := range testRoles {
+ if _, ok := plain[pkg]; !ok {
+ t.Errorf("%s has test roles but the untagged build has no such package", pkg)
+ }
+ }
+ pkgs := map[string]bool{}
+ for pkg := range plain {
+ pkgs[pkg] = true
+ }
+ for pkg := range tagged {
+ pkgs[pkg] = true
+ }
+ ran := 0
+ for pkg := range pkgs {
+ browserOnly := 0
+ names := map[string]bool{}
+ for n := range plain[pkg] {
+ names[n] = true
+ }
+ for n := range tagged[pkg] {
+ names[n] = true
+ }
+ for n := range names {
+ roles := 0
+ if plain[pkg][n] {
+ roles++ // root, or the package's test roles
+ }
+ if tagged[pkg][n] && !plain[pkg][n] {
+ browserOnly++
+ if browserRoles[pkg] {
+ roles++
+ }
+ }
+ if roles != 1 {
+ t.Errorf("%s %s runs in %d roles, not one", pkg, n, roles)
+ }
+ ran += roles
+ }
+ if browserRoles[pkg] && browserOnly == 0 {
+ t.Errorf("%s has browser roles but no test only the browser build has; its roles would run nothing", pkg)
+ }
+ }
+ for pkg := range browserRoles {
+ if !pkgs[pkg] {
+ t.Errorf("%s has browser roles but no such package", pkg)
+ }
+ }
+ if ran < 500 {
+ t.Errorf("only %d tests found across the suite; the reading has stopped seeing them", ran)
+ }
+ t.Logf("%d tests, each in one role", ran)
+}
diff --git a/hack/gotest.sh b/hack/gotest.sh
index 8b8ef25..1c295f5 100755
--- a/hack/gotest.sh
+++ b/hack/gotest.sh
@@ -10,6 +10,13 @@
# n slices of its tests. Flags after the name go to go test, after the
# role's own, and only for a whole package: a slice is its own -run.
#
+# A browser role runs only its package's browser tests: the ones
+# `go test -tags browser -list` names and plain `go test -list` does
+# not. Every other test in the package runs once already, in root or in
+# the package's test.<pkg> roles, and running it again under the tag
+# was the same check paid for twice. ci_test.go checks that every test
+# lands in exactly one role.
+#
# A slice is dealt from the package's own test list: heaviest first,
# each onto the lightest slice so far, by the seconds in
# hack/test-weights.txt (a test not listed there weighs 1). Every shard
@@ -157,9 +164,48 @@ run() {
exit "$status"
}
+# list <tags> prints the package's tests, examples and fuzz targets,
+# one per line. Its own command, not the head of a pipeline: a package
+# that fails to build must fail here, not list nothing through a grep
+# that succeeds.
+list() {
+ # shellcheck disable=SC2086 # an empty tag list is no argument at all
+ listing=$(go test $1 -list '.*' "$pkg") || {
+ echo "gotest: could not list the tests in $pkg:" >&2
+ printf '%s\n' "$listing" >&2
+ return 1
+ }
+ # Examples and fuzz seeds answer to -run as well, so they are dealt too.
+ printf '%s\n' "$listing" | grep -E '^(Test|Example|Fuzz)' || true
+}
+
+if [ "$mode" = test ] && [ -z "$slice" ]; then
+ run go test -timeout "$timeout" "$pkg" -count=1 "$@"
+fi
+
+all=$(list "$tags")
+if [ "$mode" = browser ]; then
+ # The plain tests, where the package builds without the tag at all:
+ # a package of nothing but browser files is not in go list ./....
+ plain=
+ if go list ./... | grep -qx "$(go list $tags "$pkg")"; then
+ plain=$(list "")
+ fi
+ if [ -n "$plain" ]; then
+ all=$(printf '%s\n' "$all" | grep -vxF "$plain" || true)
+ fi
+fi
+total=$(printf '%s\n' "$all" | grep -c . || true)
+if [ "$total" -eq 0 ]; then
+ echo "gotest: $pkg listed no $mode tests; refusing to report a role of nothing as a pass"
+ exit 1
+fi
+
if [ -z "$slice" ]; then
- # shellcheck disable=SC2086 # an empty $tags is no argument at all
- run go test $tags -timeout "$timeout" "$pkg" -count=1 "$@"
+ echo "gotest: $pkg runs its $total $mode tests"
+ # The role's -run first, so a -run among the flags overrides it.
+ # shellcheck disable=SC2086
+ run go test $tags -timeout "$timeout" -run "^($(printf '%s\n' "$all" | paste -sd'|' -))\$" "$pkg" -count=1 "$@"
fi
if [ $# -gt 0 ]; then
echo "gotest: flags for go test are for a whole package, not slice $slice of $pkg"
@@ -168,26 +214,11 @@ fi
k=${slice%of*}
n=${slice#*of}
-# Its own command, not the head of a pipeline: a package that fails to
-# build must fail here, not list nothing through a grep that succeeds.
-# shellcheck disable=SC2086
-listing=$(go test $tags -list '.*' "$pkg") || {
- echo "gotest: could not list the tests in $pkg:"
- printf '%s\n' "$listing"
- exit 1
-}
-# Examples and fuzz seeds answer to -run as well, so they are dealt too.
-all=$(printf '%s\n' "$listing" | grep -E '^(Test|Example|Fuzz)' || true)
-total=$(printf '%s\n' "$all" | grep -c . || true)
-if [ "$total" -eq 0 ]; then
- echo "gotest: $pkg listed no tests; refusing to report a slice of nothing as a pass"
- exit 1
-fi
mine=$(printf '%s\n' "$all" | deal "$mode" "$rel" "$n" | awk -v k="$k" '$1 == k { print $2 }')
if [ -z "$mine" ]; then
echo "gotest: slice $k of $n of $pkg was dealt none of its $total tests; lower its slice count"
exit 1
fi
-echo "gotest: slice $k of $n of $pkg runs $(printf '%s\n' "$mine" | wc -l) of $total tests"
+echo "gotest: slice $k of $n of $pkg runs $(printf '%s\n' "$mine" | wc -l) of $total $mode tests"
# shellcheck disable=SC2086
run go test $tags -timeout "$timeout" -run "^($(printf '%s\n' "$mine" | paste -sd'|' -))\$" "$pkg" -count=1
diff --git a/hack/test-weights.txt b/hack/test-weights.txt
index 9962b34..b30728c 100644
--- a/hack/test-weights.txt
+++ b/hack/test-weights.txt
@@ -10,9 +10,11 @@
# Measured 2026-10-29 on docs-t1 (16 cores, shared), from one
# `go test -json -tags browser` run of the five browser packages at
# once, the split tests timed again after splitting, and one untagged
-# `go test -json ./...` for the test lines. Re-measure from a fleet run
-# once Fiesta's CI is routed there: the fleet is arm64 with its own
-# Chromium and fonts, and these weights are docs-t1's.
+# `go test -json ./...` for the test lines. A browser role runs only
+# the tests the browser build adds, so its lines name only those; a
+# package's plain tests are weighed under test. Re-measure from a
+# fleet run once Fiesta's CI is routed there: the fleet is arm64 with
+# its own Chromium and fonts, and these weights are docs-t1's.
browser check TestEveryThemeRendersAsItDidExceptDay 44
browser check TestEveryExampleStateMeetsTheFloorsWithAMouse 40
browser check TestTheReleaseRendersAsTheSource 38
@@ -120,28 +122,19 @@ browser internal/gallery TestEveryExamplePassesAxeSignalEn 13
browser internal/gallery TestA11yScansTheGalleryDay 12
browser internal/gallery TestA11yScansTheGalleryPlain 12
browser internal/gallery TestA11yScansTheGallerySignal 12
-browser internal/gallery TestTheLeakSweepSeesEscapedPunctuation 12
browser internal/gallery TestThePreviewDefaultIsMonotoneInStageWidth 12
browser internal/gallery TestThePreviewWidgetIsUsableOnAPhone 11
browser internal/gallery TestTheBarsLanguageMenuStaysOnScreen 9
browser internal/gallery TestAFrameStopsLookingForItsNextDocument 8
-browser internal/gallery TestEveryLookupIsTraced 8
browser internal/gallery TestTheGalleryHasNoHorizontalScrollAt320 7
-browser internal/gallery TestNoEnglishProseReachesATranslatedPage 6
browser internal/gallery TestA11yScansTheCodePanels 5
browser internal/gallery TestA11yScansThePhoneViews 5
browser internal/gallery TestAMovedSectionsOldLinkReachesIt 5
browser internal/gallery TestMobileIsThePageAtItsRealSize 5
browser internal/gallery TestThePinnedBarFitsItsReservation 5
-browser internal/gallery TestEveryFrameDocumentCarriesNoindexAndItsPaddingRule 4
-browser internal/gallery TestEveryPageIsAWholeLocalisedDocument 4
-browser internal/gallery TestEveryProseKeyIsTranslated 4
browser internal/gallery TestFocusIsNeverUnderThePinnedBar 4
browser internal/gallery TestPositionSurvivesASwitch 4
-browser internal/gallery TestTheAssetDeclarationDrivesTheTree 4
-browser internal/gallery TestTheDemoIsFourDocumentsInTheShippedShell 4
browser internal/gallery TestTheDisplaySettingsMenuOnAPhone 4
-browser internal/gallery TestThePasswordScreenCarriesItsWarning 4
browser internal/gallery TestAFrameHidesWhileItNavigates 3
browser internal/gallery TestAFrameNeverShowsTheOtherSchemeWhileItLoads 3
browser internal/gallery TestAFrameShowsNothingUntilItsDocumentIsStyled 3
@@ -154,10 +147,7 @@ browser internal/gallery TestTheCopyButtonCopiesAnnouncesAndFailsSafely 3
browser internal/gallery TestADemoOpenedInItsOwnTabKeepsTheScheme 2
browser internal/gallery TestAFrameSubmitsNothing 2
browser internal/gallery TestAStoredSchemeIsOnTheGalleryPageBeforeFirstPaint 2
-browser internal/gallery TestEveryPagesH1IsItsOwnTitle 2
browser internal/gallery TestNoCodeBlockScrollsSidewaysOnAPhone 2
-browser internal/gallery TestNoPageCarriesTheSameIdTwice 2
-browser internal/gallery TestNoTitleOrStateLabelCarriesAnEmDash 2
browser internal/gallery TestTheDemoApplicationWorksWithNoScript 2
browser internal/gallery TestTheDemoRequestsRowsAreClickableEdgeToEdge 2
browser internal/gallery TestTheDisplaySettingsKeepYourPlace 2
@@ -165,11 +155,9 @@ browser internal/gallery TestTheGallerySwitchLandsOnTheNextFrame 2
browser internal/gallery TestTheGallerysOwnControlsAreExactlyTheFloor 2
browser internal/gallery TestThePhoneIndexAndTheWayBack 2
browser internal/gallery TestTheWayBackFromAShellDemoIsATarget 2
-browser internal/gallery TestTitlesUseTheApprovedSeparators 2
browser internal/www TestTheLandingPassesAxe 16
browser internal/www TestTheLanguageMenuOpensOnAPhone 14
browser internal/www TestTheLandingHasNoHorizontalScrollAt320 13
-browser internal/www TestEveryLinkInTheSiteResolves 12
browser internal/www TestTheSampleFormsStayOnThePage 10
browser internal/www TestArabicReadsRightToLeft 9
browser internal/www TestCopy 9
@@ -179,17 +167,10 @@ browser internal/www TestABrokenStoredThemeFallsBackToDay 8
browser internal/www TestTheBuntingMovesOnlyWhenMotionIsAllowed 8
browser internal/www TestTheLandingReadsWithNoScript 8
browser internal/www TestTheSiteServesFromItsOwnOrigin 8
-browser internal/www TestEveryPageLinksEveryLanguage 7
browser internal/www TestEverySamplePaneAndCourseSwitches 7
browser internal/www TestTheBuntingFollowsTheTheme 7
browser internal/www TestTheSampleSwitchDrags 7
browser internal/www TestTheStillBuntingMatchesTheMovingOne 7
-browser internal/www TestPagesHasARootAndTheDesignSystem 6
-browser internal/www TestSiteBuildAcceptsThePages 6
-browser internal/www TestStaticFilesAreCopied 6
-browser internal/www TestAHiddenSiteFileIsRefused 5
-browser internal/www TestEveryLanguageHasAPage 5
-browser internal/www TestTheDesignSystemIsMounted 5
test internal/gallery TestTheLeakSweepSeesEscapedPunctuation 12
test internal/gallery TestEveryLookupIsTraced 8
test internal/gallery TestNoEnglishProseReachesATranslatedPage 7