#!/usr/bin/env bash # apricot-async-smoke.sh — End-to-end smoke for the p2-64 launch/status/fetch protocol. # # Exercises a tiny batch (smoke 1 50) through the full async loop: # 1. launch → bare stamp on stdout # 2. status → valid JSON immediately, state in {running, unreachable} # 3. wait loop → poll until state==complete (or fail) # 4. fetch → rsync results to .local/iter// # 5. verify → at least one game_*/turn_stats.jsonl present locally # # Skips gracefully (exit 0) if apricot is unreachable, so this can run on plum # without blocking when the RUN host is offline. # # Usage: # bash scripts/apricot-async-smoke.sh # default smoke 1 50 # POLL_TIMEOUT_S=600 bash scripts/apricot-async-smoke.sh # extend the wait set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" APRICOT="${APRICOT_SSH_ALIAS:-apricot}" POLL_TIMEOUT_S="${POLL_TIMEOUT_S:-900}" POLL_INTERVAL_S="${POLL_INTERVAL_S:-15}" log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*" >&2; } # ── Reachability gate: skip if apricot can't be reached at all. ────────────── if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$APRICOT" 'echo ok' >/dev/null 2>&1; then log "apricot unreachable; skipping smoke (exit 0)" exit 0 fi # ── Step 1: launch ─────────────────────────────────────────────────────────── log "launching smoke 1 50 …" STAMP="$(bash "$SCRIPT_DIR/apricot-run.sh" launch smoke 1 50)" if [[ -z "$STAMP" ]]; then log "FAIL: launch returned empty stamp" exit 1 fi log "launched stamp=$STAMP" # ── Step 2: status (must be valid JSON, must mention the stamp) ────────────── STATUS_JSON="$(bash "$SCRIPT_DIR/apricot-run.sh" status "$STAMP")" log "initial status: $STATUS_JSON" case "$STATUS_JSON" in *"\"stamp\":\"$STAMP\""*) ;; *) log "FAIL: status JSON missing stamp field"; exit 1 ;; esac # ── Step 3: poll until complete | failed (with timeout) ────────────────────── log "polling every ${POLL_INTERVAL_S}s up to ${POLL_TIMEOUT_S}s …" DEADLINE=$(( $(date +%s) + POLL_TIMEOUT_S )) STATE="running" while (( $(date +%s) < DEADLINE )); do STATUS_JSON="$(bash "$SCRIPT_DIR/apricot-run.sh" status "$STAMP" || true)" STATE="$(echo "$STATUS_JSON" | sed -n 's/.*"state":"\([^"]*\)".*/\1/p')" log "state=$STATE ($STATUS_JSON)" case "$STATE" in complete) break ;; failed) log "FAIL: batch failed; journalctl --user -u mc-batch-$STAMP on $APRICOT"; exit 1 ;; esac sleep "$POLL_INTERVAL_S" done if [[ "$STATE" != "complete" ]]; then log "FAIL: did not reach complete within ${POLL_TIMEOUT_S}s (last state=$STATE)" exit 1 fi # ── Step 4: fetch ──────────────────────────────────────────────────────────── LOCAL_DEST="$(bash "$SCRIPT_DIR/apricot-run.sh" fetch "$STAMP")" log "fetched to: $LOCAL_DEST" # ── Step 5: verify result presence ─────────────────────────────────────────── if ! find "$LOCAL_DEST" -path '*/game_*/turn_stats.jsonl' -type f | grep -q .; then log "FAIL: no turn_stats.jsonl found under $LOCAL_DEST" exit 1 fi log "OK — async protocol smoke passed for stamp=$STAMP"