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>
This commit is contained in:
1 parent
876f5aabb1
commit
edd57f0148
253 files changed
+21753
-503
No files matched your search
@@ -3,7 +3,7 @@
|
||||
# Progression checkpoint: for each module N (that has tests in the track), build a temp copy where src/ holds the
|
||||
# reference solutions of modules <= N and the untouched student stubs for the rest, then run the tests of modules
|
||||
# M01..MN. All must pass: this proves a student who has finished module N has a green suite up to N, and that
|
||||
# no earlier solution secretly depends on a later one.
|
||||
# no earlier solution secretly depends on a later one. The optional module 07b sits between 07 and 08.
|
||||
set -uo pipefail
|
||||
track="${1:?usage: check-progression.sh <js|scala|fsharp>}"
|
||||
repo="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
@@ -15,36 +15,45 @@ files_for() {
|
||||
js:01) echo "util.js todo.js" ;;
|
||||
js:02) echo "model.js" ;;
|
||||
js:03) echo "rules.js" ;;
|
||||
js:04) echo "random.js spawn.js" ;;
|
||||
js:04) echo "random.js spawn.js state.js" ;;
|
||||
js:05) echo "game.js" ;;
|
||||
js:06) echo "view.js" ;;
|
||||
js:07) echo "io.js" ;;
|
||||
js:07b) echo "safeio.js" ;; # optional module 7b
|
||||
js:08) echo "stream.js" ;;
|
||||
js:09) echo "input.js app.js runtime.js browser.js main.js" ;;
|
||||
js:10) echo "" ;; # edits game/input/app, already included by m05/m09
|
||||
js:10) echo "replay.js" ;; # also edits game/input/stream/app/io/runtime/browser, already included by m05-m09
|
||||
js:11) echo "pong" ;;
|
||||
js:12) echo "check.js" ;;
|
||||
js:13) echo "life" ;;
|
||||
scala:01) echo "Util.scala" ;;
|
||||
scala:02) echo "Model.scala" ;;
|
||||
scala:03) echo "Rules.scala" ;;
|
||||
scala:04) echo "Random.scala Spawn.scala" ;;
|
||||
scala:04) echo "Random.scala Spawn.scala State.scala" ;;
|
||||
scala:05) echo "Game.scala" ;;
|
||||
scala:06) echo "View.scala" ;;
|
||||
scala:07) echo "IO.scala" ;;
|
||||
scala:07b) echo "SafeIO.scala" ;;
|
||||
scala:08) echo "Stream.scala" ;;
|
||||
scala:09) echo "Input.scala App.scala Runtime.scala" ;;
|
||||
scala:10) echo "" ;;
|
||||
scala:10) echo "Replay.scala" ;;
|
||||
scala:11) echo "pong" ;;
|
||||
scala:12) echo "Check.scala" ;;
|
||||
scala:13) echo "life" ;;
|
||||
fsharp:01) echo "Util.fs" ;; # Snake.fsproj lists every file from the start and is identical in both trees
|
||||
fsharp:02) echo "Model.fs" ;;
|
||||
fsharp:03) echo "Rules.fs" ;;
|
||||
fsharp:04) echo "Random.fs Spawn.fs" ;;
|
||||
fsharp:04) echo "Random.fs Spawn.fs State.fs" ;;
|
||||
fsharp:05) echo "Game.fs" ;;
|
||||
fsharp:06) echo "View.fs" ;;
|
||||
fsharp:07) echo "IO.fs" ;;
|
||||
fsharp:07b) echo "SafeIO.fs" ;;
|
||||
fsharp:08) echo "Stream.fs" ;;
|
||||
fsharp:09) echo "Input.fs App.fs Runtime.fs" ;;
|
||||
fsharp:10) echo "" ;;
|
||||
fsharp:10) echo "Replay.fs" ;;
|
||||
fsharp:11) echo "Pong" ;;
|
||||
fsharp:12) echo "Check.fs" ;;
|
||||
fsharp:13) echo "Life" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
@@ -56,23 +65,44 @@ case "$track" in
|
||||
*) echo "unknown track: $track" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
# Modules in course order. "07b" (optional) comes after 07: order is position in this list, not numeric value.
|
||||
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, unlike JS's "m07-*" glob. Before module 07b is reached its tests must not run, so delete them from
|
||||
# the temp copy (and, for F#, their line in tests/Snake.Tests.fsproj; Expecto finds test lists by attribute).
|
||||
drop_tests() {
|
||||
case "$track" in
|
||||
scala) rm -f "$tmp"/scala/test/M"$1"*.test.scala ;;
|
||||
fsharp) for f in "$tmp"/fsharp/tests/M"$1"*.fs; do
|
||||
[ -e "$f" ] || continue
|
||||
rm -f "$f"; sed -i "/Include=\"$(basename "$f")\"/d" "$tmp/fsharp/tests/Snake.Tests.fsproj"
|
||||
done ;;
|
||||
esac
|
||||
}
|
||||
|
||||
status=0
|
||||
for n in 01 02 03 04 05 06 07 08 09 10 11; do
|
||||
for n in $modules; do
|
||||
has "$n" || continue
|
||||
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 m in 01 02 03 04 05 06 07 08 09 10 11; do
|
||||
[ "$m" -le "$n" ] || break
|
||||
reached=""; past=0
|
||||
for m in $modules; do
|
||||
if [ $past -eq 1 ]; then # a later module: drop its tests if a reached module's tag is a prefix of its tag
|
||||
for r in $reached; do [[ "$m" == "$r"?* ]] && drop_tests "$m"; done
|
||||
continue
|
||||
fi
|
||||
for f in $(files_for "$m"); do
|
||||
[ -e "$tmp/$track/solutions/$f" ] || continue # module not (yet) present in this track
|
||||
rm -rf "$tmp/$track/src/$f"
|
||||
cp -r "$tmp/$track/solutions/$f" "$tmp/$track/src/$f"
|
||||
done
|
||||
reached="$reached $m"
|
||||
[ "$m" = "$n" ] && past=1
|
||||
done
|
||||
ok=1
|
||||
for m in 01 02 03 04 05 06 07 08 09 10 11; do
|
||||
[ "$m" -le "$n" ] || break
|
||||
for m in $reached; do
|
||||
has "$m" || continue
|
||||
if ! out="$(cd "$tmp/$track" && ./test.sh "M$m" 2>&1)"; then
|
||||
echo "checkpoint M$n: FAIL (M$m tests fail with solutions up to M$n)"
|
||||
|
||||
Reference in new issue
Block a user