From 7eb697f1f207cbf7d9528bb76c6a3161d8b4f724 Mon Sep 17 00:00:00 2001 From: Flo Date: Tue, 31 Mar 2026 18:43:25 +0200 Subject: [PATCH] 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 --- test/e2e.sh | 118 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/test/e2e.sh b/test/e2e.sh index 8e3d0a6..9a4b08b 100755 --- a/test/e2e.sh +++ b/test/e2e.sh @@ -42,10 +42,8 @@ REBUILD=false SKIP_HOST=false TARGET_DISTRO="" -# Results tracking -RESULTS_DISTROS="" -RESULTS_STATUS="" -RESULTS_DURATION="" +# Results tracking (temp dir for parallel result files) +RESULTS_DIR="" # ------------------------------------------------------------------------------ # Helpers @@ -215,7 +213,8 @@ run_host_interactive() { 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() { local distro="$1" shift @@ -223,10 +222,10 @@ run_distro_entry() { local start_time 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" - if ! "$@"; then + if ! "$@" > "$log_file" 2>&1; then status="FAIL" fi @@ -234,34 +233,20 @@ run_distro_entry() { end_time="$(date +%s)" local duration=$(( end_time - start_time )) - RESULTS_DISTROS="${RESULTS_DISTROS}${distro}\n" - RESULTS_STATUS="${RESULTS_STATUS}${status}\n" - RESULTS_DURATION="${RESULTS_DURATION}${duration}s\n" + # Write result file (read by print_summary) + printf '%s %s %ds\n' "$distro" "$status" "$duration" > "${RESULTS_DIR}/${distro}.result" + # Print inline status if [ "$status" = "PASS" ]; then printf '%b ✓ %s passed (%ds)%b\n' "$C_GREEN" "$distro" "$duration" "$C_RESET" >&2 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 } -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 @@ -269,29 +254,22 @@ run_distro() { print_summary() { printf '\n%b══ Summary ══%b\n' "$C_BOLD" "$C_RESET" >&2 - printf '%-12s %-20s %s\n' "DISTRO" "STATUS" "DURATION" >&2 - printf '%-12s %-20s %s\n' "------" "------" "--------" >&2 + printf '%-12s %-8s %s\n' "DISTRO" "STATUS" "DURATION" >&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 - while [ "$i" -lt "${#distros_arr[@]}" ]; do - local d="${distros_arr[$i]}" - local s="${status_arr[$i]}" - local t="${duration_arr[$i]}" - [ -z "$d" ] && { i=$((i + 1)); continue; } + local result_file + for result_file in "${RESULTS_DIR}"/*.result; do + [ -f "$result_file" ] || continue + local d s t + read -r d s t < "$result_file" local color="$C_GREEN" if [ "$s" != "PASS" ]; then color="$C_RED" any_failed=true fi - printf '%b%-12s %-20s %s%b\n' "$color" "$d" "$s" "$t" "$C_RESET" >&2 - i=$((i + 1)) + printf '%b%-12s %-8s %s%b\n' "$color" "$d" "$s" "$t" "$C_RESET" >&2 done if [ "$any_failed" = true ]; then @@ -310,6 +288,10 @@ main() { 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) if [ "$SKIP_HOST" = true ]; then 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)" fi + # Determine which distros to run + local run_distros=() if [ -n "$TARGET_DISTRO" ]; then - # Validate distro name local valid=false for d in "${DISTROS[@]}"; do if [ "$d" = "$TARGET_DISTRO" ]; then @@ -331,15 +314,54 @@ main() { if [ "$valid" = false ]; then die "Unknown distro: ${TARGET_DISTRO}. Available: ${DISTROS[*]}" fi - - run_distro "$TARGET_DISTRO" + run_distros=("$TARGET_DISTRO") else - for d in "${DISTROS[@]}"; do - run_distro "$d" + run_distros=("${DISTROS[@]}") + 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 fi 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 "$@"