|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +set -Eeuo pipefail |
| 4 | + |
| 5 | +usage() { |
| 6 | + cat <<'EOF' |
| 7 | +Usage: ci/qemu_linux_tests.sh [options] [-- V arguments] |
| 8 | +
|
| 9 | +Sync the current checkout to an existing Debian ARM64 QEMU guest, rebuild V, |
| 10 | +and run the Linux test suite. With no V arguments, the script runs `test-all`. |
| 11 | +
|
| 12 | +Options: |
| 13 | + --no-sync Use the checkout already present in the guest. |
| 14 | + --provision Install the packages used by the Linux compiler tests. |
| 15 | + -h, --help Show this help. |
| 16 | +
|
| 17 | +Environment overrides: |
| 18 | + V_QEMU_VM_DIR VM state directory (default: ~/.local/share/v3-qemu-debian13) |
| 19 | + V_QEMU_SSH_PORT Forwarded SSH port (default: 2222) |
| 20 | + V_QEMU_GUEST SSH target (default: v@127.0.0.1) |
| 21 | + V_QEMU_GUEST_REPO Guest checkout (default: /home/v/v3) |
| 22 | + V_QEMU_CPUS Virtual CPUs (default: 4) |
| 23 | + V_QEMU_MEMORY_MB Guest memory in MiB (default: 16384) |
| 24 | + V_QEMU_JOBS V test jobs (default: virtual CPU count) |
| 25 | + V_QEMU_VFLAGS Flags inherited by V subprocesses (default: -cc clang) |
| 26 | + V_QEMU_NO_FALLBACK Set V_MACOS_V3_NO_FALLBACK (default: 1) |
| 27 | + V_QEMU_STOP_AFTER Power off the guest after the run when set to 1 |
| 28 | + V_QEMU_FIRMWARE AArch64 UEFI firmware path |
| 29 | +
|
| 30 | +Examples: |
| 31 | + ci/qemu_linux_tests.sh |
| 32 | + ci/qemu_linux_tests.sh -- -cc gcc test vlib/v3/ |
| 33 | + V_QEMU_NO_FALLBACK=0 ci/qemu_linux_tests.sh -- -old-compiler test-all |
| 34 | +EOF |
| 35 | +} |
| 36 | + |
| 37 | +provision=0 |
| 38 | +sync_checkout=1 |
| 39 | +while (($# > 0)); do |
| 40 | + case "$1" in |
| 41 | + --no-sync) |
| 42 | + sync_checkout=0 |
| 43 | + shift |
| 44 | + ;; |
| 45 | + --provision) |
| 46 | + provision=1 |
| 47 | + shift |
| 48 | + ;; |
| 49 | + -h | --help) |
| 50 | + usage |
| 51 | + exit 0 |
| 52 | + ;; |
| 53 | + --) |
| 54 | + shift |
| 55 | + break |
| 56 | + ;; |
| 57 | + *) |
| 58 | + break |
| 59 | + ;; |
| 60 | + esac |
| 61 | +done |
| 62 | + |
| 63 | +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) |
| 64 | +vm_dir=${V_QEMU_VM_DIR:-"${HOME}/.local/share/v3-qemu-debian13"} |
| 65 | +ssh_port=${V_QEMU_SSH_PORT:-2222} |
| 66 | +guest=${V_QEMU_GUEST:-v@127.0.0.1} |
| 67 | +guest_repo=${V_QEMU_GUEST_REPO:-/home/v/v3} |
| 68 | +cpus=${V_QEMU_CPUS:-4} |
| 69 | +memory_mb=${V_QEMU_MEMORY_MB:-16384} |
| 70 | +jobs=${V_QEMU_JOBS:-$cpus} |
| 71 | +vflags=${V_QEMU_VFLAGS:--cc clang} |
| 72 | +no_fallback=${V_QEMU_NO_FALLBACK:-1} |
| 73 | +stop_after=${V_QEMU_STOP_AFTER:-0} |
| 74 | + |
| 75 | +qemu_bin=${V_QEMU_BIN:-$(command -v qemu-system-aarch64 || true)} |
| 76 | +rsync_bin=${V_QEMU_RSYNC:-$(command -v rsync || true)} |
| 77 | +if [[ -z "$qemu_bin" ]]; then |
| 78 | + echo 'qemu-system-aarch64 is required (on macOS: brew install qemu).' >&2 |
| 79 | + exit 1 |
| 80 | +fi |
| 81 | +if [[ -z "$rsync_bin" ]]; then |
| 82 | + echo 'rsync is required.' >&2 |
| 83 | + exit 1 |
| 84 | +fi |
| 85 | + |
| 86 | +key_file="${vm_dir}/id_ed25519" |
| 87 | +known_hosts_file="${vm_dir}/known_hosts" |
| 88 | +pid_file="${vm_dir}/qemu.pid" |
| 89 | +disk_file="${vm_dir}/disk.qcow2" |
| 90 | +vars_file="${vm_dir}/edk2-vars.fd" |
| 91 | +seed_file="${vm_dir}/seed.iso" |
| 92 | +serial_log="${vm_dir}/serial.log" |
| 93 | +qemu_log="${vm_dir}/qemu.log" |
| 94 | + |
| 95 | +for required_file in "$key_file" "$known_hosts_file" "$disk_file" "$vars_file" "$seed_file"; do |
| 96 | + if [[ ! -f "$required_file" ]]; then |
| 97 | + echo "Missing VM asset: ${required_file}" >&2 |
| 98 | + echo 'Create the Debian cloud guest first or set V_QEMU_VM_DIR.' >&2 |
| 99 | + exit 1 |
| 100 | + fi |
| 101 | +done |
| 102 | + |
| 103 | +firmware=${V_QEMU_FIRMWARE:-} |
| 104 | +if [[ -z "$firmware" ]]; then |
| 105 | + qemu_prefix=$(CDPATH= cd -- "$(dirname -- "$qemu_bin")/.." && pwd) |
| 106 | + firmware="${qemu_prefix}/share/qemu/edk2-aarch64-code.fd" |
| 107 | +fi |
| 108 | +if [[ ! -f "$firmware" ]]; then |
| 109 | + echo "Missing AArch64 UEFI firmware: ${firmware}" >&2 |
| 110 | + echo 'Set V_QEMU_FIRMWARE to the edk2 AArch64 code image.' >&2 |
| 111 | + exit 1 |
| 112 | +fi |
| 113 | + |
| 114 | +ssh_options=( |
| 115 | + -i "$key_file" |
| 116 | + -p "$ssh_port" |
| 117 | + -o "UserKnownHostsFile=${known_hosts_file}" |
| 118 | + -o StrictHostKeyChecking=yes |
| 119 | + -o ConnectTimeout=5 |
| 120 | +) |
| 121 | + |
| 122 | +vm_is_running() { |
| 123 | + [[ -s "$pid_file" ]] && kill -0 "$(<"$pid_file")" 2>/dev/null |
| 124 | +} |
| 125 | + |
| 126 | +start_vm() { |
| 127 | + if vm_is_running; then |
| 128 | + return |
| 129 | + fi |
| 130 | + if [[ -f "$pid_file" ]]; then |
| 131 | + rm -f -- "$pid_file" |
| 132 | + fi |
| 133 | + local accelerator=tcg |
| 134 | + local cpu=max |
| 135 | + if [[ $(uname -s) == Darwin ]]; then |
| 136 | + accelerator=hvf |
| 137 | + cpu=host |
| 138 | + fi |
| 139 | + "$qemu_bin" \ |
| 140 | + -machine "virt,accel=${accelerator},highmem=on" \ |
| 141 | + -cpu "$cpu" \ |
| 142 | + -smp "$cpus" \ |
| 143 | + -m "$memory_mb" \ |
| 144 | + -drive "if=pflash,format=raw,readonly=on,file=${firmware}" \ |
| 145 | + -drive "if=pflash,format=raw,file=${vars_file}" \ |
| 146 | + -drive "if=virtio,file=${disk_file},format=qcow2" \ |
| 147 | + -drive "if=virtio,file=${seed_file},format=raw,readonly=on" \ |
| 148 | + -device virtio-net-pci,netdev=net0 \ |
| 149 | + -netdev "user,id=net0,hostfwd=tcp:127.0.0.1:${ssh_port}-:22" \ |
| 150 | + -display none \ |
| 151 | + -serial "file:${serial_log}" \ |
| 152 | + -monitor none \ |
| 153 | + -daemonize \ |
| 154 | + -pidfile "$pid_file" \ |
| 155 | + -D "$qemu_log" |
| 156 | +} |
| 157 | + |
| 158 | +wait_for_ssh() { |
| 159 | + for _ in {1..120}; do |
| 160 | + if ssh "${ssh_options[@]}" "$guest" true 2>/dev/null; then |
| 161 | + return |
| 162 | + fi |
| 163 | + sleep 1 |
| 164 | + done |
| 165 | + echo "Guest SSH did not become ready on port ${ssh_port}." >&2 |
| 166 | + exit 1 |
| 167 | +} |
| 168 | + |
| 169 | +power_off() { |
| 170 | + if [[ "$stop_after" == 1 ]] && vm_is_running; then |
| 171 | + ssh "${ssh_options[@]}" "$guest" 'sudo poweroff' >/dev/null 2>&1 || true |
| 172 | + fi |
| 173 | +} |
| 174 | +trap power_off EXIT |
| 175 | + |
| 176 | +start_vm |
| 177 | +wait_for_ssh |
| 178 | + |
| 179 | +host_head=$(git -C "$repo_root" rev-parse HEAD) |
| 180 | +guest_head=$(ssh "${ssh_options[@]}" "$guest" "git -C '${guest_repo}' rev-parse HEAD") |
| 181 | +if [[ "$host_head" != "$guest_head" ]]; then |
| 182 | + echo "Host and guest baselines differ (${host_head} != ${guest_head})." >&2 |
| 183 | + echo "Update ${guest_repo} in the guest before syncing local changes." >&2 |
| 184 | + exit 1 |
| 185 | +fi |
| 186 | + |
| 187 | +if ((sync_checkout)); then |
| 188 | + git -C "$repo_root" ls-files -z --cached --others --exclude-standard | "$rsync_bin" -az \ |
| 189 | + --from0 \ |
| 190 | + --files-from=- \ |
| 191 | + --exclude '.detect_tcc*' \ |
| 192 | + -e "ssh -i ${key_file} -p ${ssh_port} -o UserKnownHostsFile=${known_hosts_file} -o StrictHostKeyChecking=yes" \ |
| 193 | + "${repo_root}/" "${guest}:${guest_repo}/" |
| 194 | +fi |
| 195 | + |
| 196 | +if ((provision)); then |
| 197 | + ssh "${ssh_options[@]}" "$guest" "sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential clang git lld rsync pkg-config libssl-dev sqlite3 libsqlite3-dev valgrind libfreetype6-dev libxi-dev libxcursor-dev libgl-dev libxrandr-dev libasound2-dev libegl-dev libwayland-dev libxkbcommon-dev libwayland-egl1 libxkbcommon-x11-dev wayland-protocols libx11-dev libgl1-mesa-dri xauth xvfb" |
| 198 | +fi |
| 199 | + |
| 200 | +if (($# == 0)); then |
| 201 | + set -- test-all |
| 202 | +fi |
| 203 | + |
| 204 | +printf -v guest_repo_q '%q' "$guest_repo" |
| 205 | +printf -v jobs_q '%q' "$jobs" |
| 206 | +printf -v vflags_q '%q' "$vflags" |
| 207 | +printf -v no_fallback_q '%q' "$no_fallback" |
| 208 | +printf -v test_command '%q ' ./vnew "$@" |
| 209 | + |
| 210 | +remote_command="cd ${guest_repo_q}" |
| 211 | +remote_command+=" && export PATH=${guest_repo_q}:\$PATH VFLAGS=${vflags_q}" |
| 212 | +remote_command+=" && if [ ! -f thirdparty/tcc/lib/libgc.a ]; then make; fi" |
| 213 | +remote_command+=" && V_C_ERROR_BUG_REPORT_DISABLED=1 ./v -old-compiler -o ./vnew cmd/v" |
| 214 | +if ((provision)); then |
| 215 | + remote_command+=" && ./vnew retry -- ./vnew install markdown" |
| 216 | + remote_command+=" && if [ ! -f thirdparty/sqlite/sqlite3.c ]; then ./vnew -old-compiler run vlib/db/sqlite/install_thirdparty_sqlite.vsh; fi" |
| 217 | +fi |
| 218 | +remote_command+=" && ./vnew wipe-cache" |
| 219 | +remote_command+=" && VJOBS=${jobs_q} V_C_ERROR_BUG_REPORT_DISABLED=1" |
| 220 | +remote_command+=" V_MACOS_V3_NO_FALLBACK=${no_fallback_q} ${test_command}" |
| 221 | +ssh "${ssh_options[@]}" "$guest" "$remote_command" |
0 commit comments