|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SPDX-License-Identifier: MPL-2.0 |
| 3 | +# |
| 4 | +# Smoke test for hypatia_record_outcome() in scripts/dispatch-runner.sh. |
| 5 | +# |
| 6 | +# Covers the no-op short-circuit paths that don't require an actual |
| 7 | +# hypatia checkout or `mix` on PATH: |
| 8 | +# 1. HYPATIA_OUTCOME_REPORT=off → silent no-op, exit 0. |
| 9 | +# 2. HYPATIA_HOME not a directory → warn to stderr, exit 0. |
| 10 | +# 3. Empty / "none" / "null" recipe_id → silent no-op, exit 0. |
| 11 | +# |
| 12 | +# End-to-end paths that *do* talk to hypatia (exit 0/2/1) require a |
| 13 | +# running mix env and are covered by the integration suite on the |
| 14 | +# hypatia side. This file just guarantees the dispatch loop never |
| 15 | +# crashes on the optional-call ladder. |
| 16 | + |
| 17 | +set -euo pipefail |
| 18 | + |
| 19 | +SCRIPT_DIR=$(dirname "$(realpath "$0")") |
| 20 | +DISPATCH_RUNNER="${SCRIPT_DIR}/../scripts/dispatch-runner.sh" |
| 21 | + |
| 22 | +if [[ ! -f "$DISPATCH_RUNNER" ]]; then |
| 23 | + echo "FAIL: dispatch-runner.sh not found at $DISPATCH_RUNNER" |
| 24 | + exit 1 |
| 25 | +fi |
| 26 | + |
| 27 | +# We source the script in a guarded mode so we get function definitions |
| 28 | +# without triggering the main loop. The script exits early when its |
| 29 | +# manifest doesn't exist, so we point it at a non-existent path and |
| 30 | +# capture the early exit. |
| 31 | +TMP=$(mktemp -d /tmp/c15-smoke-XXXXXX) |
| 32 | +trap 'rm -rf "$TMP"' EXIT |
| 33 | + |
| 34 | +# Extract just the function definitions we need (between # --- Hypatia |
| 35 | +# closed-loop contract --- and the next "# --- " section). Cheap and |
| 36 | +# avoids running the main loop. |
| 37 | +awk ' |
| 38 | + /^# --- Hypatia closed-loop contract ---/ { capture=1 } |
| 39 | + capture { print } |
| 40 | + /^record_outcome\(\)/ && capture { record_seen=1 } |
| 41 | + record_seen && /^}/ { print "# end-extract"; exit } |
| 42 | +' "$DISPATCH_RUNNER" > "$TMP/extracted.sh" |
| 43 | + |
| 44 | +# Provide a stub REPOS_BASE so HYPATIA_HOME default resolves to |
| 45 | +# something the test can control. |
| 46 | +export REPOS_BASE="$TMP/repos" |
| 47 | + |
| 48 | +# Source the extracted block into this shell. |
| 49 | +# shellcheck disable=SC1091 |
| 50 | +source "$TMP/extracted.sh" |
| 51 | + |
| 52 | +pass=0 |
| 53 | +fail=0 |
| 54 | + |
| 55 | +assert_silent_ok() { |
| 56 | + local label="$1" |
| 57 | + shift |
| 58 | + local out |
| 59 | + if out=$("$@" 2>&1); then |
| 60 | + if [[ -z "$out" ]]; then |
| 61 | + pass=$((pass + 1)) |
| 62 | + echo "PASS: $label" |
| 63 | + else |
| 64 | + fail=$((fail + 1)) |
| 65 | + echo "FAIL: $label — expected silent, got: $out" |
| 66 | + fi |
| 67 | + else |
| 68 | + fail=$((fail + 1)) |
| 69 | + echo "FAIL: $label — expected exit 0, got non-zero" |
| 70 | + fi |
| 71 | +} |
| 72 | + |
| 73 | +assert_warn_ok() { |
| 74 | + local label="$1" |
| 75 | + local expect="$2" |
| 76 | + shift 2 |
| 77 | + local out |
| 78 | + if out=$("$@" 2>&1); then |
| 79 | + if [[ "$out" == *"$expect"* ]]; then |
| 80 | + pass=$((pass + 1)) |
| 81 | + echo "PASS: $label" |
| 82 | + else |
| 83 | + fail=$((fail + 1)) |
| 84 | + echo "FAIL: $label — expected warning containing '$expect', got: $out" |
| 85 | + fi |
| 86 | + else |
| 87 | + fail=$((fail + 1)) |
| 88 | + echo "FAIL: $label — expected exit 0, got non-zero" |
| 89 | + fi |
| 90 | +} |
| 91 | + |
| 92 | +# Case 1: opt-out. |
| 93 | +HYPATIA_OUTCOME_REPORT=off \ |
| 94 | + assert_silent_ok "opt-out silently no-ops" \ |
| 95 | + hypatia_record_outcome "rid-1" "owner/repo" "src/file.py" "success" |
| 96 | + |
| 97 | +# Case 2: HYPATIA_HOME not a directory. |
| 98 | +HYPATIA_OUTCOME_REPORT=on \ |
| 99 | +HYPATIA_HOME="$TMP/does-not-exist" \ |
| 100 | + assert_warn_ok "missing HYPATIA_HOME warns and no-ops" \ |
| 101 | + "not a directory" \ |
| 102 | + hypatia_record_outcome "rid-2" "owner/repo" "src/file.py" "success" |
| 103 | + |
| 104 | +# Cases 3a-c need HYPATIA_HOME to exist so the recipe-id guard fires |
| 105 | +# rather than the missing-dir guard. |
| 106 | +mkdir -p "$TMP/repos/hypatia" |
| 107 | + |
| 108 | +# Case 3a: empty recipe_id. |
| 109 | +HYPATIA_OUTCOME_REPORT=on \ |
| 110 | +HYPATIA_HOME="$TMP/repos/hypatia" \ |
| 111 | + assert_silent_ok "empty recipe_id silently no-ops" \ |
| 112 | + hypatia_record_outcome "" "owner/repo" "src/file.py" "success" |
| 113 | + |
| 114 | +# Case 3b: "none" recipe_id. |
| 115 | +HYPATIA_OUTCOME_REPORT=on \ |
| 116 | +HYPATIA_HOME="$TMP/repos/hypatia" \ |
| 117 | + assert_silent_ok "'none' recipe_id silently no-ops" \ |
| 118 | + hypatia_record_outcome "none" "owner/repo" "src/file.py" "success" |
| 119 | + |
| 120 | +# Case 3c: "null" recipe_id. |
| 121 | +HYPATIA_OUTCOME_REPORT=on \ |
| 122 | +HYPATIA_HOME="$TMP/repos/hypatia" \ |
| 123 | + assert_silent_ok "'null' recipe_id silently no-ops" \ |
| 124 | + hypatia_record_outcome "null" "owner/repo" "src/file.py" "success" |
| 125 | + |
| 126 | +echo "" |
| 127 | +echo "Results: $pass passed, $fail failed" |
| 128 | +[[ "$fail" -eq 0 ]] |
0 commit comments