#!/usr/bin/env bash # Checks the two implementations of SBFS against each other, on the same disk. # # SplitDisk and sbfs.asm are two programs written from one specification and sharing no # code at all - one is C on the host, the other is SplitBit assembly running on the # machine. disk.sh checks the host half against the format and run.sh checks the machine # half against recorded output, but neither of those can catch the two of them agreeing # with themselves and disagreeing with each other. # # SO THIS BUILDS THE SAME DISK TWICE, once with each, and compares the images byte for # byte. Every field either one writes and the other only reads is checked here and nowhere # else: which entry a thing lands in, which block, what a directory's unused fields hold, # the version in the superblock, the free count. A disagreement in any of those is a disk # one of them can read and the other cannot, and the way that is usually discovered is # somebody's file coming back wrong months later. # # Written by Anachronaut set -u ROOT="$(cd "$(dirname "$0")/.." && pwd)" WORK="$ROOT/Tests/build/agree" ASM="$ROOT/Assembler" TOOL="$ROOT/SplitDisk" EMU="$ROOT/SplitBit" PASS=0 FAIL=0 FAILED_NAMES=() GREEN=$'\033[32m'; RED=$'\033[31m'; RESET=$'\033[0m' [ -t 1 ] || { GREEN=""; RESET=""; RED=""; } report() { local mark="$1" name="$2" note="${3:-}" if [ "$mark" = "ok" ]; then PASS=$((PASS + 1)); printf " [%sok %s] %-24s %s\n" "$GREEN" "$RESET" "$name" "$note" else FAIL=$((FAIL + 1)); FAILED_NAMES+=("$name") printf " [%sFAIL%s] %-24s %s\n" "$RED" "$RESET" "$name" "$note" fi } for tool in "$ASM" "$TOOL" "$EMU"; do [ -x "$tool" ] || { echo "$(basename "$tool") is not built."; exit 1; } done rm -rf "$WORK"; mkdir -p "$WORK" cd "$WORK" || exit 1 "$ASM" -I "$ROOT/Programs/CosmOS/Source" "$ROOT/Programs/CosmOS/Source/cosmos.asm" \ -o cosmos.bin >/dev/null 2>&1 || { echo "CosmOS would not assemble."; exit 1; } echo "Checking the two SBFS implementations against each other." # ---- The same tree, made both ways ---- # # The order matters and is the same on both sides, because both allocate first fit and both # take the first free entry: given the same operations in the same order they should reach # the same bytes, and any difference is a real one rather than an artefact of the script. "$TOOL" format host.img 128 2 >/dev/null "$TOOL" mkdir host.img /Apps >/dev/null "$TOOL" mkdir host.img /Apps/Deep >/dev/null "$TOOL" mkdir host.img /Notes >/dev/null "$TOOL" format machine.img 128 2 >/dev/null printf 'mkdir /Apps\nmkdir /Apps/Deep\nmkdir /Notes\nexit\n' \ | "$EMU" cosmos.bin --fast --disk machine.img >/dev/null 2>&1 if cmp -s host.img machine.img; then report ok "three directories" "byte for byte" else report FAIL "three directories" "$(cmp host.img machine.img 2>&1 | head -1)" fi # ---- Removing one puts the disk back exactly ---- # # A wiped entry has to be indistinguishable from one that was never used, or a disk that # has had something deleted stops matching a fresh one that never did. Both sides zero all # thirty two bytes, and this is what says so. "$TOOL" rmdir host.img /Apps/Deep >/dev/null printf 'rmdir /Apps/Deep\nexit\n' | "$EMU" cosmos.bin --fast --disk machine.img >/dev/null 2>&1 if cmp -s host.img machine.img; then report ok "and removing one" "byte for byte" else report FAIL "and removing one" "$(cmp host.img machine.img 2>&1 | head -1)" fi # ---- A file put down a path ---- # # The machine writes files through the careful order a save has to use - make a temporary, # write it, delete the original, rename the temporary - and the host writes the entry once. # Two quite different routes to what has to be the same disk. # The editor goes on both disks FIRST and by the same route, so that the only thing left # differing is how the payload got written. Putting it on one disk before the payload and # the other after was enough to move every entry and block after it, and the comparison # duly failed on a difference the script had introduced. "$ASM" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Edit.asm" -o Edit.sbx >/dev/null 2>&1 "$TOOL" put host.img Edit.sbx >/dev/null "$TOOL" put machine.img Edit.sbx >/dev/null printf 'a file that lives in a directory\n' > payload.txt "$TOOL" put host.img payload.txt /Notes/payload.txt >/dev/null printf 'load Edit.sbx\nrun /Notes/payload.txt\na\na file that lives in a directory\n.\nw\nq\nexit\n' \ | "$EMU" cosmos.bin --fast --disk machine.img >/dev/null 2>&1 if cmp -s host.img machine.img; then report ok "a file down a path" "byte for byte" else report FAIL "a file down a path" "$(cmp host.img machine.img 2>&1 | head -1)" fi # ---- And each can read what the other wrote ---- # # Matching bytes and being readable are not the same claim. A field both of them write # wrongly in the same way would pass every comparison above. "$TOOL" get machine.img /Notes/payload.txt fromMachine.txt >/dev/null 2>&1 if cmp -s payload.txt fromMachine.txt; then report ok "the host reads it back" "$(wc -c < fromMachine.txt | tr -d ' ') bytes" else report FAIL "the host reads it back" "the file came back different" fi "$TOOL" mkdir host.img /Notes/Inner >/dev/null printf 'x' > deep.txt "$TOOL" put host.img deep.txt /Notes/Inner/deep.txt >/dev/null seen=$(printf 'cd /Notes/Inner\ndir\nexit\n' \ | "$EMU" cosmos.bin --fast --disk host.img 2>&1 | grep -c "deep.txt") if [ "$seen" -ge 1 ]; then report ok "the machine reads it back" "found it three deep" else report FAIL "the machine reads it back" "the machine could not see it" fi echo if [ "$FAIL" -eq 0 ]; then echo "All $PASS agreement checks passed." exit 0 fi echo "$PASS passed, $FAIL failed: ${FAILED_NAMES[*]}" exit 1