Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/mvp-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v7.0.1

- name: Gate library and W0 gate self-tests (fixtures, no network)
run: |
bash scripts/gates/lib.selftest.sh
bash scripts/gates/w0.selftest.sh

- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ build-release:
test:
cargo test --lib

# Self-test the wave gates (scripts/gates/*.selftest.sh): fixtures, no network
test-gates:
bash scripts/gates/lib.selftest.sh
bash scripts/gates/w0.selftest.sh

# Run all tests (unit + integration)
test-all:
cargo test
Expand Down
131 changes: 131 additions & 0 deletions scripts/gates/hotspot-goldens.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
#
# Goldens for NEW:hotspot-split (W0 gate check 5). The split of main.rs into
# cli/*, server.rs into server/* and the Justfile into just/*.just is a pure
# move, so these three surfaces must not change:
#
# help/echidna[.<sub>[.<subsub>]].txt `echidna [<sub> ...] --help`, every
# subcommand, recursively (depth 3);
# "." joins the words because clap
# names never contain one
# routes.txt every `.route(...)` line under
# src/rust/, whitespace-trimmed, sorted
# router-composition.txt every Router::new, .merge, .nest,
# .nest_service, .fallback, .with_state
# and .layer line under src/rust/, so a
# route moved into a sub-router that is
# never mounted shows up as a diff
# just-list.txt `just --list`
#
# Usage:
# hotspot-goldens.sh capture [--dir DIR] [--bin PATH] write the goldens
# hotspot-goldens.sh check [--dir DIR] [--bin PATH] diff against them
#
# Capture on the pre-split tree (origin/main before the split lands), commit
# the result, and never re-capture to silence a diff after the split.
# DIR defaults to tests/golden/hotspot. Without --bin the echidna binary is
# built with `cargo build --locked --bin echidna` into $CARGO_TARGET_DIR
# (default target/). Run from the repository root.
#
# Exit codes: 0 no diff (or captured), 1 diff or missing golden, 2 cannot run.

set -euo pipefail
export LC_ALL=C

mode="${1:-}"
[ $# -gt 0 ] && shift
dir="tests/golden/hotspot"
bin=""
while [ $# -gt 0 ]; do

Check failure on line 40 in scripts/gates/hotspot-goldens.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNehpNDZyPPKPMwF&open=AaHAkNehpNDZyPPKPMwF&pullRequest=475
case "$1" in
--dir) dir="$2"; shift 2 ;;
--bin) bin="$2"; shift 2 ;;
-h|--help) sed -n '3,31p' "$0"; exit 0 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
case "$mode" in capture|check) ;; *) sed -n '3,31p' "$0" >&2; exit 2 ;; esac

for tool in just rg; do
command -v "$tool" >/dev/null 2>&1 || { echo "cannot run: $tool is not installed" >&2; exit 2; }
done

if [ -z "$bin" ]; then

Check failure on line 54 in scripts/gates/hotspot-goldens.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNehpNDZyPPKPMwG&open=AaHAkNehpNDZyPPKPMwG&pullRequest=475
cargo build --locked --quiet --bin echidna >&2 || { echo "cannot run: cargo build failed" >&2; exit 2; }
bin="${CARGO_TARGET_DIR:-target}/debug/echidna"
fi
[ -x "$bin" ] || { echo "cannot run: $bin is not executable" >&2; exit 2; }

out=$(mktemp -d)
trap 'rm -rf "$out"' EXIT
mkdir -p "$out/help"

# Subcommand names from a clap help text: the indented first words under
# "Commands:", up to the next blank line. `help` is clap's own and skipped.
subcommands() {
awk '/^Commands:/{on=1; next} on && /^$/{on=0} on && /^ [a-z0-9]/{print $1}' | grep -vx help || true
}

# help_tree <depth> <args...>
help_tree() {
local depth="$1" name sub text
shift
name=$(printf '%s' "echidna${*:+ $*}" | tr ' ' '.')
local rc=0
text=$(NO_COLOR=1 COLUMNS=100 RUST_BACKTRACE=0 "$bin" "$@" --help 2>&1) || rc=$?
if [ "$rc" -ne 0 ]; then
# A failing --help is behaviour too, and a pure move must keep it. Record
# the exit status and the message, without the thread id and the source
# path of the panic, which differ between machines.
text=$(printf '[exit %d]\n%s' "$rc" "$text" \
| sed -E "s/^thread '([^']*)' \\([0-9]+\\) panicked at .*/thread '\\1' panicked:/")
echo "note: '$*${*:+ }--help' exits $rc; recorded as behaviour" >&2
fi
printf '%s\n' "$text" > "$out/help/$name.txt"
[ "$depth" -ge 3 ] && return 0
for sub in $(printf '%s\n' "$text" | subcommands); do
help_tree $((depth + 1)) "$@" "$sub"
done
}
help_tree 0

# Prefix each matched line with the module that owns it, so moving a
# `.route(...)` from one router to another changes the golden. The owner is
# the first path component under src/rust/ with `.rs` removed: server.rs
# and server/prove.rs are both `server`, groove.rs is `groove`. That keeps
# the golden invariant under a module split, which is what it guards.
owned() {
awk '{
i = index($0, ":"); path = substr($0, 1, i - 1); line = substr($0, i + 1)
sub(/^src\/rust\//, "", path); sub(/\/.*$/, "", path); sub(/\.rs$/, "", path)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
print path ": " line
}'
}

rg --with-filename --no-line-number --glob '*.rs' '^\s*\.route\(' src/rust \
| owned | sort > "$out/routes.txt" || true
[ -s "$out/routes.txt" ] || { echo "cannot run: no .route( lines found under src/rust" >&2; exit 2; }

rg --with-filename --no-line-number --glob '*.rs' \
'(^|[^A-Za-z0-9_])Router::new\(|\.(merge|nest|nest_service|fallback|with_state|layer)\(' src/rust \
| owned | sort > "$out/router-composition.txt" || true
[ -s "$out/router-composition.txt" ] || { echo "cannot run: no Router::new( lines found under src/rust" >&2; exit 2; }

just --list > "$out/just-list.txt" 2>&1 || { echo "cannot run: just --list failed" >&2; exit 2; }

if [ "$mode" = capture ]; then

Check failure on line 118 in scripts/gates/hotspot-goldens.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNehpNDZyPPKPMwM&open=AaHAkNehpNDZyPPKPMwM&pullRequest=475
mkdir -p "$dir"
rm -rf "$dir/help"
cp -R "$out/." "$dir/"
echo "captured $(find "$dir" -type f | wc -l) golden files into $dir"
exit 0
fi

[ -d "$dir" ] || { echo "golden directory $dir is missing; capture it on the pre-split tree" >&2; exit 1; }
if diff -ruN "$dir" "$out"; then
echo "hotspot goldens: no diff ($(find "$out" -type f | wc -l) files)"
exit 0
fi
exit 1
132 changes: 132 additions & 0 deletions scripts/gates/lib.selftest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
#
# Self-test for scripts/gates/lib.sh. Each case runs a tiny gate in a child
# bash and checks its exit status (and, where it matters, its output).
#
# Usage: scripts/gates/lib.selftest.sh Exit 0 iff every case behaves.

# SC2001: sed prefixes multi-line output, which ${var//} cannot do per line.
# shellcheck disable=SC2001
set -uo pipefail

here=$(cd "$(dirname "$0")" && pwd)
lib="$here/lib.sh"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

failures=0
cases=0

# run_gate <body>: run a gate whose checks are <body>; prints its output,
# returns its exit status.
run_gate() {
bash -c ". '$lib'; gate_init selftest; $1; gate_finish"
}

# expect <name> <want: zero|nonzero> <body> [grep-pattern]
expect() {
local name="$1" want="$2" body="$3" pattern="${4:-}" out rc ok=1
cases=$((cases + 1))
out=$(run_gate "$body" 2>&1)
rc=$?
case "$want" in

Check failure on line 33 in scripts/gates/lib.selftest.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a default case (*) to handle unexpected values.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNeYpNDZyPPKPMuw&open=AaHAkNeYpNDZyPPKPMuw&pullRequest=475
zero) [ "$rc" -eq 0 ] || ok=0 ;;
nonzero) [ "$rc" -ne 0 ] || ok=0 ;;
esac
if [ -n "$pattern" ] && ! printf '%s\n' "$out" | grep -qE -- "$pattern"; then

Check failure on line 37 in scripts/gates/lib.selftest.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNeYpNDZyPPKPMuz&open=AaHAkNeYpNDZyPPKPMuz&pullRequest=475
ok=0
fi
if [ "$ok" -eq 1 ]; then

Check failure on line 40 in scripts/gates/lib.selftest.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNeYpNDZyPPKPMu0&open=AaHAkNeYpNDZyPPKPMu0&pullRequest=475
echo "ok $name (exit $rc)"
else
failures=$((failures + 1))
echo "FAIL $name: wanted $want exit${pattern:+ and output matching /$pattern/}, got exit $rc"
printf '%s\n' "$out" | sed 's/^/ | /'
fi
}

expect "all pass exits 0" zero \
'pass a "one"; pass b "two"' \
'selftest: 2 pass, 0 fail, 0 deferred'

expect "a FAIL exits non-zero" nonzero \
'pass a "one"; fail b "broken"; pass c "three"' \
'selftest: 2 pass, 1 fail, 0 deferred'

for reason in ruling:R0 secret:GITHUB_TOKEN hardware:8-core owner-host:P0-6 no-push:proof-burrower; do
expect "DEFERRED($reason) exits 0" zero \
"pass a one; deferred b '$reason' 'waiting'" \
"DEFERRED\\($reason\\)"
done

for reason in flaky "tool:just" "ruling:" "ruling" "" "secret:two words" "later:soon" "no-push"; do
expect "DEFERRED('$reason') is a FAIL" nonzero \
"pass a one; deferred b '$reason' 'waiting'" \
'not permitted'
done

expect "an empty gate fails" nonzero ':' 'no checks were recorded'

expect "a duplicated check id fails" nonzero \
'pass a one; pass a again' 'recorded twice'

expect "gate_cmd PASS on exit 0" zero 'gate_cmd a "true runs" -- true'

expect "gate_cmd FAIL on non-zero exit" nonzero \
'gate_cmd a "false runs" -- sh -c "echo boom; exit 3"' 'exit 3; boom'

# GATE_REPORT writes one JCS object per check.
cases=$((cases + 1))
report="$tmp/report.jsonl"
GATE_REPORT="$report" run_gate 'pass a "x"; deferred b secret:GITHUB_TOKEN "y"; fail c "z \"q\""' >/dev/null 2>&1
if [ "$(wc -l < "$report")" -eq 3 ] \
&& [ "$(jq -cS . "$report")" = "$(cat "$report")" ] \
&& [ "$(jq -r '.result' "$report" | tr '\n' ' ')" = "PASS DEFERRED FAIL " ] \

Check failure on line 85 in scripts/gates/lib.selftest.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNeYpNDZyPPKPMu3&open=AaHAkNeYpNDZyPPKPMu3&pullRequest=475
&& [ "$(sed -n 2p "$report")" = '{"check":"b","detail":"y","reason":"secret:GITHUB_TOKEN","result":"DEFERRED"}' ]; then
echo "ok GATE_REPORT is JCS JSON Lines"
else
failures=$((failures + 1))
echo "FAIL GATE_REPORT is JCS JSON Lines"
sed 's/^/ | /' "$report"
fi

# A report that cannot be written fails the gate, and leaves no partial file.
cases=$((cases + 1))
GATE_REPORT=/nonexistent/dir/r.jsonl run_gate 'pass a "x"' > "$tmp/out" 2>&1
rc=$?
if [ "$rc" -ne 0 ] && grep -q 'gate-report' "$tmp/out"; then
echo "ok an unwritable GATE_REPORT fails the gate (exit $rc)"
else
failures=$((failures + 1)); echo "FAIL an unwritable GATE_REPORT fails the gate: exit $rc"; sed 's/^/ | /' "$tmp/out"
fi

cases=$((cases + 1))
mkdir -p "$tmp/nojq"
for t in bash mktemp wc mv rm cat grep sed tr tail; do ln -sf "$(command -v "$t")" "$tmp/nojq/$t"; done
echo stale > "$tmp/stale.jsonl"
PATH="$tmp/nojq" GATE_REPORT="$tmp/stale.jsonl" "$tmp/nojq/bash" -c ". '$lib'; gate_init t; pass a x; gate_finish" > "$tmp/out" 2>&1
rc=$?
if [ "$rc" -ne 0 ] && [ "$(cat "$tmp/stale.jsonl")" = stale ]; then

Check failure on line 110 in scripts/gates/lib.selftest.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNeYpNDZyPPKPMu7&open=AaHAkNeYpNDZyPPKPMu7&pullRequest=475
echo "ok a report write without jq fails the gate and leaves the old file untouched"
else
failures=$((failures + 1)); echo "FAIL a report write without jq fails the gate: exit $rc"; sed 's/^/ | /' "$tmp/out"
fi

# gate_cmd records a FAIL even when the caller runs under set -e.
cases=$((cases + 1))
out=$(bash -c "set -e; . '$lib'; gate_init t; gate_cmd a d -- false; pass b y; gate_finish" 2>&1)
rc=$?
if [ "$rc" -ne 0 ] && grep -q '^FAIL a: d: exit 1' <<<"$out" && grep -q 't: 1 pass, 1 fail' <<<"$out"; then
echo "ok gate_cmd under set -e records the FAIL and runs on"
else
failures=$((failures + 1)); echo "FAIL gate_cmd under set -e: exit $rc"; sed 's/^/ | /' <<<"$out"

Check warning on line 123 in scripts/gates/lib.selftest.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal 's/^/ | /' 5 times.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNeYpNDZyPPKPMuu&open=AaHAkNeYpNDZyPPKPMuu&pullRequest=475
fi

# A newline in a detail cannot print a line that reads as another result.
expect "a newline in a detail stays on one line" nonzero \
"fail a \"\$(printf 'x\\nPASS forged: y')\"" \
'^FAIL a: x PASS forged: y$'

echo "lib.selftest: $cases cases, $failures failed"
[ "$failures" -eq 0 ]

Check failure on line 132 in scripts/gates/lib.selftest.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_echidna&issues=AaHAkNeYpNDZyPPKPMu9&open=AaHAkNeYpNDZyPPKPMu9&pullRequest=475
Loading
Loading