diff options
Diffstat (limited to 'scripts/mutate.sh')
| -rwxr-xr-x | scripts/mutate.sh | 52 |
1 files changed, 52 insertions, 0 deletions
diff --git a/scripts/mutate.sh b/scripts/mutate.sh new file mode 100755 index 0000000..e3132de --- /dev/null +++ b/scripts/mutate.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Mutation check: prove a test can fail. Replaces an exact snippet in one +# source file (must match exactly once), runs the tests, and ALWAYS restores +# the file. Exit 0 = mutant killed (some test failed, as it should); 1 = +# mutant SURVIVED (tests too weak); 3 = mutant doesn't build (invalid — +# replace a statement with `{ }` instead of deleting a loop body line). +# +# Usage (from the repo whose tests must catch it — a game root, or this lib's root): +# <path>/mutate.sh <file> <old-snippet> <new-snippet> +# e.g. scripts/mutate.sh src/MyGame.Core/Loop/InputLatch.cs \ +# '_jump |= sample.JumpPressed;' '_jump = sample.JumpPressed;' +# <path>/mutate.sh <file> <old> <new> -- <test command...> +# runs that command instead of the whole `dotnet test` (a filtered dotnet test, or any other runner: python +# unittest, ...); a dotnet command keeps the build / [FAIL] parsing, any other is killed by a non-zero exit. +set -uo pipefail + +file=$1 old=$2 new=$3 +shift 3 +cmd=(dotnet test --nologo) +if [[ ${1:-} == -- ]]; then + shift + cmd=("$@") +fi +backup=$(mktemp) +cp "$file" "$backup" +trap 'cp "$backup" "$file"; rm -f "$backup"' EXIT + +python3 - "$file" "$old" "$new" <<'EOF' || exit 2 +import sys +path, old, new = sys.argv[1:] +text = open(path, encoding="utf-8").read() +n = text.count(old) +if n != 1: + sys.exit(f"snippet matches {n} times in {path} (need exactly 1)") +open(path, "w", encoding="utf-8").write(text.replace(old, new)) +EOF + +result=$("${cmd[@]}" 2>&1) +status=$? +if [[ ${cmd[0]} != dotnet ]]; then + if (( status != 0 )); then + echo "KILLED (exit $status):"; tail -3 <<<"$result"; exit 0 + fi + echo "SURVIVED: no test failed — strengthen the tests"; exit 1 +fi +if grep -q "error CS" <<<"$result"; then + echo "INVALID: mutant doesn't build"; grep -m3 "error CS" <<<"$result"; exit 3 +fi +if grep -q "Failed!" <<<"$result"; then + echo "KILLED by:"; grep "\[FAIL\]" <<<"$result" | sed -E 's/^\[xUnit[^]]*\] +//; s/ \[FAIL\]$//' | sort -u; exit 0 +fi +echo "SURVIVED: no test failed — strengthen the tests"; exit 1 |
