rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1#!/bin/sh
2# One go test role of the suite.
3#
4# hack/gotest.sh browser <name> [go test flags] go test -tags browser, one package
5# hack/gotest.sh test <name> [go test flags] go test, one package
6# hack/gotest.sh root '<split>' go test ./... minus the split packages
7#
8# <name> is a package path with its slashes written as dots
9# (internal.designsystem), optionally ~<k>of<n> to run only the k-th of
10# n slices of its tests. Flags after the name go to go test, after the
11# role's own, and only for a whole package: a slice is its own -run.
12#
13# A browser role runs only its package's browser tests: the ones
14# `go test -tags browser -list` names and plain `go test -list` does
15# not. Every other test in the package runs once already, in root or in
16# the package's test.<pkg> roles, and running it again under the tag
17# was the same check paid for twice. ci_test.go checks that every test
18# lands in exactly one role.
19#
20# A slice is dealt from the package's own test list: heaviest first,
21# each onto the lightest slice so far, by the seconds in
22# hack/test-weights.txt (a test not listed there weighs 1). Every shard
23# computes the same deal, so the n slices of a package are a partition
24# of it: each test runs in exactly one, and a test added later lands in
25# a slice without anyone listing it. Two refusals keep that honest. A
26# package that lists no tests is a broken build or a broken listing, not
27# an empty package; and a slice dealt nothing means n is larger than the
28# package, which would report green having run nothing.
29#
30# hack/gotest.sh --deal <mode> <pkg> <n> reads test names on stdin and
31# prints "<k> <name>" for each: the deal alone, which amadan_ci_test.go
32# checks is a partition without compiling anything.
33set -eu
34
35weights=$(dirname "$0")/test-weights.txt
36
37# deal <mode> <pkg> <n>: names on stdin, "<k> <name>" out. Ties sort by
38# name, so the deal does not depend on the order the names arrive in.
39deal() {
40 awk -v mode="$1" -v pkg="$2" -v wf="$weights" '
41 BEGIN { while ((getline l < wf) > 0) { split(l, f, " "); if (f[1] == mode && f[2] == pkg) w[f[3]] = f[4] } }
42 NF { print (($1 in w) ? w[$1] : 1), $1 }' |
43 sort -k1,1nr -k2,2 |
44 awk -v n="$3" '
45 { best = 1; for (i = 2; i <= n; i++) if (load[i] < load[best]) best = i
46 load[best] += $1; print best, $2 }'
47}
48
49case "${1:-}" in
50--deal)
51 deal "$2" "$3" "$4"
52 exit 0
53 ;;
54root)
55 # Every package but the split ones, which run as roles of their own.
56 # Each split package must be one go list names: one that was renamed
57 # or deleted would otherwise drop out of both halves silently.
58 all=$(go list ./...)
59 keep=$all
60 for p in $2; do
61 ip=$(go list "./${p%@*}")
62 printf '%s\n' "$all" | grep -qx "$ip" || {
63 echo "gotest: $ip is split out of root but go list ./... does not name it"
64 exit 1
65 }
66 keep=$(printf '%s\n' "$keep" | grep -vx "$ip")
67 done
68 # shellcheck disable=SC2086 # one argument per package, on purpose
69 exec go test -count=1 $keep
70 ;;
71browser)
72 tags="-tags=browser"
73 timeout=20m
74 ;;
75test)
76 tags=
77 timeout=10m
78 ;;
79*)
80 echo "gotest: usage: gotest.sh browser|test <name> | root '<split>' | --deal <mode> <pkg> <n>"
81 exit 2
82 ;;
83esac
84mode=$1
85
86name=$2
87shift 2
88rel=$name
89slice=
90case "$name" in
91*~*)
92 rel=${name%%~*}
93 slice=${name#*~}
94 ;;
95esac
96rel=$(printf '%s' "$rel" | tr . /)
97pkg=./$rel/
98
99# Chromium writes a profile per browser into TMPDIR, and a /tmp shared
100# by a whole machine can fill, so browser roles prefer /var/tmp. Only
101# where it is writable: amadan's bwrap sandbox mounts the root
102# filesystem read-only and gives each job a private tmpfs /tmp, which
103# is then the right place. Not inside the checkout, ever: a scratch
104# module built under it would find this repo's .git walking up.
105if [ "$mode" = browser ] && [ -z "${TMPDIR:-}" ] && [ -w /var/tmp ]; then
106 TMPDIR=/var/tmp
107 export TMPDIR
108fi
109
110# The headless shell never restores a page from the back/forward cache,
111# and ui's TestBusyButtonDrive needs one (no flag changes that; full
112# Chromium of the same version does). Where `chromium` is a headless
113# shell, as amadan's cloud runner links it, the packages the Makefile
114# names in FULL_CHROMIUM use full Chromium for Testing from beside it
115# when it is installed there. Only those: on the fleet full Chromium
116# took the design system's slices from minutes to as long as twenty.
117# Discovery only: a readlink without -f leaves the browser to the
118# harness's own search rather than failing the role.
119case " ${FULL_CHROMIUM:-} " in
120*" $rel "*) full=yes ;;
121*) full= ;;
122esac
123if [ "$mode" = browser ] && [ -n "$full" ] && [ -z "${FIESTA_CHROME:-}" ] &&
124 shell=$(command -v chromium 2>/dev/null) && shell=$(readlink -f "$shell" 2>/dev/null); then
125 case "${shell##*/}" in
126 headless_shell | chrome-headless-shell)
127 # The same build number as the shell: one Playwright installs both.
128 build=${shell#*/chromium_headless_shell-}
129 for c in "${shell%/chromium_headless_shell-*}/chromium-${build%%/*}"/chrome-linux*/chrome; do
130 if [ -x "$c" ]; then
131 FIESTA_CHROME=$c
132 export FIESTA_CHROME
133 fi
134 done
135 ;;
136 esac
137fi
138
139# Full Chromium's crash handler wants a database under its config
140# directory, ~/.config by default, and will not start without one
141# ("chrome_crashpad_handler: --database is required"). The cloud
142# runner's HOME is read-only, so there it gets a scratch one.
143# Which browser a role drove is the first question a red browser role
144# raises, and nothing else in the log answers it.
145if [ "$mode" = browser ]; then
146 echo "gotest: on $(uname -n), driving ${FIESTA_CHROME:-the harness's own choice ($(command -v chromium 2>/dev/null || echo none on PATH))}"
147fi
148
149scratch=
150if [ "$mode" = browser ] && [ -z "${CHROME_CONFIG_HOME:-}" ] && ! [ -w "${HOME:-/}" ]; then
151 scratch=$(mktemp -d)
152 CHROME_CONFIG_HOME=$scratch
153 export CHROME_CONFIG_HOME
154fi
155
156# run is exec, unless there is scratch to remove after go test.
157run() {
158 if [ -z "$scratch" ]; then
159 exec "$@"
160 fi
161 status=0
162 "$@" || status=$?
163 rm -rf "$scratch"
164 exit "$status"
165}
166
167# list <tags> prints the package's tests, examples and fuzz targets,
168# one per line. Its own command, not the head of a pipeline: a package
169# that fails to build must fail here, not list nothing through a grep
170# that succeeds.
171list() {
172 # shellcheck disable=SC2086 # an empty tag list is no argument at all
173 listing=$(go test $1 -list '.*' "$pkg") || {
174 echo "gotest: could not list the tests in $pkg:" >&2
175 printf '%s\n' "$listing" >&2
176 return 1
177 }
178 # Examples and fuzz seeds answer to -run as well, so they are dealt too.
179 printf '%s\n' "$listing" | grep -E '^(Test|Example|Fuzz)' || true
180}
181
182if [ "$mode" = test ] && [ -z "$slice" ]; then
183 run go test -timeout "$timeout" "$pkg" -count=1 "$@"
184fi
185
186all=$(list "$tags")
187if [ "$mode" = browser ]; then
188 # The plain tests, where the package builds without the tag at all:
189 # a package of nothing but browser files is not in go list ./....
190 plain=
191 if go list ./... | grep -qx "$(go list $tags "$pkg")"; then
192 plain=$(list "")
193 fi
194 if [ -n "$plain" ]; then
195 all=$(printf '%s\n' "$all" | grep -vxF "$plain" || true)
196 fi
197fi
198total=$(printf '%s\n' "$all" | grep -c . || true)
199if [ "$total" -eq 0 ]; then
200 echo "gotest: $pkg listed no $mode tests; refusing to report a role of nothing as a pass"
201 exit 1
202fi
203
204if [ -z "$slice" ]; then
205 echo "gotest: $pkg runs its $total $mode tests"
206 # The role's -run first, so a -run among the flags overrides it.
207 # shellcheck disable=SC2086
208 run go test $tags -timeout "$timeout" -run "^($(printf '%s\n' "$all" | paste -sd'|' -))\$" "$pkg" -count=1 "$@"
209fi
210if [ $# -gt 0 ]; then
211 echo "gotest: flags for go test are for a whole package, not slice $slice of $pkg"
212 exit 2
213fi
214
215k=${slice%of*}
216n=${slice#*of}
217mine=$(printf '%s\n' "$all" | deal "$mode" "$rel" "$n" | awk -v k="$k" '$1 == k { print $2 }')
218if [ -z "$mine" ]; then
219 echo "gotest: slice $k of $n of $pkg was dealt none of its $total tests; lower its slice count"
220 exit 1
221fi
222echo "gotest: slice $k of $n of $pkg runs $(printf '%s\n' "$mine" | wc -l) of $total $mode tests"
223# shellcheck disable=SC2086
224run go test $tags -timeout "$timeout" -run "^($(printf '%s\n' "$mine" | paste -sd'|' -))\$" "$pkg" -count=1
225