Audit pass: - Frame rule counts frames since the last move, so every step lasts exactly movesEvery(score) frames, also right after a speed-up - clampTime + MAX_GAP_MS: a hidden tab no longer replays the whole gap - Pong Restart keeps held keys - Fact corrections (mulberry32 output coverage, Haskell IO, uniform) - New fixture cases from a mutation analysis Extensions (all three tracks, shared fixtures, course text): - Turn queue (Exercise 10.6), persisted high score (10.7), replay viewer (10.8), play() end-to-end helper (9.5) - Optional State (4.7) and optional Module 7b, stack-safe IO - Module 12, property-based testing with generators, shrinking, invariants and a model-based oracle - Module 13, Conway's Life with a Click event - JS type checking via jsconfig, JSDoc and types.d.ts - Maintainer tools: scripts/mutate.mjs, scripts/typecheck-js.sh, Playwright end-to-end suite (e2e/, scripts/e2e.sh) Docs: docs/contract.md is the canonical contract; specs and build log under docs/superpowers. Verified: JS 728, Scala 689, F# 691 unit tests; stubs and progression M01-M13 and M07b; 176 end-to-end tests on Chromium and WebKit. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
80 lines
3.5 KiB
Bash
Executable File
80 lines
3.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Usage: scripts/check-stubs.sh <js|scala|fsharp>
|
||
# Runs each module M01..M13 (and the optional M07b) that has tests against the stubs in src/. Fails if any module's tests pass,
|
||
# and also fails (exit 2) on tooling/build errors, so a broken toolchain can never look like "stubs fail".
|
||
set -uo pipefail
|
||
track="${1:?usage: check-stubs.sh <js|scala|fsharp>}"
|
||
repo="$(cd "$(dirname "$0")/.." && pwd)"
|
||
dir="$repo/$track"
|
||
export PATH="$HOME/.dotnet:$HOME/.local/bin:$PATH"
|
||
|
||
case "$track" in
|
||
js) tool=node; has() { compgen -G "$dir/test/m$1-*.test.js" >/dev/null; } ;;
|
||
scala) tool="${SCALA_CLI:-scala-cli}"; has() { compgen -G "$dir/test/M$1*.test.scala" >/dev/null; } ;;
|
||
fsharp) tool=dotnet; has() { compgen -G "$dir/tests/M$1*.fs" >/dev/null; } ;;
|
||
*) echo "unknown track: $track" >&2; exit 2 ;;
|
||
esac
|
||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||
echo "check-stubs: required tool '$tool' not found on PATH" >&2; exit 2
|
||
fi
|
||
[ -x "$dir/test.sh" ] || { echo "check-stubs: $dir/test.sh missing or not executable" >&2; exit 2; }
|
||
|
||
# Succeeds if the (ANSI-stripped) output in $1 shows tests really ran and at least one failed
|
||
# (stub throws / ??? / failwith count as failures), as opposed to a compile or tooling error.
|
||
tests_failed() {
|
||
case "$track" in
|
||
js) ! grep -qE 'SyntaxError|ERR_MODULE_NOT_FOUND|does not provide an export|ReferenceError' <<<"$1" \
|
||
&& grep -qE '^ℹ tests [1-9]' <<<"$1" && grep -qE '^ℹ fail [1-9]' <<<"$1" ;;
|
||
scala) ! grep -qE 'Compilation failed|\[error\]|Error compiling|Downloading failed' <<<"$1" \
|
||
&& grep -qE '==> X' <<<"$1" ;;
|
||
fsharp) ! grep -qE 'error (FS|MSB|NU)[0-9]+|[Bb]uild failed|Build FAILED' <<<"$1" \
|
||
&& grep -qE '[1-9][0-9]* (failed|errored)' <<<"$1" ;;
|
||
esac
|
||
}
|
||
|
||
# Modules in course order; "07b" is the optional Module 7b.
|
||
modules="01 02 03 04 05 06 07 07b 08 09 10 11 12 13"
|
||
|
||
# Scala's `./test.sh M07` (munit glob "*M07*") and F#'s (Expecto --filter-test-list, a substring) also select the
|
||
# M07b tests. Their failures would hide an M07 that wrongly passes, so for such a module run the tests in a temp
|
||
# copy without the longer-tagged modules' test files (F#: also without their line in tests/Snake.Tests.fsproj).
|
||
run_module() {
|
||
local n="$1" longer="" t f tmp
|
||
if [ "$track" != js ]; then
|
||
for t in $modules; do [[ "$t" == "$n"?* ]] && has "$t" && longer="$longer $t"; done
|
||
fi
|
||
if [ -z "$longer" ]; then (cd "$dir" && ./test.sh "M$n" 2>&1); return; fi
|
||
tmp="$(mktemp -d)"
|
||
tar -C "$repo" --exclude=.git --exclude=node_modules --exclude=bin --exclude=obj \
|
||
--exclude=.scala-build --exclude=.bsp --exclude=.superpowers -cf - . | tar -C "$tmp" -xf -
|
||
for t in $longer; do
|
||
case "$track" in
|
||
scala) rm -f "$tmp"/scala/test/M"$t"*.test.scala ;;
|
||
fsharp) for f in "$tmp"/fsharp/tests/M"$t"*.fs; do
|
||
rm -f "$f"; sed -i "/Include=\"$(basename "$f")\"/d" "$tmp/fsharp/tests/Snake.Tests.fsproj"
|
||
done ;;
|
||
esac
|
||
done
|
||
(cd "$tmp/$track" && ./test.sh "M$n" 2>&1); local rc=$?
|
||
rm -rf "$tmp"
|
||
return $rc
|
||
}
|
||
|
||
status=0
|
||
for n in $modules; do
|
||
m="M$n"
|
||
if ! has "$n"; then echo "$m: no tests"; continue; fi
|
||
out="$(run_module "$n")"; rc=$?
|
||
out="$(sed $'s/\x1b\\[[0-9;?]*[A-Za-z]//g' <<<"$out")"
|
||
if [ $rc -eq 0 ]; then
|
||
echo "$m: FAIL (tests pass against stubs)"; status=1
|
||
elif tests_failed "$out"; then
|
||
echo "$m: ok (fails against stubs)"
|
||
else
|
||
echo "$m: ERROR (tooling/build error, exit $rc; not a test failure)" >&2
|
||
echo "$out" >&2
|
||
exit 2
|
||
fi
|
||
done
|
||
exit $status
|