The fork had a full suite and no CI that ran it. Upstream's test workflows trigger on stable/testing/unstable; this repo only has main. And Gitea does not read .github/workflows at all once .gitea/workflows exists, so those files were decoration here. 115 of the 116 test files under shater/** had never executed in CI even once, which is how TestDNSFilterRemoteBlocklistHTTPClient stayed red across two published releases without anyone noticing. The gate is a job inside release.yml that build-apk needs, because a separate workflow cannot block another one. It runs the suite under the shipped tag set, on Linux — 6 of 7 test files in transport/wireguard and 12 in shater/generate compile only there or only under those tags, and those are exactly the files covering AmneziaWG. Three guards stop it from passing by running nothing, which is the failure this whole change is about. The tag set may only ADD test files, never remove one. Every package go list says has tests must appear as "ok <pkg>" in the output, so a suite that collapses to "no test files" fails instead of passing. And the panel run counts its test files first, because node --test exits 0 with "pass 0" when the glob matches nothing. The publish step used to exit 0 having published nothing: its assertions all live inside a loop over artifacts, so an empty directory ran the body zero times and reported success. It now counts what it published and fails on zero. Verified by extracting the shipped step text and running it against stubs: empty artifacts gives exit 0 before and exit 10 after; the rolling-release readback still fires its own exit 14. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
89 lines
3.4 KiB
Bash
89 lines
3.4 KiB
Bash
#!/usr/bin/env bash
|
||
#
|
||
# run-panel-tests.sh — the admin-panel half of the release test gate.
|
||
#
|
||
# panel/package.json has declared a `test` script since the SPA was scaffolded
|
||
# and nothing had ever called it: not release.yml, not build-shaterd.sh (which
|
||
# only runs `npm ci` + `npm run build`). This script is what CI calls, and it
|
||
# does the one thing `npm test` alone cannot: prove that tests actually RAN.
|
||
#
|
||
# `node --test src/*.test.ts` with no matching file leaves the glob unexpanded;
|
||
# node then reports `pass 0` and exits 0 — a green CI step that ran nothing,
|
||
# which is the exact failure class this whole change is about. So: the test
|
||
# files are counted BEFORE the run (a rename to *.spec.ts is named as such
|
||
# rather than showing up as a mystery), and the pass count is asserted > 0 and
|
||
# the fail count 0 after it.
|
||
#
|
||
# KNOWN LIMIT: node --test counts a *.test.ts file that declares no cases at
|
||
# all as one passing "test" (the module loaded). So an emptied-out file still
|
||
# reads as pass 1 here. Deleting, renaming or breaking the file is caught;
|
||
# gutting its contents while keeping the name is not.
|
||
#
|
||
# NODE VERSION: >= 22.6. The tests are TypeScript executed directly by
|
||
# `node --test`; type stripping does not exist before then, so on node 20 the
|
||
# run dies with a syntax error. CI pins node 24 for this step (the SPA *build*
|
||
# still uses node 20 — that one goes through vite/tsc and does not care).
|
||
#
|
||
# Usage: scripts/run-panel-tests.sh
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
REPO="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||
cd "$REPO/panel"
|
||
|
||
node_major="$(node -p 'process.versions.node.split(".")[0]')"
|
||
node_minor="$(node -p 'process.versions.node.split(".")[1]')"
|
||
if [ "$node_major" -lt 22 ] || { [ "$node_major" -eq 22 ] && [ "$node_minor" -lt 6 ]; }; then
|
||
echo " ERROR: panel tests are TypeScript under \`node --test\` and need node >= 22.6" >&2
|
||
echo " (got $(node --version)). Type stripping does not exist before that." >&2
|
||
exit 1
|
||
fi
|
||
|
||
# The glob `npm test` itself uses. Counted here so "somebody renamed the tests"
|
||
# is reported as that, instead of as a suspiciously fast green step.
|
||
shopt -s nullglob
|
||
files=(src/*.test.ts)
|
||
shopt -u nullglob
|
||
if [ "${#files[@]}" -eq 0 ]; then
|
||
echo " ERROR: no panel/src/*.test.ts — panel/package.json's \`test\` script" >&2
|
||
echo " would match nothing and still exit 0. Fix the glob or the files." >&2
|
||
exit 1
|
||
fi
|
||
|
||
echo "== panel tests (node $(node --version)), ${#files[@]} file(s) =="
|
||
if [ ! -d node_modules ]; then
|
||
npm ci
|
||
fi
|
||
|
||
out=""
|
||
rc=0
|
||
set +e
|
||
out="$(npm test --silent 2>&1)"
|
||
rc=$?
|
||
set -e
|
||
sed 's/^/ /' <<<"$out"
|
||
|
||
if [ "$rc" -ne 0 ]; then
|
||
echo " FAILED: npm test exited $rc" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# node --test's summary is `ℹ pass N` (spec reporter) or `# pass N` (tap).
|
||
passed="$(sed -n 's/.*[[:space:]]pass[[:space:]]\{1,\}\([0-9]\{1,\}\).*/\1/p' <<<"$out" | tail -1)"
|
||
failed="$(sed -n 's/.*[[:space:]]fail[[:space:]]\{1,\}\([0-9]\{1,\}\).*/\1/p' <<<"$out" | tail -1)"
|
||
if [ -z "$passed" ]; then
|
||
echo " FAILED: could not find a pass count in node --test output — the gate" >&2
|
||
echo " cannot tell a green run from an empty one." >&2
|
||
exit 1
|
||
fi
|
||
if [ "$passed" -lt 1 ]; then
|
||
echo " FAILED: 0 panel tests ran. \`npm test\` returned success having done nothing." >&2
|
||
exit 1
|
||
fi
|
||
if [ -n "$failed" ] && [ "$failed" -gt 0 ]; then
|
||
echo " FAILED: $failed panel test(s) failed." >&2
|
||
exit 1
|
||
fi
|
||
|
||
echo " OK: $passed panel test(s) passed."
|