Bash · 10132 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance - Previously Untested Features
4 # =====================================
5 # Tests for POSIX features that are implemented but lack test coverage
6
7 # Colors (POSIX-compliant way)
8 RED='\033[0;31m'
9 GREEN='\033[0;32m'
10 YELLOW='\033[1;33m'
11 BLUE='\033[0;34m'
12 NC='\033[0m'
13
14 # Test identification
15 TEST_PREFIX="[posix-untested]"
16 CURRENT_SECTION=""
17 TEST_NUM=0
18
19 PASSED=0
20 FAILED=0
21 SKIPPED=0
22 FAILED_TESTS_LIST=""
23
24 # Get script directory (POSIX way)
25 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
26 SHELL_BIN="${SHELL_BIN:?ERROR: SHELL_BIN must be set}"
27 BASH_REF="${BASH_REF:-bash}"
28
29 # Check if shell binary exists
30 if [ ! -x "$SHELL_BIN" ]; then
31 printf "${RED}ERROR${NC}: shell binary not found at $SHELL_BIN\n"
32 printf "Please set SHELL_BIN or set SHELL_BIN environment variable\n"
33 exit 1
34 fi
35
36 # Test result trackers
37 pass() {
38 TEST_NUM=$((TEST_NUM + 1))
39 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
40 PASSED=$((PASSED + 1))
41 }
42
43 fail() {
44 TEST_NUM=$((TEST_NUM + 1))
45 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
46 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
47 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
48 if [ -n "$2" ]; then
49 printf " posix: %s\n" "$2"
50 fi
51 if [ -n "$3" ]; then
52 printf " shell: %s\n" "$3"
53 fi
54 FAILED=$((FAILED + 1))
55 }
56
57 skip() {
58 TEST_NUM=$((TEST_NUM + 1))
59 printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
60 SKIPPED=$((SKIPPED + 1))
61 }
62
63 section() {
64 # Extract section number from header like "131. COMMAND BUILTIN"
65 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
66 TEST_NUM=0
67 printf "\n${BLUE}==========================================\n"
68 printf "%s\n" "$1"
69 printf "==========================================${NC}\n"
70 }
71
72 # Compare output between sh and fortsh
73 compare_posix_output() {
74 test_name="$1"
75 test_cmd="$2"
76
77 # Run with POSIX sh
78 posix_output=$(env $RC_DISABLE_ENV "$BASH_REF" -c "$test_cmd" 2>&1)
79 posix_exit=$?
80
81 # Run with fortsh
82 shell_output=$(env $RC_DISABLE_ENV "$SHELL_BIN" -c "$test_cmd" 2>&1)
83 shell_exit=$?
84
85 # Compare outputs
86 if [ "$posix_output" = "$shell_output" ] && [ "$posix_exit" = "$shell_exit" ]; then
87 pass "$test_name"
88 else
89 fail "$test_name" "$posix_output" "$shell_output"
90 fi
91 }
92
93 # Normalize shell error messages by stripping shell name and "line N: " prefix
94 # POSIX doesn't mandate error message format, so we normalize for comparison
95 normalize_error() {
96 echo "$1" | sed -e 's|^[^ ]*/[a-z]*sh[0-9]*: |sh: |' -e 's|^[a-z]*sh[0-9]*: |sh: |' -e 's/line [0-9]*: //'
97 }
98
99 # Compare error output, normalizing line number differences
100 compare_posix_error() {
101 test_name="$1"
102 test_cmd="$2"
103
104 posix_output=$(env $RC_DISABLE_ENV "$BASH_REF" -c "$test_cmd" 2>&1)
105 posix_exit=$?
106
107 shell_output=$(env $RC_DISABLE_ENV "$SHELL_BIN" -c "$test_cmd" 2>&1)
108 shell_exit=$?
109
110 posix_norm=$(normalize_error "$posix_output")
111 shell_norm=$(normalize_error "$shell_output")
112
113 if [ "$posix_norm" = "$shell_norm" ] && [ "$posix_exit" = "$shell_exit" ]; then
114 pass "$test_name"
115 else
116 fail "$test_name" "$posix_output" "$shell_output"
117 fi
118 }
119
120 # Test that fortsh accepts an option/command without error
121 test_accepts() {
122 test_name="$1"
123 test_cmd="$2"
124
125 if env $RC_DISABLE_ENV "$SHELL_BIN" -c "$test_cmd" >/dev/null 2>&1; then
126 pass "$test_name"
127 else
128 fail "$test_name" "should succeed" "failed or not implemented"
129 fi
130 }
131
132 # Test that command fails as expected
133 test_fails() {
134 test_name="$1"
135 test_cmd="$2"
136
137 if ! env $RC_DISABLE_ENV "$SHELL_BIN" -c "$test_cmd" >/dev/null 2>&1; then
138 pass "$test_name"
139 else
140 fail "$test_name" "should fail" "succeeded unexpectedly"
141 fi
142 }
143
144 # =====================================
145 # TESTS START HERE
146 # =====================================
147
148 section "131. COMMAND BUILTIN - PATH SEARCH"
149
150 compare_posix_output "command -v ls" 'command -v ls >/dev/null && echo found'
151 compare_posix_output "command -v nonexistent" 'command -v nonexistent >/dev/null || echo notfound'
152 test_accepts "command -p ls" 'command -p ls / >/dev/null'
153 compare_posix_output "command without options" 'command echo test'
154
155 section "132. MULTI-FD REDIRECTIONS"
156
157 # Create test file
158 compare_posix_output "fd 3 redirect write" 'exec 3>/tmp/posix_fd3.txt; echo test >&3; exec 3>&-; cat /tmp/posix_fd3.txt; rm /tmp/posix_fd3.txt'
159 compare_posix_output "fd 4 redirect read" 'echo data > /tmp/posix_fd4.txt; exec 4</tmp/posix_fd4.txt; read line <&4; echo $line; exec 4<&-; rm /tmp/posix_fd4.txt'
160 compare_posix_output "fd duplication" 'exec 5>&1; echo stdout >&5; exec 5>&-'
161 compare_posix_output "fd 3 and 4 together" 'exec 3>/tmp/posix_3.txt 4>/tmp/posix_4.txt; echo a >&3; echo b >&4; exec 3>&- 4>&-; cat /tmp/posix_3.txt /tmp/posix_4.txt; rm /tmp/posix_3.txt /tmp/posix_4.txt'
162
163 section "133. EXEC WITH SHELL REDIRECTIONS"
164
165 # Note: These redirect the shell itself, not just one command
166 # Save stdout to FD 3, redirect, write, restore, then cat (otherwise cat hangs on file still open for writing)
167 compare_posix_output "exec redirect stdout" 'exec 3>&1; exec >/tmp/posix_exec_out.txt; echo redirected; exec >&3; cat /tmp/posix_exec_out.txt; rm /tmp/posix_exec_out.txt'
168 compare_posix_output "exec redirect stdin" 'echo testdata > /tmp/posix_exec_in.txt; exec </tmp/posix_exec_in.txt; read data; echo $data; rm /tmp/posix_exec_in.txt'
169
170 section "134. SET OPTION INTERACTIONS"
171
172 compare_posix_output "set -e with true" 'set -e; true; echo ok'
173 compare_posix_output "set -u with unset var" 'set -u; echo ${UNSET_VAR:-default}'
174 compare_posix_output "set -C noclobber" 'set -C; echo test > /tmp/posix_clobber.txt; echo ok; rm /tmp/posix_clobber.txt'
175 compare_posix_output "set -C override with >|" 'set -C; echo test > /tmp/posix_clobber2.txt; echo override >| /tmp/posix_clobber2.txt; cat /tmp/posix_clobber2.txt; rm /tmp/posix_clobber2.txt'
176
177 section "135. SPECIAL BUILTIN ERROR HANDLING"
178
179 # POSIX: Special builtins must exit non-interactive shell on error
180 # Testing with subshells to avoid killing the test script
181
182 compare_posix_error "readonly error exits" '(readonly VAR=1; VAR=2 2>/dev/null; echo should not print) || echo exited'
183 compare_posix_output "export invalid" '(export 123INVALID=value 2>/dev/null; echo should not print) || echo exited'
184 compare_posix_output "set invalid option" '(set -@ 2>/dev/null; echo should not print) || echo exited'
185
186 section "136. ULIMIT TESTS"
187
188 test_accepts "ulimit display" 'ulimit'
189 test_accepts "ulimit -a all" 'ulimit -a >/dev/null'
190 test_accepts "ulimit -n files" 'ulimit -n >/dev/null'
191 test_accepts "ulimit -s stack" 'ulimit -s >/dev/null 2>&1'
192
193 section "137. NESTED PARAMETER EXPANSION"
194
195 compare_posix_output "nested default" 'unset A B; echo ${A:-${B:-default}}'
196 compare_posix_output "nested with set var" 'A=inner; unset B; echo ${B:-${A}}'
197 compare_posix_output "double nested" 'unset A B C; echo ${A:-${B:-${C:-triple}}}'
198
199 section "138. PARAMETER LENGTH EDGE CASES"
200
201 compare_posix_output "length of $@" 'set -- a b c; echo ${#@}'
202 compare_posix_output "length of $*" 'set -- a b c; echo ${#*}'
203 compare_posix_output "length of empty" 'VAR=; echo ${#VAR}'
204 compare_posix_output "length of unset" 'unset VAR; echo ${#VAR}'
205
206 section "139. QUOTING EDGE CASES"
207
208 compare_posix_output "empty double quotes" 'VAR=""; echo x${VAR}y'
209 compare_posix_output "empty single quotes" "VAR=''; echo x\${VAR}y"
210 compare_posix_output "adjacent quotes concat" 'echo "a"b"c"'
211 compare_posix_output "quote in quote" 'echo "it'\''s"'
212
213 section "140. BACKSLASH NEWLINE CONTINUATION"
214
215 compare_posix_output "line continuation in string" 'echo "test\
216 continuation"'
217 compare_posix_output "line continuation in command" 'ec\
218 ho test'
219
220 section "141. COMMENT IN COMMAND SUBSTITUTION"
221
222 compare_posix_output "comment in subshell" '$(# this is a comment
223 echo test)'
224 compare_posix_output "comment in backtick" '`# comment
225 echo test`'
226
227 section "142. REDIRECTION EDGE CASES"
228
229 compare_posix_output "close stdin" 'cat <&- 2>&1 | head -1'
230 compare_posix_error "close stdout" 'echo test >&- 2>&1'
231 compare_posix_output "read/write mode" 'echo data > /tmp/posix_rw.txt; cat <> /tmp/posix_rw.txt; rm /tmp/posix_rw.txt'
232
233 section "143. ERROR HANDLING EDGE CASES"
234
235 test_fails "assign to positional param" '$1=value 2>/dev/null'
236 test_fails "readonly reassign" 'readonly VAR=1; VAR=2 2>/dev/null'
237 # Test that execution continues after arithmetic error
238 # Check that "continued" appears in the output (error format may vary)
239 test_accepts "division by zero" 'echo $((5/0)) | cat; echo continued 2>&1 | grep -q continued'
240
241 section "144. SET -n (NOEXEC) TESTING"
242
243 # This tests if set -n is implemented
244 compare_posix_output "set -n parse only" 'set -n; echo "should not execute"; false' || skip "set -n not implemented"
245
246 section "145. SET -m (MONITOR) TESTING"
247
248 test_accepts "set -m monitor mode" 'set -m; set +m'
249 compare_posix_output "set -m doesn't affect output" 'set -m; echo test; set +m'
250
251 # =====================================
252 # SUMMARY
253 # =====================================
254
255 printf "\n==========================================\n"
256 printf "UNTESTED FEATURES TEST RESULTS ${TEST_PREFIX}\n"
257 printf "==========================================\n"
258 printf "${GREEN}Passed:${NC} %d\n" "$PASSED"
259 printf "${RED}Failed:${NC} %d\n" "$FAILED"
260 printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
261 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
262 printf "==========================================\n"
263
264 # Calculate pass rate
265 if [ "$((PASSED + FAILED))" -gt 0 ]; then
266 pass_rate=$((PASSED * 100 / (PASSED + FAILED)))
267 printf "Pass rate: %d%%\n" "$pass_rate"
268 fi
269
270 if [ "$FAILED" -gt 0 ]; then
271 printf "\n${RED}Failed tests:${NC}\n"
272 printf "%b" "$FAILED_TESTS_LIST"
273 printf "==========================================\n"
274 fi
275
276 if [ "$FAILED" -eq 0 ]; then
277 printf "${GREEN}ALL UNTESTED FEATURES TESTS PASSED!${NC} ✓\n"
278 exit 0
279 else
280 printf "${RED}SOME TESTS FAILED${NC} ✗\n"
281 exit 1
282 fi