#!/usr/bin/env bash # Headless QEMU verify: Linux guest park + native desktop + KDE scanout. set -Eeuo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" IMG="${IMG_OVERRIDE:-$ROOT/ISO/deshab-dev.img}" LOG="${QEMU_SERIAL_LOG:-/home/deshab/qemu_serial_loop1.log}" MON="${QEMU_MONITOR:-telnet:127.0.0.1:45454,server,nowait}" TIMEOUT="${QEMU_VERIFY_TIMEOUT:-480}" SEND_KDE="${QEMU_SEND_KDE:-1}" SERIAL_TCP="${QEMU_SERIAL_TCP:-127.0.0.1:45455}" rm -f "$LOG" : > "$LOG" export IMG_OVERRIDE="$IMG" export QEMU_SERIAL_LOG="$LOG" export QEMU_DISPLAY=none export QEMU_MONITOR="$MON" export QEMU_SERIAL_TCP="$SERIAL_TCP" # Do not attach the AHCI SATA *sidecar*. The boot disk is already AHCI # (ISO/deshab-dev.img). The old sata_fat32_dsk.img hid ESP deshab.elf. export SATA_IMG_OVERRIDE="/tmp/deshab-verify-no-sata" export NVME_IMG_OVERRIDE="/tmp/deshab-verify-no-nvme" export USB_IMG_OVERRIDE="/tmp/deshab-verify-no-usb" echo "[verify] starting QEMU, log=$LOG timeout=${TIMEOUT}s serial_tcp=$SERIAL_TCP" cd "$ROOT/ISO" # Strip CRLF if the script was edited on Windows. sed -i 's/\r$//' ./run_qemu_kvm.sh 2>/dev/null || true bash ./run_qemu_kvm.sh & QEMU_PID=$! cleanup() { set +e if kill -0 "$QEMU_PID" 2>/dev/null; then kill "$QEMU_PID" 2>/dev/null || true sleep 1 kill -9 "$QEMU_PID" 2>/dev/null || true fi } trap cleanup EXIT log_text() { # Serial logs often contain CR / NUL; strip so grep anchors work. tr -d '\r\0' < "$LOG" 2>/dev/null || true } log_has() { log_text | grep -qE "$1" } send_serial() { local msg="$1" local host="${SERIAL_TCP%%:*}" local port="${SERIAL_TCP##*:}" python3 - "$host" "$port" "$msg" <<'PY' || true import socket, sys host, port, msg = sys.argv[1], int(sys.argv[2]), sys.argv[3] s = socket.create_connection((host, port), 3) s.sendall(msg.encode()) s.close() PY } sent_kde=0 dsk_started=0 plasma_painted=0 deadline=$((SECONDS + TIMEOUT)) guest_ok=0 session_ok=0 kde_ok=0 desktop_ready=0 while kill -0 "$QEMU_PID" 2>/dev/null && [[ $SECONDS -lt $deadline ]]; do if log_has 'EXEC_READY received from /dev/utsm' && \ log_has 'guest parked \(daemon ready\)'; then guest_ok=1 fi if log_has '\[DSK\] session=(kde|native)$' || \ log_has '\[DSK\] session=ask, waiting for 1/2'; then session_ok=1 fi if log_has 'desktop ready'; then desktop_ready=1 fi if log_has 'session=ask, waiting for 1/2'; then if [[ "$SEND_KDE" == "1" && "$sent_kde" == "0" ]]; then sleep 1 echo "[verify] sending serial 2 for KDE (chooser)" send_serial '2' sent_kde=1 fi fi if [[ "$desktop_ready" == "1" && "$SEND_KDE" == "1" && "$sent_kde" == "0" ]]; then echo "[verify] sending serial 2 for KDE (native desktop)" send_serial '2' sent_kde=1 deadline=$((SECONDS + 180)) fi # "Plasma failed" is sometimes dropped by kmsg. The loader error # and the chroot exit status are the ones that actually reach serial. if log_has '\[deshab-kde\] Plasma failed|invalid ELF header|error while loading shared libraries|exited with status [1-9]|fatal signal'; then kde_ok=3 elif log_has '\[deshab-kde\] starting Plasma'; then kde_ok=1 elif [[ "$kde_ok" != "1" ]] && \ log_has 'KDE preflight missing|KDE scanout timeout|KDE failed|could not bind|chroot session exited'; then kde_ok=3 elif [[ "$kde_ok" == "0" ]] && \ log_has 'serial 2: launch KDE|kde: launching fullscreen Plasma|KDE host loop|KDE spawned|\[deshab-kde\] interpreter alive|\[deshab-kde\] entering KDE'; then kde_ok=2 fi # P9: Plasma 只活着还不够,必须真的推帧。Xorg/modesetting 每次提交 # 都发 RESOURCE_FLUSH;utsm.elf 里加了 FLUSH_N 计数日志(每 64 次一行)。 # starting Plasma 之后出现 FLUSH_N 才算 Plasma 画了。 if [[ "$kde_ok" == "1" ]]; then if log_has '\[VGPU\] FLUSH_N'; then plasma_painted=1 fi fi # drm fbdev SET_SCANOUT at boot is not Plasma. "entering KDE" only # means the session reached the chroot setup, not that Plasma painted. if [[ "$kde_ok" == "1" ]] && [[ "$guest_ok" == "1" ]]; then if [[ "$sent_kde" == "1" ]]; then sleep 60 else sleep 8 fi # starting Plasma is logged before the child returns. Recheck # after the wait so a 127 during this window is not a false OK. if log_has '\[deshab-kde\] Plasma failed|invalid ELF header|error while loading shared libraries|exited with status [1-9]|fatal signal'; then kde_ok=3 elif log_has '\[VGPU\] FLUSH_N'; then plasma_painted=1 break else break fi fi if [[ "$guest_ok" == "1" && "$session_ok" == "1" && "$kde_ok" == "3" ]]; then sleep 20 break fi if [[ "$dsk_started" == "0" ]] && log_has '\[DSK\] FUCK config loaded'; then dsk_started=1 deadline=$((SECONDS + 240)) echo "[verify] DSK started, extending wait 240s" fi sleep 2 echo "[verify] t=${SECONDS}s log=$(wc -c < "$LOG" 2>/dev/null || echo 0) guest=$guest_ok session=$session_ok desktop=$desktop_ready kde=$kde_ok" done echo "===== VERIFY SUMMARY =====" echo "guest_ok=$guest_ok session_ok=$session_ok kde_ok=$kde_ok sent_kde=$sent_kde desktop_ready=$desktop_ready plasma_painted=$plasma_painted" echo "----- markers -----" log_text | grep -E 'EXEC_READY|guest parked|session=ask|session=kde|session=native|KDE |Linux guest|FUCK config loaded|switch_root|utsm-linux|desktop ready|serial 2|FAT32 volume LBA|desktop load|user/desktop|VGPU|scanout|FLUSH|deshab-kde|vdc|persist|segfault|fatal signal|Xorg.0.log|Fatal server|overlay' | tail -n 120 || true if [[ "$guest_ok" != "1" || "$session_ok" != "1" ]]; then echo VERIFY_FAIL exit 1 fi if [[ "$kde_ok" == "1" ]]; then if [[ "$plasma_painted" == "1" ]]; then echo VERIFY_KDE_PLASMA_OK exit 0 else echo VERIFY_KDE_ALIVE_NO_FRAME exit 1 fi fi if [[ "$kde_ok" == "2" ]]; then echo VERIFY_KDE_SPAWNED exit 1 fi echo VERIFY_PARTIAL_OK exit 1