diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/testing/lib/maint-scenario.sh | 62 | ||||
| -rw-r--r-- | scripts/testing/lib/run-common.sh | 61 | ||||
| -rw-r--r-- | scripts/testing/run-maint-nspawn.sh | 60 | ||||
| -rw-r--r-- | scripts/testing/run-maint-scenarios.sh | 65 | ||||
| -rwxr-xr-x | scripts/testing/run-test-baremetal.sh | 65 | ||||
| -rwxr-xr-x | scripts/testing/run-test.sh | 82 |
6 files changed, 171 insertions, 224 deletions
diff --git a/scripts/testing/lib/maint-scenario.sh b/scripts/testing/lib/maint-scenario.sh new file mode 100644 index 0000000..e7fcdda --- /dev/null +++ b/scripts/testing/lib/maint-scenario.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Shared contract and execution helpers for maint scenario transports. + +# Extract one declared variable from a scenario file in a clean subshell. +_scenario_var() { # <file> <varname> + bash -c 'set -eu; source "$1" || exit 9; eval "printf %s \"\${$2-}\""' \ + _probe "$1" "$2" 2>/dev/null +} + +_validate_scenario() { # <file> -> fatal on contract violation + local f="$1" base probe_err + base=$(basename "$f") + probe_err=$(bash -c 'set -eu; source "$1" + : "${SCENARIO_DESC:?missing SCENARIO_DESC}" + : "${SCENARIO_GROUP:?missing SCENARIO_GROUP}" + : "${SCENARIO_PROFILES:?missing SCENARIO_PROFILES}" + for p in $SCENARIO_PROFILES; do + case "$p" in btrfs|zfs|any) ;; *) + echo "bad profile token: $p" >&2; exit 1 ;; + esac + done + declare -f scenario_break scenario_fix scenario_assert >/dev/null \ + || { echo "missing scenario_break/fix/assert" >&2; exit 1; }' \ + _probe "$f" 2>&1 >/dev/null) \ + || fatal "scenario contract violation in $base: $probe_err" +} + +run_scenario() { # <index> + local i="$1" name="${S_NAMES[$1]}" f="${S_FILES[$1]}" + section "Scenario: $name — ${S_DESCS[$1]}" + # shellcheck disable=SC1090 + source "$f" + local ok=true + step "break" + if ! scenario_break; then + error "$name: break step failed" + ok=false + fi + if [ "$ok" = "true" ]; then + step "fix" + if ! scenario_fix; then + error "$name: fix step failed" + ok=false + fi + fi + if [ "$ok" = "true" ]; then + step "assert" + if ! scenario_assert; then + error "$name: post-state assertion failed" + ok=false + fi + fi + unset -f scenario_break scenario_fix scenario_assert + unset SCENARIO_DESC SCENARIO_GROUP SCENARIO_PROFILES + if [ "$ok" = "true" ]; then + success "PASS: $name" + PASS+=("$name") + else + error "FAIL: $name" + FAIL+=("$name") + fi +} diff --git a/scripts/testing/lib/run-common.sh b/scripts/testing/lib/run-common.sh new file mode 100644 index 0000000..b5696db --- /dev/null +++ b/scripts/testing/lib/run-common.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Transport-independent polling and reporting for archsetup test runners. + +archsetup_process_running() { # <transport-function> + local transport="$1" + "$transport" "pgrep -f '[b]ash archsetup' > /dev/null" 2>/dev/null +} + +wait_for_archsetup() { # <transport> <max-polls> <seconds> <progress-every> [callback] + local transport="$1" max_polls="$2" poll_seconds="$3" + local progress_every="$4" progress_callback="${5:-}" + ARCHSETUP_POLL_COUNT=0 + + while [ "$ARCHSETUP_POLL_COUNT" -lt "$max_polls" ]; do + archsetup_process_running "$transport" || return 0 + sleep "$poll_seconds" + ARCHSETUP_POLL_COUNT=$((ARCHSETUP_POLL_COUNT + 1)) + if [ "$progress_every" -gt 0 ] \ + && [ $((ARCHSETUP_POLL_COUNT % progress_every)) -eq 0 ] \ + && [ -n "$progress_callback" ]; then + "$progress_callback" "$ARCHSETUP_POLL_COUNT" + fi + done + return 124 +} + +write_archsetup_test_report() { + local file="$1" title="$2" test_id="$3" method="$4" context="$5" + local completed="$6" passed="$7" count_passed="$8" count_failed="$9" + shift 9 + local count_warnings="$1" artifacts="$2" validation="FAILED" + [ "$passed" = "true" ] && validation="PASSED" + + { + echo "========================================" + echo "$title" + echo "========================================" + echo + echo "Test ID: $test_id" + echo "Date: $(date +'%Y-%m-%d %H:%M:%S')" + echo "Test Method: $method" + if [ -n "$context" ]; then + echo + printf '%s\n' "$context" + fi + echo + echo "Results:" + echo " ArchSetup Completed: $completed (completion marker, not the installer's exit code)" + echo " Validation: $validation" + echo + echo "Validation Summary:" + echo " Passed: $count_passed" + echo " Failed: $count_failed" + echo " Warnings: $count_warnings" + if [ -n "$artifacts" ]; then + echo + printf '%s\n' "$artifacts" + fi + echo + } > "$file" +} diff --git a/scripts/testing/run-maint-nspawn.sh b/scripts/testing/run-maint-nspawn.sh index 04d4a32..dcc7c16 100644 --- a/scripts/testing/run-maint-nspawn.sh +++ b/scripts/testing/run-maint-nspawn.sh @@ -32,6 +32,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" source "$SCRIPT_DIR/lib/logging.sh" +source "$SCRIPT_DIR/lib/maint-scenario.sh" SCENARIO_DIR="${MAINT_SCENARIO_DIR:-$SCRIPT_DIR/maint-scenarios}" MAINT_SRC="${MAINT_SRC:-$HOME/.dotfiles/maint/src/maint}" @@ -63,29 +64,6 @@ done S_FILES=() S_NAMES=() S_DESCS=() -_scenario_var() { # <file> <varname> - bash -c 'set -eu; source "$1" || exit 9; eval "printf %s \"\${$2-}\""' \ - _probe "$1" "$2" 2>/dev/null -} - -_validate_scenario() { # <file> -> fatal on contract violation - local f="$1" base probe_err - base=$(basename "$f") - probe_err=$(bash -c 'set -eu; source "$1" - : "${SCENARIO_DESC:?missing SCENARIO_DESC}" - : "${SCENARIO_GROUP:?missing SCENARIO_GROUP}" - : "${SCENARIO_PROFILES:?missing SCENARIO_PROFILES}" - for p in $SCENARIO_PROFILES; do - case "$p" in btrfs|zfs|any) ;; *) - echo "bad profile token: $p" >&2; exit 1 ;; - esac - done - declare -f scenario_break scenario_fix scenario_assert >/dev/null \ - || { echo "missing scenario_break/fix/assert" >&2; exit 1; }' \ - _probe "$f" 2>&1 >/dev/null) \ - || fatal "scenario contract violation in $base: $probe_err" -} - build_plan() { [ -d "$SCENARIO_DIR" ] || fatal "scenario dir not found: $SCENARIO_DIR" local f base @@ -212,42 +190,6 @@ success "Container ready" PASS=() FAIL=() -run_scenario() { # <index> - local i="$1" name="${S_NAMES[$1]}" f="${S_FILES[$1]}" - section "Scenario: $name — ${S_DESCS[$1]}" - # shellcheck disable=SC1090 - source "$f" - local ok=true - step "break" - if ! scenario_break; then - error "$name: break step failed" - ok=false - fi - if [ "$ok" = "true" ]; then - step "fix" - if ! scenario_fix; then - error "$name: fix step failed" - ok=false - fi - fi - if [ "$ok" = "true" ]; then - step "assert" - if ! scenario_assert; then - error "$name: post-state assertion failed" - ok=false - fi - fi - unset -f scenario_break scenario_fix scenario_assert - unset SCENARIO_DESC SCENARIO_GROUP SCENARIO_PROFILES - if [ "$ok" = "true" ]; then - success "PASS: $name" - PASS+=("$name") - else - error "FAIL: $name" - FAIL+=("$name") - fi -} - for i in "${!S_FILES[@]}"; do run_scenario "$i" done diff --git a/scripts/testing/run-maint-scenarios.sh b/scripts/testing/run-maint-scenarios.sh index 448f6d9..17481e9 100644 --- a/scripts/testing/run-maint-scenarios.sh +++ b/scripts/testing/run-maint-scenarios.sh @@ -35,6 +35,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" source "$SCRIPT_DIR/lib/logging.sh" source "$SCRIPT_DIR/lib/vm-utils.sh" +source "$SCRIPT_DIR/lib/maint-scenario.sh" SCENARIO_DIR="${MAINT_SCENARIO_DIR:-$SCRIPT_DIR/maint-scenarios}" MAINT_SRC="${MAINT_SRC:-$HOME/.dotfiles/maint/src/maint}" @@ -72,32 +73,6 @@ S_FILES=() S_NAMES=() S_DESCS=() S_GROUPS=() # Groups in first-appearance order. GROUP_ORDER=() -# Extract one declared var from a scenario file by sourcing it in a clean -# subshell. Scenario files must only define vars + functions; any top-level -# command fails here (no helpers exist), which is the contract. -_scenario_var() { # <file> <varname> - bash -c 'set -eu; source "$1" || exit 9; eval "printf %s \"\${$2-}\""' \ - _probe "$1" "$2" 2>/dev/null -} - -_validate_scenario() { # <file> -> fatal on contract violation - local f="$1" base probe_err - base=$(basename "$f") - probe_err=$(bash -c 'set -eu; source "$1" - : "${SCENARIO_DESC:?missing SCENARIO_DESC}" - : "${SCENARIO_GROUP:?missing SCENARIO_GROUP}" - : "${SCENARIO_PROFILES:?missing SCENARIO_PROFILES}" - for p in $SCENARIO_PROFILES; do - case "$p" in btrfs|zfs|any) ;; *) - echo "bad profile token: $p" >&2; exit 1 ;; - esac - done - declare -f scenario_break scenario_fix scenario_assert >/dev/null \ - || { echo "missing scenario_break/fix/assert" >&2; exit 1; }' \ - _probe "$f" 2>&1 >/dev/null) \ - || fatal "scenario contract violation in $base: $probe_err" -} - _profile_matches() { # <profiles> -> 0 if scenario runs under $FS_PROFILE local p for p in $1; do @@ -283,44 +258,6 @@ import json; print(len(json.load(open('$RESULTS_DIR/bootstrap-status.json'))['me PASS=() FAIL=() -run_scenario() { # <index> - local i="$1" name="${S_NAMES[$1]}" f="${S_FILES[$1]}" - section "Scenario: $name — ${S_DESCS[$1]}" - # Sourced in the runner's shell so break/fix/assert see the helpers; - # unset afterwards so the next scenario can't inherit stale functions. - # shellcheck disable=SC1090 - source "$f" - local ok=true - step "break" - if ! scenario_break; then - error "$name: break step failed" - ok=false - fi - if [ "$ok" = "true" ]; then - step "fix" - if ! scenario_fix; then - error "$name: fix step failed" - ok=false - fi - fi - if [ "$ok" = "true" ]; then - step "assert" - if ! scenario_assert; then - error "$name: post-state assertion failed" - ok=false - fi - fi - unset -f scenario_break scenario_fix scenario_assert - unset SCENARIO_DESC SCENARIO_GROUP SCENARIO_PROFILES - if [ "$ok" = "true" ]; then - success "PASS: $name" - PASS+=("$name") - else - error "FAIL: $name" - FAIL+=("$name") - fi -} - section "Preparing VM" stop_qemu 2>/dev/null || true step "Restoring clean-install snapshot" diff --git a/scripts/testing/run-test-baremetal.sh b/scripts/testing/run-test-baremetal.sh index d837389..4692fda 100755 --- a/scripts/testing/run-test-baremetal.sh +++ b/scripts/testing/run-test-baremetal.sh @@ -23,6 +23,7 @@ source "$SCRIPT_DIR/lib/logging.sh" source "$SCRIPT_DIR/lib/validation.sh" # live helpers: ssh_cmd, capture_*_state, analyze_log_diff, generate_issue_report source "$SCRIPT_DIR/lib/vm-utils.sh" # inject_root_key + SSH_OPTS/SSH_KEY_OPT for key auth source "$SCRIPT_DIR/lib/testinfra.sh" # run_testinfra_validation (authoritative validator) +source "$SCRIPT_DIR/lib/run-common.sh" # Parse arguments ROLLBACK_FIRST=false @@ -203,30 +204,25 @@ if ! $VALIDATE_ONLY; then # Start archsetup in background ssh_cmd "cd /tmp/archsetup-test && nohup bash archsetup > $REMOTE_LOG 2>&1 &" - success "ArchSetup started in background" + sleep 3 + if archsetup_process_running ssh_cmd; then + success "ArchSetup started in background" + else + fatal "ArchSetup process not found after launch" + fi # Poll for completion step "Monitoring archsetup progress" - POLL_COUNT=0 MAX_POLLS=180 # 90 minutes max - while [ $POLL_COUNT -lt $MAX_POLLS ]; do - if ssh_cmd "ps aux | grep '[b]ash archsetup' > /dev/null" 2>/dev/null; then - sleep 30 - POLL_COUNT=$((POLL_COUNT + 1)) - if [ $((POLL_COUNT % 10)) -eq 0 ]; then - ELAPSED_MINS=$((POLL_COUNT / 2)) - info "Still running... ($ELAPSED_MINS minutes elapsed)" - # Show last line of progress - LAST_LINE=$(ssh_cmd "tail -1 $REMOTE_LOG 2>/dev/null" || echo "") - [ -n "$LAST_LINE" ] && info " $LAST_LINE" - fi - else - break - fi - done + baremetal_progress() { + local last_line + info "Still running... ($(("$1" / 2)) minutes elapsed)" + last_line=$(ssh_cmd "tail -1 $REMOTE_LOG 2>/dev/null" || echo "") + [ -n "$last_line" ] && info " $last_line" + } - if [ $POLL_COUNT -ge $MAX_POLLS ]; then + if ! wait_for_archsetup ssh_cmd "$MAX_POLLS" 30 10 baremetal_progress; then error "ArchSetup timed out after 90 minutes" ARCHSETUP_COMPLETED=timeout else @@ -294,31 +290,14 @@ fi section "Generating Test Report" REPORT_FILE="$TEST_RESULTS_DIR/test-report.txt" -cat > "$REPORT_FILE" << EOFREPORT -======================================== -Bare Metal ArchSetup Test Report -======================================== - -Test ID: $TIMESTAMP -Date: $(date +'%Y-%m-%d %H:%M:%S') -Target: $TARGET_HOST -Test Method: Bare Metal ZFS - -Results: - ArchSetup Completed: $ARCHSETUP_COMPLETED (completion marker, not the installer's exit code) - Validation: $(if $TEST_PASSED; then echo "PASSED"; else echo "FAILED"; fi) - -Validation Summary: - Passed: $VALIDATION_PASSED - Failed: $VALIDATION_FAILED - Warnings: $VALIDATION_WARNINGS - -Artifacts: - Log file: $LOGFILE - Report: $REPORT_FILE - Results: $TEST_RESULTS_DIR/ - -EOFREPORT +printf -v RUN_REPORT_ARTIFACTS \ + 'Artifacts:\n Log file: %s\n Report: %s\n Results: %s/' \ + "$LOGFILE" "$REPORT_FILE" "$TEST_RESULTS_DIR" +write_archsetup_test_report \ + "$REPORT_FILE" "Bare Metal ArchSetup Test Report" "$TIMESTAMP" \ + "Bare Metal ZFS" "Target: $TARGET_HOST" "$ARCHSETUP_COMPLETED" \ + "$TEST_PASSED" "$VALIDATION_PASSED" "$VALIDATION_FAILED" \ + "$VALIDATION_WARNINGS" "$RUN_REPORT_ARTIFACTS" info "Test report saved: $REPORT_FILE" diff --git a/scripts/testing/run-test.sh b/scripts/testing/run-test.sh index a5c3691..0f57678 100755 --- a/scripts/testing/run-test.sh +++ b/scripts/testing/run-test.sh @@ -25,6 +25,7 @@ source "$SCRIPT_DIR/lib/vm-utils.sh" source "$SCRIPT_DIR/lib/network-diagnostics.sh" source "$SCRIPT_DIR/lib/validation.sh" source "$SCRIPT_DIR/lib/testinfra.sh" +source "$SCRIPT_DIR/lib/run-common.sh" # Parse arguments KEEP_VM=false @@ -248,7 +249,11 @@ sshpass -p "$ROOT_PASSWORD" ssh -T -n $SSH_OPTS \ # Verify the process started sleep 3 -if vm_exec "$ROOT_PASSWORD" "pgrep -f 'bash archsetup'" >> "$LOGFILE" 2>/dev/null; then +vm_transport() { + vm_exec "$ROOT_PASSWORD" "$@" +} + +if archsetup_process_running vm_transport; then success "ArchSetup started in background on VM" else fatal "ArchSetup process not found after launch" @@ -256,29 +261,14 @@ fi # Poll for completion step "Monitoring archsetup progress (polling every 30 seconds)..." -POLL_COUNT=0 MAX_POLLS=300 # 150 minutes max (300 * 30 seconds); a full install with heavy # AUR builds (e.g. vagrant) can exceed 90 min on a slow mirror -while [ $POLL_COUNT -lt $MAX_POLLS ]; do - # Check if archsetup process is still running - if vm_exec "$ROOT_PASSWORD" "ps aux | grep '[b]ash archsetup' > /dev/null" 2>/dev/null; then - # Still running, wait and continue - sleep 30 - POLL_COUNT=$((POLL_COUNT + 1)) - - # Show progress every 5 minutes - if [ $((POLL_COUNT % 10)) -eq 0 ]; then - ELAPSED_MINS=$((POLL_COUNT / 2)) - info "Still running... ($ELAPSED_MINS minutes elapsed)" - fi - else - # Process finished - break - fi -done +vm_progress() { + info "Still running... ($(("$1" / 2)) minutes elapsed)" +} -if [ $POLL_COUNT -ge $MAX_POLLS ]; then +if ! wait_for_archsetup vm_transport "$MAX_POLLS" 30 10 vm_progress; then error "ArchSetup timed out after 150 minutes" ARCHSETUP_COMPLETED=timeout else @@ -357,44 +347,20 @@ fi section "Generating Test Report" REPORT_FILE="$TEST_RESULTS_DIR/test-report.txt" -cat > "$REPORT_FILE" << EOFREPORT -======================================== -ArchSetup Test Report -======================================== - -Test ID: $TIMESTAMP -Date: $(date +'%Y-%m-%d %H:%M:%S') -Test Method: QEMU snapshot-based - -VM Configuration: - Disk: $DISK_PATH - Snapshot: $SNAPSHOT_NAME - SSH: localhost:$SSH_PORT - -Results: - ArchSetup Completed: $ARCHSETUP_COMPLETED (completion marker, not the installer's exit code) - Validation: $(if $TEST_PASSED; then echo "PASSED"; else echo "FAILED"; fi) - -Validation Summary: - Passed: $VALIDATION_PASSED - Failed: $VALIDATION_FAILED - Warnings: $VALIDATION_WARNINGS - -Issue Attribution: - ArchSetup Issues: ${#ARCHSETUP_ISSUES[@]} - Base Install Issues: ${#BASE_INSTALL_ISSUES[@]} - Unknown Issues: ${#UNKNOWN_ISSUES[@]} - -Artifacts: - Log file: $LOGFILE - Report: $REPORT_FILE - Results: $TEST_RESULTS_DIR/ - Issue Report: $TEST_RESULTS_DIR/issue-report.txt - Pre-install logs: $TEST_RESULTS_DIR/pre-install/ - Post-install logs: $TEST_RESULTS_DIR/post-install/ - Analysis: $TEST_RESULTS_DIR/analysis/ - -EOFREPORT +printf -v RUN_REPORT_CONTEXT \ + 'VM Configuration:\n Disk: %s\n Snapshot: %s\n SSH: localhost:%s' \ + "$DISK_PATH" "$SNAPSHOT_NAME" "$SSH_PORT" +printf -v RUN_REPORT_ARTIFACTS \ + 'Issue Attribution:\n ArchSetup Issues: %s\n Base Install Issues: %s\n Unknown Issues: %s\n\nArtifacts:\n Log file: %s\n Report: %s\n Results: %s/\n Issue Report: %s/issue-report.txt\n Pre-install logs: %s/pre-install/\n Post-install logs: %s/post-install/\n Analysis: %s/analysis/' \ + "${#ARCHSETUP_ISSUES[@]}" "${#BASE_INSTALL_ISSUES[@]}" \ + "${#UNKNOWN_ISSUES[@]}" "$LOGFILE" "$REPORT_FILE" "$TEST_RESULTS_DIR" \ + "$TEST_RESULTS_DIR" "$TEST_RESULTS_DIR" "$TEST_RESULTS_DIR" \ + "$TEST_RESULTS_DIR" +write_archsetup_test_report \ + "$REPORT_FILE" "ArchSetup Test Report" "$TIMESTAMP" \ + "QEMU snapshot-based" "$RUN_REPORT_CONTEXT" "$ARCHSETUP_COMPLETED" \ + "$TEST_PASSED" "$VALIDATION_PASSED" "$VALIDATION_FAILED" \ + "$VALIDATION_WARNINGS" "$RUN_REPORT_ARTIFACTS" info "Test report saved: $REPORT_FILE" |
