#!/usr/bin/env bash # Offline regression test for run.sh's own argv construction: no real QEMU, # no kernel image. A fake `qemu-system-arm` placed first on PATH dumps the # argv it was handed (one token per line) and exits, so this pins the exact # contract run.sh:121-138 and rootfs/sbin/init:54,161 share without either # side moving: init decides ttyS0 vs ttyS1 for the Modbus alias purely by # grepping warden.ctl off /proc/cmdline, so run.sh has to keep two promises # every single invocation -- the ctl pci-serial device, when present, comes # BEFORE the rs485 one in argv (virt's PCI bus enumerates in that order), # and an rs485 pci-serial device (real or null-backed) is always there so # the port count init relies on never shifts. # # What is worth pinning: nothing else exercises this. test-ui-drive-rs485.sh # stubs run.sh out entirely (a fake VM), and the only real boot in CI # (boot-smoke.sh) passes neither --ctl nor --rs485, so a swapped # `[ -n "$CTL" ]`/`[ -n "$RS485" ]` block, or a dropped null-chardev # fallback, would reach a panel as Modbus polls landing on the debug channel # (run.sh:131-133's own incident) before anything here caught it. # # bash run-sh-args-test.sh set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUN_SH="$HERE/../run.sh" SCRATCH="$(mktemp -d /tmp/runshargs.XXXXXX)" trap 'rm -rf "$SCRATCH"' EXIT FAIL=0 pass() { printf '[PASS] %s\n' "$1"; } fail() { printf '[FAIL] %s\n' "$1"; FAIL=1; } KERNEL="$SCRATCH/fake-zImage" INITRD="$SCRATCH/fake-initramfs.cpio.gz" : > "$KERNEL" : > "$INITRD" BIN="$SCRATCH/bin" mkdir -p "$BIN" cat > "$BIN/qemu-system-arm" <<'STUB' #!/usr/bin/env bash # Stand-in for the real binary: record argv, one token per line, and exit # straight away. $ARGV_CAPTURE names where -- run.sh always `exec`s this as # its very last step, so nothing downstream of it ever runs. printf '%s\n' "$@" > "$ARGV_CAPTURE" STUB chmod +x "$BIN/qemu-system-arm" # run_case ARGV_FILE EXTRA_ARGS...: invoke the real run.sh --no-disk (so # nothing under the real qemu/out/ is ever touched) with the fake binary # first on PATH, capturing its argv into ARGV_FILE. Fails the case loudly if # run.sh itself exits nonzero -- a silent empty capture would otherwise look # just like "the assertions below simply found nothing". run_case() { local argv_file="$1"; shift local out rc out="$(cd "$SCRATCH" && PATH="$BIN:$PATH" ARGV_CAPTURE="$argv_file" \ bash "$RUN_SH" --kernel "$KERNEL" --initrd "$INITRD" --no-disk "$@" 2>&1)" rc=$? [ "$rc" -eq 0 ] || { fail "run.sh exited $rc for: $* -- output: $out"; return 1; } [ -s "$argv_file" ] || { fail "run.sh produced no captured argv for: $*"; return 1; } return 0 } # chardev_line ARGV_FILE PREFIX: 1-indexed line number of the first argv # token starting with PREFIX (the socket/null chardev spec, which always # immediately follows the "-chardev" token it belongs to), or empty. chardev_line() { grep -n -m1 "^$2" "$1" | cut -d: -f1; } # --- case: neither --ctl nor --rs485 -> null-backed rs485, no ctl device --- argv="$SCRATCH/argv-neither.txt" if run_case "$argv"; then if ! grep -qF 'id=ctl' "$argv"; then pass "neither flag: no ctl chardev/device at all" else fail "neither flag: a ctl chardev/device appeared unrequested" fi if grep -qF -- '-append' "$argv" && ! grep -qw 'warden.ctl' "$argv"; then pass "neither flag: -append omits warden.ctl" else fail "neither flag: -append should omit warden.ctl" fi if [ -n "$(chardev_line "$argv" 'null,id=rs485')" ] \ && grep -qF 'pci-serial,chardev=rs485' "$argv"; then pass "neither flag: null-backed rs485 pci-serial device is still present" else fail "neither flag: expected a null-backed rs485 device (port count must not shift)" fi fi # --- case: --rs485 alone -> real rs485 device, still no ctl device --------- argv="$SCRATCH/argv-rs485-only.txt" if run_case "$argv" --rs485 "$SCRATCH/rs.sock"; then if ! grep -qF 'id=ctl' "$argv"; then pass "rs485 only: no ctl chardev/device" else fail "rs485 only: a ctl chardev/device appeared unrequested" fi if ! grep -qw 'warden.ctl' "$argv"; then pass "rs485 only: -append omits warden.ctl" else fail "rs485 only: -append should omit warden.ctl" fi if [ -n "$(chardev_line "$argv" "socket,id=rs485,path=$SCRATCH/rs.sock,")" ]; then pass "rs485 only: rs485 chardev carries the requested socket path" else fail "rs485 only: rs485 chardev did not carry the requested socket path" fi fi # --- case: --ctl alone -> ctl device first, null-backed rs485 still present, # and warden.ctl on the cmdline ------------------------------------------ argv="$SCRATCH/argv-ctl-only.txt" if run_case "$argv" --ctl "$SCRATCH/ctl.sock"; then ctl_ln="$(chardev_line "$argv" "socket,id=ctl,path=$SCRATCH/ctl.sock,")" rs_ln="$(chardev_line "$argv" 'null,id=rs485')" if [ -n "$ctl_ln" ] && [ -n "$rs_ln" ] && [ "$ctl_ln" -lt "$rs_ln" ]; then pass "ctl only: ctl chardev (line $ctl_ln) precedes the null rs485 chardev (line $rs_ln)" else fail "ctl only: expected ctl chardev before a null-backed rs485 chardev, got ctl=$ctl_ln rs485=$rs_ln" fi if grep -qw 'warden.ctl' "$argv"; then pass "ctl only: -append carries warden.ctl" else fail "ctl only: -append should carry warden.ctl" fi fi # --- case: --ctl and --rs485 together -> ctl device still enumerates first - argv="$SCRATCH/argv-both.txt" if run_case "$argv" --ctl "$SCRATCH/ctl.sock" --rs485 "$SCRATCH/rs.sock"; then ctl_ln="$(chardev_line "$argv" "socket,id=ctl,path=$SCRATCH/ctl.sock,")" rs_ln="$(chardev_line "$argv" "socket,id=rs485,path=$SCRATCH/rs.sock,")" if [ -n "$ctl_ln" ] && [ -n "$rs_ln" ] && [ "$ctl_ln" -lt "$rs_ln" ]; then pass "both flags: ctl chardev (line $ctl_ln) precedes the rs485 chardev (line $rs_ln)" else fail "both flags: expected ctl chardev before rs485 chardev, got ctl=$ctl_ln rs485=$rs_ln" fi if grep -qw 'warden.ctl' "$argv"; then pass "both flags: -append carries warden.ctl" else fail "both flags: -append should carry warden.ctl" fi fi if [ "$FAIL" -eq 0 ]; then echo "ALL RUN.SH ARGV TESTS PASSED" exit 0 else echo "RUN.SH ARGV TESTS FAILED" exit 1 fi