SchOS/tools/posix/schcom_test.sh
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

154 lines
5.7 KiB
Shell

#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-or-later
#
# 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,format=raw \
-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