|
| 1 | +#!/bin/bash |
| 2 | +# Tests for warp-notify.sh hang protection. |
| 3 | +# |
| 4 | +# Verifies that warp-notify.sh: |
| 5 | +# 1. Completes immediately when the target is writable (the happy path). |
| 6 | +# 2. Exits cleanly within the configured timeout when the target's output |
| 7 | +# buffer is full and never drained (the bug scenario: Warp UI hung). |
| 8 | +# 3. Defaults to a sane upper bound (2s) without explicit configuration. |
| 9 | +# 4. Same guarantees apply to the legacy variant. |
| 10 | +# |
| 11 | +# Implementation notes: |
| 12 | +# - We simulate "Warp UI hung" by pointing WARP_NOTIFY_TARGET at a FIFO |
| 13 | +# with no reader. The kernel blocks open()/write() on such a FIFO the |
| 14 | +# same way it blocks writes to a slave PTY whose master isn't reading, |
| 15 | +# which is the exact failure mode we observed in production. |
| 16 | +# - We export WARP_CLI_AGENT_PROTOCOL_VERSION and WARP_CLIENT_VERSION so |
| 17 | +# should_use_structured returns true and we exercise the write path. |
| 18 | + |
| 19 | +set -uo pipefail |
| 20 | + |
| 21 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd)" |
| 22 | + |
| 23 | +export WARP_CLI_AGENT_PROTOCOL_VERSION=1 |
| 24 | +export WARP_CLIENT_VERSION="v0.2026.04.01.08.00.stable_00" |
| 25 | + |
| 26 | +PASSED=0 |
| 27 | +FAILED=0 |
| 28 | + |
| 29 | +assert_eq() { |
| 30 | + local test_name="$1" |
| 31 | + local expected="$2" |
| 32 | + local actual="$3" |
| 33 | + if [ "$expected" = "$actual" ]; then |
| 34 | + echo " ✓ $test_name" |
| 35 | + PASSED=$((PASSED + 1)) |
| 36 | + else |
| 37 | + echo " ✗ $test_name" |
| 38 | + echo " expected: $expected" |
| 39 | + echo " actual: $actual" |
| 40 | + FAILED=$((FAILED + 1)) |
| 41 | + fi |
| 42 | +} |
| 43 | + |
| 44 | +assert_lt() { |
| 45 | + local test_name="$1" |
| 46 | + local actual="$2" |
| 47 | + local upper="$3" |
| 48 | + if [ "$actual" -lt "$upper" ] 2>/dev/null; then |
| 49 | + echo " ✓ $test_name ($actual < $upper)" |
| 50 | + PASSED=$((PASSED + 1)) |
| 51 | + else |
| 52 | + echo " ✗ $test_name (got $actual, expected < $upper)" |
| 53 | + FAILED=$((FAILED + 1)) |
| 54 | + fi |
| 55 | +} |
| 56 | + |
| 57 | +cleanup() { |
| 58 | + [ -n "${FIFO:-}" ] && rm -f "$FIFO" |
| 59 | +} |
| 60 | +trap cleanup EXIT |
| 61 | + |
| 62 | +run_notify() { |
| 63 | + local script="$1" |
| 64 | + shift |
| 65 | + local start end |
| 66 | + start=$(date +%s) |
| 67 | + bash "$script" "warp://cli-agent" '{"v":1,"agent":"claude","event":"test"}' "$@" |
| 68 | + LAST_RC=$? |
| 69 | + end=$(date +%s) |
| 70 | + LAST_ELAPSED=$((end - start)) |
| 71 | +} |
| 72 | + |
| 73 | +echo "=== warp-notify.sh hang protection ===" |
| 74 | + |
| 75 | +echo "" |
| 76 | +echo "--- Fast path: writable target completes immediately ---" |
| 77 | +WARP_NOTIFY_TARGET=/dev/null run_notify "$SCRIPT_DIR/warp-notify.sh" |
| 78 | +assert_eq "writable target exits 0" "0" "$LAST_RC" |
| 79 | +assert_lt "writable target completes under 2s" "$LAST_ELAPSED" "2" |
| 80 | + |
| 81 | +echo "" |
| 82 | +echo "--- Hang protection: blocked target times out at configured limit ---" |
| 83 | +FIFO=$(mktemp -u) |
| 84 | +mkfifo "$FIFO" |
| 85 | +WARP_NOTIFY_TARGET="$FIFO" WARP_NOTIFY_TIMEOUT_SEC=1 \ |
| 86 | + run_notify "$SCRIPT_DIR/warp-notify.sh" |
| 87 | +assert_eq "blocked target still exits 0 (best-effort)" "0" "$LAST_RC" |
| 88 | +# Timeout=1s plus watchdog/teardown overhead — generous bound to avoid CI flake. |
| 89 | +assert_lt "blocked target exits within 4s" "$LAST_ELAPSED" "4" |
| 90 | +rm -f "$FIFO" |
| 91 | + |
| 92 | +echo "" |
| 93 | +echo "--- Default timeout caps unbounded waits ---" |
| 94 | +FIFO=$(mktemp -u) |
| 95 | +mkfifo "$FIFO" |
| 96 | +WARP_NOTIFY_TARGET="$FIFO" run_notify "$SCRIPT_DIR/warp-notify.sh" |
| 97 | +assert_eq "default timeout still exits 0" "0" "$LAST_RC" |
| 98 | +# Default is 2s; allow 5s for CI scheduling jitter. |
| 99 | +assert_lt "default timeout exits within 5s" "$LAST_ELAPSED" "5" |
| 100 | +rm -f "$FIFO" |
| 101 | + |
| 102 | +echo "" |
| 103 | +echo "=== legacy/warp-notify.sh hang protection ===" |
| 104 | + |
| 105 | +echo "" |
| 106 | +echo "--- Fast path: writable target completes immediately ---" |
| 107 | +WARP_NOTIFY_TARGET=/dev/null run_notify "$SCRIPT_DIR/legacy/warp-notify.sh" |
| 108 | +assert_eq "legacy writable target exits 0" "0" "$LAST_RC" |
| 109 | +assert_lt "legacy writable target completes under 2s" "$LAST_ELAPSED" "2" |
| 110 | + |
| 111 | +echo "" |
| 112 | +echo "--- Hang protection: blocked target times out ---" |
| 113 | +FIFO=$(mktemp -u) |
| 114 | +mkfifo "$FIFO" |
| 115 | +WARP_NOTIFY_TARGET="$FIFO" WARP_NOTIFY_TIMEOUT_SEC=1 \ |
| 116 | + run_notify "$SCRIPT_DIR/legacy/warp-notify.sh" |
| 117 | +assert_eq "legacy blocked target still exits 0" "0" "$LAST_RC" |
| 118 | +assert_lt "legacy blocked target exits within 4s" "$LAST_ELAPSED" "4" |
| 119 | +rm -f "$FIFO" |
| 120 | + |
| 121 | +echo "" |
| 122 | +echo "=== Results: $PASSED passed, $FAILED failed ===" |
| 123 | + |
| 124 | +if [ "$FAILED" -gt 0 ]; then |
| 125 | + exit 1 |
| 126 | +fi |
0 commit comments