tenseleyflow/bensch / 219dd82

Browse files

extract shared test harness for shell script tests

Rename FORTSH_BIN -> SHELL_BIN (now required, no default path).
Keep BASH_REF as-is — semantically correct as reference shell.
Rename temp file prefix fortsh_timeout -> bensch_timeout.

Source: fortsh/tests/builtins/test_harness.sh
Authored by espadonne
SHA
219dd82c1e69060133664d43e86802467c8621c7
Parents
3d3ed91
Tree
e1abf91

1 changed file

StatusFile+-
A suites/test_harness.sh 183 0
suites/test_harness.shadded
@@ -0,0 +1,183 @@
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/bensch_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
+SHELL_BIN="${SHELL_BIN:?ERROR: SHELL_BIN must be set}"
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 "$SHELL_BIN" ]; then
56
+    printf "${RED}ERROR${NC}: shell binary not found at $SHELL_BIN\n"
57
+    printf "Please run 'make' first or set SHELL_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" "$SHELL_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" "$SHELL_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" "$SHELL_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" "$SHELL_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" "$SHELL_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
+}