mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
the summarise step is if: always(), and under github's bash -eo pipefail an unmatched glob stays literal, the redirect fails, pipefail carries it into the assignment and -e kills the step. so a failed fuzz run went red twice, once for the real reason.
149 lines
5.1 KiB
YAML
149 lines
5.1 KiB
YAML
name: replay-ui
|
|
|
|
# Sanderling fuzzing sanderling's own replay UI. Dispatch-only: it takes minutes
|
|
# and it is a demo of the product loop, not a merge gate.
|
|
#
|
|
# The shape is: produce a real trace, serve it with `sanderling replay`, then run
|
|
# a spec against that UI. Any violation fails the job. Six of the seven
|
|
# properties in replay-ui/sanderling/spec.ts are cross-panel agreements that hold
|
|
# for any trace; the seventh is the stock noUncaughtExceptions. None of them
|
|
# needs recalibrating when the fixture changes.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
seed:
|
|
description: seed for the dogfood run
|
|
default: "3"
|
|
max-steps:
|
|
description: step budget for the dogfood run
|
|
default: "80"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
dogfood:
|
|
timeout-minutes: 45
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: Set up bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: "1.3.13"
|
|
|
|
# Pinned stable plus the AppArmor sysctl: the same setup ci.yml's browser
|
|
# job needs to get headless Chrome up on ubuntu-latest.
|
|
- name: Set up Chrome
|
|
uses: browser-actions/setup-chrome@v1
|
|
with:
|
|
chrome-version: stable
|
|
|
|
- name: Allow Chrome under unprivileged user namespaces
|
|
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
|
|
- name: Verify headless Chrome starts
|
|
run: |
|
|
chrome --version
|
|
chrome --headless --no-sandbox --disable-gpu --disable-dev-shm-usage \
|
|
--dump-dom 'data:text/html,<title>ok</title>'
|
|
|
|
# The UI the spec drives is the one embedded in this binary, so the build
|
|
# has to come after any change to replay-ui/src.
|
|
- name: Build sanderling
|
|
run: make sanderling-web
|
|
|
|
# A trace with a violation and uncaught exceptions in it, so the UI has
|
|
# something to render in every panel the spec looks at. No
|
|
# --exit-on-violation here: the run is the fixture, and stopping it at the
|
|
# first violation would leave a four-step trace to fuzz.
|
|
- name: Record a fixture trace
|
|
run: |
|
|
python3 -m http.server 8792 --bind 127.0.0.1 \
|
|
--directory test/browser/testdata/throwing &
|
|
ready=""
|
|
for _ in $(seq 1 30); do
|
|
curl -sf http://127.0.0.1:8792/ >/dev/null && { ready=1; break; }
|
|
sleep 1
|
|
done
|
|
if [ -z "$ready" ]; then
|
|
echo "the fixture http server never answered on 127.0.0.1:8792" >&2
|
|
exit 1
|
|
fi
|
|
./bin/sanderling test \
|
|
--platform web \
|
|
--spec test/browser/testdata/throwing/spec.ts \
|
|
--bundle-id http://127.0.0.1:8792/ \
|
|
--duration 5m --max-steps 25 --seed 7 \
|
|
--output runs/fixture
|
|
|
|
- name: Serve the trace with sanderling replay
|
|
run: |
|
|
# Flags before the positional argument: Go's flag package stops
|
|
# parsing at the first non-flag word.
|
|
./bin/sanderling replay --port 8793 --no-open runs/fixture &
|
|
ready=""
|
|
for _ in $(seq 1 30); do
|
|
curl -sf http://127.0.0.1:8793/api/runs >/dev/null && { ready=1; break; }
|
|
sleep 1
|
|
done
|
|
if [ -z "$ready" ]; then
|
|
echo "sanderling replay never served /api/runs on 127.0.0.1:8793" >&2
|
|
exit 1
|
|
fi
|
|
run_id="$(ls runs/fixture | head -1)"
|
|
echo "RUN_URL=http://127.0.0.1:8793/runs/$run_id/steps/1" >> "$GITHUB_ENV"
|
|
curl -sf "http://127.0.0.1:8793/runs/$run_id/steps/1" >/dev/null
|
|
|
|
# Inputs go through env rather than into the script text: a `${{ }}` is
|
|
# substituted before bash ever sees the line, so a seed of `$(id)` would
|
|
# run as a command.
|
|
- name: Fuzz the replay UI
|
|
run: |
|
|
./bin/sanderling test \
|
|
--platform web \
|
|
--spec replay-ui/sanderling/spec.ts \
|
|
--bundle-id "$RUN_URL" \
|
|
--duration 10m \
|
|
--max-steps "$MAX_STEPS" \
|
|
--seed "$SEED" \
|
|
--exit-on-violation \
|
|
--output runs/dogfood
|
|
env:
|
|
SEED: ${{ inputs.seed }}
|
|
MAX_STEPS: ${{ inputs.max-steps }}
|
|
|
|
- name: Summarise
|
|
if: always()
|
|
run: |
|
|
{
|
|
echo "### replay-ui dogfood"
|
|
echo
|
|
echo "- seed \`$SEED\`, budget $MAX_STEPS steps"
|
|
for dir in runs/dogfood/*/; do
|
|
[ -f "$dir/trace.jsonl" ] || continue
|
|
steps=$(wc -l < "$dir/trace.jsonl" | tr -d ' ')
|
|
violations=$(grep -c '"violations":\[' "$dir/trace.jsonl" || true)
|
|
echo "- $steps steps recorded, $violations step(s) with violations"
|
|
done
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
env:
|
|
SEED: ${{ inputs.seed }}
|
|
MAX_STEPS: ${{ inputs.max-steps }}
|
|
|
|
- name: Upload runs
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: replay-ui-runs
|
|
path: runs/
|
|
retention-days: 14
|