feat: run e2e container tests in parallel

Build images sequentially (shared layer cache), then run BATS and
interactive tests across all distros in parallel. Output captured
to temp files, failures show log tail. Significantly faster for
multi-distro runs.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Flo
2026-03-31 18:43:25 +02:00
parent c967e75ffc
commit 7eb697f1f2

View File

@@ -42,10 +42,8 @@ REBUILD=false
SKIP_HOST=false SKIP_HOST=false
TARGET_DISTRO="" TARGET_DISTRO=""
# Results tracking # Results tracking (temp dir for parallel result files)
RESULTS_DISTROS="" RESULTS_DIR=""
RESULTS_STATUS=""
RESULTS_DURATION=""
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Helpers # Helpers
@@ -215,7 +213,8 @@ run_host_interactive() {
fi fi
} }
# Generic entry that times a named test phase and records results # Generic entry that times a named test phase and records results.
# Output is written to a log file; result is recorded in RESULTS_DIR.
run_distro_entry() { run_distro_entry() {
local distro="$1" local distro="$1"
shift shift
@@ -223,10 +222,10 @@ run_distro_entry() {
local start_time local start_time
start_time="$(date +%s)" start_time="$(date +%s)"
printf '\n%b══ %s ══%b\n' "$C_BOLD" "$distro" "$C_RESET" >&2 local log_file="${RESULTS_DIR}/${distro}.log"
local status="PASS" local status="PASS"
if ! "$@"; then if ! "$@" > "$log_file" 2>&1; then
status="FAIL" status="FAIL"
fi fi
@@ -234,34 +233,20 @@ run_distro_entry() {
end_time="$(date +%s)" end_time="$(date +%s)"
local duration=$(( end_time - start_time )) local duration=$(( end_time - start_time ))
RESULTS_DISTROS="${RESULTS_DISTROS}${distro}\n" # Write result file (read by print_summary)
RESULTS_STATUS="${RESULTS_STATUS}${status}\n" printf '%s %s %ds\n' "$distro" "$status" "$duration" > "${RESULTS_DIR}/${distro}.result"
RESULTS_DURATION="${RESULTS_DURATION}${duration}s\n"
# Print inline status
if [ "$status" = "PASS" ]; then if [ "$status" = "PASS" ]; then
printf '%b ✓ %s passed (%ds)%b\n' "$C_GREEN" "$distro" "$duration" "$C_RESET" >&2 printf '%b ✓ %s passed (%ds)%b\n' "$C_GREEN" "$distro" "$duration" "$C_RESET" >&2
else else
printf '%b ✗ %s %s (%ds)%b\n' "$C_RED" "$distro" "$status" "$duration" "$C_RESET" >&2 printf '%b ✗ %s FAIL (%ds)%b\n' "$C_RED" "$distro" "$duration" "$C_RESET" >&2
# Show last 20 lines of log on failure
printf '%b Log tail:%b\n' "$C_YELLOW" "$C_RESET" >&2
tail -20 "$log_file" >&2
fi fi
} }
run_container_phases() {
local distro="$1"
if ! build_image "$distro"; then
return 1
fi
if ! run_tests "$distro"; then
return 1
fi
if ! run_interactive_tests "$distro"; then
return 1
fi
}
run_distro() {
local distro="$1"
run_distro_entry "$distro" run_container_phases "$distro"
}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Summary # Summary
@@ -269,29 +254,22 @@ run_distro() {
print_summary() { print_summary() {
printf '\n%b══ Summary ══%b\n' "$C_BOLD" "$C_RESET" >&2 printf '\n%b══ Summary ══%b\n' "$C_BOLD" "$C_RESET" >&2
printf '%-12s %-20s %s\n' "DISTRO" "STATUS" "DURATION" >&2 printf '%-12s %-8s %s\n' "DISTRO" "STATUS" "DURATION" >&2
printf '%-12s %-20s %s\n' "------" "------" "--------" >&2 printf '%-12s %-8s %s\n' "------" "------" "--------" >&2
local distros_arr status_arr duration_arr
IFS=$'\n' read -r -d '' -a distros_arr <<< "$(printf '%b' "$RESULTS_DISTROS")" || true
IFS=$'\n' read -r -d '' -a status_arr <<< "$(printf '%b' "$RESULTS_STATUS")" || true
IFS=$'\n' read -r -d '' -a duration_arr <<< "$(printf '%b' "$RESULTS_DURATION")" || true
local i=0
local any_failed=false local any_failed=false
while [ "$i" -lt "${#distros_arr[@]}" ]; do local result_file
local d="${distros_arr[$i]}" for result_file in "${RESULTS_DIR}"/*.result; do
local s="${status_arr[$i]}" [ -f "$result_file" ] || continue
local t="${duration_arr[$i]}" local d s t
[ -z "$d" ] && { i=$((i + 1)); continue; } read -r d s t < "$result_file"
local color="$C_GREEN" local color="$C_GREEN"
if [ "$s" != "PASS" ]; then if [ "$s" != "PASS" ]; then
color="$C_RED" color="$C_RED"
any_failed=true any_failed=true
fi fi
printf '%b%-12s %-20s %s%b\n' "$color" "$d" "$s" "$t" "$C_RESET" >&2 printf '%b%-12s %-8s %s%b\n' "$color" "$d" "$s" "$t" "$C_RESET" >&2
i=$((i + 1))
done done
if [ "$any_failed" = true ]; then if [ "$any_failed" = true ]; then
@@ -310,6 +288,10 @@ main() {
info "Using runtime: ${RUNTIME}" info "Using runtime: ${RUNTIME}"
# Set up results directory
RESULTS_DIR="$(mktemp -d)"
trap 'rm -rf "$RESULTS_DIR"' EXIT
# Run interactive tests on the host first (covers macOS ssh-keygen) # Run interactive tests on the host first (covers macOS ssh-keygen)
if [ "$SKIP_HOST" = true ]; then if [ "$SKIP_HOST" = true ]; then
info "Skipping host interactive tests (--skip-host)" info "Skipping host interactive tests (--skip-host)"
@@ -319,8 +301,9 @@ main() {
info "tmux not found — skipping host interactive tests (install with: brew install tmux)" info "tmux not found — skipping host interactive tests (install with: brew install tmux)"
fi fi
# Determine which distros to run
local run_distros=()
if [ -n "$TARGET_DISTRO" ]; then if [ -n "$TARGET_DISTRO" ]; then
# Validate distro name
local valid=false local valid=false
for d in "${DISTROS[@]}"; do for d in "${DISTROS[@]}"; do
if [ "$d" = "$TARGET_DISTRO" ]; then if [ "$d" = "$TARGET_DISTRO" ]; then
@@ -331,15 +314,54 @@ main() {
if [ "$valid" = false ]; then if [ "$valid" = false ]; then
die "Unknown distro: ${TARGET_DISTRO}. Available: ${DISTROS[*]}" die "Unknown distro: ${TARGET_DISTRO}. Available: ${DISTROS[*]}"
fi fi
run_distros=("$TARGET_DISTRO")
run_distro "$TARGET_DISTRO"
else else
for d in "${DISTROS[@]}"; do run_distros=("${DISTROS[@]}")
run_distro "$d" fi
# Phase 1: Build images sequentially (benefits from shared layer cache)
info "Building ${#run_distros[@]} container image(s)..."
for d in "${run_distros[@]}"; do
if ! build_image "$d"; then
printf '%b ✗ %s build failed%b\n' "$C_RED" "$d" "$C_RESET" >&2
printf '%s FAIL 0s\n' "$d" > "${RESULTS_DIR}/${d}.result"
fi
done
# Phase 2: Run tests in parallel
local pids=()
local pid_distros=()
for d in "${run_distros[@]}"; do
# Skip distros that failed to build
[ -f "${RESULTS_DIR}/${d}.result" ] && continue
run_distro_entry "$d" run_container_test_phases "$d" &
pids+=($!)
pid_distros+=("$d")
done
if [ ${#pids[@]} -gt 0 ]; then
info "Running ${#pids[@]} distro(s) in parallel: ${pid_distros[*]}"
# Wait for all background jobs
local i=0
while [ "$i" -lt "${#pids[@]}" ]; do
wait "${pids[$i]}" 2>/dev/null || true
i=$((i + 1))
done done
fi fi
print_summary print_summary
} }
# Container test phases (without build — build is done in phase 1)
run_container_test_phases() {
local distro="$1"
if ! run_tests "$distro"; then
return 1
fi
if ! run_interactive_tests "$distro"; then
return 1
fi
}
main "$@" main "$@"