2
0

test-desktop.sh 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. #!/usr/bin/env bash
  2. # Boots the mjdev live ISO in QEMU (headless, KVM) and verifies the wayland desktop
  3. # actually starts: captures the session log to ./log.txt and screenshots to /tmp/iso-test,
  4. # then prints PASS/FAIL by inspecting the log. No ISO rebuild — tests an existing ISO.
  5. #
  6. # .claude/skills/mjdev-qemu-test/test-desktop.sh [ISO] [--shots]
  7. # ISO path to the live iso (default: releases/<app>-<ver>.iso)
  8. # --shots also capture screenshots via QMP (needs qmp_drive.py alongside)
  9. #
  10. # What it checks in ~/.cache/mjdev-desktop/session.log:
  11. # PASS -> "shell started" present AND no "RenderException"/"cannot open shared object"
  12. # FAIL -> prints the first error line (missing lib / renderer / Skiko GL context / ...)
  13. set -euo pipefail
  14. HERE="$(cd "$(dirname "$0")" && pwd)"
  15. ROOT="$(cd "$HERE/../../.." && pwd)" # project root (.claude/skills/<name>/ -> root)
  16. CATALOG="$ROOT/gradle/libs.versions.toml"
  17. rc(){ sed -n "s/^$1 *= *\"\(.*\)\"/\1/p" "$CATALOG"; }
  18. APP="$(rc app-name)"; VER="$(rc app-pkg-version)"
  19. ISO=""; SHOTS=0
  20. for a in "$@"; do case "$a" in --shots) SHOTS=1;; *) ISO="$a";; esac; done
  21. ISO="${ISO:-$ROOT/releases/$APP-$VER.iso}"
  22. [ -f "$ISO" ] || { echo "iso not found: $ISO (build with ./gradlew makeIso)"; exit 1; }
  23. WORK=/tmp/iso-test; mkdir -p "$WORK"
  24. command -v qemu-system-x86_64 >/dev/null || { echo "need qemu-system-x86_64"; exit 1; }
  25. echo ">> extracting kernel/initrd from $(basename "$ISO")"
  26. xorriso -osirrox on -indev "$ISO" \
  27. -extract /live/vmlinuz "$WORK/vmlinuz" -extract /live/initrd.img "$WORK/initrd.img" 2>/dev/null
  28. KVM=(); [ -w /dev/kvm ] && KVM=(-enable-kvm -cpu host)
  29. # --- phase 1: serial login -> dump session.log to project-root log.txt ---
  30. echo ">> booting (serial) to capture session.log ..."
  31. {
  32. sleep 30; printf '\n\nmjdev\n'; sleep 4
  33. printf 'echo ===LOGSTART===; cat ~/.cache/mjdev-desktop/session.log.1 2>/dev/null; echo "[--- current ---]"; cat ~/.cache/mjdev-desktop/session.log 2>/dev/null; echo ===LOGEND===\n'
  34. sleep 7
  35. } | timeout 80 qemu-system-x86_64 "${KVM[@]}" -m 4096 -smp 4 \
  36. -kernel "$WORK/vmlinuz" -initrd "$WORK/initrd.img" -append "boot=live console=ttyS0,115200" \
  37. -cdrom "$ISO" -nographic -serial mon:stdio > "$WORK/serial.raw" 2>&1 || true
  38. tr -d '\r' < "$WORK/serial.raw" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' \
  39. | sed -n '/===LOGSTART===/,/===LOGEND===/p' > "$ROOT/log.txt"
  40. # --- phase 2 (optional): screenshots via QMP ---
  41. if [ "$SHOTS" = 1 ] && [ -f "$HERE/qmp_drive.py" ]; then
  42. echo ">> booting (framebuffer) for screenshots ..."
  43. rm -f "$WORK/qmp.sock"
  44. qemu-system-x86_64 "${KVM[@]}" -m 4096 -smp 4 \
  45. -kernel "$WORK/vmlinuz" -initrd "$WORK/initrd.img" -append "boot=live console=ttyS0,115200" \
  46. -cdrom "$ISO" -device virtio-vga -usb -device usb-tablet \
  47. -qmp "unix:$WORK/qmp.sock,server,nowait" -display none > "$WORK/qemu-shots.log" 2>&1 &
  48. python3 "$HERE/qmp_drive.py" "$WORK/qmp.sock" || true
  49. fi
  50. # --- verdict ---
  51. echo "================ session.log -> log.txt ================"
  52. sed -n '1,40p' "$ROOT/log.txt"
  53. echo "========================================================"
  54. if grep -q "shell started" "$ROOT/log.txt" \
  55. && ! grep -qiE "RenderException|cannot open shared object|failed to create wlr" "$ROOT/log.txt"; then
  56. echo "RESULT: PASS — compositor + shell started, no fatal render/link error"
  57. else
  58. echo "RESULT: FAIL — first error:"
  59. grep -m1 -iE "RenderException|cannot open shared object|failed to create|EGL_BAD|error" "$ROOT/log.txt" || echo " (no obvious error line; inspect log.txt)"
  60. fi
  61. [ "$SHOTS" = 1 ] && ls -la "$WORK"/*.png 2>/dev/null || true