mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-11 18:34:01 +01:00
Much of testsAndMisc/linux_configuration cannot be tested on the host: 15 files use `chattr +i` (so a bad test is not simply revertable), the shutdown installers write units that power the machine off, and boot_recovery installs pacman hooks. The repo's own CI says as much -- its Ubuntu job runs only the side-effect-free tests, noting the rest "drive pacman, /etc, systemd or adb". vmbox runs them in a throwaway Arch guest and verifies the result from the HOST, which is the part that is not obvious: a successful shutdown kills the connection carrying the report, and ssh returns 255 for a clean poweroff, a crash and a dropped link alike. So the verdict comes from two artefacts that outlive the VM -- a QMP event log written by a recorder attached at launch (attach later and qemu has already exited), and the serial console log. Five outcomes are distinguished: clean poweroff (SHUTDOWN guest:true plus a serial tail reaching "reboot: Power down"), dirty stop, guest-reset (rebooted instead of powering off), GUEST_PANICKED, and hung. A POWERDOWN event proves nothing -- it means the request was delivered, not that anything acted on it. Reset is a qcow2 backing-file trick: the golden image is built once and sealed (chmod 444 plus a sha256 sidecar verified on every launch, since booting the base would silently invalidate every overlay), and each sandbox is a thin overlay, so reset is rm + recreate. No root anywhere -- user-mode networking with per-VM forwarded ssh ports, and a socket multicast segment for VM-to-VM. The guest clock is pinned and re-applied on every launch, because the shutdown installers gate on a 21:00-05:00 window; NTP is masked in the image or it silently undoes the pin. Repos are cloned from a read-only share scoped to an explicit directory, never $HOME, and credentials are sentinel values. `vm build` ends by booting a throwaway sandbox off the sealed image and refusing to ship if ssh does not answer. Several distinct faults produced an identical "boots fine, connection reset" symptom; checks inside the build VM only prove state at seal time, so the gate has to be an end-to-end boot. Tests: 8 bats cases over the verdict table and name/meta/serial handling; shellcheck and ruff clean; every file within the 250-line cap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdyMBGCrGVGDWtJ9xe48f3
115 lines
3.6 KiB
Bash
115 lines
3.6 KiB
Bash
#!/usr/bin/env bats
|
|
# Host-side unit tests for vmbox's pure functions. These deliberately do NOT
|
|
# boot a VM: booting is covered by the destructive end-to-end demo in README.
|
|
# Everything here runs against a temporary VMBOX_HOME.
|
|
|
|
setup() {
|
|
VMBOX_TEST_HOME="$(mktemp -d)"
|
|
export VMBOX_HOME="$VMBOX_TEST_HOME"
|
|
REPO="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
export REPO
|
|
# shellcheck source=../lib/common.sh
|
|
source "$REPO/lib/common.sh"
|
|
}
|
|
|
|
teardown() {
|
|
[[ -n "${VMBOX_TEST_HOME:-}" && -d "$VMBOX_TEST_HOME" ]] && rm -rf "$VMBOX_TEST_HOME"
|
|
}
|
|
|
|
@test "validate_vm_name accepts ordinary names" {
|
|
run validate_vm_name "demo"
|
|
[ "$status" -eq 0 ]
|
|
[ "$output" = "demo" ]
|
|
run validate_vm_name "test-1_x"
|
|
[ "$status" -eq 0 ]
|
|
}
|
|
|
|
@test "validate_vm_name rejects path traversal and spaces" {
|
|
run validate_vm_name "../escape"
|
|
[ "$status" -ne 0 ]
|
|
run validate_vm_name "bad name"
|
|
[ "$status" -ne 0 ]
|
|
run validate_vm_name ""
|
|
[ "$status" -ne 0 ]
|
|
}
|
|
|
|
@test "meta_set overwrites rather than duplicating a key" {
|
|
mkdir -p "$(vm_dir demo)"
|
|
meta_set demo rtc "2026-01-01T00:00:00"
|
|
meta_set demo rtc "2026-08-22T20:55:00"
|
|
run bash -c "grep -c '^rtc=' '$(vm_meta demo)'"
|
|
[ "$output" = "1" ]
|
|
run meta_get demo rtc
|
|
[ "$output" = "2026-08-22T20:55:00" ]
|
|
}
|
|
|
|
@test "vm_next_serial starts at 0 and continues past the highest index" {
|
|
mkdir -p "$(vm_dir demo)"
|
|
run vm_next_serial demo
|
|
[ "$output" = "0" ]
|
|
touch "$(vm_dir demo)/serial.0.log" "$(vm_dir demo)/serial.7.log"
|
|
run vm_next_serial demo
|
|
[ "$output" = "8" ]
|
|
}
|
|
|
|
@test "vm_is_running is false for a stale pidfile" {
|
|
mkdir -p "$(vm_dir demo)"
|
|
echo "999999" > "$(vm_pidfile demo)"
|
|
run vm_is_running demo
|
|
[ "$status" -ne 0 ]
|
|
}
|
|
|
|
@test "verdict classifies a guest poweroff as clean only with the serial marker" {
|
|
source "$REPO/lib/verdict.sh"
|
|
mkdir -p "$(vm_dir demo)"
|
|
local ev="$(vm_dir demo)/events.jsonl"
|
|
printf '%s\n' '{"event":"SHUTDOWN","data":{"guest":true,"reason":"guest-shutdown"}}' > "$ev"
|
|
|
|
run verdict_last_event "$ev"
|
|
[ "$output" = "guest-shutdown" ]
|
|
|
|
# Truncated serial log -> the machine stopped, but not cleanly.
|
|
local ser="$(vm_dir demo)/serial.0.log"
|
|
printf 'Stopping some unit...\n' > "$ser"
|
|
run verdict_serial_complete "$ser"
|
|
[ "$status" -ne 0 ]
|
|
|
|
printf 'reboot: Power down\n' >> "$ser"
|
|
run verdict_serial_complete "$ser"
|
|
[ "$status" -eq 0 ]
|
|
}
|
|
|
|
@test "verdict distinguishes reset, panic and host-initiated stops" {
|
|
source "$REPO/lib/verdict.sh"
|
|
mkdir -p "$(vm_dir demo)"
|
|
local ev="$(vm_dir demo)/events.jsonl"
|
|
|
|
printf '%s\n' '{"event":"SHUTDOWN","data":{"guest":true,"reason":"guest-reset"}}' > "$ev"
|
|
run verdict_last_event "$ev"
|
|
[ "$output" = "guest-reset" ]
|
|
|
|
printf '%s\n' '{"event":"GUEST_PANICKED","data":{}}' > "$ev"
|
|
run verdict_last_event "$ev"
|
|
[ "$output" = "panic" ]
|
|
|
|
printf '%s\n' '{"event":"SHUTDOWN","data":{"guest":false,"reason":"host-qmp-quit"}}' > "$ev"
|
|
run verdict_last_event "$ev"
|
|
[ "$output" = "host-qmp-quit" ]
|
|
|
|
: > "$ev"
|
|
run verdict_last_event "$ev"
|
|
[ "$output" = "none" ]
|
|
}
|
|
|
|
@test "verdict reads the LAST event, so an earlier boot does not win" {
|
|
source "$REPO/lib/verdict.sh"
|
|
mkdir -p "$(vm_dir demo)"
|
|
local ev="$(vm_dir demo)/events.jsonl"
|
|
{
|
|
printf '%s\n' '{"event":"SHUTDOWN","data":{"guest":true,"reason":"guest-reset"}}'
|
|
printf '%s\n' '{"event":"SHUTDOWN","data":{"guest":true,"reason":"guest-shutdown"}}'
|
|
} > "$ev"
|
|
run verdict_last_event "$ev"
|
|
[ "$output" = "guest-shutdown" ]
|
|
}
|