test-helpers.sh 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #!/usr/bin/env bash
  2. # Helper functions for Claude Code skill tests
  3. # Run Claude Code with a prompt and capture output
  4. # Usage: run_claude "prompt text" [timeout_seconds] [allowed_tools]
  5. run_claude() {
  6. local prompt="$1"
  7. local timeout="${2:-60}"
  8. local allowed_tools="${3:-}"
  9. local output_file=$(mktemp)
  10. # Build command as an argv array so timeout wraps claude directly.
  11. local cmd=(claude -p "$prompt")
  12. if [ -n "$allowed_tools" ]; then
  13. cmd+=(--allowed-tools="$allowed_tools")
  14. fi
  15. # Run Claude in headless mode with timeout. Redirect stdin from
  16. # /dev/null so the CLI can't block waiting for input and hang the suite.
  17. if timeout "$timeout" "${cmd[@]}" > "$output_file" 2>&1 < /dev/null; then
  18. cat "$output_file"
  19. rm -f "$output_file"
  20. return 0
  21. else
  22. local exit_code=$?
  23. cat "$output_file" >&2
  24. rm -f "$output_file"
  25. return $exit_code
  26. fi
  27. }
  28. # Check if output contains a pattern
  29. # Usage: assert_contains "output" "pattern" "test name"
  30. # Matching is case-insensitive: patterns are prose keywords, and models
  31. # freely capitalize skill terms ("Do Not Trust", "Spec Compliance").
  32. assert_contains() {
  33. local output="$1"
  34. local pattern="$2"
  35. local test_name="${3:-test}"
  36. if echo "$output" | grep -qi "$pattern"; then
  37. echo " [PASS] $test_name"
  38. return 0
  39. else
  40. echo " [FAIL] $test_name"
  41. echo " Expected to find: $pattern"
  42. echo " In output:"
  43. echo "$output" | sed 's/^/ /'
  44. return 1
  45. fi
  46. }
  47. # Check if output does NOT contain a pattern
  48. # Usage: assert_not_contains "output" "pattern" "test name"
  49. assert_not_contains() {
  50. local output="$1"
  51. local pattern="$2"
  52. local test_name="${3:-test}"
  53. if echo "$output" | grep -qi "$pattern"; then
  54. echo " [FAIL] $test_name"
  55. echo " Did not expect to find: $pattern"
  56. echo " In output:"
  57. echo "$output" | sed 's/^/ /'
  58. return 1
  59. else
  60. echo " [PASS] $test_name"
  61. return 0
  62. fi
  63. }
  64. # Check if output matches a count
  65. # Usage: assert_count "output" "pattern" expected_count "test name"
  66. assert_count() {
  67. local output="$1"
  68. local pattern="$2"
  69. local expected="$3"
  70. local test_name="${4:-test}"
  71. local actual=$(echo "$output" | grep -ci "$pattern" || echo "0")
  72. if [ "$actual" -eq "$expected" ]; then
  73. echo " [PASS] $test_name (found $actual instances)"
  74. return 0
  75. else
  76. echo " [FAIL] $test_name"
  77. echo " Expected $expected instances of: $pattern"
  78. echo " Found $actual instances"
  79. echo " In output:"
  80. echo "$output" | sed 's/^/ /'
  81. return 1
  82. fi
  83. }
  84. # Check if pattern A appears before pattern B
  85. # Usage: assert_order "output" "pattern_a" "pattern_b" "test name"
  86. assert_order() {
  87. local output="$1"
  88. local pattern_a="$2"
  89. local pattern_b="$3"
  90. local test_name="${4:-test}"
  91. # Get line numbers where patterns appear
  92. local line_a=$(echo "$output" | grep -ni "$pattern_a" | head -1 | cut -d: -f1)
  93. local line_b=$(echo "$output" | grep -ni "$pattern_b" | head -1 | cut -d: -f1)
  94. if [ -z "$line_a" ]; then
  95. echo " [FAIL] $test_name: pattern A not found: $pattern_a"
  96. echo " In output:"
  97. echo "$output" | sed 's/^/ /'
  98. return 1
  99. fi
  100. if [ -z "$line_b" ]; then
  101. echo " [FAIL] $test_name: pattern B not found: $pattern_b"
  102. echo " In output:"
  103. echo "$output" | sed 's/^/ /'
  104. return 1
  105. fi
  106. if [ "$line_a" -lt "$line_b" ]; then
  107. echo " [PASS] $test_name (A at line $line_a, B at line $line_b)"
  108. return 0
  109. else
  110. echo " [FAIL] $test_name"
  111. echo " Expected '$pattern_a' before '$pattern_b'"
  112. echo " But found A at line $line_a, B at line $line_b"
  113. return 1
  114. fi
  115. }
  116. # Create a temporary test project directory
  117. # Usage: test_project=$(create_test_project)
  118. create_test_project() {
  119. local test_dir=$(mktemp -d)
  120. echo "$test_dir"
  121. }
  122. # Cleanup test project
  123. # Usage: cleanup_test_project "$test_dir"
  124. cleanup_test_project() {
  125. local test_dir="$1"
  126. if [ -d "$test_dir" ]; then
  127. rm -rf "$test_dir"
  128. fi
  129. }
  130. # Create a simple plan file for testing
  131. # Usage: create_test_plan "$project_dir" "$plan_name"
  132. create_test_plan() {
  133. local project_dir="$1"
  134. local plan_name="${2:-test-plan}"
  135. local plan_file="$project_dir/docs/superpowers/plans/$plan_name.md"
  136. mkdir -p "$(dirname "$plan_file")"
  137. cat > "$plan_file" <<'EOF'
  138. # Test Implementation Plan
  139. ## Task 1: Create Hello Function
  140. Create a simple hello function that returns "Hello, World!".
  141. **File:** `src/hello.js`
  142. **Implementation:**
  143. ```javascript
  144. export function hello() {
  145. return "Hello, World!";
  146. }
  147. ```
  148. **Tests:** Write a test that verifies the function returns the expected string.
  149. **Verification:** `npm test`
  150. ## Task 2: Create Goodbye Function
  151. Create a goodbye function that takes a name and returns a goodbye message.
  152. **File:** `src/goodbye.js`
  153. **Implementation:**
  154. ```javascript
  155. export function goodbye(name) {
  156. return `Goodbye, ${name}!`;
  157. }
  158. ```
  159. **Tests:** Write tests for:
  160. - Default name
  161. - Custom name
  162. - Edge cases (empty string, null)
  163. **Verification:** `npm test`
  164. EOF
  165. echo "$plan_file"
  166. }
  167. # Export functions for use in tests
  168. export -f run_claude
  169. export -f assert_contains
  170. export -f assert_not_contains
  171. export -f assert_count
  172. export -f assert_order
  173. export -f create_test_project
  174. export -f cleanup_test_project
  175. export -f create_test_plan