The compositor already walked a surface table, but nothing had ever run
two clients at once, so "it composites" rested on a single rectangle.
schcom-test now starts two overlapping clients and checks the overlap
region from the host: the later surface has to win there. A compositor
that cannot order two surfaces cannot host a window manager, and that is
the property SchWM will be built on.
Two bugs in the test itself, both of which would have read as compositor
faults:
- it waited for the first "committed" line and screenshotted then, so
the second client had not drawn yet. It waits for both now.
- one probe point sat at 400,300 -- dead centre of an 800x600 screen,
which is exactly where the pointer is drawn. Moved clear of it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
138 lines
4.8 KiB
Shell
138 lines
4.8 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
|
|
BUILD=${BUILD:-build}
|
|
mkdir -p "$BUILD"
|
|
|
|
LOG=$BUILD/schcom-test.log
|
|
PPM=${1:-$BUILD/schcom.ppm}
|
|
rm -f "$LOG" "$PPM"
|
|
|
|
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 \
|
|
-hda schos.img -hdb build/schfs.img -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
|