Files
functional-programming-snake/scripts/check-stubs.sh
T
Lucas WintherandClaude Fable 5.2 edd57f0148 feat(course): audit fixes and twelve extensions (Modules 7b, 12, 13)
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>
2026-11-11 12:05:29 +01:00

80 lines
3.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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