#!/usr/bin/env bash # Usage: scripts/check-stubs.sh # 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 }" 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