SchOS/tools/posix/schcom_test.sh
Nexoniarz 181c420f21 schcom: prove two clients and stacking order
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>
2026-08-10 04:19:09 +02:00

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