|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Test suite for tool-latency.sh PostToolUse hook |
| 3 | +# Run: bash tests/test-tool-latency.sh |
| 4 | + |
| 5 | +set -u |
| 6 | +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| 7 | +ROOT_DIR="$(dirname "$SCRIPT_DIR")" |
| 8 | +HOOK="$ROOT_DIR/template/hooks/tool-latency.sh" |
| 9 | +PASSED=0; FAILED=0 |
| 10 | + |
| 11 | +pass() { echo " ✓ $1"; PASSED=$((PASSED+1)); } |
| 12 | +fail() { echo " ✗ $1: $2"; FAILED=$((FAILED+1)); } |
| 13 | + |
| 14 | +# Run hook in an isolated tmpdir so PROJECT_HASH is deterministic and |
| 15 | +# the counter file does not collide with the user's real metrics. |
| 16 | +WORK=$(mktemp -d) |
| 17 | +trap 'rm -rf "$WORK"' EXIT |
| 18 | +cd "$WORK" |
| 19 | + |
| 20 | +# md5 hash of the temp PWD — must match _hash() in the hook |
| 21 | +HASH=$(printf '%s' "$PWD" | md5sum 2>/dev/null | cut -c1-8 || \ |
| 22 | + printf '%s' "$PWD" | md5 -q 2>/dev/null | cut -c1-8 || \ |
| 23 | + printf '%s' "$PWD" | cksum | cut -d' ' -f1) |
| 24 | +COUNTER="/tmp/claude-tool-latency-${HASH}" |
| 25 | +rm -f "$COUNTER" |
| 26 | + |
| 27 | +echo "═══ tool-latency.sh tests ═══" |
| 28 | + |
| 29 | +# 1. Happy path: tool_name + duration_ms numeric → one line appended |
| 30 | +echo '{"tool_name":"Bash","duration_ms":234,"hook_event_name":"PostToolUse"}' | bash "$HOOK" |
| 31 | +if [[ -f "$COUNTER" ]] && grep -qE '^Bash\|234$' "$COUNTER"; then |
| 32 | + pass "happy path: writes 'Bash|234'" |
| 33 | +else |
| 34 | + fail "happy path" "expected 'Bash|234' line in $COUNTER, got: $(cat "$COUNTER" 2>/dev/null || echo '<missing>')" |
| 35 | +fi |
| 36 | + |
| 37 | +# 2. Multiple calls accumulate |
| 38 | +echo '{"tool_name":"Edit","duration_ms":12}' | bash "$HOOK" |
| 39 | +echo '{"tool_name":"Read","duration_ms":3}' | bash "$HOOK" |
| 40 | +LINES=$(wc -l < "$COUNTER" | tr -d ' ') |
| 41 | +if [[ "$LINES" == "3" ]]; then |
| 42 | + pass "accumulates across calls (3 lines)" |
| 43 | +else |
| 44 | + fail "accumulates" "expected 3 lines, got $LINES" |
| 45 | +fi |
| 46 | + |
| 47 | +# 3. Missing duration_ms → no-op (back-compat with Claude Code <v2.1.119) |
| 48 | +rm -f "$COUNTER" |
| 49 | +echo '{"tool_name":"Bash"}' | bash "$HOOK" |
| 50 | +if [[ ! -f "$COUNTER" ]]; then |
| 51 | + pass "missing duration_ms → no-op" |
| 52 | +else |
| 53 | + fail "missing duration_ms" "counter should not exist; got: $(cat "$COUNTER")" |
| 54 | +fi |
| 55 | + |
| 56 | +# 4. Non-numeric duration → no-op (defensive) |
| 57 | +rm -f "$COUNTER" |
| 58 | +echo '{"tool_name":"Bash","duration_ms":"slow"}' | bash "$HOOK" |
| 59 | +if [[ ! -f "$COUNTER" ]]; then |
| 60 | + pass "non-numeric duration_ms → no-op" |
| 61 | +else |
| 62 | + fail "non-numeric duration" "counter should not exist; got: $(cat "$COUNTER")" |
| 63 | +fi |
| 64 | + |
| 65 | +# 5. Pipe in tool_name is sanitized (won't break the parser) |
| 66 | +rm -f "$COUNTER" |
| 67 | +echo '{"tool_name":"weird|tool","duration_ms":7}' | bash "$HOOK" |
| 68 | +if grep -qE '^weird_tool\|7$' "$COUNTER"; then |
| 69 | + pass "pipe in tool_name sanitized to _" |
| 70 | +else |
| 71 | + fail "pipe sanitization" "expected 'weird_tool|7', got: $(cat "$COUNTER")" |
| 72 | +fi |
| 73 | + |
| 74 | +# 6. Empty stdin → no-op, exit 0 |
| 75 | +rm -f "$COUNTER" |
| 76 | +printf '' | bash "$HOOK" |
| 77 | +RC=$? |
| 78 | +if [[ "$RC" == "0" && ! -f "$COUNTER" ]]; then |
| 79 | + pass "empty stdin → exit 0, no-op" |
| 80 | +else |
| 81 | + fail "empty stdin" "rc=$RC, counter exists=$([[ -f $COUNTER ]] && echo yes || echo no)" |
| 82 | +fi |
| 83 | + |
| 84 | +# 7. Aggregation logic that session-report.sh uses (simulated) |
| 85 | +rm -f "$COUNTER" |
| 86 | +for d in 100 50 800 30; do |
| 87 | + echo "{\"tool_name\":\"Bash\",\"duration_ms\":$d}" | bash "$HOOK" |
| 88 | +done |
| 89 | +TOTAL=$(awk -F'|' 'BEGIN{s=0} {if($2 ~ /^[0-9]+$/) s+=$2} END{print s+0}' "$COUNTER") |
| 90 | +SLOWEST=$(awk -F'|' '$2 ~ /^[0-9]+$/ {if ($2+0 > max) {max=$2+0; tool=$1}} END{if (tool!="") printf "%s=%dms", tool, max; else print "none"}' "$COUNTER") |
| 91 | +if [[ "$TOTAL" == "980" && "$SLOWEST" == "Bash=800ms" ]]; then |
| 92 | + pass "session-report aggregation: total=980ms, slowest=Bash=800ms" |
| 93 | +else |
| 94 | + fail "aggregation" "total=$TOTAL slowest=$SLOWEST (expected 980 + Bash=800ms)" |
| 95 | +fi |
| 96 | + |
| 97 | +rm -f "$COUNTER" |
| 98 | + |
| 99 | +echo |
| 100 | +echo "----------" |
| 101 | +echo "$PASSED passed, $FAILED failed" |
| 102 | +[[ "$FAILED" == "0" ]] && exit 0 || exit 1 |
0 commit comments