#!/usr/bin/env bash # test/e2e.sh — Run BATS tests inside containers across Linux distros # Usage: test/e2e.sh [--runtime docker|podman] [--rebuild] [distro] set -o errexit set -o nounset set -o pipefail IFS=$'\n\t' # ------------------------------------------------------------------------------ # Constants # ------------------------------------------------------------------------------ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" readonly SCRIPT_DIR REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" readonly REPO_ROOT readonly CONTAINER_DIR="${SCRIPT_DIR}/containers" readonly IMAGE_PREFIX="git-harden-test" # Distro matrix: name=image readonly DISTROS="ubuntu debian fedora alpine arch" # Colors (empty if not a terminal) if [ -t 2 ]; then readonly C_RED='\033[0;31m' readonly C_GREEN='\033[0;32m' readonly C_YELLOW='\033[0;33m' readonly C_BOLD='\033[1m' readonly C_RESET='\033[0m' else readonly C_RED='' readonly C_GREEN='' readonly C_YELLOW='' readonly C_BOLD='' readonly C_RESET='' fi # Mutable state RUNTIME="" REBUILD=false TARGET_DISTRO="" # Results tracking RESULTS_DISTROS="" RESULTS_STATUS="" RESULTS_DURATION="" # ------------------------------------------------------------------------------ # Helpers # ------------------------------------------------------------------------------ die() { printf '%bError:%b %s\n' "$C_RED" "$C_RESET" "$1" >&2 exit 1 } info() { printf '%b[INFO]%b %s\n' "$C_YELLOW" "$C_RESET" "$1" >&2 } # ------------------------------------------------------------------------------ # Argument parsing # ------------------------------------------------------------------------------ parse_args() { while [ $# -gt 0 ]; do case "$1" in --runtime) [ $# -ge 2 ] || die "--runtime requires an argument (docker or podman)" RUNTIME="$2" shift 2 ;; --rebuild) REBUILD=true shift ;; --help|-h) usage exit 0 ;; -*) die "Unknown option: $1" ;; *) TARGET_DISTRO="$1" shift ;; esac done } usage() { cat >&2 <<'EOF' Usage: test/e2e.sh [OPTIONS] [DISTRO] Run BATS tests inside containers across Linux distributions. Options: --runtime Container runtime (auto-detected if omitted) --rebuild Force image rebuild ignoring cache --help, -h Show this help Distros: ubuntu, debian, fedora, alpine, arch Examples: test/e2e.sh # Run all distros test/e2e.sh alpine # Run Alpine only test/e2e.sh --runtime podman fedora # Use Podman, Fedora only EOF } # ------------------------------------------------------------------------------ # Runtime detection # ------------------------------------------------------------------------------ detect_runtime() { if [ -n "$RUNTIME" ]; then if ! command -v "$RUNTIME" >/dev/null 2>&1; then die "${RUNTIME} is not installed" fi return fi local has_docker=false local has_podman=false if command -v docker >/dev/null 2>&1; then has_docker=true fi if command -v podman >/dev/null 2>&1; then has_podman=true fi if [ "$has_docker" = true ]; then RUNTIME="docker" elif [ "$has_podman" = true ]; then RUNTIME="podman" else die "Neither docker nor podman found. Install one of them:\n brew install docker\n brew install podman" fi # Verify the daemon is running if ! "$RUNTIME" info >/dev/null 2>&1; then die "${RUNTIME} daemon is not running. Is the ${RUNTIME} service started?" fi } # ------------------------------------------------------------------------------ # Build & run # ------------------------------------------------------------------------------ build_image() { local distro="$1" local containerfile="${CONTAINER_DIR}/Containerfile.${distro}" local image_name="${IMAGE_PREFIX}:${distro}" if [ ! -f "$containerfile" ]; then die "Containerfile not found: $containerfile" fi local build_args=() if [ "$REBUILD" = true ]; then build_args+=("--no-cache") fi info "Building ${image_name}..." if ! "$RUNTIME" build \ "${build_args[@]}" \ -f "$containerfile" \ -t "$image_name" \ "$REPO_ROOT" 2>&1; then return 1 fi } run_tests() { local distro="$1" local image_name="${IMAGE_PREFIX}:${distro}" info "Running tests on ${distro}..." if ! "$RUNTIME" run \ --rm \ --network=none \ "$image_name" \ bash test/run.sh 2>&1; then return 1 fi } run_distro() { local distro="$1" local start_time start_time="$(date +%s)" printf '\n%b══ %s ══%b\n' "$C_BOLD" "$distro" "$C_RESET" >&2 local status="PASS" if ! build_image "$distro"; then status="FAIL (build)" elif ! run_tests "$distro"; then status="FAIL (tests)" fi local end_time end_time="$(date +%s)" local duration=$(( end_time - start_time )) # Append to results RESULTS_DISTROS="${RESULTS_DISTROS}${distro}\n" RESULTS_STATUS="${RESULTS_STATUS}${status}\n" RESULTS_DURATION="${RESULTS_DURATION}${duration}s\n" 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 fi } # ------------------------------------------------------------------------------ # Summary # ------------------------------------------------------------------------------ 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 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 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)) done if [ "$any_failed" = true ]; then return 1 fi return 0 } # ------------------------------------------------------------------------------ # Main # ------------------------------------------------------------------------------ main() { parse_args "$@" detect_runtime info "Using runtime: ${RUNTIME}" if [ -n "$TARGET_DISTRO" ]; then # Validate distro name local valid=false for d in $DISTROS; do if [ "$d" = "$TARGET_DISTRO" ]; then valid=true break fi done if [ "$valid" = false ]; then die "Unknown distro: ${TARGET_DISTRO}. Available: ${DISTROS}" fi run_distro "$TARGET_DISTRO" else for d in $DISTROS; do run_distro "$d" done fi print_summary } main "$@"