diff options
Diffstat (limited to '')
| -rw-r--r-- | scripts/lib.sh | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100644 index 0000000..d5aecac --- /dev/null +++ b/scripts/lib.sh | |||
| @@ -0,0 +1,168 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | # | ||
| 3 | # scripts/lib.sh — shared config and helpers for the rs-dos scripts. | ||
| 4 | # | ||
| 5 | # Sourced, never executed. Everything here is the single definition of | ||
| 6 | # "how we build and how we boot": the build script, the image assembler, | ||
| 7 | # the QEMU runner and the tests all read their settings from this file. | ||
| 8 | # | ||
| 9 | # Config is environment-overridable (TARGET, PROFILE, SYSTEM_IMG, OVMF, | ||
| 10 | # QEMU, MEM, SMP) so callers and CI can tune it without editing scripts. | ||
| 11 | set -euo pipefail | ||
| 12 | |||
| 13 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 14 | PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" | ||
| 15 | TESTS_DIR="$SCRIPT_DIR/tests" | ||
| 16 | |||
| 17 | # ---- config --------------------------------------------------------------- | ||
| 18 | TARGET="${TARGET:-x86_64-unknown-uefi}" | ||
| 19 | PROFILE="${PROFILE:-release}" | ||
| 20 | OUT_DIR="${OUT_DIR:-$PROJECT_DIR/target/$TARGET/$PROFILE}" | ||
| 21 | KERNEL="$OUT_DIR/rsdos.efi" | ||
| 22 | SYSTEM_IMG="${SYSTEM_IMG:-/tmp/system.img}" | ||
| 23 | OVMF="${OVMF:-/usr/share/edk2/x64/OVMF.4m.fd}" | ||
| 24 | QEMU="${QEMU:-qemu-system-x86_64}" | ||
| 25 | MEM="${MEM:-256M}" | ||
| 26 | SMP="${SMP:-1}" | ||
| 27 | |||
| 28 | # Binaries whose launch surface is a window rather than a terminal | ||
| 29 | # (ADR 0006): staged into /bin/gui, everything else into /bin/cli. | ||
| 30 | GUI_BINS="paint" | ||
| 31 | |||
| 32 | cd "$PROJECT_DIR" | ||
| 33 | |||
| 34 | # ---- build ---------------------------------------------------------------- | ||
| 35 | # | ||
| 36 | # The one and only cargo invocation in the repo. Builds the kernel and | ||
| 37 | # every external binary in a single pass so the image can stage both. | ||
| 38 | |||
| 39 | cargo_flags() { | ||
| 40 | [ "$PROFILE" = "release" ] && printf '%s' "--release" | ||
| 41 | } | ||
| 42 | |||
| 43 | build_all() { | ||
| 44 | echo "=== build (kernel + bins, $PROFILE) ===" | ||
| 45 | # shellcheck disable=SC2046 # cargo_flags is a flag word, intentionally split | ||
| 46 | cargo build --workspace --target "$TARGET" $(cargo_flags) | ||
| 47 | } | ||
| 48 | |||
| 49 | # ---- system image --------------------------------------------------------- | ||
| 50 | |||
| 51 | # stage_image [mksystem args...] — assemble /tmp/system.img if it is stale. | ||
| 52 | # Freshness is decided inside mksystem.sh; an up-to-date image is never | ||
| 53 | # touched, so files written at runtime survive. | ||
| 54 | stage_image() { | ||
| 55 | "$SCRIPT_DIR/mksystem.sh" "$@" | ||
| 56 | } | ||
| 57 | |||
| 58 | # ---- QEMU ----------------------------------------------------------------- | ||
| 59 | |||
| 60 | # qemu_args — the common machine every boot shares: OVMF, the system | ||
| 61 | # image as the single SATA drive, 256M q35. Callers append display, | ||
| 62 | # monitor and serial choices. | ||
| 63 | qemu_args() { | ||
| 64 | printf '%s\n' \ | ||
| 65 | -bios "$OVMF" \ | ||
| 66 | -drive "file=$SYSTEM_IMG,format=raw,if=none,id=disk" \ | ||
| 67 | -device ide-hd,drive=disk,bus=ide.0 \ | ||
| 68 | -m "$MEM" \ | ||
| 69 | -machine q35 \ | ||
| 70 | -cpu qemu64 \ | ||
| 71 | -no-reboot \ | ||
| 72 | -smp "$SMP" | ||
| 73 | } | ||
| 74 | |||
| 75 | # ---- test harness --------------------------------------------------------- | ||
| 76 | # | ||
| 77 | # Every test-*.sh does: | ||
| 78 | # TNAME=disk; source lib.sh; test_boot # waits for the shell | ||
| 79 | # sendkeys "ls /disk" "poweroff" | ||
| 80 | # wait_qemu_exit | ||
| 81 | # Runtime files are namespaced per test so parallel runs cannot collide. | ||
| 82 | |||
| 83 | TNAME="${TNAME:-rsdos}" | ||
| 84 | QPID="/tmp/rsdos-$TNAME-qpid" | ||
| 85 | SOCK="/tmp/rsdos-$TNAME-qmon.sock" | ||
| 86 | LOG="/tmp/rsdos-$TNAME-serial.log" | ||
| 87 | QLOG="/tmp/rsdos-$TNAME-qemu.log" | ||
| 88 | |||
| 89 | fail() { | ||
| 90 | echo "FAIL: $1" | ||
| 91 | [ -f "$LOG" ] && tail -40 "$LOG" | ||
| 92 | exit 1 | ||
| 93 | } | ||
| 94 | |||
| 95 | # wait_log FILE PATTERN TIMEOUT-SECONDS — poll a file for a regex. | ||
| 96 | wait_log() { | ||
| 97 | local i=0 | ||
| 98 | while [ "$i" -lt $((${3:-60} * 10)) ]; do | ||
| 99 | grep -q "$2" "$1" 2>/dev/null && return 0 | ||
| 100 | sleep 0.1 | ||
| 101 | i=$((i + 1)) | ||
| 102 | done | ||
| 103 | return 1 | ||
| 104 | } | ||
| 105 | |||
| 106 | # wait_qemu_exit TIMEOUT-SECONDS — poll until the pid is gone. | ||
| 107 | wait_qemu_exit() { | ||
| 108 | local i=0 | ||
| 109 | while [ "$i" -lt $((${1:-30} * 10)) ]; do | ||
| 110 | kill -0 "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || return 0 | ||
| 111 | sleep 0.1 | ||
| 112 | i=$((i + 1)) | ||
| 113 | done | ||
| 114 | return 1 | ||
| 115 | } | ||
| 116 | |||
| 117 | # qemu_hmp CMD — run one HMP command on the monitor socket. | ||
| 118 | qemu_hmp() { | ||
| 119 | python3 - "$SOCK" "$1" <<'EOF' | ||
| 120 | import socket, sys, time | ||
| 121 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
| 122 | s.connect(sys.argv[1]) | ||
| 123 | s.sendall((sys.argv[2] + "\n").encode()) | ||
| 124 | time.sleep(0.4) | ||
| 125 | EOF | ||
| 126 | } | ||
| 127 | |||
| 128 | # sendkeys "line" ... — type shell commands into the guest via sendkeys.py. | ||
| 129 | sendkeys() { | ||
| 130 | python3 "$TESTS_DIR/sendkeys.py" --sock "$SOCK" "$@" | ||
| 131 | } | ||
| 132 | |||
| 133 | # test_cleanup — reap a leftover QEMU from a previous run of this test | ||
| 134 | # and clear this test's runtime files. Called before test_boot. | ||
| 135 | test_cleanup() { | ||
| 136 | kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true | ||
| 137 | rm -f "$SOCK" "$LOG" | ||
| 138 | } | ||
| 139 | |||
| 140 | # test_on_exit — EXIT trap: never leave a QEMU behind, but keep the logs | ||
| 141 | # (they are the post-mortem for a failed test). | ||
| 142 | test_on_exit() { | ||
| 143 | kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true | ||
| 144 | } | ||
| 145 | |||
| 146 | # test_boot [extra-qemu-args...] — boot the system image headless with the | ||
| 147 | # serial log at $LOG and a monitor socket at $SOCK, then wait for the | ||
| 148 | # shell. Extra args attach more drives (e.g. a CRASHLOG stick). | ||
| 149 | test_boot() { | ||
| 150 | test_cleanup | ||
| 151 | trap test_on_exit EXIT | ||
| 152 | # A stale/poisoned OVMF varstore makes the firmware skip the | ||
| 153 | # \EFI\BOOT fallback entirely — every test boots from a fresh copy. | ||
| 154 | # Namespaced per test so parallel runs cannot race on it. | ||
| 155 | local nv="/tmp/rsdos-ovmf-vars-$TNAME.fd" | ||
| 156 | rm -f "$nv" | ||
| 157 | cp "$OVMF" "$nv" | ||
| 158 | local -a qargs | ||
| 159 | mapfile -t qargs < <(qemu_args) | ||
| 160 | "$QEMU" -bios "$nv" \ | ||
| 161 | "${qargs[@]}" \ | ||
| 162 | -serial "file:$LOG" \ | ||
| 163 | -monitor "unix:$SOCK,server,nowait" \ | ||
| 164 | -display none \ | ||
| 165 | "$@" >"$QLOG" 2>&1 & | ||
| 166 | echo $! >"$QPID" | ||
| 167 | wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" | ||
| 168 | } | ||
