rastrillo / fiesta Public

Refuse escapes, spaced brackets and capitals in the gates' grammar

Review found three ways past the gates that a browser reads as names
1.0 already has and the gates read as new: a CSS escape
(@keyframes rst-\73pin {} is rst-spin to a browser, and so replaces
1.0's spinner; @property --rst-\73p-4 registers --rst-sp-4), spaces
inside an attribute selector's brackets ([ rst-page] is [rst-page]),
and capitals in an attribute name (HTML attribute names match
whatever their case, so [rst-PAGE] is [rst-page]). Written as
@media (width: 731px) { [ rst-PAGE] { padding-inline: 100px } }, the
last two passed the new-rule gate, the spelling gate (which looks for
a literal "[rst-") and the coverage gate (which reads [rst-…] and
.rst-… literally), at a width no photograph takes.

Rather than decode every spelling a browser accepts, the files are
held to the one spelling the gates read. plainSpelling refuses a CSS
escape, whitespace inside brackets, and an attribute or class name
that is not lower case. The grammar applies it before it parses a
selector; the spelling gate (assertNoOrphans, for tokens.css and
shell.css) and the coverage gate apply it to every selector, :where()
and :is() included; and the new-rule gate refuses a record or an
at-rule that carries an escape in its at-rules or property name. All
five of 1.0.0's stylesheets pass it, checked in
TestTheNewRuleGateHasNoKnownHoles, so the rule is one 1.0 already
keeps.

Red before, green after: the three review examples appended to 1.0.0's
tokens.css passed TestTheNewRuleGateHasNoKnownHoles before and are
refused after; the grammar table's [ rst-page], [rst-page ],
[rst-PAGE], .rst-PAGE, [rst-\70 age] and .rst-\73nippet were accepted
or refused for the wrong reason before. With the media rule appended
to the tree's tokens.css, TestNoAttributeSelectorIsAnOrphan and
TestEveryExampleAttributeAndClassIsStyled passed before and name it
after. [rst-page="  "] is now refused for its whitespace.

The 1.0 photographs' rig: the comment on WithDeterministicRaster now
claims what was measured (0 of 400 pairs apart against 4 of 400 under
the same load, and only for photographs taken this way), and
--force-color-profile=srgb leaves its list, since chromedp already
passes it. Each 1.0 photograph test logs the browser's executable,
product and revision, its launch arguments and its GPU feature
status, and TestTheOneOhRigRastersInSoftware fails a browser that was
not launched with the raster flags or reports rasterization or
multiple raster threads enabled. On docs-t1 Debian's chromium wrapper
adds --enable-gpu-rasterization of its own; the status still reads
rasterization disabled_software and multiple_raster_threads
disabled_off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev e2207edc6c4b78221b9b0349838d8eeab50e3b52 parent a7ded2f
6 files changed, +178 −12
  • check/baseline_test.go +17 −0
  • check/coverage_test.go +3 −0
  • check/oneoh_browser_test.go +61 −3
  • check/selectors_test.go +84 −1
  • check/spelling_test.go +3 −0
  • internal/harness/rig.go +10 −8

CI passed — run details

CI log
=== gofmt ===
=== no-rastrillo ===
go test -run '^(TestFiestaNeverImportsRastrillo|TestThePublicPackagesLinkNoBrowser)$' -count=1 .
ok  	amadan.net/rastrillo/fiesta	0.212s
=== 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.02s)
PASS
ok  	amadan.net/rastrillo/fiesta/internal/build	0.026s
=== history ===
history: 18 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	2.738s
ok  	amadan.net/rastrillo/fiesta/check	7.047s
ok  	amadan.net/rastrillo/fiesta/example	0.146s
?   	amadan.net/rastrillo/fiesta/hack/seed/english	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/axe	0.080s
ok  	amadan.net/rastrillo/fiesta/internal/build	1.181s
ok  	amadan.net/rastrillo/fiesta/internal/codeview	0.005s
?   	amadan.net/rastrillo/fiesta/internal/harness	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/markup	0.009s
ok  	amadan.net/rastrillo/fiesta/internal/nodetest	0.373s
ok  	amadan.net/rastrillo/fiesta/internal/site	2.444s
ok  	amadan.net/rastrillo/fiesta/internal/tokens	0.014s
suite: root took 20s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m10.410000s 0m1.870000s
--- 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 35 of 105 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	65.537s
suite: test.internal.gallery~1of3 took 67s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m46.850000s 0m1.080000s
--- 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 105 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	62.052s
suite: test.internal.gallery~2of3 took 64s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m45.290000s 0m1.070000s
--- 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 35 of 105 test tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	58.916s
suite: test.internal.gallery~3of3 took 61s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m42.500000s 0m1.140000s
--- 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	48.040s
suite: test.cmd.fiesta~1of2 took 50s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m31.780000s 0m1.940000s
--- 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	53.054s
suite: test.cmd.fiesta~2of2 took 55s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m34.180000s 0m2.300000s
--- 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 11 of 25 test tests
ok  	amadan.net/rastrillo/fiesta/internal/www	60.893s
suite: test.internal.www~1of2 took 73s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m46.460000s 0m1.620000s
--- 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 14 of 25 test tests
ok  	amadan.net/rastrillo/fiesta/internal/www	59.039s
suite: test.internal.www~2of2 took 71s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m44.260000s 0m1.550000s
--- shard 8 of 48 ---
==> shard 8 of 48: make staticcheck
go run honnef.co/go/tools/cmd/staticcheck@v0.8.2 -tags browser ./...
suite: staticcheck took 14s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m13.450000s 0m0.960000s
--- shard 9 of 48 ---
==> shard 9 of 48: make govulncheck
go run golang.org/x/vuln/cmd/govulncheck@v1.8.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 11s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m3.080000s 0m0.360000s
--- 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' .
2:22AM INF Unknown SCM platform. Use --platform to include links in findings. host=amadan.net
2:23AM INF 18 commits scanned.
2:23AM INF scanned ~8205561 bytes (8.21 MB) in 11.3s
2:23AM INF no leaks found
suite: gitleaks took 13s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m6.940000s 0m0.280000s
--- shard 11 of 48 ---
==> shard 11 of 48: make browser.internal.harness
./hack/gotest.sh browser 'internal.harness'
gotest: on ip-172-31-20-193, 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	4.373s
suite: browser.internal.harness took 17s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m2.000000s 0m1.370000s
--- shard 12 of 48 ---
==> shard 12 of 48: make browser.check~1of13
./hack/gotest.sh browser 'check~1of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 1 of 13 of ./check/ runs 16 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	88.190s
suite: browser.check~1of13 took 102s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m48.420000s 0m14.730000s
--- shard 13 of 48 ---
==> shard 13 of 48: make browser.check~2of13
./hack/gotest.sh browser 'check~2of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 2 of 13 of ./check/ runs 16 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	92.660s
suite: browser.check~2of13 took 107s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m48.320000s 0m12.340000s
--- shard 14 of 48 ---
==> shard 14 of 48: make browser.check~3of13
./hack/gotest.sh browser 'check~3of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 3 of 13 of ./check/ runs 15 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	87.607s
suite: browser.check~3of13 took 102s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m42.220000s 0m12.790000s
--- shard 15 of 48 ---
==> shard 15 of 48: make browser.check~4of13
./hack/gotest.sh browser 'check~4of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 4 of 13 of ./check/ runs 15 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	90.443s
suite: browser.check~4of13 took 105s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m54.980000s 0m17.140000s
--- shard 16 of 48 ---
==> shard 16 of 48: make browser.check~5of13
./hack/gotest.sh browser 'check~5of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 5 of 13 of ./check/ runs 17 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	78.840s
suite: browser.check~5of13 took 93s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m37.290000s 0m12.760000s
--- shard 17 of 48 ---
==> shard 17 of 48: make browser.check~6of13
./hack/gotest.sh browser 'check~6of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 6 of 13 of ./check/ runs 17 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	80.464s
suite: browser.check~6of13 took 94s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m45.820000s 0m16.100000s
--- shard 18 of 48 ---
==> shard 18 of 48: make browser.check~7of13
./hack/gotest.sh browser 'check~7of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 7 of 13 of ./check/ runs 17 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	85.732s
suite: browser.check~7of13 took 100s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m45.230000s 0m14.570000s
--- shard 19 of 48 ---
==> shard 19 of 48: make browser.check~8of13
./hack/gotest.sh browser 'check~8of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 8 of 13 of ./check/ runs 17 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	88.518s
suite: browser.check~8of13 took 103s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m43.050000s 0m14.640000s
--- shard 20 of 48 ---
==> shard 20 of 48: make browser.check~9of13
./hack/gotest.sh browser 'check~9of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 9 of 13 of ./check/ runs 16 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	75.559s
suite: browser.check~9of13 took 90s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m30.190000s 0m9.770000s
--- shard 21 of 48 ---
==> shard 21 of 48: make browser.check~10of13
./hack/gotest.sh browser 'check~10of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 10 of 13 of ./check/ runs 16 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	85.684s
suite: browser.check~10of13 took 100s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m39.460000s 0m12.450000s
--- shard 22 of 48 ---
==> shard 22 of 48: make browser.check~11of13
./hack/gotest.sh browser 'check~11of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 11 of 13 of ./check/ runs 16 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	81.262s
suite: browser.check~11of13 took 95s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m42.630000s 0m14.450000s
--- shard 23 of 48 ---
==> shard 23 of 48: make browser.check~12of13
./hack/gotest.sh browser 'check~12of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 12 of 13 of ./check/ runs 17 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	83.505s
suite: browser.check~12of13 took 98s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m43.930000s 0m15.040000s
--- shard 24 of 48 ---
==> shard 24 of 48: make browser.check~13of13
./hack/gotest.sh browser 'check~13of13'
gotest: on ip-172-31-20-193, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 13 of 13 of ./check/ runs 17 of 212 browser tests
ok  	amadan.net/rastrillo/fiesta/check	77.753s
suite: browser.check~13of13 took 92s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m37.490000s 0m11.070000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 1 of 18 of ./internal/gallery/ runs 6 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	34.403s
suite: browser.internal.gallery~1of18 took 48s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m15.020000s 0m3.280000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 2 of 18 of ./internal/gallery/ runs 6 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	49.652s
suite: browser.internal.gallery~2of18 took 64s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m16.240000s 0m3.910000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 3 of 18 of ./internal/gallery/ runs 6 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	60.525s
suite: browser.internal.gallery~3of18 took 75s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m17.130000s 0m4.290000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 4 of 18 of ./internal/gallery/ runs 6 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	59.694s
suite: browser.internal.gallery~4of18 took 74s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.430000s 0m5.600000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 5 of 18 of ./internal/gallery/ runs 6 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	62.318s
suite: browser.internal.gallery~5of18 took 76s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m17.540000s 0m4.810000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 6 of 18 of ./internal/gallery/ runs 6 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	53.970s
suite: browser.internal.gallery~6of18 took 68s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m16.160000s 0m3.990000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 7 of 18 of ./internal/gallery/ runs 5 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	54.126s
suite: browser.internal.gallery~7of18 took 68s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m14.200000s 0m2.910000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 8 of 18 of ./internal/gallery/ runs 5 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	60.111s
suite: browser.internal.gallery~8of18 took 74s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m18.770000s 0m5.570000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 9 of 18 of ./internal/gallery/ runs 5 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	66.023s
suite: browser.internal.gallery~9of18 took 80s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m22.990000s 0m7.180000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 10 of 18 of ./internal/gallery/ runs 2 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	61.635s
suite: browser.internal.gallery~10of18 took 76s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m14.590000s 0m2.890000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 11 of 18 of ./internal/gallery/ runs 2 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	60.933s
suite: browser.internal.gallery~11of18 took 75s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m14.520000s 0m2.890000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 12 of 18 of ./internal/gallery/ runs 2 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	63.363s
suite: browser.internal.gallery~12of18 took 77s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.220000s 0m5.090000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 13 of 18 of ./internal/gallery/ runs 3 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	67.032s
suite: browser.internal.gallery~13of18 took 81s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.880000s 0m5.460000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 14 of 18 of ./internal/gallery/ runs 4 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	69.146s
suite: browser.internal.gallery~14of18 took 83s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.780000s 0m5.610000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 15 of 18 of ./internal/gallery/ runs 4 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	66.217s
suite: browser.internal.gallery~15of18 took 81s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.970000s 0m5.790000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 16 of 18 of ./internal/gallery/ runs 5 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	63.345s
suite: browser.internal.gallery~16of18 took 77s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m16.730000s 0m4.300000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 17 of 18 of ./internal/gallery/ runs 5 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	67.569s
suite: browser.internal.gallery~17of18 took 82s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m20.650000s 0m6.080000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 18 of 18 of ./internal/gallery/ runs 5 of 83 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	68.335s
suite: browser.internal.gallery~18of18 took 83s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m19.620000s 0m5.570000s
--- 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-20-193, 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	33.640s
suite: browser.internal.gallery.history took 47s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m21.460000s 0m2.090000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 1 of 4 of ./internal/www/ runs 4 of 22 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	69.072s
suite: browser.internal.www~1of4 took 82s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m31.010000s 0m2.130000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 2 of 4 of ./internal/www/ runs 6 of 22 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	67.410s
suite: browser.internal.www~2of4 took 80s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m45.370000s 0m2.730000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 3 of 4 of ./internal/www/ runs 6 of 22 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	80.883s
suite: browser.internal.www~3of4 took 94s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m52.300000s 0m2.940000s
--- 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-20-193, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 4 of 4 of ./internal/www/ runs 6 of 22 browser tests
ok  	amadan.net/rastrillo/fiesta/internal/www	77.874s
suite: browser.internal.www~4of4 took 91s; CPU, user then system, of this shell and then of the role:
0m0.000000s 0m0.000000s
0m46.110000s 0m2.990000s
diff --git a/check/baseline_test.go b/check/baseline_test.go
index dae5747..bba871b 100644
--- a/check/baseline_test.go
+++ b/check/baseline_test.go
@@ -720,6 +720,12 @@ func refusedAdditions(t *testing.T, files []sideBySide) []string {
// last four are additions the gate must let through, so the test
// cannot pass by refusing everything.
func TestTheNewRuleGateHasNoKnownHoles(t *testing.T) {
+ // The spelling rules that refuse escaped, spaced and capitalised
+ // names must not refuse 1.0.0's own CSS, or the grammar would be
+ // a rule 1.0 itself breaks.
+ for _, f := range everyStylesheet(t) {
+ assertPlainSpelling(t, "check/testdata/seed/"+baseline[f.src], f.wasBytes)
+ }
for _, c := range []struct {
css, names string // the rule appended, and what a refusal names
refused bool
@@ -731,6 +737,9 @@ func TestTheNewRuleGateHasNoKnownHoles(t *testing.T) {
{`@keyframes rst-gate-control { to { opacity: 1; } }`, "@keyframes rst-gate-control", false},
{`@property --rst-gate-control { syntax: "<length>"; inherits: false; initial-value: 0px; }`, "@property --rst-gate-control", false},
{`:root { --rst-gate-control: 1px; }`, "--rst-gate-control", false},
+ {`@media (width: 731px) { [ rst-PAGE] { padding-inline: 100px; } }`, "[ rst-PAGE]", true},
+ {`@media (width: 731px) { @property --rst-\73p-4 { syntax: "<length>"; inherits: false; initial-value: 48px; } }`, `--rst-\73p-4`, true},
+ {`@media (width: 731px) { @keyframes rst-\73pin {} }`, `rst-\73pin`, true},
{`[rst-page-foot] [rst-dropdown] { margin: 0; }`, "[rst-page-foot] [rst-dropdown]", false},
} {
files := everyStylesheet(t)
@@ -762,6 +771,8 @@ func TestTheNewRuleGateHasNoKnownHoles(t *testing.T) {
// has replaces it, empty or not.
func newAtRuleRefused(at string, oldProps map[string]bool, oldAtRules map[string]int) string {
switch {
+ case strings.ContainsRune(at, '\\'):
+ return "holds a CSS escape, outside the grammar: @keyframes rst-\\73pin is rst-spin to a browser and a new name to a plain reading"
case oldAtRules[at] > 0:
return "is a second definition of one 1.0.0 has; the later one wins on every 1.0 page"
case strings.HasPrefix(at, "@keyframes rst-"):
@@ -778,6 +789,12 @@ func newAtRuleRefused(at string, oldProps map[string]bool, oldAtRules map[string
// newRecordRefused says why one record 1.0.0 did not make is not
// allowed, or "" when it is.
func newRecordRefused(d declaration, names, oldProps map[string]bool, oldAtRules map[string]int, exceptions map[string]complexSel, used map[string]bool) string {
+ // An escape in any name a record carries spells a name the plain
+ // reading below would not recognise (--rst-\73p-4 is --rst-sp-4),
+ // so none is read at all: the CSS writes names out.
+ if strings.ContainsRune(d.Context+d.Property, '\\') {
+ return "holds a CSS escape in its at-rules or property, outside the grammar"
+ }
contexts := strings.Split(d.Context, " > ")
last := contexts[len(contexts)-1]
if d.Selector == "" || strings.HasPrefix(last, "@keyframes ") {
diff --git a/check/coverage_test.go b/check/coverage_test.go
index e6bfe5a..a91087c 100644
--- a/check/coverage_test.go
+++ b/check/coverage_test.go
@@ -219,6 +219,9 @@ func vocabularyOf(sheets ...string) map[string]bool {
// idea of that component.
func TestEveryExampleAttributeAndClassIsStyled(t *testing.T) {
css := styledCSS()
+ // The vocabulary below is read off the literal [rst-…] and .rst-…;
+ // a spelling a browser matches and that read misses is refused first.
+ assertPlainSpelling(t, "tokens.css and shell.css", css)
classes := styledClasses(css)
exempted := map[string]bool{}
for _, s := range everyState(t, fiesta.FS()) {
diff --git a/check/oneoh_browser_test.go b/check/oneoh_browser_test.go
index 0cb3d9e..a0070a6 100644
--- a/check/oneoh_browser_test.go
+++ b/check/oneoh_browser_test.go
@@ -4,17 +4,22 @@ package check
import (
"context"
+ "encoding/json"
"fmt"
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
+ "slices"
"strings"
"testing"
"time"
+ "github.com/chromedp/cdproto/browser"
+ "github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/emulation"
+ "github.com/chromedp/cdproto/systeminfo"
"github.com/chromedp/chromedp"
"amadan.net/rastrillo/fiesta"
@@ -127,12 +132,65 @@ func oneOhRig(t *testing.T, theme string, media *emulation.SetEmulatedMediaParam
}, harness.WithHover(), harness.WithDeterministicRaster())
ctx, cancel := context.WithTimeout(rig.Context(), 300*time.Second)
t.Cleanup(cancel)
+ b := oneOhBrowserOf(t, ctx)
+ t.Logf("1.0 photographs on %s, %s (%s); launched with %s; GPU features %s", harness.ChromePath(t), b.product, b.revision, strings.Join(b.args, " "), b.features)
if media != nil {
mustRun(t, ctx, media)
}
return rig, ctx
}
+// oneOhBrowser is what the browser behind a 1.0 rig says it is: its
+// product and revision, the arguments it was launched with, and its
+// GPU feature status (chrome://gpu's table), which is where a raster
+// flag the build ignored would show.
+type oneOhBrowser struct {
+ product, revision string
+ args []string
+ features map[string]string
+}
+
+func oneOhBrowserOf(t *testing.T, ctx context.Context) oneOhBrowser {
+ t.Helper()
+ var b oneOhBrowser
+ mustRun(t, ctx, chromedp.ActionFunc(func(ctx context.Context) error {
+ bctx := cdp.WithExecutor(ctx, chromedp.FromContext(ctx).Browser)
+ var err error
+ if _, b.product, b.revision, _, _, err = browser.GetVersion().Do(bctx); err != nil {
+ return err
+ }
+ if b.args, err = browser.GetBrowserCommandLine().Do(bctx); err != nil {
+ return err
+ }
+ gpu, _, _, _, err := systeminfo.GetInfo().Do(bctx)
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(gpu.FeatureStatus, &b.features)
+ }))
+ return b
+}
+
+// TestTheOneOhRigRastersInSoftware: the browser behind the 1.0
+// photographs took the raster flags and does not raster on a GPU. The
+// photographs are only held to zero tolerance because two loads of
+// one page paint alike in this configuration; a build that ignored a
+// flag would bring back the one-level flake with no word of why.
+func TestTheOneOhRigRastersInSoftware(t *testing.T) {
+ _, ctx := oneOhRig(t, "day", nil)
+ b := oneOhBrowserOf(t, ctx)
+ for _, flag := range []string{"--num-raster-threads=1", "--disable-gpu", "--disable-gpu-rasterization", "--disable-partial-raster", "--disable-lcd-text"} {
+ if !slices.Contains(b.args, flag) {
+ t.Errorf("%s was not launched with %s: %v", b.product, flag, b.args)
+ }
+ }
+ for _, feature := range []string{"rasterization", "multiple_raster_threads"} {
+ if v := b.features[feature]; v == "" || strings.HasPrefix(v, "enabled") {
+ t.Errorf("%s reports %s as %q; the 1.0 photographs want it off", b.product, feature, v)
+ }
+ }
+}
+
// oneOhLeg is one way of looking at a page.
type oneOhLeg struct {
width int64
@@ -200,9 +258,9 @@ func oneOhShot(t *testing.T, rig *harness.Rig, ctx context.Context, side, doc st
// sameOneOh photographs doc on both sides in each leg and fails each
// leg whose pictures differ by a single pixel. One pair is enough:
-// the rig rasters deterministically (WithDeterministicRaster), and
-// without it one state's callout corner came out one level apart in
-// about 1 pair in 100 under load, on two loads of one stylesheet.
+// the rig rasters on one thread in software (WithDeterministicRaster),
+// under which 400 pairs of one page agreed under load; without it one
+// state's callout corner came out one level apart in 4 of them.
func sameOneOh(t *testing.T, rig *harness.Rig, ctx context.Context, doc string, legs []oneOhLeg) {
t.Helper()
for _, leg := range legs {
diff --git a/check/selectors_test.go b/check/selectors_test.go
index 0152d49..0d6b332 100644
--- a/check/selectors_test.go
+++ b/check/selectors_test.go
@@ -27,6 +27,80 @@ import (
// - :is(), :where(), * and anything else are outside it, except *
// inside a :not() argument, where it can only narrow.
+// plainSpelling refuses the spellings of a name that the gates' plain
+// reading would take for a different name, though a browser matches
+// the same elements: a CSS escape (\73 is s, so rst-\73pin is
+// rst-spin), whitespace inside an attribute selector's brackets
+// ([ rst-page] is [rst-page]), and capitals in an attribute or class
+// name (HTML attribute names match whatever their case, so [rst-PAGE]
+// is [rst-page] on every 1.0 page). Rather than decode each spelling,
+// the files are held to the one spelling the gates read. It is a
+// lexical check, so the spelling and coverage gates apply it to every
+// selector, :where() and :is() included, and the grammar before it
+// parses one.
+func plainSpelling(sel string) error {
+ if strings.ContainsRune(sel, '\\') {
+ return fmt.Errorf("a CSS escape (\\) is outside the grammar")
+ }
+ for i := 0; i < len(sel); i++ {
+ switch c := sel[i]; c {
+ case '"', '\'':
+ end := strings.IndexByte(sel[i+1:], c)
+ if end < 0 {
+ return fmt.Errorf("an unclosed string")
+ }
+ i += end + 1
+ case '[':
+ end := i + 1
+ for end < len(sel) && sel[end] != ']' {
+ if q := sel[end]; q == '"' || q == '\'' {
+ if k := strings.IndexByte(sel[end+1:], q); k >= 0 {
+ end += k + 1
+ }
+ }
+ end++
+ }
+ inner := sel[i+1 : min(end, len(sel))]
+ if strings.ContainsAny(inner, " \t\n\r\f") {
+ return fmt.Errorf("whitespace inside [%s] is outside the grammar", inner)
+ }
+ name := inner
+ if k := strings.IndexAny(inner, "~|^$*="); k >= 0 {
+ name = inner[:k]
+ }
+ if name != strings.ToLower(name) {
+ return fmt.Errorf("the attribute name %q is not lower case", name)
+ }
+ i = end
+ case '.':
+ if m := identifier.FindString(sel[i+1:]); m != "" && m != strings.ToLower(m) {
+ return fmt.Errorf("the class name %q is not lower case", m)
+ }
+ }
+ }
+ return nil
+}
+
+// assertPlainSpelling holds every selector of css to plainSpelling, so
+// a rule spelt to slip past a gate's reading fails by name in the
+// spelling and coverage gates as well as in the grammar.
+func assertPlainSpelling(t *testing.T, name, css string) {
+ t.Helper()
+ for _, rule := range parseCSSRules(css) {
+ for _, sel := range rule.selectors {
+ if strings.HasPrefix(sel, "@") {
+ if strings.ContainsRune(sel, '\\') {
+ t.Errorf("%s:%d: %q holds a CSS escape; write the name the escape spells", name, rule.line, sel)
+ }
+ continue
+ }
+ if err := plainSpelling(sel); err != nil {
+ t.Errorf("%s:%d: %q: %v", name, rule.line, sel, err)
+ }
+ }
+ }
+}
+
// attrSel is one attribute selector: Op is "", "=" or "~=".
type attrSel struct{ Name, Op, Value string }
@@ -60,6 +134,9 @@ type complexSel struct {
// parseSelector reads one alternative of a selector list in the
// grammar above, or says what in it is outside the grammar.
func parseSelector(sel string) (complexSel, error) {
+ if err := plainSpelling(sel); err != nil {
+ return complexSel{}, fmt.Errorf("%s: %w", sel, err)
+ }
p := &selParser{s: strings.TrimSpace(sel)}
c, err := p.complex(false, false)
if err == nil && p.i < len(p.s) {
@@ -480,7 +557,13 @@ func TestOnlyNewMarkupPassesTheGrammar(t *testing.T) {
{"* [rst-snippet]", false, "`*`"},
{"[rst-page]:has(> [rst-snippet], > [rst-row])", false, "> [rst-row]"},
{`[rst-page=""]`, false, "positively requires"},
- {`[rst-page=" "]`, false, "positively requires"},
+ {`[rst-page=" "]`, false, "whitespace"},
+ {"[ rst-page]", false, "whitespace"},
+ {"[rst-page ]", false, "whitespace"},
+ {"[rst-PAGE]", false, "lower case"},
+ {".rst-PAGE", false, "lower case"},
+ {`[rst-\70 age]`, false, "escape"},
+ {`.rst-\73nippet`, false, "escape"},
{`[rst-dropdown~=""]`, false, "matches nothing"},
{`.rst-page[rst-row]`, false, "positively requires"},
{"[rst-snippet]", true, ""},
diff --git a/check/spelling_test.go b/check/spelling_test.go
index 65c96d4..2bdeb20 100644
--- a/check/spelling_test.go
+++ b/check/spelling_test.go
@@ -339,6 +339,9 @@ func assertTwins(t *testing.T, name, css string, floor int) {
func assertNoOrphans(t *testing.T, name, css string) {
t.Helper()
+ // A selector spelt to slip past the literal "[rst-" read below
+ // ([ rst-page], [rst-PAGE], an escape) is refused by name first.
+ assertPlainSpelling(t, name, css)
for _, rule := range parseCSSRules(css) {
twins := make(map[string]bool, len(rule.selectors))
for _, sel := range rule.selectors {
diff --git a/internal/harness/rig.go b/internal/harness/rig.go
index 3962db5..d667649 100644
--- a/internal/harness/rig.go
+++ b/internal/harness/rig.go
@@ -85,13 +85,16 @@ func WithCoarsePointer() Option {
return func(c *config) { c.coarsePointer = true }
}
-// WithDeterministicRaster launches Chromium painting every pixel the
-// same way on every load: one raster thread, no GPU and no GPU
-// rasterization, no partial raster, grayscale antialiasing and an sRGB
-// colour profile. A drive that compares two photographs at zero
-// tolerance needs it: under load, with several raster threads, one
-// antialiased pixel at the edge of a painted surface came out one
-// level apart on two loads of one page under one stylesheet.
+// WithDeterministicRaster launches Chromium with one raster thread, no
+// GPU and no GPU rasterization, no partial raster and grayscale text
+// antialiasing, for drives that compare two photographs at zero
+// tolerance. Under load the default rig painted one antialiased pixel
+// one level apart on two loads of one page under one stylesheet, in 4
+// of 400 pairs; this configuration painted 0 of 400 pairs apart under
+// the same load. That is what it is shown to do, nothing more: two
+// photographs taken this way agree with each other, and a difference
+// only GPU raster, partial repaint or LCD text would show is outside
+// what they compare.
func WithDeterministicRaster() Option {
return func(c *config) { c.oneRaster = true }
}
@@ -162,7 +165,6 @@ func New(t *testing.T, build func(origin string) http.Handler, opts ...Option) *
chromedp.Flag("disable-gpu-rasterization", true),
chromedp.Flag("disable-partial-raster", true),
chromedp.Flag("disable-lcd-text", true),
- chromedp.Flag("force-color-profile", "srgb"),
)
}
allocCtx, cancelAlloc := chromedp.NewExecAllocator(context.Background(), allocOpts...)