From 0adcfaa5c7e479e9c433b92836a5548a19168e05 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 13 Sep 2026 10:59:31 -0500 Subject: fix(test): keep each failed install attempt's log The retry loop refetched the guest's newest install log after every failed attempt, so `test-logs/` only ever held the last one. When a retry followed a pacstrap stall, the saved log showed the retry's "Disk in use" and the stall behind it was gone. Each failed attempt's log now lands in `test-logs/-install-attempt.log`. --- scripts/test-install.sh | 26 ++++++++++++++++++++++++ tests/unit/test_test_install.bats | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/scripts/test-install.sh b/scripts/test-install.sh index cbca9b3..7a7f991 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -587,6 +587,30 @@ ssh_cmd() { -p "$SSH_PORT" root@localhost "$@" 2>/dev/null } +# Keep a failed attempt's install log under its own name. The retry loop +# refetches the guest's newest /tmp/archangel-*.log after every failed +# attempt, and the final *-install.log only ever holds the last one — so +# the failure that triggered a retry used to be gone by the time anyone +# read test-logs/ (2026-09-12: attempt 2's "Disk in use" was all that +# survived of a pacstrap stall). Pure: takes the captured text, never +# talks to the guest. An empty capture writes an empty file, so "attempt ran, +# log was empty" stays distinguishable from "attempt never ran". Returns 1 +# on a write failure; the caller treats that as non-fatal. +# +# Usage: save_attempt_log +save_attempt_log() { + local log_dir="$1" config_name="$2" attempt="$3" log_text="$4" + local file="$log_dir/${config_name}-install-attempt${attempt}.log" + # 2>/dev/null goes before the output redirect so it also silences the + # redirect's own "Permission denied"; the caller prints its own warning. + if [[ -n "$log_text" ]]; then + printf '%s\n' "$log_text" 2>/dev/null > "$file" || return 1 + else + : 2>/dev/null > "$file" || return 1 + fi + return 0 +} + # Decide whether a failed install is a transient pacstrap/network flake # (worth retrying) or a deterministic regression (fail fast). Returns 0 # only when the install log shows BOTH pacstrap's own base-install @@ -1159,6 +1183,8 @@ run_test() { # pacstrap's failure text survives. Read just the latest log — # a retry leaves a second timestamped log behind. install_log=$(ssh_cmd "cat \"\$(ls -t /tmp/archangel-*.log 2>/dev/null | head -1)\"" 2>/dev/null) || true + save_attempt_log "$LOG_DIR" "$config_name" "$attempt" "$install_log" \ + || warn "Could not save attempt $attempt's install log to $LOG_DIR" if [[ "$attempt" -lt 3 ]] && is_transient_install_failure "$install_log"; then warn "Install attempt $attempt hit a transient pacstrap flake — retrying ($((attempt + 1))/3)" continue diff --git a/tests/unit/test_test_install.bats b/tests/unit/test_test_install.bats index 52ea037..3fb7c19 100644 --- a/tests/unit/test_test_install.bats +++ b/tests/unit/test_test_install.bats @@ -14,6 +14,48 @@ setup() { source "${BATS_TEST_DIRNAME}/../../scripts/test-install.sh" } +############################# +# save_attempt_log +############################# +# The retry loop refetches the guest's newest /tmp/archangel-*.log on +# every failed attempt, so a retried install used to keep only the last +# attempt's log — the failure that triggered the retry was gone by the +# time anyone read test-logs/. save_attempt_log keeps each one. + +# Normal: a failed attempt's log lands under its own attempt-numbered name. +@test "save_attempt_log writes the attempt's log under an attempt-numbered name" { + local dir="$BATS_TEST_TMPDIR/logs" + mkdir -p "$dir" + run save_attempt_log "$dir" zfs-encrypt 1 $'line one\nline two' + [ "$status" -eq 0 ] + [ -f "$dir/zfs-encrypt-install-attempt1.log" ] + [ "$(cat "$dir/zfs-encrypt-install-attempt1.log")" = $'line one\nline two' ] +} + +# Boundary: an empty capture still writes a file, so "attempt ran, log +# was empty" stays distinguishable from "attempt never happened". +@test "save_attempt_log writes an empty file for an empty capture" { + local dir="$BATS_TEST_TMPDIR/logs" + mkdir -p "$dir" + run save_attempt_log "$dir" mirror 2 "" + [ "$status" -eq 0 ] + [ -f "$dir/mirror-install-attempt2.log" ] + [ ! -s "$dir/mirror-install-attempt2.log" ] +} + +# Error: an unwritable log dir fails the save without aborting the caller. +@test "save_attempt_log returns non-zero when the log dir is unwritable" { + [ "$EUID" -ne 0 ] || skip "running as root" + local dir="$BATS_TEST_TMPDIR/logs" + mkdir -p "$dir" + chmod 000 "$dir" + run save_attempt_log "$dir" mirror 1 "content" + chmod 755 "$dir" + [ "$status" -eq 1 ] + [ -z "$output" ] + [ ! -f "$dir/mirror-install-attempt1.log" ] +} + ############################# # is_transient_install_failure ############################# -- cgit v1.2.3