Bash · 6314 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # Shared test harness for builtin tests
4 # =====================================
5 # Source this file from individual builtin test scripts.
6 # Each test file must set TEST_PREFIX before sourcing.
7
8 # Portable timeout: macOS has gtimeout (from coreutils) or no timeout
9 if command -v timeout >/dev/null 2>&1; then
10 run_with_timeout() { timeout "$@"; }
11 elif command -v gtimeout >/dev/null 2>&1; then
12 run_with_timeout() { gtimeout "$@"; }
13 else
14 run_with_timeout() {
15 _rwt_secs="$1"; shift
16 _rwt_tmp=$(mktemp /tmp/fortsh_timeout.XXXXXX)
17 ( "$@" ) > "$_rwt_tmp" 2>&1 &
18 _rwt_pid=$!
19 ( sleep "$_rwt_secs"; kill "$_rwt_pid" 2>/dev/null ) &
20 _rwt_wd=$!
21 wait "$_rwt_pid" 2>/dev/null
22 _rwt_rc=$?
23 kill "$_rwt_wd" 2>/dev/null
24 wait "$_rwt_wd" 2>/dev/null
25 cat "$_rwt_tmp"
26 rm -f "$_rwt_tmp"
27 return $_rwt_rc
28 }
29 fi
30
31 # Colors (POSIX-compliant way)
32 RED='\033[0;31m'
33 GREEN='\033[0;32m'
34 YELLOW='\033[1;33m'
35 BLUE='\033[0;34m'
36 NC='\033[0m'
37
38 CURRENT_SECTION=""
39 TEST_NUM=0
40
41 PASSED=0
42 FAILED=0
43 SKIPPED=0
44 FAILED_TESTS_LIST=""
45
46 # Get script directory (POSIX way)
47 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
48 FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../bin/fortsh}"
49
50 # Reference bash for expected output — must be bash 4+ for assoc arrays, case transforms
51 # macOS ships bash 3.2 which lacks these; override with BASH_REF=/opt/homebrew/bin/bash
52 BASH_REF="${BASH_REF:-bash}"
53
54 # Check if fortsh exists
55 if [ ! -x "$FORTSH_BIN" ]; then
56 printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
57 printf "Please run 'make' first or set FORTSH_BIN environment variable\n"
58 exit 1
59 fi
60
61 # Temp directory for test files
62 TEST_TMPDIR=$(mktemp -d)
63 cleanup() { rm -rf "$TEST_TMPDIR" 2>/dev/null; }
64 trap cleanup EXIT INT TERM
65
66 pass() {
67 TEST_NUM=$((TEST_NUM + 1))
68 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
69 PASSED=$((PASSED + 1))
70 }
71
72 fail() {
73 TEST_NUM=$((TEST_NUM + 1))
74 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
75 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
76 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
77 if [ -n "$2" ]; then printf " expected: %s\n" "$2"; fi
78 if [ -n "$3" ]; then printf " got: %s\n" "$3"; fi
79 FAILED=$((FAILED + 1))
80 }
81
82 skip() {
83 TEST_NUM=$((TEST_NUM + 1))
84 printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
85 SKIPPED=$((SKIPPED + 1))
86 }
87
88 section() {
89 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
90 TEST_NUM=0
91 printf "\n${BLUE}==========================================\n%s\n==========================================${NC}\n" "$1"
92 }
93
94 # Normalize shell name prefixes in error messages for comparison
95 normalize_shell_name() {
96 printf '%s\n' "$1" | sed -e 's/bash: line [0-9]*: /SHELL: /g' \
97 -e 's/fortsh: line [0-9]*: /SHELL: /g' \
98 -e 's/bash: /SHELL: /g' \
99 -e 's/fortsh: /SHELL: /g'
100 }
101
102 # Per-test timeout (seconds) — prevents hanging tests from blocking CI
103 TEST_TIMEOUT="${TEST_TIMEOUT:-10}"
104
105 # Helper: compare output against bash
106 compare_output() {
107 test_name="$1"; command="$2"
108 expected=$(run_with_timeout "$TEST_TIMEOUT" "$BASH_REF" -c "$command" 2>&1)
109 actual=$(run_with_timeout "$TEST_TIMEOUT" "$FORTSH_BIN" -c "$command" 2>&1)
110 norm_expected=$(normalize_shell_name "$expected")
111 norm_actual=$(normalize_shell_name "$actual")
112 if [ "$norm_expected" = "$norm_actual" ]; then pass "$test_name"
113 else fail "$test_name" "$expected" "$actual"; fi
114 }
115
116 # Helper: compare exit code only
117 compare_exit() {
118 test_name="$1"; command="$2"
119 run_with_timeout "$TEST_TIMEOUT" "$BASH_REF" -c "$command" >/dev/null 2>&1; expected=$?
120 run_with_timeout "$TEST_TIMEOUT" "$FORTSH_BIN" -c "$command" >/dev/null 2>&1; actual=$?
121 if [ "$expected" = "$actual" ]; then pass "$test_name"
122 else fail "$test_name" "exit $expected" "exit $actual"; fi
123 }
124
125 # Helper: compare both output and exit code
126 compare_both() {
127 test_name="$1"; command="$2"
128 expected_out=$(run_with_timeout "$TEST_TIMEOUT" "$BASH_REF" -c "$command" 2>&1); expected_exit=$?
129 actual_out=$(run_with_timeout "$TEST_TIMEOUT" "$FORTSH_BIN" -c "$command" 2>&1); actual_exit=$?
130 norm_expected=$(normalize_shell_name "$expected_out")
131 norm_actual=$(normalize_shell_name "$actual_out")
132 if [ "$norm_expected" = "$norm_actual" ] && [ "$expected_exit" = "$actual_exit" ]; then
133 pass "$test_name"
134 else
135 fail "$test_name" "out='$expected_out' exit=$expected_exit" "out='$actual_out' exit=$actual_exit"
136 fi
137 }
138
139 # Helper: check fortsh output matches expected string
140 check_output() {
141 test_name="$1"; command="$2"; expected="$3"
142 actual=$(timeout "$TEST_TIMEOUT" "$FORTSH_BIN" -c "$command" 2>&1)
143 if [ "$actual" = "$expected" ]; then pass "$test_name"
144 else fail "$test_name" "$expected" "$actual"; fi
145 }
146
147 # Helper: check fortsh exit code matches expected
148 check_exit() {
149 test_name="$1"; command="$2"; expected="$3"
150 timeout "$TEST_TIMEOUT" "$FORTSH_BIN" -c "$command" >/dev/null 2>&1; actual=$?
151 if [ "$actual" = "$expected" ]; then pass "$test_name"
152 else fail "$test_name" "exit $expected" "exit $actual"; fi
153 }
154
155 # Print summary — call at end of each test file
156 print_summary() {
157 printf "\n==========================================\n"
158 printf "%s TEST RESULTS\n" "$TEST_PREFIX"
159 printf "==========================================\n"
160 printf "Passed: %d\n" "$PASSED"
161 printf "Failed: %d\n" "$FAILED"
162 printf "Skipped: %d\n" "$SKIPPED"
163 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
164 printf "==========================================\n"
165
166 if [ $((PASSED + FAILED)) -gt 0 ]; then
167 PASS_RATE=$((PASSED * 100 / (PASSED + FAILED)))
168 printf "Pass rate: %d%%\n" "$PASS_RATE"
169 fi
170
171 if [ "$FAILED" -gt 0 ]; then
172 printf "\nFailed tests:\n"
173 printf "%b" "$FAILED_TESTS_LIST"
174 printf "==========================================\n"
175 fi
176
177 if [ "$FAILED" -eq 0 ]; then
178 printf "ALL %s TESTS PASSED!\n" "$TEST_PREFIX"
179 exit 0
180 else
181 exit 1
182 fi
183 }