tools: scripts that run from anywhere, and a mutation test that ran nothing

./mkiso.sh from tools/posix/ failed with "missing build/kernel/kernel --
run make first" on a tree that was built perfectly well: the paths were
relative to the caller's directory rather than the repo. mkiso.sh and
schcom_test.sh now resolve the root from their own location, the way
memory-sync.sh already did. An output path given to mkiso.sh is made
absolute first, so `mkiso.sh out.iso` still writes where the caller is
standing and not where the repo is.

mutate.sh had it worse: it began with `cd /mnt/d/DEV/SchOS`, an absolute
WSL path from the Windows machine it was written on, so it could not run
against any other checkout at all.

Fixing that exposed why it mattered. Every one of its nine mutations
reported "rejected at compile time" -- and none of them had reached a
compiler. The host tools moved to build/tools/ during the bt/ -> tools/
migration and this script still asked for build/mkschfs, so make answered
"No rule to make target" and the harness read that as the mutation being
caught by the build. A mutation test whose every case is silently skipped
is precisely the thing its own header warns about: a test that cannot
fail, believed anyway.

With the paths corrected all nine are caught by the suites, which is what
this was always supposed to demonstrate:

  file_read ignores the end-of-file clamp        -> extracted file differs
  dirent_walk advances past the next entry       -> entries lost
  dir_lookup returns the wrong objectid          -> fsck: no such file
  extents point one block past their data        -> mkschfs write fails
  an overwrite drops the tail of the split       -> FAIL: CoW keeps the run
  truncate leaves stale bytes in the tail block  -> FAIL: reads as zeros
  committed frees never return to the free map   -> FAIL: create hog -> -3
  hard links do not bump nlink                   -> FAIL: nlink=1, expected 2
  data checksum covers only the first 16 bytes   -> FAIL: tree walk

The build failure is now printed rather than swallowed, so the next time
a target is renamed it says so instead of reporting nine passes. Two
mutations also reported a bare "caught:" with no reason -- the suite had
failed but nothing matched the quote patterns -- so the fallback is now
the last thing the suite actually said.

schfs-check and schfs-test still pass, and the tree restores clean.
This commit is contained in:
Nexoniarz 2026-08-11 10:39:58 +02:00
commit 141f2785ab
3 changed files with 60 additions and 8 deletions

View file

@ -49,8 +49,25 @@
set -e
BUILD=${BUILD:-build}
# Run from anywhere: resolve the repo root from this script's own location
# rather than trusting the caller's working directory. Everything below is
# relative to it, and running from tools/posix/ used to fail with "missing
# build/kernel/kernel -- run make first" on a tree that was perfectly built.
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo=$(CDPATH= cd -- "$script_dir/../.." && pwd)
# An output path given on the command line means where the *caller* is, not
# where the repo is, so it is made absolute before the cd below moves the goal
# posts under it. Without an argument the disc lands at the repo root, which is
# where `make iso` has always put it.
invoked_from=$(pwd)
OUT=${1:-schos.iso}
case $OUT in
/*) ;;
*) OUT=$invoked_from/$OUT ;;
esac
cd "$repo"
BUILD=${BUILD:-build}
STAGING="$BUILD/iso"
SYSROOT=${SYSROOT:-sysroot}

View file

@ -8,7 +8,22 @@
# say -- would otherwise look exactly like a passing mutation test, which is
# the same failure this script exists to catch.
set -e
cd /mnt/d/DEV/SchOS
# Run from anywhere. This used to `cd /mnt/d/DEV/SchOS` -- an absolute WSL path
# from the Windows machine this was written on, which meant the script could not
# run on any other checkout at all.
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo=$(CDPATH= cd -- "$script_dir/../.." && pwd)
cd "$repo"
# Where the host tools land. These moved to build/tools/ in the bt/ -> tools/
# migration and this script kept the old names; make then answered "No rule to
# make target", which the harness reported as "rejected at compile time". Every
# mutation looked handled and not one of them ever reached a test.
MKSCHFS=build/tools/mkschfs
FSCK=build/tools/schfs_fsck
OBJT=build/tools/schfs_objtest
OBJ=kernel/fs/schfs/obj.c
OBJW=kernel/fs/schfs/objw.c
@ -29,6 +44,16 @@ trap restore EXIT
FAILED=0
# Quote the line that says why, falling back to the last thing the suite said.
# Two mutations used to report a bare "caught:" with nothing after it -- the
# suite had failed, but nothing in it matched the patterns below, so the
# evidence was an exit status and an empty string.
why() {
line=$(grep -aE 'FAIL|Only in|differ|loop|error|assert' "$1" | head -1)
[ -n "$line" ] || line=$(grep -av '^[[:space:]]*$' "$1" | tail -1)
printf '%s' "$line" | sed 's/^ *//'
}
# try <description> <file> <sed-expression> [suite]
#
# suite is "image" (the default: build an image and diff it against the source
@ -50,29 +75,31 @@ try() {
fi
if [ "$suite" = obj ]; then
if ! make build/schfs_objtest > /tmp/mk.log 2>&1; then
if ! make "$OBJT" > /tmp/mk.log 2>&1; then
echo " rejected at compile time"
sed -n '$p' /tmp/mk.log | sed 's/^/ /'
return
fi
if ./build/schfs_objtest > /tmp/rt.log 2>&1; then
if "./$OBJT" > /tmp/rt.log 2>&1; then
echo " NOT CAUGHT -- the suite passed with this bug present"
FAILED=1
else
echo " caught: $(grep -aE 'FAIL' /tmp/rt.log | head -1 | sed 's/^ *//')"
echo " caught: $(why /tmp/rt.log)"
fi
return
fi
if ! make build/mkschfs build/schfs_fsck > /tmp/mk.log 2>&1; then
if ! make "$MKSCHFS" "$FSCK" > /tmp/mk.log 2>&1; then
echo " rejected at compile time"
sed -n '$p' /tmp/mk.log | sed 's/^/ /'
return
fi
if sh tools/schfs_roundtrip.sh ./build/mkschfs ./build/schfs_fsck \
if sh tools/posix/schfs_roundtrip.sh "./$MKSCHFS" "./$FSCK" \
> /tmp/rt.log 2>&1; then
echo " NOT CAUGHT -- the suite passed with this bug present"
FAILED=1
else
echo " caught: $(grep -aE 'FAIL|Only in|differ|loop' /tmp/rt.log | head -1 | sed 's/^ *//')"
echo " caught: $(why /tmp/rt.log)"
fi
}

View file

@ -10,6 +10,14 @@
# captures whichever QEMU considers active, which need not be the one /dev/fb0
# maps.
set -e
# Run from anywhere: resolve the repo root from this script's own location
# rather than trusting the caller's working directory. Everything below is
# relative to it, and running from tools/posix/ used to fail with "missing
# build/kernel/kernel -- run make first" on a tree that was perfectly built.
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo=$(CDPATH= cd -- "$script_dir/../.." && pwd)
cd "$repo"
BUILD=${BUILD:-build}
mkdir -p "$BUILD"