Bash · 27314 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance Advanced Test Suite for shell
4 # =====================================
5 # Additional coverage based on OpenGroup POSIX.1-2017 specification
6 # Fills gaps not covered in basic, extended, and builtins test suites
7 #
8 # Coverage areas:
9 # - Advanced control flow (break/continue with levels)
10 # - Additional special built-ins (exec, command)
11 # - Extended set options (-f, -x, -v, -a)
12 # - File descriptor operations
13 # - Advanced arithmetic operators
14 # - Signal handling extensions
15 # - Pathname expansion edge cases
16 # - IFS edge cases
17 # - Function recursion and advanced usage
18
19 # Colors (POSIX-compliant way)
20 RED='\033[0;31m'
21 GREEN='\033[0;32m'
22 YELLOW='\033[1;33m'
23 BLUE='\033[0;34m'
24 NC='\033[0m'
25
26 # Test identification
27 TEST_PREFIX="[posix-advanced]"
28 CURRENT_SECTION=""
29 TEST_NUM=0
30
31 PASSED=0
32 FAILED=0
33 SKIPPED=0
34 FAILED_TESTS_LIST=""
35
36 # Get script directory (POSIX way)
37 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
38 SHELL_BIN="${SHELL_BIN:?ERROR: SHELL_BIN must be set}"
39 BASH_REF="${BASH_REF:-bash}"
40
41 # Check if shell binary exists
42 if [ ! -x "$SHELL_BIN" ]; then
43 printf "${RED}ERROR${NC}: shell binary not found at $SHELL_BIN\n"
44 printf "Please set SHELL_BIN or set SHELL_BIN environment variable\n"
45 exit 1
46 fi
47
48 # Test result trackers
49 pass() {
50 TEST_NUM=$((TEST_NUM + 1))
51 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
52 PASSED=$((PASSED + 1))
53 }
54
55 fail() {
56 TEST_NUM=$((TEST_NUM + 1))
57 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
58 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
59 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
60 if [ -n "$2" ]; then
61 printf " posix: %s\n" "$2"
62 fi
63 if [ -n "$3" ]; then
64 printf " shell: %s\n" "$3"
65 fi
66 FAILED=$((FAILED + 1))
67 }
68
69 skip() {
70 TEST_NUM=$((TEST_NUM + 1))
71 printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
72 SKIPPED=$((SKIPPED + 1))
73 }
74
75 section() {
76 # Extract section number from header like "51. BREAK CONTINUE"
77 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
78 TEST_NUM=0
79 printf "\n"
80 printf "${BLUE}==========================================\n"
81 printf "%s\n" "$1"
82 printf "==========================================${NC}\n"
83 }
84
85 # Normalize shell error messages by stripping shell name and "line N: " prefix
86 normalize_output() {
87 sed -e 's|^[^ ]*/[a-z]*sh[0-9]*: |sh: |' -e 's|^[a-z]*sh[0-9]*: |sh: |' -e 's/line [0-9]*: //'
88 }
89
90 # Helper function to run command in both shells and compare
91 compare_posix_output() {
92 test_name="$1"
93 command="$2"
94 posix_file="/tmp/posix_adv_$$_posix"
95 shell_file="/tmp/posix_adv_$$_shell"
96
97 # Run in POSIX shell (sh)
98 "$BASH_REF" -c "$command" 2>&1 | normalize_output > "$posix_file" || true
99
100 # Run in shell under test
101 "$SHELL_BIN" -c "$command" 2>&1 | normalize_output > "$shell_file" || true
102
103 # Compare outputs
104 if diff -q "$posix_file" "$shell_file" > /dev/null 2>&1; then
105 pass "$test_name"
106 else
107 fail "$test_name" "$(cat "$posix_file")" "$(cat "$shell_file")"
108 fi
109
110 rm -f "$posix_file" "$shell_file"
111 }
112
113 # Helper function to compare exit codes
114 compare_posix_exit_code() {
115 test_name="$1"
116 command="$2"
117
118 "$BASH_REF" -c "$command" > /dev/null 2>&1
119 posix_exit=$?
120
121 "$SHELL_BIN" -c "$command" > /dev/null 2>&1
122 shell_exit=$?
123
124 if [ "$posix_exit" -eq "$shell_exit" ]; then
125 pass "$test_name"
126 else
127 fail "$test_name" "exit=$posix_exit" "exit=$shell_exit"
128 fi
129 }
130
131 # Cleanup
132 cleanup() {
133 rm -f /tmp/posix_adv_$$_* 2>/dev/null
134 rm -rf /tmp/posix_advanced_test_* 2>/dev/null
135 }
136 trap cleanup EXIT INT TERM
137
138 section "51. BREAK AND CONTINUE IN LOOPS"
139
140 compare_posix_output "break in for loop" 'for i in 1 2 3 4 5; do echo $i; if [ $i -eq 3 ]; then break; fi; done'
141 compare_posix_output "continue in for loop" 'for i in 1 2 3 4 5; do if [ $i -eq 3 ]; then continue; fi; echo $i; done'
142 compare_posix_output "break in while loop" 'i=0; while [ $i -lt 5 ]; do i=$((i+1)); echo $i; if [ $i -eq 3 ]; then break; fi; done'
143 compare_posix_output "continue in while loop" 'i=0; while [ $i -lt 5 ]; do i=$((i+1)); if [ $i -eq 3 ]; then continue; fi; echo $i; done'
144 compare_posix_output "break in until loop" 'i=0; until [ $i -ge 5 ]; do i=$((i+1)); echo $i; if [ $i -eq 3 ]; then break; fi; done'
145
146 section "52. NESTED LOOPS WITH BREAK/CONTINUE"
147
148 compare_posix_output "break inner loop" 'for i in 1 2; do for j in a b c; do echo $i$j; if [ $j = b ]; then break; fi; done; done'
149 compare_posix_output "break with level" 'for i in 1 2; do for j in a b; do echo $i$j; if [ $j = b ]; then break 2; fi; done; done'
150 compare_posix_output "continue outer loop" 'for i in 1 2 3; do for j in a b; do echo $i$j; if [ $j = a ]; then continue 2; fi; done; done'
151
152 section "53. EXEC BUILTIN"
153
154 compare_posix_output "exec with redirect" 'exec 3>&1; echo test >&3; exec 3>&-; echo done'
155 compare_posix_exit_code "exec replace shell" '(exec true); echo $?'
156 compare_posix_output "exec without command" 'exec 2>&1; pwd >/dev/null'
157
158 section "54. COMMAND BUILTIN"
159
160 compare_posix_output "command -v test" 'command -v test | grep -c test'
161 compare_posix_output "command -v echo" 'command -v echo | grep -c echo'
162 compare_posix_exit_code "command -v nonexistent" '! command -v nonexistent_cmd_xyz'
163
164 section "55. READ BUILTIN - THOROUGH TESTING"
165
166 compare_posix_output "read single var" 'echo hello | read VAR 2>/dev/null || VAR=hello; echo $VAR'
167 compare_posix_output "read multiple vars" 'echo one two three | { read A B C; echo $A $B $C; }'
168 compare_posix_output "read with IFS" 'IFS=:; echo a:b:c | { read X Y Z; echo $X $Y $Z; }'
169 compare_posix_output "read remaining to last" 'echo a b c d e | { read X Y Z; echo "$Z"; }'
170
171 section "56. FILE DESCRIPTOR DUPLICATION"
172
173 compare_posix_output "dup stdout to fd3" 'exec 3>&1; echo test >&3'
174 compare_posix_output "dup stdin from fd" 'exec 3</dev/null; cat <&3; exec 3<&-; echo ok'
175 compare_posix_exit_code "close stdout" '(exec 1>&-; echo test 2>/dev/null); echo $?'
176
177 section "57. SET -F (NOGLOB)"
178
179 compare_posix_output "noglob disables expansion" 'set -f; echo /tmp/*.xyz; set +f'
180 compare_posix_output "noglob with literal star" 'set -f; VAR="a * b"; echo $VAR; set +f'
181 compare_posix_output "glob after set +f" 'set -f; set +f; echo /tmp 2>/dev/null | grep -c tmp'
182
183 section "58. SET -X (XTRACE)"
184
185 compare_posix_output "xtrace shows commands" 'set -x; echo test 2>/tmp/xtrace_$$; set +x; grep -c echo /tmp/xtrace_$$; rm -f /tmp/xtrace_$$'
186 compare_posix_output "xtrace in function" 'f() { set -x; echo inner 2>/tmp/xtrace_fn_$$; set +x; }; f; grep -c echo /tmp/xtrace_fn_$$; rm -f /tmp/xtrace_fn_$$'
187
188 section "59. SET -V (VERBOSE)"
189
190 compare_posix_output "verbose shows input" 'set -v; : test 2>&1 | grep -c test; set +v'
191
192 section "60. SET -A (ALLEXPORT)"
193
194 compare_posix_output "allexport exports vars" 'set -a; TEST_VAR=value; sh -c "echo \$TEST_VAR"'
195 compare_posix_output "allexport off" 'set +a; TEST2=val; sh -c "echo \${TEST2:-empty}"'
196
197 section "61. TRAP WITH SIGNALS"
198
199 compare_posix_output "trap INT signal" 'trap "echo caught" INT; trap | grep INT'
200 compare_posix_output "trap TERM signal" 'trap "echo term" TERM; trap | grep TERM'
201 compare_posix_output "trap HUP signal" 'trap "echo hup" HUP; trap | grep HUP'
202 compare_posix_output "trap with number" 'trap "echo sig15" 15; trap | grep -c 15'
203
204 section "62. TRAP INHERITANCE IN SUBSHELLS"
205
206 compare_posix_output "trap not inherited" 'trap "echo parent" EXIT; (trap | grep -c EXIT || echo 0)'
207 compare_posix_output "subshell can set trap" '(trap "echo sub" EXIT; exit 0)'
208
209 section "63. PARAMETER EXPANSION :? ERROR"
210
211 compare_posix_exit_code "error if unset" 'unset VAR; echo ${VAR:?error} 2>/dev/null'
212 compare_posix_exit_code "error if null" 'VAR=; echo ${VAR:?null} 2>/dev/null'
213 compare_posix_output "no error if set" 'VAR=test; echo ${VAR:?error}'
214
215 section "64. PARAMETER EXPANSION WITH SPECIAL PARAMS"
216
217 compare_posix_output "length of positional params" 'set -- a b c; echo ${#@}'
218 compare_posix_output "length of $*" 'set -- x y; echo ${#*}'
219 compare_posix_output "length of $1" 'set -- hello; echo ${#1}'
220
221 section "65. WAIT WITH ARGUMENTS"
222
223 compare_posix_output "wait specific PID" 'sleep 0.1 & pid=$!; wait $pid; echo $?'
224 compare_posix_output "wait all background" '(sleep 0.1 &); wait; echo ok'
225 compare_posix_exit_code "wait nonexistent PID" 'wait 999999'
226
227 section "66. IFS EDGE CASES"
228
229 compare_posix_output "empty IFS" 'IFS=; VAR="a b c"; set -- $VAR; echo $#'
230 compare_posix_output "IFS whitespace only" 'IFS=" \t\n"; VAR="a b"; set -- $VAR; echo $# $1 $2'
231 compare_posix_output "IFS custom delimiter" 'IFS=,; VAR="a,b,c"; set -- $VAR; echo $2'
232 compare_posix_output "IFS leading delimiters" 'IFS=:; VAR=":a:b"; set -- $VAR; echo $#'
233
234 section "67. ARITHMETIC BITWISE OPERATORS"
235
236 compare_posix_output "bitwise AND" 'echo $((12 & 10))'
237 compare_posix_output "bitwise OR" 'echo $((12 | 10))'
238 compare_posix_output "bitwise XOR" 'echo $((12 ^ 10))'
239 compare_posix_output "bitwise NOT" 'echo $((~5))'
240 compare_posix_output "left shift" 'echo $((3 << 2))'
241 compare_posix_output "right shift" 'echo $((12 >> 2))'
242
243 section "68. ARITHMETIC ASSIGNMENT OPERATORS"
244
245 compare_posix_output "add assign" 'X=5; echo $((X += 3)); echo $X'
246 compare_posix_output "subtract assign" 'X=10; echo $((X -= 3)); echo $X'
247 compare_posix_output "multiply assign" 'X=4; echo $((X *= 2)); echo $X'
248 compare_posix_output "divide assign" 'X=20; echo $((X /= 4)); echo $X'
249 compare_posix_output "modulo assign" 'X=17; echo $((X %= 5)); echo $X'
250
251 section "69. ARITHMETIC INCREMENT/DECREMENT"
252
253 compare_posix_output "post increment" 'X=5; echo $((X++)); echo $X'
254 compare_posix_output "pre increment" 'X=5; echo $((++X)); echo $X'
255 compare_posix_output "post decrement" 'X=5; echo $((X--)); echo $X'
256 compare_posix_output "pre decrement" 'X=5; echo $((--X)); echo $X'
257
258 section "70. ARITHMETIC TERNARY OPERATOR"
259
260 compare_posix_output "ternary true" 'echo $((5 > 3 ? 10 : 20))'
261 compare_posix_output "ternary false" 'echo $((5 < 3 ? 10 : 20))'
262 compare_posix_output "ternary nested" 'echo $((1 ? 2 : 3 ? 4 : 5))'
263
264 section "71. PIPELINE NEGATION"
265
266 compare_posix_exit_code "negate true" '! true'
267 compare_posix_exit_code "negate false" '! false'
268 compare_posix_output "negate pipeline" '! echo test | grep -q xyz; echo $?'
269 compare_posix_exit_code "negate compound" '! (exit 0)'
270
271 section "72. THREE-STAGE PIPELINES"
272
273 compare_posix_output "three stage pipe" 'echo abc | tr a A | tr b B'
274 compare_posix_output "four stage pipe" 'echo test | cat | cat | wc -l'
275 compare_posix_exit_code "multi-stage exit" 'true | true | true'
276
277 section "73. CDPATH VARIABLE"
278
279 compare_posix_output "CDPATH usage" 'mkdir -p /tmp/posix_cdpath_test/subdir; CDPATH=/tmp/posix_cdpath_test; (cd subdir 2>/dev/null && pwd) | grep -c subdir; rm -rf /tmp/posix_cdpath_test'
280
281 section "74. OLDPWD VARIABLE"
282
283 compare_posix_output "OLDPWD set" 'OLD=/tmp; cd /tmp >/dev/null; cd / >/dev/null; echo $OLDPWD | grep -c tmp'
284 compare_posix_output "cd - uses OLDPWD" 'cd /tmp >/dev/null; cd / >/dev/null; cd - >/dev/null; pwd | grep -c tmp'
285
286 section "75. PATHNAME EXPANSION - BRACKET EXPRESSIONS"
287
288 mkdir -p /tmp/posix_advanced_test_bracket
289 touch /tmp/posix_advanced_test_bracket/a1.txt /tmp/posix_advanced_test_bracket/a2.txt
290 touch /tmp/posix_advanced_test_bracket/b1.txt /tmp/posix_advanced_test_bracket/b2.txt
291 touch /tmp/posix_advanced_test_bracket/c1.txt
292
293 compare_posix_output "bracket range" 'ls /tmp/posix_advanced_test_bracket/[a-b]1.txt 2>/dev/null | wc -l'
294 compare_posix_output "bracket negation" 'ls /tmp/posix_advanced_test_bracket/[!a]*.txt 2>/dev/null | wc -l'
295 compare_posix_output "bracket char class" 'ls /tmp/posix_advanced_test_bracket/[[:lower:]]1.txt 2>/dev/null | wc -l'
296
297 rm -rf /tmp/posix_advanced_test_bracket
298
299 section "76. PATHNAME EXPANSION - DOTFILE MATCHING"
300
301 mkdir -p /tmp/posix_advanced_test_dotfile
302 touch /tmp/posix_advanced_test_dotfile/.hidden
303 touch /tmp/posix_advanced_test_dotfile/visible
304
305 compare_posix_output "star no dotfiles" 'ls /tmp/posix_advanced_test_dotfile/* 2>/dev/null | wc -l'
306 compare_posix_output "explicit dot match" 'ls /tmp/posix_advanced_test_dotfile/.* 2>/dev/null | grep -c hidden'
307
308 rm -rf /tmp/posix_advanced_test_dotfile
309
310 section "77. FOR LOOP VARIATIONS"
311
312 compare_posix_output "for with glob" 'touch /tmp/posix_for_1.txt /tmp/posix_for_2.txt /tmp/posix_for_3.txt; for f in /tmp/posix_for_*.txt; do echo $f; done | wc -l; rm /tmp/posix_for_*.txt'
313 compare_posix_output "for with no items" 'for x in; do echo $x; done; echo empty'
314 compare_posix_output "for with quoted items" 'for i in "a b" "c d"; do echo $i; done | wc -l'
315
316 section "78. CASE PATTERN MATCHING"
317
318 compare_posix_output "case multiple pattern" 'x=b; case $x in a|b|c) echo match;; *) echo no;; esac'
319 compare_posix_output "case glob pattern" 'x=hello; case $x in h*) echo prefix;; esac'
320 compare_posix_output "case question mark" 'x=ab; case $x in ??) echo two;; esac'
321 compare_posix_output "case bracket" 'x=a; case $x in [abc]) echo bracket;; esac'
322
323 section "79. FUNCTION VARIATIONS"
324
325 compare_posix_output "function recursion" 'fact() { if [ $1 -le 1 ]; then echo 1; else echo $(($1 * $(fact $(($1 - 1))))); fi; }; fact 5'
326 compare_posix_output "function unset" 'f() { echo test; }; f; unset -f f; command -v f >/dev/null 2>&1; echo $?'
327 compare_posix_output "nested return" 'a() { b; echo $?; }; b() { return 42; }; a'
328
329 section "80. EXPANSION IN DIFFERENT CONTEXTS"
330
331 compare_posix_output "tilde in assignment" 'VAR=~; echo $VAR | grep -c ^/'
332 compare_posix_output "expansion in case" 'VAR=test; case $VAR in test) echo match;; esac'
333 compare_posix_output "no expansion in quotes" 'echo "~" | grep -c "~"'
334
335 section "81. REDIRECTION ORDER AND PRECEDENCE"
336
337 compare_posix_output "multiple redirects" 'echo test >/tmp/redir1 2>&1 >/tmp/redir2; cat /tmp/redir1 /tmp/redir2 2>/dev/null | wc -l; rm -f /tmp/redir1 /tmp/redir2'
338 compare_posix_output "redirect compound cmd" '{ echo a; echo b; } >/tmp/redir_compound; wc -l < /tmp/redir_compound; rm -f /tmp/redir_compound'
339
340 section "82. ERROR HANDLING IN EXPANSIONS"
341
342 compare_posix_exit_code "division by zero" 'echo $((5 / 0)) 2>/dev/null'
343 compare_posix_exit_code "expansion error" 'set -u; echo $UNDEFINED_VAR_XYZ 2>/dev/null'
344
345 section "83. SPECIAL PARAMETERS - ADDITIONAL"
346
347 compare_posix_output "PPID is numeric" 'echo $PPID | grep -c "^[0-9]*$"'
348 compare_posix_output "LINENO exists" 'echo $LINENO | grep -c "^[0-9]*$"'
349
350 section "84. ALIAS AND UNALIAS"
351
352 compare_posix_output "alias definition" 'alias ll="ls -l"; alias ll | grep -c "ls -l"'
353 compare_posix_output "unalias removes" 'alias test_alias=echo; unalias test_alias; alias test_alias 2>&1 | grep -c "not found"'
354
355 section "85. HASH BUILTIN OPERATIONS"
356
357 compare_posix_output "hash -r clears" 'hash -r; echo $?'
358 compare_posix_exit_code "hash command" 'hash echo 2>/dev/null'
359
360 section "86. MULTIPLE SEMICOLONS AND EDGE CASES"
361
362 compare_posix_output "multiple semicolons" 'echo a;; echo b'
363 compare_posix_output "semicolon at start" '; echo test'
364 compare_posix_exit_code "empty command" ''
365
366 section "87. BACKSLASH LINE CONTINUATION"
367
368 compare_posix_output "backslash continuation" 'echo hel\
369 lo'
370 compare_posix_output "backslash in command" 'ec\
371 ho test'
372
373 section "88. COMMENT HANDLING"
374
375 compare_posix_output "comment after command" 'echo visible # this is comment'
376 compare_posix_output "comment alone" '# comment
377 echo after'
378
379 section "89. QUOTING EDGE CASES"
380
381 compare_posix_output "empty string quotes" 'echo "" | wc -l'
382 compare_posix_output "adjacent quotes" 'echo "a"b"c"'
383 compare_posix_output "quote within quote" "echo \"it's\" | grep -c \"'\""
384
385 section "90. COMPOUND COMMAND REDIRECTION"
386
387 compare_posix_output "subshell with redirect" '(echo a; echo b) | wc -l'
388 compare_posix_output "brace group redirect" '{ echo x; echo y; } | wc -l'
389 compare_posix_output "if statement redirect" 'if true; then echo yes; fi | cat'
390
391 section "91. POSIX PARAMETER LENGTH"
392
393 compare_posix_output "length of empty" 'X=""; echo ${#X}'
394 compare_posix_output "length with spaces" 'X="a b c"; echo ${#X}'
395 compare_posix_output "length special chars" 'X="$@#!"; echo ${#X}'
396 compare_posix_output "length unicode" 'X="abc"; echo ${#X}'
397
398 section "92. POSIX DEFAULT VALUES VARIANTS"
399
400 compare_posix_output ":-colon vs dash" 'X=""; echo "${X:-default}" "${X-default}"'
401 compare_posix_output ":=colon vs equals" 'unset Y; echo "${Y:=def}"; echo $Y'
402 compare_posix_output ":+colon vs plus" 'X=val; echo "${X:+alt}" "${X+alt}"'
403 compare_posix_output ":?colon vs question" 'X=val; echo "${X:?err}" 2>/dev/null'
404
405 section "93. POSIX PATTERN REMOVAL EDGE CASES"
406
407 compare_posix_output "remove empty pattern" 'X=test; echo "${X#}"'
408 compare_posix_output "remove star pattern" 'X=test; echo "${X#*}"'
409 compare_posix_output "remove all with ##" 'X=test; echo "${X##*}"'
410 compare_posix_output "suffix empty" 'X=test; echo "${X%}"'
411 compare_posix_output "suffix star" 'X=test; echo "${X%*}"'
412 compare_posix_output "suffix all" 'X=test; echo "${X%%*}"'
413
414 section "94. POSIX ARITHMETIC OPERATORS"
415
416 compare_posix_output "arithmetic modulo" 'echo $((17 % 5))'
417 compare_posix_output "arithmetic negative" 'echo $((-5 + 3))'
418 compare_posix_output "arithmetic parens" 'echo $(((2 + 3) * 4))'
419 compare_posix_output "arithmetic bitwise and" 'echo $((12 & 10))'
420 compare_posix_output "arithmetic bitwise or" 'echo $((12 | 10))'
421 compare_posix_output "arithmetic bitwise xor" 'echo $((12 ^ 10))'
422 compare_posix_output "arithmetic shift left" 'echo $((1 << 4))'
423 compare_posix_output "arithmetic shift right" 'echo $((16 >> 2))'
424
425 section "95. POSIX ARITHMETIC COMPARISONS"
426
427 compare_posix_output "arith less than" 'echo $((3 < 5))'
428 compare_posix_output "arith greater than" 'echo $((5 > 3))'
429 compare_posix_output "arith less equal" 'echo $((3 <= 3))'
430 compare_posix_output "arith greater equal" 'echo $((3 >= 3))'
431 compare_posix_output "arith equal" 'echo $((5 == 5))'
432 compare_posix_output "arith not equal" 'echo $((5 != 3))'
433 compare_posix_output "arith logical and" 'echo $((1 && 1))'
434 compare_posix_output "arith logical or" 'echo $((0 || 1))'
435
436 section "96. POSIX COMMAND SUBSTITUTION EDGE CASES"
437
438 compare_posix_output "cmd sub with quotes" 'echo $(echo "hello world")'
439 compare_posix_output "cmd sub trailing newlines" 'echo "$(printf "a\n\n\n")"b'
440 compare_posix_output "cmd sub with pipe" 'echo $(echo test | tr t T)'
441 compare_posix_output "cmd sub with redirect" 'echo $(cat </dev/null)'
442 compare_posix_output "backtick with quotes" 'echo `echo "hello"`'
443
444 section "97. POSIX SUBSHELL VARIABLE ISOLATION"
445
446 compare_posix_output "subshell no export" 'X=1; (X=2; echo $X); echo $X'
447 compare_posix_output "subshell unset" 'X=1; (unset X; echo ${X:-empty}); echo $X'
448 # Note: (( expr )) is arithmetic syntax in bash, not nested subshell. Compare exit codes.
449 compare_posix_exit_code "double paren arithmetic" '(( echo nested ))'
450 compare_posix_output "subshell exit" '(exit 5); echo $?'
451
452 section "98. POSIX BRACE GROUP VS SUBSHELL"
453
454 compare_posix_output "brace group modifies" 'X=1; { X=2; }; echo $X'
455 compare_posix_output "subshell isolates" 'X=1; (X=2); echo $X'
456 compare_posix_output "brace with semicolon" '{ echo a; echo b; }'
457 compare_posix_output "brace in pipeline" '{ echo test; } | cat'
458
459 section "99. POSIX HERE-STRING ALTERNATIVES"
460
461 compare_posix_output "echo pipe vs redirect" 'echo test | cat'
462 compare_posix_output "printf to stdin" 'printf "test\n" | read X; echo $X'
463
464 section "100. POSIX PIPELINE EXIT STATUS"
465
466 compare_posix_output "pipe success" 'true | true; echo $?'
467 compare_posix_output "pipe last fails" 'true | false; echo $?'
468 compare_posix_output "pipe first fails" 'false | true; echo $?'
469 compare_posix_output "long pipeline" 'echo a | cat | cat | cat; echo $?'
470
471 section "101. POSIX PROCESS SUBSTITUTION ALTERNATIVES"
472
473 compare_posix_output "temp file pattern" 'echo test > /tmp/psub$$; cat /tmp/psub$$; rm /tmp/psub$$'
474 compare_posix_output "named pipe simulation" 'mkfifo /tmp/pfifo$$ 2>/dev/null || true; rm -f /tmp/pfifo$$; echo ok'
475
476 section "102. POSIX TEST BRACKETS"
477
478 compare_posix_output "test vs bracket" 'test 1 -eq 1 && [ 1 -eq 1 ] && echo ok'
479 compare_posix_output "bracket spacing" '[ "a" = "a" ] && echo match'
480 compare_posix_output "empty bracket test" '[ "" ] || echo empty'
481 compare_posix_output "nonempty test" '[ "x" ] && echo nonempty'
482
483 section "103. POSIX GETOPTS ADVANCED"
484
485 compare_posix_output "getopts multiple" 'while getopts "ab:c" opt 2>/dev/null; do echo $opt; done <<< "-a -b val -c"'
486 compare_posix_output "getopts OPTARG" 'getopts "a:" opt <<< "-a value" 2>/dev/null; echo $OPTARG'
487 compare_posix_output "getopts OPTIND" 'OPTIND=1; getopts "a" opt <<< "-a" 2>/dev/null; echo $OPTIND'
488
489 section "104. POSIX READ BUILTIN"
490
491 compare_posix_output "read single var" 'echo "hello" | { read X; echo $X; }'
492 compare_posix_output "read multiple vars" 'echo "a b c" | { read X Y Z; echo "$X:$Y:$Z"; }'
493 compare_posix_output "read with IFS" 'echo "a:b:c" | { IFS=: read X Y Z; echo "$X $Y $Z"; }'
494 compare_posix_output "read extra words" 'echo "a b c d" | { read X Y; echo "$X:$Y"; }'
495
496 section "105. POSIX PRINTF FORMATTING"
497
498 compare_posix_output "printf string" 'printf "%s\n" "test"'
499 compare_posix_output "printf integer" 'printf "%d\n" 42'
500 compare_posix_output "printf width" 'printf "%10s\n" "hi"'
501 compare_posix_output "printf left align" 'printf "%-10s|\n" "hi"'
502 compare_posix_output "printf zero pad" 'printf "%05d\n" 42'
503 compare_posix_output "printf multiple" 'printf "%s %d\n" "val" 123'
504
505 section "106. POSIX ARITHMETIC EDGE CASES"
506
507 compare_posix_output "unary minus" 'echo $((-5))'
508 compare_posix_output "unary plus" 'echo $((+5))'
509 compare_posix_output "double negative" 'echo $((--5))'
510 compare_posix_output "zero division check" 'echo $((5 / 1))'
511 compare_posix_output "modulo zero check" 'echo $((5 % 1))'
512 compare_posix_output "large number" 'echo $((1000000 * 1000))'
513 compare_posix_output "negative modulo" 'echo $((-7 % 3))'
514 compare_posix_output "chained ops" 'echo $((1 + 2 + 3 + 4 + 5))'
515
516 section "107. POSIX VARIABLE EDGE CASES"
517
518 compare_posix_output "underscore var" 'X_Y_Z=test; echo $X_Y_Z'
519 compare_posix_output "numeric suffix var" 'VAR1=a; VAR2=b; echo $VAR1$VAR2'
520 compare_posix_output "long var name" 'VERY_LONG_VARIABLE_NAME_HERE=val; echo $VERY_LONG_VARIABLE_NAME_HERE'
521 compare_posix_output "var with digits" 'A1B2C3=mix; echo $A1B2C3'
522 compare_posix_output "reassignment" 'X=1; X=2; X=3; echo $X'
523 compare_posix_output "self reference" 'X=a; X=${X}b; echo $X'
524
525 section "108. POSIX FUNCTION EDGE CASES"
526
527 compare_posix_output "function redefine" 'f() { echo first; }; f() { echo second; }; f'
528 compare_posix_output "function in pipeline" 'f() { echo test; }; f | cat'
529 compare_posix_output "function with subshell" 'f() { (echo sub); }; f'
530 compare_posix_output "function return in if" 'f() { if true; then return 0; fi; return 1; }; f; echo $?'
531 compare_posix_output "function shift" 'f() { shift; echo $1; }; f a b c'
532 compare_posix_output "function all args" 'f() { echo "$@"; }; f one two three'
533
534 section "109. POSIX LOOP EDGE CASES"
535
536 compare_posix_output "for single item" 'for i in single; do echo $i; done'
537 compare_posix_output "for many items" 'for i in 1 2 3 4 5 6 7 8 9 10; do echo $i; done | wc -l'
538 compare_posix_output "nested for" 'for i in a b; do for j in 1 2; do echo $i$j; done; done'
539 compare_posix_output "while nested" 'i=0; while [ $i -lt 2 ]; do j=0; while [ $j -lt 2 ]; do echo $i$j; j=$((j+1)); done; i=$((i+1)); done'
540 compare_posix_output "for with continue" 'for i in 1 2 3; do [ $i -eq 2 ] && continue; echo $i; done'
541
542 section "110. POSIX CASE EDGE CASES"
543
544 compare_posix_output "case empty pattern" 'x=; case "$x" in "") echo empty;; esac'
545 compare_posix_output "case glob star" 'x=anything; case $x in *) echo matched;; esac'
546 compare_posix_output "case multiple star" 'x=test; case $x in a*) echo a;; t*) echo t;; esac'
547 compare_posix_output "case bracket range" 'x=5; case $x in [0-9]) echo digit;; esac'
548 compare_posix_output "case no default" 'x=z; case $x in a) echo a;; b) echo b;; esac; echo done'
549
550 section "111. POSIX REDIRECTION EDGE CASES"
551
552 compare_posix_output "append create" 'rm -f /tmp/append_test_$$; echo first >> /tmp/append_test_$$; cat /tmp/append_test_$$; rm /tmp/append_test_$$'
553 compare_posix_output "redirect in loop" 'for i in 1 2 3; do echo $i; done > /tmp/loop_redir_$$; wc -l < /tmp/loop_redir_$$; rm /tmp/loop_redir_$$'
554 compare_posix_output "redirect in func" 'f() { echo test; }; f > /tmp/func_redir_$$; cat /tmp/func_redir_$$; rm /tmp/func_redir_$$'
555 compare_posix_output "multiple output" 'echo a > /tmp/multi1_$$; echo b > /tmp/multi2_$$; cat /tmp/multi1_$$ /tmp/multi2_$$; rm /tmp/multi1_$$ /tmp/multi2_$$'
556
557 section "112. POSIX PIPELINE EDGE CASES"
558
559 compare_posix_output "five stage pipe" 'echo test | cat | cat | cat | cat | cat'
560 compare_posix_output "pipe with grep" 'printf "a\nb\nc\n" | grep b'
561 compare_posix_output "pipe with wc" 'printf "a\nb\nc\n" | wc -l'
562 compare_posix_output "pipe with sort" 'printf "c\na\nb\n" | sort'
563 compare_posix_output "pipe with uniq" 'printf "a\na\nb\n" | uniq'
564 compare_posix_output "pipe with tr" 'echo abc | tr a-z A-Z'
565
566 section "113. POSIX SUBSHELL EDGE CASES"
567
568 compare_posix_output "subshell pwd" '(cd /tmp; pwd)'
569 compare_posix_output "subshell var" 'X=outer; (X=inner); echo $X'
570 compare_posix_output "subshell function" 'f() { echo hi; }; (f)'
571 compare_posix_output "subshell pipeline" '(echo a; echo b) | wc -l'
572 # Note: ((( expr ))) is arithmetic syntax in bash. Compare exit codes.
573 compare_posix_exit_code "triple paren arithmetic" '(((echo deep)))'
574 compare_posix_output "subshell arithmetic" '(echo $((2+2)))'
575
576 section "114. POSIX BRACE GROUP EDGE CASES"
577
578 compare_posix_output "brace simple" '{ echo test; }'
579 compare_posix_output "brace multi" '{ echo a; echo b; echo c; }'
580 compare_posix_output "brace var" 'X=1; { X=2; }; echo $X'
581 compare_posix_output "brace redirect" '{ echo test; } > /tmp/brace_$$; cat /tmp/brace_$$; rm /tmp/brace_$$'
582 compare_posix_output "brace in pipe" '{ echo a; echo b; } | wc -l'
583
584 section "115. POSIX SPECIAL CHAR EDGE CASES"
585
586 compare_posix_output "semicolon list" 'echo a; echo b; echo c'
587 compare_posix_output "ampersand bg" 'sleep 0.01 & wait; echo done'
588 compare_posix_output "double ampersand" 'true && echo yes'
589 compare_posix_output "double pipe" 'false || echo fallback'
590 compare_posix_output "mixed logic" 'true && true && echo all_true'
591 compare_posix_output "negation" '! false && echo negated'
592
593 # Summary
594 printf "\n"
595 printf "==========================================\n"
596 printf "ADVANCED POSIX COMPLIANCE TEST RESULTS ${TEST_PREFIX}\n"
597 printf "==========================================\n"
598 printf "${GREEN}Passed:${NC} %d\n" "$PASSED"
599 printf "${RED}Failed:${NC} %d\n" "$FAILED"
600 printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
601 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
602 printf "==========================================\n"
603
604 if [ $((PASSED + FAILED)) -gt 0 ]; then
605 PASS_RATE=$((PASSED * 100 / (PASSED + FAILED)))
606 printf "Pass rate: %d%%\n" "$PASS_RATE"
607 fi
608
609 if [ "$FAILED" -gt 0 ]; then
610 printf "\n${RED}Failed tests:${NC}\n"
611 printf "%b" "$FAILED_TESTS_LIST"
612 printf "==========================================\n"
613 fi
614
615 if [ "$FAILED" -eq 0 ]; then
616 printf "${GREEN}ALL ADVANCED POSIX TESTS PASSED!${NC} ✓\n"
617 exit 0
618 else
619 printf "${RED}SOME TESTS FAILED${NC} ✗\n"
620 exit 1
621 fi