Bash · 20196 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance Test Suite for rush
4 # =====================================
5 # Tests compliance with POSIX shell specification
6 # Uses /bin/sh for comparison (typically dash or bash in POSIX mode)
7
8 # Note: Using only POSIX-compliant constructs in this script
9 # No bash-isms allowed!
10
11 # Colors (POSIX-compliant way)
12 RED='\033[0;31m'
13 GREEN='\033[0;32m'
14 YELLOW='\033[1;33m'
15 BLUE='\033[0;34m'
16 NC='\033[0m'
17
18 # Test identification
19 TEST_PREFIX="[posix-test]"
20 CURRENT_SECTION=""
21 TEST_NUM=0
22
23 PASSED=0
24 FAILED=0
25 SKIPPED=0
26 FAILED_TESTS_LIST=""
27
28 # Get script directory (POSIX way)
29 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
30 RUSH_BIN="${RUSH_BIN:-$SCRIPT_DIR/../target/release/rush}"
31
32 # Check if fortsh exists
33 if [ ! -x "$RUSH_BIN" ]; then
34 printf "${RED}ERROR${NC}: rush binary not found at $RUSH_BIN\n"
35 printf "Please run 'make' first or set RUSH_BIN environment variable\n"
36 exit 1
37 fi
38
39 # Test result trackers
40 pass() {
41 TEST_NUM=$((TEST_NUM + 1))
42 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
43 PASSED=$((PASSED + 1))
44 }
45
46 fail() {
47 TEST_NUM=$((TEST_NUM + 1))
48 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
49 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
50 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
51 if [ -n "$2" ]; then
52 printf " posix: %s\n" "$2"
53 fi
54 if [ -n "$3" ]; then
55 printf " rush: %s\n" "$3"
56 fi
57 FAILED=$((FAILED + 1))
58 }
59
60 skip() {
61 TEST_NUM=$((TEST_NUM + 1))
62 printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
63 SKIPPED=$((SKIPPED + 1))
64 }
65
66 section() {
67 # Extract section number from header like "3. POSIX PARAMETER EXPANSION"
68 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
69 TEST_NUM=0
70 printf "\n"
71 printf "${BLUE}==========================================\n"
72 printf "%s\n" "$1"
73 printf "==========================================${NC}\n"
74 }
75
76 # Helper function to run command in both shells and compare
77 compare_posix_output() {
78 test_name="$1"
79 command="$2"
80 posix_file="/tmp/posix_comp_$$_posix"
81 fortsh_file="/tmp/posix_comp_$$_fortsh"
82
83 # Run in POSIX shell (sh)
84 bash -c "$command" > "$posix_file" 2>&1 || true
85
86 # Run in rush
87 "$RUSH_BIN" -c "$command" > "$fortsh_file" 2>&1 || true
88
89 # Compare outputs
90 if diff -q "$posix_file" "$fortsh_file" > /dev/null 2>&1; then
91 pass "$test_name"
92 else
93 fail "$test_name" "$(cat "$posix_file")" "$(cat "$fortsh_file")"
94 fi
95
96 rm -f "$posix_file" "$fortsh_file"
97 }
98
99 # Helper function to compare exit codes
100 compare_posix_exit_code() {
101 test_name="$1"
102 command="$2"
103
104 bash -c "$command" > /dev/null 2>&1
105 posix_exit=$?
106
107 "$RUSH_BIN" -c "$command" > /dev/null 2>&1
108 fortsh_exit=$?
109
110 if [ "$posix_exit" -eq "$fortsh_exit" ]; then
111 pass "$test_name"
112 else
113 fail "$test_name" "exit=$posix_exit" "exit=$fortsh_exit"
114 fi
115 }
116
117 # Cleanup
118 cleanup() {
119 rm -f /tmp/posix_comp_$$_* 2>/dev/null
120 rm -f /tmp/posix_test_* 2>/dev/null
121 }
122 trap cleanup EXIT INT TERM
123
124 section "1. POSIX BASIC COMMANDS"
125
126 compare_posix_output "echo simple" "echo hello"
127 compare_posix_output "echo with args" "echo one two three"
128 compare_posix_output "printf basic" "printf 'test\n'"
129 compare_posix_output "printf with args" "printf '%s %d\n' hello 42"
130
131 section "2. POSIX VARIABLE EXPANSION"
132
133 compare_posix_output "simple variable" "VAR=test; echo \$VAR"
134 compare_posix_output "variable in quotes" 'VAR=test; echo "$VAR"'
135 compare_posix_output "multiple vars" "A=hello; B=world; echo \$A \$B"
136 compare_posix_output "undefined variable" "echo \$UNDEFINED_VAR_XYZ_987"
137
138 section "3. POSIX PARAMETER EXPANSION"
139
140 # Basic parameter expansion
141 compare_posix_output "default value" 'echo "${UNSET:-default}"'
142 compare_posix_output "assign default" 'UNSET=; echo "${UNSET:=assigned}"; echo $UNSET'
143 compare_posix_output "error if unset" 'echo "${VAR:+alternative}"'
144 compare_posix_output "string length" 'VAR=hello; echo "${#VAR}"'
145
146 # Prefix removal (# and ##)
147 compare_posix_output "remove shortest prefix" 'VAR=foo.bar.baz; echo "${VAR#*.}"'
148 compare_posix_output "remove longest prefix" 'VAR=foo.bar.baz; echo "${VAR##*.}"'
149 compare_posix_output "prefix no match" 'VAR=hello; echo "${VAR#x*}"'
150 compare_posix_output "prefix remove slash" 'VAR=/usr/local/bin; echo "${VAR#/*/}"'
151
152 # Suffix removal (% and %%)
153 compare_posix_output "remove shortest suffix" 'VAR=foo.bar.baz; echo "${VAR%.*}"'
154 compare_posix_output "remove longest suffix" 'VAR=foo.bar.baz; echo "${VAR%%.*}"'
155 compare_posix_output "suffix no match" 'VAR=hello; echo "${VAR%x*}"'
156 compare_posix_output "suffix remove extension" 'VAR=file.tar.gz; echo "${VAR%.gz}"'
157
158 section "4. POSIX COMMAND SUBSTITUTION"
159
160 compare_posix_output "backtick substitution" 'echo `echo test`'
161 compare_posix_output "dollar paren substitution" "echo \$(echo test)"
162 compare_posix_output "nested substitution" "echo \$(echo \$(echo nested))"
163
164 section "5. POSIX ARITHMETIC"
165
166 # POSIX arithmetic uses expr or $(( ))
167 compare_posix_output "expr addition" "expr 5 + 3"
168 compare_posix_output "expr multiplication" "expr 4 \* 3"
169 compare_posix_output "expr division" "expr 15 / 3"
170
171 section "6. POSIX REDIRECTION"
172
173 compare_posix_output "output redirect" "echo test > /tmp/posix_test_out; cat /tmp/posix_test_out"
174 compare_posix_output "append redirect" "echo line1 > /tmp/posix_test_app; echo line2 >> /tmp/posix_test_app; wc -l < /tmp/posix_test_app"
175 compare_posix_output "input redirect" "echo input > /tmp/posix_test_in; cat < /tmp/posix_test_in"
176 compare_posix_output "stderr redirect" "ls /nonexistent 2>&1 | grep -c 'cannot access\|No such\|not found'"
177
178 section "7. POSIX PIPELINES"
179
180 compare_posix_output "simple pipe" "echo hello | cat"
181 compare_posix_output "two-stage pipe" "echo test | cat | tr t T"
182 compare_posix_output "pipe with filter" "printf 'a\nb\nc\n' | grep b"
183
184 section "8. POSIX TEST COMMAND"
185
186 compare_posix_exit_code "test -f file" "touch /tmp/posix_test_file && test -f /tmp/posix_test_file"
187 compare_posix_exit_code "test -d directory" "test -d /tmp"
188 compare_posix_exit_code "test -n nonempty" "test -n 'hello'"
189 compare_posix_exit_code "test -z empty" "test -z ''"
190 compare_posix_exit_code "test string =" "test 'hello' = 'hello'"
191 compare_posix_exit_code "test string !=" "test 'hello' != 'world'"
192 compare_posix_exit_code "test number -eq" "test 5 -eq 5"
193 compare_posix_exit_code "test number -ne" "test 5 -ne 3"
194 compare_posix_exit_code "test number -gt" "test 5 -gt 3"
195 compare_posix_exit_code "test number -ge" "test 5 -ge 5"
196 compare_posix_exit_code "test number -lt" "test 3 -lt 5"
197 compare_posix_exit_code "test number -le" "test 3 -le 3"
198
199 section "9. POSIX CONDITIONALS"
200
201 compare_posix_output "if true" "if true; then echo yes; fi"
202 compare_posix_output "if false else" "if false; then echo no; else echo yes; fi"
203 compare_posix_output "if-elif-else" "X=2; if [ \$X -eq 1 ]; then echo one; elif [ \$X -eq 2 ]; then echo two; else echo other; fi"
204
205 section "10. POSIX LOOPS"
206
207 compare_posix_output "for loop" "for i in a b c; do echo \$i; done"
208 compare_posix_output "while loop" "i=3; while [ \$i -gt 0 ]; do echo \$i; i=\$((i - 1)); done"
209 compare_posix_output "until loop" "i=1; until [ \$i -gt 3 ]; do echo \$i; i=\$((i + 1)); done"
210
211 section "11. POSIX CASE STATEMENT"
212
213 compare_posix_output "case exact match" "x=2; case \$x in 1) echo one;; 2) echo two;; esac"
214 compare_posix_output "case pattern match" "x=hello; case \$x in h*) echo h_prefix;; esac"
215 compare_posix_output "case default" "x=z; case \$x in a) echo a;; b) echo b;; *) echo default;; esac"
216 compare_posix_output "case multiple patterns" "x=b; case \$x in a|b|c) echo abc;; *) echo other;; esac"
217
218 section "12. POSIX FUNCTIONS"
219
220 compare_posix_output "simple function" "func() { echo hello; }; func"
221 compare_posix_output "function with args" "func() { echo \$1 \$2; }; func foo bar"
222 compare_posix_output "function return" "func() { return 42; }; func; echo \$?"
223 compare_posix_output "function \$# args" "func() { echo \$#; }; func a b c"
224
225 section "13. POSIX SPECIAL VARIABLES"
226
227 compare_posix_output "\$? exit status" "true; echo \$?"
228 compare_posix_output "\$? after false" "false; echo \$?"
229 compare_posix_output "\$# argument count" "set -- a b c; echo \$#"
230 compare_posix_output "\$@ all arguments" "set -- a b c; echo \$@"
231 compare_posix_output "\$* all arguments" "set -- a b c; echo \$*"
232 compare_posix_output "\$0 script name" "echo \$0 | grep -c sh"
233
234 section "14. POSIX LOGICAL OPERATORS"
235
236 compare_posix_exit_code "true && true" "true && true"
237 compare_posix_exit_code "true && false" "true && false"
238 compare_posix_exit_code "false || true" "false || true"
239 compare_posix_exit_code "false || false" "false || false"
240 compare_posix_output "command && echo" "true && echo success"
241 compare_posix_output "command || echo" "false || echo fallback"
242 compare_posix_output "! negation" "! false && echo negated"
243
244 section "15. POSIX QUOTING"
245
246 compare_posix_output "single quote literal" "echo '\$VAR'"
247 compare_posix_output "double quote expand" 'VAR=test; echo "$VAR"'
248 compare_posix_output "escape in double" 'echo "test\$var"'
249 compare_posix_output "backslash escape" 'echo test\ word'
250
251 section "16. POSIX SUBSHELLS"
252
253 compare_posix_output "subshell grouping" "(echo a; echo b) | wc -l"
254 compare_posix_output "subshell var isolation" "(VAR=inner; echo \$VAR); echo \$VAR"
255
256 section "17. POSIX COMPOUND COMMANDS"
257
258 compare_posix_output "command grouping {}" "{ echo a; echo b; } | wc -l"
259 compare_posix_output "command list ;" "echo a; echo b"
260
261 section "18. POSIX HERE DOCUMENTS"
262
263 compare_posix_output "simple heredoc" "cat <<EOF
264 line1
265 line2
266 EOF"
267
268 compare_posix_output "heredoc with vars" "VAR=test; cat <<EOF
269 value=\$VAR
270 EOF"
271
272 compare_posix_output "quoted heredoc" "cat <<'EOF'
273 \$VAR
274 EOF"
275
276 section "19. POSIX WORD EXPANSION ORDER"
277
278 # POSIX specifies: tilde, parameter, command subst, arithmetic, field splitting, pathname, quote removal
279 compare_posix_output "expansion order" "VAR='a b'; echo \$VAR"
280 compare_posix_output "quoted expansion" 'VAR="a b"; echo "$VAR"'
281
282 section "20. POSIX PATHNAME EXPANSION (GLOBBING)"
283
284 # Setup test files
285 mkdir -p /tmp/posix_test_glob
286 touch /tmp/posix_test_glob/a.txt /tmp/posix_test_glob/b.txt /tmp/posix_test_glob/c.dat
287
288 compare_posix_output "glob * pattern" "ls /tmp/posix_test_glob/*.txt 2>/dev/null | wc -l"
289 compare_posix_output "glob ? pattern" "ls /tmp/posix_test_glob/?.txt 2>/dev/null | wc -l"
290 compare_posix_output "glob [abc] pattern" "ls /tmp/posix_test_glob/[ab].txt 2>/dev/null | wc -l"
291
292 section "21. POSIX FIELD SPLITTING (IFS)"
293
294 compare_posix_output "default IFS" "VAR='a b c'; set -- \$VAR; echo \$#"
295 compare_posix_output "custom IFS" "IFS=:; VAR='a:b:c'; set -- \$VAR; echo \$1"
296
297 section "22. POSIX EXIT STATUS"
298
299 compare_posix_exit_code "true exit status" "true"
300 compare_posix_exit_code "false exit status" "false"
301 compare_posix_exit_code "command not found" "nonexistent_command_xyz 2>/dev/null"
302 compare_posix_exit_code "return from function" "func() { return 3; }; func"
303
304 section "23. POSIX SET BUILTIN"
305
306 compare_posix_output "set positional" "set -- a b c; echo \$1 \$2 \$3"
307 compare_posix_output "set shift" "set -- a b c; shift; echo \$1"
308 compare_posix_output "set shift n" "set -- a b c d; shift 2; echo \$1"
309
310 section "24. POSIX EXPORT"
311
312 compare_posix_output "export variable" "export VAR=test; sh -c 'echo \$VAR'"
313
314 section "25. POSIX READONLY"
315
316 compare_posix_exit_code "readonly assignment" "readonly VAR=test; VAR=new 2>/dev/null"
317
318 section "26. POSIX UNSET"
319
320 compare_posix_output "unset variable" "VAR=test; unset VAR; echo \${VAR:-empty}"
321 compare_posix_output "unset nonexistent" "unset NONEXISTENT_VAR; echo ok"
322 compare_posix_exit_code "unset readonly fails" "readonly X=1; unset X 2>/dev/null"
323 compare_posix_output "unset function" "f() { echo hi; }; unset -f f; f 2>/dev/null || echo gone"
324
325 section "27. POSIX EVAL"
326
327 compare_posix_output "eval simple" "eval 'echo hello'"
328 compare_posix_output "eval variable" "CMD='echo test'; eval \$CMD"
329 compare_posix_output "eval assignment" "eval 'X=5'; echo \$X"
330 compare_posix_output "eval command subst" "eval 'echo \$(echo nested)'"
331 compare_posix_output "eval with semicolon" "eval 'echo a; echo b'"
332
333 section "28. POSIX EXEC"
334
335 compare_posix_output "exec replaces shell" "exec echo done"
336 compare_posix_exit_code "exec nonexistent" "exec /nonexistent/command 2>/dev/null"
337
338 section "29. POSIX COLON BUILTIN"
339
340 compare_posix_output "colon no-op" ": ; echo ok"
341 compare_posix_exit_code "colon exit status" ":"
342 compare_posix_output "colon with args" ": arg1 arg2; echo ok"
343 compare_posix_output "colon in if" "if :; then echo yes; fi"
344
345 section "30. POSIX DOT/SOURCE"
346
347 # Create temp script
348 echo 'SOURCED_VAR=from_source' > /tmp/posix_test_source.sh
349 compare_posix_output "dot source script" ". /tmp/posix_test_source.sh; echo \$SOURCED_VAR"
350 compare_posix_exit_code "dot nonexistent" ". /nonexistent_file 2>/dev/null"
351
352 section "31. POSIX CD AND PWD"
353
354 compare_posix_output "cd and pwd" "cd /tmp && pwd"
355 compare_posix_output "cd - returns to OLDPWD" "cd /tmp; cd /; cd -"
356 compare_posix_exit_code "cd nonexistent" "cd /nonexistent_dir 2>/dev/null"
357 compare_posix_output "pwd builtin" "pwd | grep -c /"
358
359 section "32. POSIX UMASK"
360
361 compare_posix_output "umask display" "umask | grep -E '^[0-9]{3,4}\$'"
362 compare_posix_output "umask set and restore" "OLD=\$(umask); umask 077; umask \$OLD"
363
364 section "33. POSIX WAIT"
365
366 compare_posix_output "wait for background" "sleep 0.1 & wait; echo done"
367 compare_posix_exit_code "wait no jobs" "wait"
368
369 section "34. POSIX TIMES"
370
371 # times is optional but common
372 compare_posix_output "times output exists" "times 2>/dev/null | head -1 || echo skipped"
373
374 section "35. POSIX BREAK AND CONTINUE"
375
376 compare_posix_output "break in for" "for i in 1 2 3 4 5; do [ \$i -eq 3 ] && break; echo \$i; done"
377 compare_posix_output "continue in for" "for i in 1 2 3 4 5; do [ \$i -eq 3 ] && continue; echo \$i; done"
378 compare_posix_output "break in while" "i=0; while [ \$i -lt 10 ]; do i=\$((i+1)); [ \$i -eq 3 ] && break; echo \$i; done"
379 compare_posix_output "break 2 nested" "for i in a b; do for j in 1 2 3; do [ \$j -eq 2 ] && break 2; echo \$i\$j; done; done"
380 compare_posix_output "continue 2 nested" "for i in a b; do for j in 1 2 3; do [ \$j -eq 2 ] && continue 2; echo \$i\$j; done; done"
381
382 section "36. POSIX SIGNAL HANDLING"
383
384 compare_posix_output "trap list" "trap 2>/dev/null; echo ok"
385 compare_posix_output "trap on exit" "trap 'echo exiting' EXIT; exit 0"
386 compare_posix_exit_code "trap reset" "trap - INT"
387
388 section "37. POSIX BACKGROUND AND JOBS"
389
390 compare_posix_output "background job" "sleep 0.1 & echo started; wait"
391 compare_posix_output "\$! last background pid" "sleep 0.1 & echo \$! | grep -E '^[0-9]+\$'"
392
393 section "38. POSIX ALIAS"
394
395 compare_posix_output "alias definition" "alias ll='ls -l'; alias | grep ll"
396 compare_posix_output "unalias" "alias x='echo test'; unalias x; alias | grep -c 'x=' || echo 0"
397
398 section "39. POSIX COMMAND SEARCH"
399
400 compare_posix_output "type builtin" "type echo | grep -c builtin"
401 compare_posix_output "command -v" "command -v echo | grep -c echo"
402 compare_posix_exit_code "command not found" "command -v nonexistent_xyz 2>/dev/null"
403
404 section "40. POSIX COMPLEX EXPANSIONS"
405
406 compare_posix_output "nested parameter expansion" 'A=hello; B=A; eval "echo \$$B"'
407 compare_posix_output "expansion in assignment" 'X=$(echo test); echo $X'
408 compare_posix_output "arithmetic in expansion" 'echo $((2 + 3 * 4))'
409 compare_posix_output "multiple substitutions" 'A=1; B=2; echo $(echo $A) $(echo $B)'
410
411 section "41. POSIX ADDITIONAL TESTS"
412
413 compare_posix_output "expr modulo" "expr 10 % 3"
414 compare_posix_output "double bracket" "X=5; [ \$X -gt 3 ] && [ \$X -lt 10 ] && echo range"
415 compare_posix_output "if negation" "if ! false; then echo yes; fi"
416 compare_posix_output "while true break" "i=0; while true; do i=\$((i+1)); [ \$i -ge 3 ] && break; done; echo \$i"
417 compare_posix_output "case pipe pattern" "x=a; case \$x in a|b|c) echo match;; esac"
418
419 section "42. POSIX STRING TESTS"
420
421 compare_posix_output "string length" 'X=hello; echo ${#X}'
422 compare_posix_output "string default" 'echo ${UNDEFINED:-default}'
423 compare_posix_output "string assign" 'unset X; : ${X:=assigned}; echo $X'
424 compare_posix_output "string error" 'X=val; echo ${X:+alternate}'
425 compare_posix_output "prefix removal" 'X=hello.world; echo ${X#*.}'
426
427 section "43. POSIX NUMERIC TESTS"
428
429 compare_posix_output "arith add" 'echo $((5 + 3))'
430 compare_posix_output "arith sub" 'echo $((10 - 4))'
431 compare_posix_output "arith mul" 'echo $((6 * 7))'
432 compare_posix_output "arith div" 'echo $((20 / 4))'
433 compare_posix_output "arith mod" 'echo $((17 % 5))'
434 compare_posix_output "arith neg" 'echo $((-5))'
435 compare_posix_output "arith paren" 'echo $(((2 + 3) * 4))'
436
437 section "44. POSIX FILE TESTS"
438
439 compare_posix_exit_code "test file exists" "test -e /etc/passwd"
440 compare_posix_exit_code "test file regular" "test -f /etc/passwd"
441 compare_posix_exit_code "test dir" "test -d /tmp"
442 compare_posix_exit_code "test readable" "test -r /etc/passwd"
443 compare_posix_exit_code "test not exists" "test -e /nonexistent_xyz"
444
445 section "45. POSIX LOOP TESTS"
446
447 compare_posix_output "for numbers" "for i in 1 2 3; do echo \$i; done"
448 compare_posix_output "while count" "i=0; while [ \$i -lt 3 ]; do i=\$((i+1)); done; echo \$i"
449 compare_posix_output "until count" "i=0; until [ \$i -ge 3 ]; do i=\$((i+1)); done; echo \$i"
450 compare_posix_output "for break" "for i in 1 2 3 4 5; do [ \$i -eq 3 ] && break; echo \$i; done"
451 compare_posix_output "for continue" "for i in 1 2 3; do [ \$i -eq 2 ] && continue; echo \$i; done"
452
453 section "46. POSIX FUNCTION TESTS"
454
455 compare_posix_output "func basic" "f() { echo test; }; f"
456 compare_posix_output "func args" "f() { echo \$1 \$2; }; f a b"
457 compare_posix_output "func return" "f() { return 5; }; f; echo \$?"
458 compare_posix_output "func local sim" "f() { (X=local; echo \$X); }; X=global; f; echo \$X"
459
460 section "47. POSIX REDIRECT TESTS"
461
462 compare_posix_output "redirect out" "echo test > /tmp/redir_out_\$\$; cat /tmp/redir_out_\$\$; rm /tmp/redir_out_\$\$"
463 compare_posix_output "redirect append" "echo a > /tmp/redir_app_\$\$; echo b >> /tmp/redir_app_\$\$; wc -l < /tmp/redir_app_\$\$; rm /tmp/redir_app_\$\$"
464 compare_posix_output "redirect in" "echo test > /tmp/redir_in_\$\$; cat < /tmp/redir_in_\$\$; rm /tmp/redir_in_\$\$"
465 compare_posix_output "redirect stderr" "ls /nonexistent 2>/dev/null; echo done"
466
467 section "48. POSIX PIPE TESTS"
468
469 compare_posix_output "pipe simple" "echo test | cat"
470 compare_posix_output "pipe chain" "echo test | cat | cat | cat"
471 compare_posix_output "pipe filter" "printf 'a\nb\nc\n' | grep b"
472 compare_posix_output "pipe count" "printf 'a\nb\nc\n' | wc -l"
473
474 section "49. POSIX SUBSHELL TESTS"
475
476 compare_posix_output "subshell basic" "(echo test)"
477 compare_posix_output "subshell var" "X=outer; (X=inner; echo \$X); echo \$X"
478 compare_posix_output "subshell exit" "(exit 42); echo \$?"
479 compare_posix_output "subshell pipe" "(echo a; echo b) | wc -l"
480
481 section "50. POSIX BRACE TESTS"
482
483 compare_posix_output "brace basic" "{ echo test; }"
484 compare_posix_output "brace multi" "{ echo a; echo b; echo c; }"
485 compare_posix_output "brace var" "X=1; { X=2; }; echo \$X"
486 compare_posix_output "brace pipe" "{ echo a; echo b; } | wc -l"
487
488 # Summary
489 printf "\n"
490 printf "==========================================\n"
491 printf "POSIX COMPLIANCE TEST RESULTS ${TEST_PREFIX}\n"
492 printf "==========================================\n"
493 printf "${GREEN}Passed:${NC} %d\n" "$PASSED"
494 printf "${RED}Failed:${NC} %d\n" "$FAILED"
495 printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
496 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
497 printf "==========================================\n"
498
499 if [ $((PASSED + FAILED)) -gt 0 ]; then
500 PASS_RATE=$((PASSED * 100 / (PASSED + FAILED)))
501 printf "Pass rate: %d%%\n" "$PASS_RATE"
502 fi
503
504 if [ "$FAILED" -gt 0 ]; then
505 printf "\n${RED}Failed tests:${NC}\n"
506 printf "%b" "$FAILED_TESTS_LIST"
507 printf "==========================================\n"
508 fi
509
510 if [ "$FAILED" -eq 0 ]; then
511 printf "${GREEN}ALL POSIX COMPLIANCE TESTS PASSED!${NC} ✓\n"
512 exit 0
513 else
514 printf "${RED}SOME TESTS FAILED${NC} ✗\n"
515 exit 1
516 fi