SchOS/tools/posix/schcom_test.sh
Nexoniarz ed77244df0 boot/uefi: the system information panel, and the offset that faked a version
ui_panel was written for this screen during the loader restyle and then
never called -- dead code sitting in boot/common/ui.c waiting for the row
that would use it. This is that row: F8 -> System Information, showing
firmware vendor and revision, UEFI revision, CPU brand, usable memory,
current mode and whether ACPI was found.

Everything is read when the screen is drawn rather than captured at
startup, because the resolution row above it can have changed the mode,
and a panel still reporting what the machine booted with is worse than no
panel at all.

Two facts are deliberately read through the same code that feeds the
kernel -- efi_type_to_e820() for what counts as usable memory, and
find_rsdp() for ACPI. A panel with its own idea of either would be a
second implementation to keep in step, and the ACPI line exists precisely
because a missing RSDP means no MADT, no IOAPIC and no SMP, which has
already been debugged once from the far end.

The screenshot caught a bug in the screen it was taken to verify: UEFI
revision read "21587.2286". EFI_TABLE_HEADER begins with a UINT64
Signature, so Revision is at offset 8 and I had read offset 4 -- the
upper half of "IBI SYST", formatted as a version number. It now reads 2.7
under OVMF, which is correct.

Verified by driving the menu over the QEMU monitor and reading the
pixels: five rows with the hint updated to 1-5, the panel centred, and
every value cross-checkable -- EDK II, firmware 1.0, UEFI 2.7, "QEMU
Virtual CPU version 2.5+" against -cpu qemu64, 1018 MB of the 1 GB given,
1280x800 matching what fbshot captures, ACPI present. The highlight sits
9 px inside both panel borders, symmetric.

F8 has a 1.5 s window, so the capture presses it repeatedly through boot
rather than guessing when OVMF finishes -- and only F8, since any other
key breaks the wait and boots.

Also: every ESP drive now says format=raw. QEMU warned about probing the
format on each of the fifteen or so places that attach it, which is new
noise on what is now the only boot path. 110 [ok], no failures, no
warnings.
2026-08-11 10:52:43 +02:00

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,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