#!/usr/bin/env bash # # scripts/smoke.sh — end-to-end smoke suite for banger. # # Drives a real create → start → ssh → exec → delete cycle against # real Firecracker + real KVM on the host. Intended as a pre-release # gate: the Go unit + integration tests don't and can't cover the # post-machine.Start path (socket ownership, guest boot, vsock agent # wait, guest SSH, workspace prepare). If this suite fails, don't # ship. # # State lives under $BANGER_SMOKE_XDG_DIR (set by `make smoke`, # defaults to build/smoke/xdg). It's ISOLATED from the invoking # user's real banger install via XDG_{CONFIG,STATE,CACHE,RUNTIME} # overrides, but PERSISTED across runs — so the first smoke pulls # the golden image, subsequent smokes reuse it. `make smoke-clean` # wipes it. # # Invoked via `make smoke`, which sets the three env vars below. # Don't run this directly unless you know they're set. set -euo pipefail log() { printf '[smoke] %s\n' "$*" >&2; } die() { printf '[smoke] FAIL: %s\n' "$*" >&2; exit 1; } : "${BANGER_SMOKE_BIN_DIR:?must point at the instrumented binary dir, set by make smoke}" : "${BANGER_SMOKE_COVER_DIR:?must point at the coverage dir, set by make smoke}" : "${BANGER_SMOKE_XDG_DIR:?must point at the isolated XDG root, set by make smoke}" BANGER="$BANGER_SMOKE_BIN_DIR/banger" BANGERD="$BANGER_SMOKE_BIN_DIR/bangerd" VSOCK_AGENT="$BANGER_SMOKE_BIN_DIR/banger-vsock-agent" for bin in "$BANGER" "$BANGERD" "$VSOCK_AGENT"; do [[ -x "$bin" ]] || die "binary missing or not executable: $bin" done # Persistent XDG dirs (state, cache, config) so repeated smoke # runs reuse the pulled golden image instead of re-downloading # ~300MB each time. Runtime dir needs to be fresh per-run because # it holds sockets the daemon cleans up on stop and refuses to # reuse if any are stale. mkdir -p \ "$BANGER_SMOKE_XDG_DIR/config" \ "$BANGER_SMOKE_XDG_DIR/state" \ "$BANGER_SMOKE_XDG_DIR/cache" runtime_dir="$(mktemp -d -t banger-smoke-runtime-XXXXXX)" # shellcheck disable=SC2064 trap "rm -rf '$runtime_dir'" EXIT chmod 0700 "$runtime_dir" export XDG_CONFIG_HOME="$BANGER_SMOKE_XDG_DIR/config" export XDG_STATE_HOME="$BANGER_SMOKE_XDG_DIR/state" export XDG_CACHE_HOME="$BANGER_SMOKE_XDG_DIR/cache" export XDG_RUNTIME_DIR="$runtime_dir" # Point banger at its companion binaries inside the smoke build. export BANGER_DAEMON_BIN="$BANGERD" export BANGER_VSOCK_AGENT_BIN="$VSOCK_AGENT" # Instrumented binaries dump coverage here on clean exit. export GOCOVERDIR="$BANGER_SMOKE_COVER_DIR" mkdir -p "$GOCOVERDIR" # Any smoke daemon left behind from a prior run that crashed mid- # scenario would reuse the stale socket path and confuse # ensureDaemon. Best-effort stop; ignore if nothing is running. "$BANGER" daemon stop >/dev/null 2>&1 || true # banger's vmDNS binds 127.0.0.1:42069 (UDP) hard. If the user's # real (non-smoke) daemon is running, its listener holds the port # and the smoke daemon's Open() fails before any scenario runs. # Fail fast with an actionable message — don't guess whether to # stop the user's daemon for them. if command -v ss >/dev/null 2>&1 && ss -Huln 2>/dev/null | awk '{print $4}' | grep -q '[:.]42069$'; then die 'port 127.0.0.1:42069 is already bound (likely your real banger daemon); stop it with `banger daemon stop` and re-run `make smoke`' fi # --- doctor ----------------------------------------------------------- log 'doctor: checking host readiness' if ! "$BANGER" doctor; then die 'doctor reported failures; fix the host before running smoke' fi # --- bare vm run ------------------------------------------------------ log "bare vm run: create + start + ssh + exec 'echo smoke-bare-ok' + --rm" bare_out="$("$BANGER" vm run --rm -- echo smoke-bare-ok)" || die "bare vm run exit $?" grep -q 'smoke-bare-ok' <<<"$bare_out" || die "bare vm run stdout missing marker: $bare_out" # --- workspace vm run ------------------------------------------------- log 'workspace vm run: preparing a throwaway git repo' repodir="$runtime_dir/fake-repo" mkdir -p "$repodir" ( cd "$repodir" git init -q -b main git config commit.gpgsign false git config user.name smoke git config user.email smoke@smoke echo 'smoke-workspace-marker' > smoke-file.txt git add . git commit -q -m init ) log "workspace vm run: create + start + workspace prepare + cat guest file + --rm" ws_out="$("$BANGER" vm run --rm "$repodir" -- cat /root/repo/smoke-file.txt)" || die "workspace vm run exit $?" grep -q 'smoke-workspace-marker' <<<"$ws_out" || die "workspace vm run didn't ship smoke-file.txt: $ws_out" # --- daemon stop (flushes coverage) ----------------------------------- log 'stopping daemon so instrumented binaries flush coverage' "$BANGER" daemon stop >/dev/null 2>&1 || true # Give the daemon a moment to write its covdata pod before the trap # tears down runtime_dir. sleep 0.5 log 'all scenarios passed'