open-standards/wai/cache-conformance/run.sh
Transaction Science b6dffcff83
Some checks failed
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 10s
wai-audit collector and player integration / does this change touch it (push) Successful in 43s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 1m0s
wai-fleet service / does this change touch it (push) Successful in 1m0s
wai-app and Create / wai-app · does this change touch it (push) Successful in 1m1s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 1m3s
WAI operation catalogue / does this change touch it (push) Successful in 1m2s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 1m8s
wai-ledger service / does this change touch it (push) Successful in 1m2s
wai-fleet service / service · macos (push) Successful in 45s
wai-quantum for JavaScript / does this change touch it (push) Successful in 1m2s
wai-sink-android native sink library / does this change touch it (push) Successful in 48s
wai-sink-swift native sink package / does this change touch it (push) Successful in 46s
WAI Studio / does this change touch it (push) Successful in 46s
wai-quantum studio and desktop / does this change touch it (push) Successful in 49s
wai-audit collector and player integration / collector · linux (push) Successful in 1m20s
wai-ledger service / service · macos (push) Successful in 1m2s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 1m2s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 1m26s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 1m21s
wai-ledger service / service · linux (push) Failing after 1m50s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 3m15s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 2m9s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m54s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 3m44s
wai-fleet service / service · linux (push) Successful in 3m1s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 3m12s
wai-quantum studio and desktop / studio · browser (push) Successful in 4m9s
wai-quantum for JavaScript / javascript · linux (push) Successful in 4m29s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 4m28s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 5m26s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 6m10s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 5m45s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 8m3s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 7m22s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 7m23s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 9m59s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 10m52s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 13m34s
WAI Studio / Studio in WebKit · linux (push) Successful in 12m47s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 15m58s
cache-conformance tests what a cache answers from its store, and says how much it answered
Every MUST test could pass on a first GET, which a cache relays from the origin, so a cache that
broke §9 rule 3 only on what it stored passed the suite. C14 asks again, a second after the first
GET, for an object, a head proof, an alias, a live-edge part, a receipt file, a playlist receipt,
a key log and a parameter set, and holds each answer to the origin's bytes, ETag, Content-Type,
Cache-Control and Content-Length, with no Vary. Every 206 is held to the origin's headers too.

Three negative controls show the driver catches those faults: stored changes the headers of every
answer after a path's first, ranged those of every 206, and reflip a byte of every answer after a
path's first. S3 says how many of C14's repeats the
cache answered without reaching the origin and S4 how many of the run's requests reached it; the
summary marks a labelled cache whose store answered none as a relay.

A WAI_CACHE_DIR whose labels file is empty fails the run as an absent one does, and a labelled
cache that does not start has a row in the summary. wai http origin sends no Date while its
clock reads before the epoch (RFC 9110 §6.6.1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 12:07:00 -05:00

103 lines
5.6 KiB
Shell
Executable file

#!/usr/bin/env bash
# Cache conformance (extensions/http-delivery.md §9): stage the corpus's objects, start
# the reference origin, and run driver.py
#
# 1. against the origin itself, which must pass every MUST test (the positive control);
# 2. against each negative-control proxy in mutators.py, which must fail the tests its
# defect reaches (the driver's own falsification);
# 3. against each labelled cache an environment provides: <dir>/labels lists the
# labels, and <dir>/<label>/start.sh <port> <origin URL> starts one in front of the
# origin (with version and config files beside it). <dir> is /opt/wai-cache, or
# WAI_CACHE_DIR where that is set. The public repository holds no cache
# configuration; without labels in /opt/wai-cache only 1 and 2 run, and the summary
# says so. A WAI_CACHE_DIR that names no label is an error, since it was asked for.
# The summary says how many repeat GETs each cache answered from its store, and marks
# one that answered none as a relay.
#
# WAI=<path to a `wai` built with http> [WAI_CACHE_DIR=<dir>] \
# bash wai/cache-conformance/run.sh <out-dir>
#
# Reports go to <out-dir>/reports: report-<label>.json and requirements-<label>.md.
# Exit status 1 when a control does not behave as stated or a labelled cache fails a
# MUST test.
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
OUT="${1:?usage: run.sh <out-dir>}"
WAI="${WAI:?set WAI to a wai binary built with the http feature}"
CACHES="${WAI_CACHE_DIR:-/opt/wai-cache}"
mkdir -p "$OUT/reports"
ROOT="$OUT/root"
python3 "$HERE/driver.py" stage "$HERE/../http-conformance" "$ROOT" || exit 1
pids=()
cleanup() { for p in "${pids[@]}"; do kill "$p" 2>/dev/null; done; }
trap cleanup EXIT
free_port() { python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()'; }
wait_port() { for _ in $(seq 1 100); do python3 -c "import socket,sys; socket.create_connection(('127.0.0.1',$1),1)" 2>/dev/null && return 0; sleep 0.1; done; return 1; }
OP="$(free_port)"
"$WAI" http origin --root "$ROOT" --port "$OP" --count-hits --live-prefix live/ --live-max-age 30 > "$OUT/origin.log" 2>&1 &
pids+=($!)
wait_port "$OP" || { echo "::error::the origin did not start"; cat "$OUT/origin.log"; exit 1; }
ORIGIN="http://127.0.0.1:$OP"
fail=0
summary="| run | expectation | MUST passed | result |\n|---|---|---|---|\n"
run_driver() { # label base expect-pass [expected failing ids]
local label="$1" base="$2" expect="$3"; shift 3
python3 "$HERE/driver.py" run --base "$base" --root "$ROOT" --origin "$ORIGIN" --label "$label" \
--report "$OUT/reports/report-$label.json" > "$OUT/reports/$label.log" 2>&1
local rc=$?
local passed; passed="$(tail -1 "$OUT/reports/$label.log" | sed 's/.*: //')"
if [ "$expect" = pass ]; then
if [ $rc -eq 0 ]; then summary+="| $label | every MUST passes | $passed | ok |\n"
else summary+="| $label | every MUST passes | $passed | **failed** |\n"; echo "::error::$label failed a MUST test"; cat "$OUT/reports/$label.log"; fail=1; fi
else
local missing=""
for id in "$@"; do grep -q "^FAIL $id:" "$OUT/reports/$label.log" || missing="$missing $id"; done
if [ $rc -ne 0 ] && [ -z "$missing" ]; then summary+="| $label | fails $* | $passed | caught |\n"
else summary+="| $label | fails $* | $passed | **not caught:$missing** |\n"; echo "::error::$label: the driver did not catch$missing (exit $rc)"; cat "$OUT/reports/$label.log"; fail=1; fi
fi
}
# 1. The origin, directly.
run_driver origin-direct "$ORIGIN" pass
# 2. The negative controls.
expect_of() { case "$1" in flip) echo "C1 C3 C11 C14" ;; gzip) echo "C7" ;; truncate) echo "C1 C2 C14" ;; vary) echo "C13" ;; stored) echo "C14" ;; ranged) echo "C3 C5 C8" ;; reflip) echo "C1 C14" ;; esac; }
for m in flip gzip truncate vary stored ranged reflip; do
MP="$(free_port)"
python3 "$HERE/mutators.py" --mode "$m" --listen "$MP" --upstream "$ORIGIN" > "$OUT/mutator-$m.log" 2>&1 &
pids+=($!)
wait_port "$MP" || { echo "::error::mutator $m did not start"; fail=1; continue; }
# shellcheck disable=SC2086
run_driver "control-$m" "http://127.0.0.1:$MP" fail $(expect_of "$m")
done
# 3. Labelled caches, where the environment provides them.
labels="$(cat "$CACHES/labels" 2>/dev/null | xargs)"
if [ -n "$labels" ]; then
for label in $labels; do
CP="$(free_port)"
"$CACHES/$label/start.sh" "$CP" "$ORIGIN" > "$OUT/cache-$label.log" 2>&1 &
pids+=($!)
wait_port "$CP" || { summary+="| $label | every MUST passes | — | **did not start** |\n"; echo "::error::cache $label did not start"; fail=1; continue; }
python3 "$HERE/driver.py" run --base "http://127.0.0.1:$CP" --root "$ROOT" --origin "$ORIGIN" --label "$label" \
--version "$(cat "$CACHES/$label/version" 2>/dev/null || echo unknown)" \
--config "$CACHES/$label/config" --report "$OUT/reports/report-$label.json" > "$OUT/reports/$label.log" 2>&1
rc=$?
stored="$(sed -n 's/^info S3: //p' "$OUT/reports/$label.log")"
case "$stored" in 0\ of*) stored="**a relay:** $stored" ;; esac
summary+="| $label | every MUST passes | $(tail -1 "$OUT/reports/$label.log" | sed 's/.*: //') | $([ $rc -eq 0 ] && echo ok || echo '**failed**'); $stored |\n"
[ $rc -eq 0 ] || fail=1
done
elif [ -n "${WAI_CACHE_DIR:-}" ]; then
summary+="| labelled caches | — | — | **no labels in $CACHES/labels** |\n"; echo "::error::WAI_CACHE_DIR names no label: $CACHES/labels"; fail=1
else
summary+="| labelled caches | — | — | none in this environment (no labels in $CACHES/labels): controls only |\n"
fi
printf "%b" "$summary" | tee "$OUT/reports/summary.md"
[ -n "${GITHUB_STEP_SUMMARY:-}" ] && printf "%b" "$summary" >> "$GITHUB_STEP_SUMMARY"
exit $fail