./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.
154 lines
5.7 KiB
Shell
154 lines
5.7 KiB
Shell
#!/bin/sh
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Run Schcom and a client inside SchOS, then check the actual pixels from the
|
|
# host with QEMU's screendump. The host has no connection to the compositor, so
|
|
# it is a real oracle: if the block is the right colour in the right place, the
|
|
# buffer really was shared, composited and presented.
|
|
#
|
|
# One display device on purpose (-vga std, no virtio-gpu): with two, screendump
|
|
# 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"
|
|
|
|
LOG=$BUILD/schcom-test.log
|
|
PPM=${1:-$BUILD/schcom.ppm}
|
|
rm -f "$LOG" "$PPM"
|
|
|
|
# The firmware varstore is written by OVMF, so this run gets its own copy
|
|
# rather than sharing the pristine one every other run starts from.
|
|
cp -f build/OVMF_VARS.fd build/schcom-OVMF_VARS.fd
|
|
|
|
PORT=$(python3 - <<'EOF'
|
|
import socket
|
|
s=socket.socket(); s.bind(('127.0.0.1',0)); print(s.getsockname()[1]); s.close()
|
|
EOF
|
|
)
|
|
|
|
(
|
|
sleep 26
|
|
printf 'schcom --frames 24 &\n'; sleep 6
|
|
printf 'schcom-demo --colour 0x00C04020 --at 100 80 --size 240 160 --wait 40 &\n'; sleep 10
|
|
printf 'schcom-demo --colour 0x002060C0 --at 260 180 --size 240 160 --wait 30 &\n'; sleep 12
|
|
printf '\n'
|
|
sleep 40
|
|
) | timeout -k 5 120 qemu-system-x86_64 \
|
|
-machine pc-i440fx-9.2 -cpu qemu64,+rdtscp \
|
|
-drive if=pflash,format=raw,unit=0,readonly=on,file=build/OVMF_CODE.fd \
|
|
-drive if=pflash,format=raw,unit=1,file=build/schcom-OVMF_VARS.fd \
|
|
-drive file=fat:rw:build/esp,index=0,media=disk \
|
|
-drive file=build/schfs.img,format=raw,index=1,media=disk \
|
|
-m 1G -smp 1 \
|
|
-display none -vga std \
|
|
-monitor tcp:127.0.0.1:$PORT,server,nowait \
|
|
-chardev stdio,id=c0,logfile="$LOG" -serial chardev:c0 \
|
|
>/dev/null 2>&1 &
|
|
QPID=$!
|
|
|
|
# Wait until *both* clients have committed, then photograph. Breaking on the
|
|
# first one screenshots a desktop the second client has not reached yet, which
|
|
# looks exactly like a compositor that cannot handle two clients.
|
|
for i in $(seq 1 90); do
|
|
[ "$(grep -ac 'committed' "$LOG" 2>/dev/null)" -ge 2 ] && break
|
|
sleep 1
|
|
done
|
|
sleep 2
|
|
|
|
python3 - "$PORT" "$PPM" <<'EOF'
|
|
import socket, sys, time
|
|
port, ppm = int(sys.argv[1]), sys.argv[2]
|
|
s = socket.create_connection(('127.0.0.1', port), timeout=10)
|
|
time.sleep(0.5)
|
|
s.sendall(b'screendump ' + ppm.encode() + b'\n')
|
|
time.sleep(2.0)
|
|
s.close()
|
|
EOF
|
|
|
|
sleep 1
|
|
kill -9 $QPID 2>/dev/null || true
|
|
wait 2>/dev/null || true
|
|
|
|
echo "=== guest output ==="
|
|
sed 's/\x1b\[[0-9;=]*[A-Za-z]//g' "$LOG" | tr -d '\r' | grep -aE 'schcom|surface|committed|listening' | head -12
|
|
|
|
echo "=== pixel check (host oracle) ==="
|
|
python3 - "$PPM" <<'EOF'
|
|
import sys
|
|
path = sys.argv[1]
|
|
try:
|
|
data = open(path, 'rb').read()
|
|
except FileNotFoundError:
|
|
print("FAIL: no screendump produced"); sys.exit(1)
|
|
|
|
# Binary PPM: P6, whitespace-separated width height maxval, then RGB triples.
|
|
def token(buf, i):
|
|
while buf[i:i+1].isspace(): i += 1
|
|
j = i
|
|
while not buf[j:j+1].isspace(): j += 1
|
|
return buf[i:j], j
|
|
|
|
if data[:2] != b'P6':
|
|
print("FAIL: not a P6 PPM"); sys.exit(1)
|
|
i = 2
|
|
w, i = token(data, i); h, i = token(data, i); mx, i = token(data, i)
|
|
w, h = int(w), int(h)
|
|
px = data[i+1:]
|
|
print(f" screendump {w}x{h}")
|
|
|
|
def at(x, y):
|
|
o = (y*w + x)*3
|
|
return px[o], px[o+1], px[o+2]
|
|
|
|
def near(got, want, tol=8):
|
|
return all(abs(a-b) <= tol for a, b in zip(got, want))
|
|
|
|
fails = 0
|
|
inside = at(150, 120) # middle of the 240x160 block at 100,80
|
|
if near(inside, (0xC0, 0x40, 0x20)):
|
|
print(f" [ok] surface pixel at 150,120 is {inside} -- the client's colour")
|
|
else:
|
|
print(f" FAIL: 150,120 is {inside}, expected (192, 64, 32)"); fails += 1
|
|
|
|
desktop = at(20, 20) # compositor background 0x203040
|
|
if near(desktop, (0x20, 0x30, 0x40)):
|
|
print(f" [ok] desktop pixel at 20,20 is {desktop} -- compositor background")
|
|
else:
|
|
print(f" FAIL: 20,20 is {desktop}, expected (32, 48, 64)"); fails += 1
|
|
|
|
border = at(100, 80) # top-left of the first surface
|
|
if near(border, (0xFF, 0xFF, 0xFF)):
|
|
print(f" [ok] border pixel at 100,80 is {border} -- surface landed exactly")
|
|
else:
|
|
print(f" FAIL: 100,80 is {border}, expected white border"); fails += 1
|
|
|
|
# Second client, created later, so it is above the first where they overlap.
|
|
second = at(460, 320) # inside surface 2, clear of the pointer
|
|
if near(second, (0x20, 0x60, 0xC0)):
|
|
print(f" [ok] second surface at 460,320 is {second} -- two clients composited")
|
|
else:
|
|
print(f" FAIL: 460,320 is {second}, expected (32, 96, 192)"); fails += 1
|
|
|
|
# 260,180..340,240 is where the two overlap. The later surface must win: if
|
|
# stacking were ignored the first client's colour would show through here, and
|
|
# a compositor that cannot order two windows cannot host a window manager.
|
|
overlap = at(300, 220)
|
|
if near(overlap, (0x20, 0x60, 0xC0)):
|
|
print(f" [ok] overlap at 300,220 is {overlap} -- newer surface on top")
|
|
elif near(overlap, (0xC0, 0x40, 0x20)):
|
|
print(f" FAIL: overlap shows the older surface -- stacking order ignored"); fails += 1
|
|
else:
|
|
print(f" FAIL: overlap at 300,220 is {overlap}, expected the newer surface"); fails += 1
|
|
|
|
print("schcom end-to-end: PASSED" if not fails else f"schcom end-to-end: {fails} FAILED")
|
|
sys.exit(1 if fails else 0)
|
|
EOF
|