#!/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 8 &\n'; sleep 6 printf 'schcom-demo --colour 0x00C04020 --at 100 80 --size 240 160 --wait 25\n'; sleep 14 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 the client says it committed, then photograph. for i in $(seq 1 60); do grep -aq 'committed' "$LOG" 2>/dev/null && 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 surface: white border 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 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