Bash · 28493 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance Extended Test Suite for shell
4 # =====================================
5 # Comprehensive POSIX compliance testing based on IEEE Std 1003.1-2017
6 # This extends the basic test suite with additional coverage
7
8 # Colors (POSIX-compliant way)
9 RED='\033[0;31m'
10 GREEN='\033[0;32m'
11 YELLOW='\033[1;33m'
12 BLUE='\033[0;34m'
13 NC='\033[0m'
14
15 # Test identification
16 TEST_PREFIX="[posix-extended]"
17 CURRENT_SECTION=""
18 TEST_NUM=0
19
20 PASSED=0
21 FAILED=0
22 SKIPPED=0
23 FAILED_TESTS_LIST=""
24
25 # Get script directory (POSIX way)
26 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
27 SHELL_BIN="${SHELL_BIN:?ERROR: SHELL_BIN must be set}"
28 BASH_REF="${BASH_REF:-bash}"
29
30 # Check if shell binary exists
31 if [ ! -x "$SHELL_BIN" ]; then
32 printf "${RED}ERROR${NC}: shell binary not found at $SHELL_BIN\n"
33 printf "Please set SHELL_BIN or set SHELL_BIN environment variable\n"
34 exit 1
35 fi
36
37 # Test result trackers
38 pass() {
39 TEST_NUM=$((TEST_NUM + 1))
40 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
41 PASSED=$((PASSED + 1))
42 }
43
44 fail() {
45 TEST_NUM=$((TEST_NUM + 1))
46 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
47 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
48 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
49 if [ -n "$2" ]; then
50 printf " posix: %s\n" "$2"
51 fi
52 if [ -n "$3" ]; then
53 printf " shell: %s\n" "$3"
54 fi
55 FAILED=$((FAILED + 1))
56 }
57
58 skip() {
59 TEST_NUM=$((TEST_NUM + 1))
60 printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
61 SKIPPED=$((SKIPPED + 1))
62 }
63
64 section() {
65 # Extract section number from header like "26. EXTENDED TEST"
66 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
67 TEST_NUM=0
68 printf "\n"
69 printf "${BLUE}==========================================\n"
70 printf "%s\n" "$1"
71 printf "==========================================${NC}\n"
72 }
73
74 # Helper function to run command in both shells and compare
75 compare_posix_output() {
76 test_name="$1"
77 command="$2"
78 posix_file="/tmp/posix_ext_$$_posix"
79 shell_file="/tmp/posix_ext_$$_fortsh"
80
81 # Run in POSIX shell (sh)
82 "$BASH_REF" -c "$command" > "$posix_file" 2>&1 || true
83
84 # Run in shell under test
85 "$SHELL_BIN" -c "$command" > "$shell_file" 2>&1 || true
86
87 # Compare outputs
88 if diff -q "$posix_file" "$shell_file" > /dev/null 2>&1; then
89 pass "$test_name"
90 else
91 fail "$test_name" "$(cat "$posix_file")" "$(cat "$shell_file")"
92 fi
93
94 rm -f "$posix_file" "$shell_file"
95 }
96
97 # Helper function to compare exit codes
98 compare_posix_exit_code() {
99 test_name="$1"
100 command="$2"
101
102 "$BASH_REF" -c "$command" > /dev/null 2>&1
103 posix_exit=$?
104
105 "$SHELL_BIN" -c "$command" > /dev/null 2>&1
106 shell_exit=$?
107
108 if [ "$posix_exit" -eq "$shell_exit" ]; then
109 pass "$test_name"
110 else
111 fail "$test_name" "exit=$posix_exit" "exit=$shell_exit"
112 fi
113 }
114
115 # Cleanup
116 cleanup() {
117 rm -f /tmp/posix_ext_$$_* 2>/dev/null
118 rm -f /tmp/posix_test_* 2>/dev/null
119 rm -rf /tmp/posix_test_dir 2>/dev/null
120 }
121 trap cleanup EXIT INT TERM
122
123 # Setup test environment
124 mkdir -p /tmp/posix_test_dir
125 cd /tmp/posix_test_dir || exit 1
126
127 section "26. EXTENDED TEST COMMAND - FILE TYPE TESTS"
128
129 # Create test files with different properties
130 touch /tmp/posix_test_regular
131 chmod 644 /tmp/posix_test_regular
132 echo "data" > /tmp/posix_test_nonempty
133 mkdir -p /tmp/posix_test_directory
134 mkfifo /tmp/posix_test_fifo 2>/dev/null || skip "mkfifo" "not available"
135 ln -sf /tmp/posix_test_regular /tmp/posix_test_symlink 2>/dev/null
136
137 compare_posix_exit_code "test -e exists" "test -e /tmp/posix_test_regular"
138 compare_posix_exit_code "test -e nonexistent" "! test -e /tmp/nonexistent_xyz_$$"
139 compare_posix_exit_code "test -f regular file" "test -f /tmp/posix_test_regular"
140 compare_posix_exit_code "test -d directory" "test -d /tmp/posix_test_directory"
141 compare_posix_exit_code "test -s non-empty" "test -s /tmp/posix_test_nonempty"
142 compare_posix_exit_code "test -s empty" "! test -s /tmp/posix_test_regular"
143
144 # Symlink tests (if supported)
145 if [ -L /tmp/posix_test_symlink ]; then
146 compare_posix_exit_code "test -L symlink" "test -L /tmp/posix_test_symlink"
147 compare_posix_exit_code "test -h symlink" "test -h /tmp/posix_test_symlink"
148 fi
149
150 # FIFO tests (if created successfully)
151 if [ -p /tmp/posix_test_fifo ]; then
152 compare_posix_exit_code "test -p fifo" "test -p /tmp/posix_test_fifo"
153 fi
154
155 section "27. EXTENDED TEST COMMAND - FILE PERMISSION TESTS"
156
157 # Create files with specific permissions
158 touch /tmp/posix_test_readable
159 chmod 644 /tmp/posix_test_readable
160 touch /tmp/posix_test_writable
161 chmod 644 /tmp/posix_test_writable
162 touch /tmp/posix_test_executable
163 chmod 755 /tmp/posix_test_executable
164
165 compare_posix_exit_code "test -r readable" "test -r /tmp/posix_test_readable"
166 compare_posix_exit_code "test -w writable" "test -w /tmp/posix_test_writable"
167 compare_posix_exit_code "test -x executable" "test -x /tmp/posix_test_executable"
168 compare_posix_exit_code "test ! -x nonexec" "! test -x /tmp/posix_test_readable"
169
170 section "28. EXTENDED TEST COMMAND - FILE COMPARISON"
171
172 # Create files for comparison
173 echo "old" > /tmp/posix_test_old
174 sleep 1
175 echo "new" > /tmp/posix_test_new
176 ln -f /tmp/posix_test_old /tmp/posix_test_hardlink 2>/dev/null
177
178 compare_posix_exit_code "test -nt newer than" "test /tmp/posix_test_new -nt /tmp/posix_test_old"
179 compare_posix_exit_code "test -ot older than" "test /tmp/posix_test_old -ot /tmp/posix_test_new"
180
181 # Hard link test (if supported)
182 if [ -f /tmp/posix_test_hardlink ]; then
183 compare_posix_exit_code "test -ef same file" "test /tmp/posix_test_old -ef /tmp/posix_test_hardlink"
184 fi
185
186 section "29. EXTENDED TEST COMMAND - STRING TESTS"
187
188 compare_posix_exit_code "test -n nonempty string" "test -n 'hello'"
189 compare_posix_exit_code "test -z empty string" "test -z ''"
190 compare_posix_exit_code "test string = equal" "test 'abc' = 'abc'"
191 compare_posix_exit_code "test string != not equal" "test 'abc' != 'xyz'"
192 compare_posix_exit_code "test unary string" "test 'hello'"
193 compare_posix_exit_code "test ! negation" "! test -z 'hello'"
194
195 section "30. EXTENDED TEST COMMAND - INTEGER COMPARISON"
196
197 compare_posix_exit_code "test -eq equal" "test 42 -eq 42"
198 compare_posix_exit_code "test -ne not equal" "test 42 -ne 13"
199 compare_posix_exit_code "test -gt greater" "test 10 -gt 5"
200 compare_posix_exit_code "test -ge greater or equal" "test 10 -ge 10"
201 compare_posix_exit_code "test -lt less" "test 5 -lt 10"
202 compare_posix_exit_code "test -le less or equal" "test 5 -le 5"
203 compare_posix_exit_code "test negative numbers" "test -5 -lt 0"
204
205 section "31. EXTENDED TEST COMMAND - LOGICAL OPERATORS"
206
207 compare_posix_exit_code "test -a and" "test 5 -gt 3 -a 10 -gt 8"
208 compare_posix_exit_code "test -o or" "test 5 -gt 10 -o 10 -gt 8"
209 compare_posix_exit_code "test ! negation" "! test 5 -gt 10"
210 compare_posix_exit_code "test ( ) grouping" "test \( 5 -gt 3 \) -a \( 10 -gt 8 \)"
211
212 section "32. EXTENDED PARAMETER EXPANSION - DEFAULT VALUES"
213
214 compare_posix_output "use default unset" 'unset VAR; echo "${VAR-default}"'
215 compare_posix_output "use default null" 'VAR=; echo "${VAR-default}"'
216 compare_posix_output "use default null colon" 'VAR=; echo "${VAR:-default}"'
217 compare_posix_output "use default set" 'VAR=value; echo "${VAR-default}"'
218 compare_posix_output "assign default unset" 'unset VAR; echo "${VAR=assigned}"; echo $VAR'
219 compare_posix_output "assign default null colon" 'VAR=; echo "${VAR:=assigned}"; echo $VAR'
220
221 section "33. EXTENDED PARAMETER EXPANSION - ERROR IF UNSET"
222
223 compare_posix_exit_code "error if unset" 'unset VAR; echo "${VAR?error}" 2>/dev/null'
224 compare_posix_exit_code "error if null colon" 'VAR=; echo "${VAR:?error}" 2>/dev/null'
225 compare_posix_output "no error if set" 'VAR=value; echo "${VAR?error}"'
226
227 section "34. EXTENDED PARAMETER EXPANSION - ALTERNATIVE VALUE"
228
229 compare_posix_output "alt unset" 'unset VAR; echo "${VAR+alternative}"'
230 compare_posix_output "alt null" 'VAR=; echo "${VAR+alternative}"'
231 compare_posix_output "alt null colon" 'VAR=; echo "${VAR:+alternative}"'
232 compare_posix_output "alt set" 'VAR=value; echo "${VAR+alternative}"'
233 compare_posix_output "alt set colon" 'VAR=value; echo "${VAR:+alternative}"'
234
235 section "35. EXTENDED PARAMETER EXPANSION - STRING LENGTH"
236
237 compare_posix_output "length empty" 'VAR=; echo "${#VAR}"'
238 compare_posix_output "length short" 'VAR=hi; echo "${#VAR}"'
239 compare_posix_output "length long" 'VAR="hello world"; echo "${#VAR}"'
240 compare_posix_output "length special chars" 'VAR="a b c"; echo "${#VAR}"'
241
242 section "36. EXTENDED PARAMETER EXPANSION - PATTERN REMOVAL"
243
244 # Prefix removal
245 compare_posix_output "prefix # simple" 'VAR=hello; echo "${VAR#hel}"'
246 compare_posix_output "prefix # nomatch" 'VAR=hello; echo "${VAR#xyz}"'
247 compare_posix_output "prefix # glob" 'VAR=foo.bar.baz; echo "${VAR#*.}"'
248 compare_posix_output "prefix ## glob" 'VAR=foo.bar.baz; echo "${VAR##*.}"'
249 compare_posix_output "prefix # star" 'VAR=/usr/local/bin; echo "${VAR#*/}"'
250 compare_posix_output "prefix ## star" 'VAR=/usr/local/bin; echo "${VAR##*/}"'
251
252 # Suffix removal
253 compare_posix_output "suffix % simple" 'VAR=hello; echo "${VAR%lo}"'
254 compare_posix_output "suffix % nomatch" 'VAR=hello; echo "${VAR%xyz}"'
255 compare_posix_output "suffix % glob" 'VAR=foo.bar.baz; echo "${VAR%.*}"'
256 compare_posix_output "suffix %% glob" 'VAR=foo.bar.baz; echo "${VAR%%.*}"'
257 compare_posix_output "suffix % extension" 'VAR=file.tar.gz; echo "${VAR%.gz}"'
258 compare_posix_output "suffix %% extension" 'VAR=file.tar.gz; echo "${VAR%%.*}"'
259
260 section "37. ARITHMETIC EXPANSION - BASIC OPERATIONS"
261
262 compare_posix_output "arith addition" 'echo $((5 + 3))'
263 compare_posix_output "arith subtraction" 'echo $((10 - 4))'
264 compare_posix_output "arith multiplication" 'echo $((6 * 7))'
265 compare_posix_output "arith division" 'echo $((20 / 4))'
266 compare_posix_output "arith modulo" 'echo $((17 % 5))'
267 compare_posix_output "arith negative" 'echo $((-5 + 10))'
268 compare_posix_output "arith zero" 'echo $((0 + 0))'
269
270 section "38. ARITHMETIC EXPANSION - PRECEDENCE"
271
272 compare_posix_output "arith precedence mult" 'echo $((2 + 3 * 4))'
273 compare_posix_output "arith precedence paren" 'echo $(((2 + 3) * 4))'
274 compare_posix_output "arith precedence div" 'echo $((10 - 8 / 2))'
275 compare_posix_output "arith precedence complex" 'echo $((2 * 3 + 4 * 5))'
276
277 section "39. ARITHMETIC EXPANSION - VARIABLES"
278
279 compare_posix_output "arith var" 'X=5; echo $((X + 3))'
280 compare_posix_output "arith var no dollar" 'X=10; Y=20; echo $((X + Y))'
281 compare_posix_output "arith var assign" 'X=5; Y=$((X * 2)); echo $Y'
282 compare_posix_output "arith var complex" 'A=3; B=4; echo $((A * A + B * B))'
283
284 section "40. ARITHMETIC EXPANSION - COMPARISON"
285
286 compare_posix_output "arith compare eq true" 'echo $((5 == 5))'
287 compare_posix_output "arith compare eq false" 'echo $((5 == 3))'
288 compare_posix_output "arith compare ne true" 'echo $((5 != 3))'
289 compare_posix_output "arith compare ne false" 'echo $((5 != 5))'
290 compare_posix_output "arith compare lt" 'echo $((3 < 5))'
291 compare_posix_output "arith compare le" 'echo $((5 <= 5))'
292 compare_posix_output "arith compare gt" 'echo $((7 > 5))'
293 compare_posix_output "arith compare ge" 'echo $((5 >= 5))'
294
295 section "41. ARITHMETIC EXPANSION - LOGICAL"
296
297 compare_posix_output "arith logical and true" 'echo $((1 && 1))'
298 compare_posix_output "arith logical and false" 'echo $((1 && 0))'
299 compare_posix_output "arith logical or true" 'echo $((0 || 1))'
300 compare_posix_output "arith logical or false" 'echo $((0 || 0))'
301 compare_posix_output "arith logical not true" 'echo $((! 0))'
302 compare_posix_output "arith logical not false" 'echo $((! 1))'
303
304 section "42. SPECIAL PARAMETERS"
305
306 compare_posix_output "\$\$ process id type" 'echo $$ | grep -c "^[0-9][0-9]*$"'
307 compare_posix_output "\$- shell flags type" 'echo $- | grep -c "^[a-z]*$"'
308 compare_posix_output "\$# arg count" 'set -- a b c; echo $#'
309 compare_posix_output "\$1 first arg" 'set -- first second; echo $1'
310 compare_posix_output "\$2 second arg" 'set -- first second; echo $2'
311 compare_posix_output "\$9 ninth arg" 'set -- a b c d e f g h i j; echo $9'
312 compare_posix_output "\$* all args" 'set -- a b c; echo $*'
313 compare_posix_output "\$@ all args" 'set -- a b c; echo $@'
314
315 section "43. ADDITIONAL POSIX BUILTINS - CD/PWD"
316
317 compare_posix_output "cd to /tmp" 'cd /tmp && pwd'
318 compare_posix_output "cd relative" 'cd /tmp && cd .. && pwd | grep -c "^/"'
319 compare_posix_output "cd $HOME" 'cd && pwd | grep -c "^/"'
320
321 section "44. ADDITIONAL POSIX BUILTINS - UNSET"
322
323 compare_posix_output "unset variable" 'VAR=test; unset VAR; echo ${VAR:-unset}'
324 compare_posix_output "unset nonexistent" 'unset NONEXISTENT_VAR_XYZ; echo ok'
325
326 section "45. ADDITIONAL POSIX BUILTINS - EVAL"
327
328 compare_posix_output "eval simple" 'CMD="echo hello"; eval $CMD'
329 compare_posix_output "eval with var" 'X=5; CMD="echo \$X"; eval $CMD'
330 compare_posix_output "eval complex" 'A=echo; B=test; eval $A $B'
331
332 section "46. ADDITIONAL POSIX BUILTINS - COLON"
333
334 compare_posix_output ": null command" ': ; echo ok'
335 compare_posix_output ": with args" ': this is ignored; echo ok'
336 compare_posix_exit_code ": exit status" ':'
337
338 section "47. TILDE EXPANSION"
339
340 compare_posix_output "tilde home" 'echo ~ | grep -c "^/"'
341 compare_posix_output "tilde in path" 'echo ~/test | grep -c "^/"'
342
343 section "48. FIELD SPLITTING - ADVANCED"
344
345 compare_posix_output "IFS multiple fields" 'IFS=:; VAR="a:b:c:d"; set -- $VAR; echo $# $1 $4'
346 compare_posix_output "IFS whitespace" 'IFS=" "; VAR="a b c"; set -- $VAR; echo $#'
347 compare_posix_output "IFS comma" 'IFS=,; VAR="x,y,z"; set -- $VAR; echo $2'
348
349 section "49. BACKGROUND JOBS"
350
351 compare_posix_exit_code "background process" 'sleep 0.1 & wait'
352 compare_posix_output "background exit" '(exit 0) & wait $!; echo $?'
353
354 section "50. COMMAND GROUPING"
355
356 compare_posix_output "subshell isolation" 'X=1; (X=2; echo $X); echo $X'
357 compare_posix_output "brace grouping" 'X=1; { X=2; echo $X; }; echo $X'
358 compare_posix_output "subshell exit" '(exit 5); echo $?'
359
360 section "51. GLOB PATTERN MATCHING"
361
362 # Create temp dir for glob tests
363 GLOB_DIR="/tmp/posix_glob_test_$$"
364 mkdir -p "$GLOB_DIR"
365 touch "$GLOB_DIR/file1.txt" "$GLOB_DIR/file2.txt" "$GLOB_DIR/file3.log"
366 touch "$GLOB_DIR/abc" "$GLOB_DIR/abd" "$GLOB_DIR/acd"
367
368 compare_posix_output "star matches multiple" 'cd '"$GLOB_DIR"' && echo file*.txt | tr " " "\n" | wc -l'
369 compare_posix_output "question mark single char" 'cd '"$GLOB_DIR"' && echo ab? | tr " " "\n" | wc -l'
370 compare_posix_output "bracket range" 'cd '"$GLOB_DIR"' && echo a[bc]d | tr " " "\n" | wc -l'
371 compare_posix_output "no match literal" 'cd '"$GLOB_DIR"' && echo zzz*.xyz 2>/dev/null || echo "zzz*.xyz"'
372
373 rm -rf "$GLOB_DIR"
374
375 section "52. DOTFILE HANDLING"
376
377 DOT_DIR="/tmp/posix_dot_test_$$"
378 mkdir -p "$DOT_DIR"
379 touch "$DOT_DIR/.hidden" "$DOT_DIR/visible"
380
381 compare_posix_output "star no dotfiles" 'cd '"$DOT_DIR"' && echo * | grep -c hidden || echo 0'
382 compare_posix_output "explicit dot matches" 'cd '"$DOT_DIR"' && ls -d .* 2>/dev/null | grep -c hidden'
383
384 rm -rf "$DOT_DIR"
385
386 section "53. QUOTING IN GLOB"
387
388 QUOTE_DIR="/tmp/posix_quote_glob_$$"
389 mkdir -p "$QUOTE_DIR"
390 touch "$QUOTE_DIR/star"
391
392 compare_posix_output "quoted star literal" 'cd '"$QUOTE_DIR"' && echo "*" | grep -c "\\*"'
393 compare_posix_output "single quote prevents glob" "cd '$QUOTE_DIR' && echo '*' | grep -c '\\*'"
394
395 rm -rf "$QUOTE_DIR"
396
397 section "54. EMPTY AND NULL EXPANSION"
398
399 compare_posix_output "unset var empty" 'unset X; echo "[$X]"'
400 compare_posix_output "null var empty" 'X=""; echo "[$X]"'
401 compare_posix_output "unset in arith" 'unset X; echo $((X + 1))'
402
403 section "55. SPECIAL VARIABLES READONLY"
404
405 compare_posix_output "cannot assign to ?" '(eval "?=5" 2>/dev/null); echo ok'
406 compare_posix_output "cannot assign to $" '(eval "\$=5" 2>/dev/null); echo ok'
407
408 section "56. COMPLEX COMMAND LISTS"
409
410 compare_posix_output "and-or chain" 'true && echo yes || echo no'
411 compare_posix_output "or-and chain" 'false || echo fallback && echo then'
412 compare_posix_output "semicolon list" 'echo a; echo b; echo c'
413 compare_posix_output "mixed operators" 'true && true && echo all || echo none'
414
415 section "57. NESTED SUBSHELLS"
416
417 compare_posix_output "double nesting" '( ( echo deep ) )'
418 compare_posix_output "triple nesting" '( ( ( echo deeper ) ) )'
419 compare_posix_output "nested with vars" 'X=1; ( X=2; ( X=3; echo $X ) ); echo $X'
420
421 section "58. BRACE GROUP EDGE CASES"
422
423 compare_posix_output "brace needs space" '{ echo test; }'
424 compare_posix_output "brace with semicolon" '{ echo a; echo b; }'
425 compare_posix_output "brace preserves vars" 'X=1; { X=2; }; echo $X'
426
427 section "59. ARITHMETIC BASE LITERALS"
428
429 compare_posix_output "octal 010" 'echo $((010))'
430 compare_posix_output "hex 0x10" 'echo $((0x10))'
431 compare_posix_output "hex 0xFF" 'echo $((0xFF))'
432 compare_posix_output "mixed bases" 'echo $((010 + 0x10 + 10))'
433
434 section "60. STRING LENGTH EDGE CASES"
435
436 compare_posix_output "length empty" 'X=""; echo ${#X}'
437 compare_posix_output "length one" 'X="a"; echo ${#X}'
438 compare_posix_output "length with spaces" 'X="a b c"; echo ${#X}'
439 compare_posix_output "length special chars" 'X="$@#"; echo ${#X}'
440
441 section "61. DEFAULT VALUE EDGE CASES"
442
443 compare_posix_output "default unset" 'unset X; echo ${X:-default}'
444 compare_posix_output "default null" 'X=""; echo ${X:-default}'
445 compare_posix_output "default set" 'X="value"; echo ${X:-default}'
446 compare_posix_output "no-colon unset" 'unset X; echo ${X-default}'
447 compare_posix_output "no-colon null" 'X=""; echo ${X-default}'
448
449 section "62. PATTERN REMOVAL EDGE CASES"
450
451 compare_posix_output "prefix no match" 'X="hello"; echo ${X#xyz}'
452 compare_posix_output "suffix no match" 'X="hello"; echo ${X%xyz}'
453 compare_posix_output "prefix full match" 'X="aaa"; echo ${X##a*}'
454 compare_posix_output "suffix full match" 'X="aaa"; echo ${X%%*a}'
455
456 section "63. POSITIONAL PARAMS EDGE CASES"
457
458 compare_posix_output "set clears all" 'set -- a b c; set --; echo $#'
459 compare_posix_output "set replaces" 'set -- a b; set -- x y z; echo $#'
460 compare_posix_output "shift all" 'set -- a b c; shift 3; echo $#'
461
462 section "64. EXIT STATUS PROPAGATION"
463
464 compare_posix_output "pipeline exit" 'true | true | false; echo $?'
465 compare_posix_output "subshell exit" '(exit 42); echo $?'
466 compare_posix_output "brace exit" '{ exit 7; }; echo $?'
467 compare_posix_output "command sub exit" 'X=$(exit 5); echo $?'
468
469 section "65. WORD SPLITTING IFS VARIANTS"
470
471 compare_posix_output "IFS empty no split" 'IFS=""; X="a b c"; set -- $X; echo $#'
472 compare_posix_output "IFS colon" 'IFS=":"; X="a:b:c"; set -- $X; echo $#'
473 compare_posix_output "IFS multiple" 'IFS=":;"; X="a:b;c"; set -- $X; echo $#'
474 compare_posix_output "IFS whitespace" 'IFS=" "; X="a b c"; set -- $X; echo $#'
475 compare_posix_output "IFS default" 'X="a b c"; set -- $X; echo $#'
476
477 section "66. PATHNAME EXPANSION DISABLING"
478
479 compare_posix_output "noglob off" 'touch /tmp/glob_test_a.x /tmp/glob_test_b.x 2>/dev/null; ls /tmp/glob_test_*.x 2>/dev/null | wc -l'
480 compare_posix_output "noglob on" 'set -f; echo /tmp/glob_test_*.x; set +f'
481
482 section "67. TILDE EXPANSION CONTEXTS"
483
484 compare_posix_output "tilde alone" 'echo ~ | grep -c "^/"'
485 compare_posix_output "tilde in var" 'X=~; echo $X | grep -c "^/"'
486 compare_posix_output "tilde quoted" 'echo "~" | grep -c "~"'
487 compare_posix_output "tilde in path" 'echo ~/. | grep -c "^/"'
488
489 section "68. ASSIGNMENT CONTEXTS"
490
491 compare_posix_output "simple assign" 'X=hello; echo $X'
492 compare_posix_output "assign with cmd sub" 'X=$(echo test); echo $X'
493 compare_posix_output "assign with arith" 'X=$((5+3)); echo $X'
494 compare_posix_output "multiple assign" 'X=1 Y=2 Z=3; echo $X $Y $Z'
495 compare_posix_output "assign before cmd" 'X=val sh -c "echo \$X"'
496
497 section "69. SPECIAL CHARACTER HANDLING"
498
499 compare_posix_output "escaped newline" 'echo a\
500 b'
501 compare_posix_output "escaped dollar" 'echo \$VAR'
502 compare_posix_output "escaped backslash" 'echo \\\\'
503 compare_posix_output "escaped quote" 'echo \"quoted\"'
504
505 section "70. WHILE LOOP VARIATIONS"
506
507 compare_posix_output "while with pipe" 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done | wc -l'
508 compare_posix_output "while false" 'while false; do echo never; done; echo done'
509 compare_posix_output "while break" 'i=0; while true; do i=$((i+1)); [ $i -ge 3 ] && break; done; echo $i'
510 compare_posix_output "while continue" 'i=0; while [ $i -lt 5 ]; do i=$((i+1)); [ $i -eq 3 ] && continue; echo $i; done'
511
512 section "71. UNTIL LOOP VARIATIONS"
513
514 compare_posix_output "until basic" 'i=0; until [ $i -ge 3 ]; do echo $i; i=$((i+1)); done'
515 compare_posix_output "until true" 'until true; do echo never; done; echo done'
516 compare_posix_output "until with break" 'i=0; until false; do i=$((i+1)); [ $i -ge 3 ] && break; done; echo $i'
517
518 section "72. FOR LOOP WORD LIST"
519
520 compare_posix_output "for with glob" 'touch /tmp/for_test_1.z /tmp/for_test_2.z 2>/dev/null; for f in /tmp/for_test_*.z; do echo found; done | wc -l'
521 compare_posix_output "for empty" 'for x in; do echo never; done; echo done'
522 compare_posix_output "for with expansion" 'X="a b c"; for i in $X; do echo $i; done | wc -l'
523 compare_posix_output "for quoted" 'for i in "a b" "c d"; do echo "[$i]"; done'
524
525 section "73. FUNCTION ARGUMENT HANDLING"
526
527 compare_posix_output "func args count" 'f() { echo $#; }; f a b c'
528 compare_posix_output "func args values" 'f() { echo $1 $2 $3; }; f x y z'
529 compare_posix_output "func args shift" 'f() { shift; echo $1; }; f a b c'
530 compare_posix_output "func args all" 'f() { echo $@; }; f 1 2 3'
531 compare_posix_output "func nested call" 'a() { b; }; b() { echo hello; }; a'
532
533 section "74. RETURN AND EXIT"
534
535 compare_posix_output "return value" 'f() { return 42; }; f; echo $?'
536 compare_posix_output "return default" 'f() { true; return; }; f; echo $?'
537 compare_posix_output "exit in subshell" '(exit 5); echo $?'
538 compare_posix_output "exit from func" 'f() { exit 7; }; f; echo never'
539
540 section "75. EVAL COMPLEX"
541
542 compare_posix_output "eval variable expansion" 'X=VAR; VAR=value; eval echo \$$X'
543 compare_posix_output "eval multiple" 'eval "A=1; B=2"; echo $A $B'
544 compare_posix_output "eval with special" 'eval "echo hello world"'
545 compare_posix_output "eval nested" 'eval eval echo test'
546
547 section "76. EXEC REDIRECTIONS"
548
549 compare_posix_output "exec redirect stdout" 'exec >/tmp/exec_test_$$; echo test; exec >&-; cat /tmp/exec_test_$$; rm /tmp/exec_test_$$'
550
551 section "77. TRAP IN SUBSHELL"
552
553 compare_posix_output "trap inherited" '(trap "echo trapped" EXIT; exit 0) 2>/dev/null'
554 compare_posix_output "trap reset in subshell" 'trap "echo outer" EXIT; (trap - EXIT; echo inner) 2>/dev/null; trap - EXIT'
555
556 section "78. COLON COMMAND"
557
558 compare_posix_output "colon returns 0" ':; echo $?'
559 compare_posix_output "colon with args" ': arg1 arg2 arg3; echo $?'
560 compare_posix_output "colon with expansion" 'X=test; : $X; echo $?'
561 compare_posix_output "colon in if" 'if :; then echo yes; fi'
562
563 section "79. DOT SOURCE"
564
565 compare_posix_output "dot sources" 'echo "X=sourced" > /tmp/dot_test_$$; . /tmp/dot_test_$$; echo $X; rm /tmp/dot_test_$$'
566 compare_posix_output "dot with args" 'echo "echo \$1" > /tmp/dot_arg_$$; . /tmp/dot_arg_$$ hello; rm /tmp/dot_arg_$$'
567
568 section "80. UNSET BEHAVIOR"
569
570 compare_posix_output "unset variable" 'X=test; unset X; echo ${X:-empty}'
571 compare_posix_output "unset function" 'f() { echo hi; }; unset -f f; f 2>/dev/null || echo gone'
572 compare_posix_output "unset nonexistent" 'unset NONEXISTENT_VAR_XYZ; echo $?'
573
574 section "81. POSIX STRING OPERATIONS"
575
576 compare_posix_output "string concat" 'A=hello; B=world; echo $A$B'
577 compare_posix_output "string in quotes" 'A="hello world"; echo "$A"'
578 compare_posix_output "string length indirect" 'A=test; echo ${#A}'
579 compare_posix_output "empty string" 'A=""; echo "[$A]"'
580 compare_posix_output "string with newline" 'A="line1
581 line2"; echo "$A" | wc -l'
582
583 section "82. POSIX NUMERIC OPERATIONS"
584
585 compare_posix_output "add subtract" 'echo $((10 - 3 + 5))'
586 compare_posix_output "multiply divide" 'echo $((20 / 4 * 2))'
587 compare_posix_output "parentheses" 'echo $(((2 + 3) * (4 + 1)))'
588 compare_posix_output "comparison chain" 'echo $((5 > 3 && 3 > 1))'
589 compare_posix_output "ternary simulation" 'X=5; [ $X -gt 3 ] && echo big || echo small'
590
591 section "83. POSIX ARRAY SIMULATION"
592
593 compare_posix_output "positional as array" 'set -- a b c d e; echo $3'
594 compare_posix_output "array length" 'set -- a b c d e; echo $#'
595 compare_posix_output "array slice" 'set -- a b c d e; shift 2; echo $1'
596 compare_posix_output "array all" 'set -- a b c; echo "$@"'
597 compare_posix_output "array iterate" 'set -- a b c; for x in "$@"; do echo $x; done'
598
599 section "84. POSIX PATTERN MATCHING"
600
601 compare_posix_output "glob question" 'touch /tmp/pat_a /tmp/pat_b 2>/dev/null; ls /tmp/pat_? 2>/dev/null | wc -l; rm -f /tmp/pat_a /tmp/pat_b'
602 compare_posix_output "glob star" 'touch /tmp/patstar_1 /tmp/patstar_2 2>/dev/null; ls /tmp/patstar_* 2>/dev/null | wc -l; rm -f /tmp/patstar_*'
603 compare_posix_output "glob bracket" 'touch /tmp/patbr_a /tmp/patbr_b 2>/dev/null; ls /tmp/patbr_[ab] 2>/dev/null | wc -l; rm -f /tmp/patbr_*'
604 compare_posix_output "case pattern star" 'x=hello; case $x in h*) echo yes;; esac'
605 compare_posix_output "case pattern question" 'x=ab; case $x in ??) echo two;; esac'
606
607 section "85. POSIX ENVIRONMENT"
608
609 compare_posix_output "HOME exists" 'echo $HOME | grep -c "^/"'
610 compare_posix_output "PATH exists" 'echo $PATH | grep -c ":"'
611 compare_posix_output "PWD exists" 'echo $PWD | grep -c "^/"'
612 compare_posix_output "export visible" 'export X=val; sh -c "echo \$X"'
613 compare_posix_output "env assignment" 'X=test sh -c "echo \$X"'
614
615 section "86. POSIX SPECIAL EXPANSIONS"
616
617 compare_posix_output "dollar dollar" 'echo $$ | grep -E "^[0-9]+$" | wc -l'
618 compare_posix_output "dollar question" 'true; echo $?'
619 compare_posix_output "dollar bang" 'sleep 0.01 & echo $! | grep -E "^[0-9]+$" | wc -l; wait'
620 compare_posix_output "dollar hash" 'set -- a b c; echo $#'
621 compare_posix_output "dollar zero" 'echo $0 | wc -c | xargs test 0 -lt && echo yes'
622
623 section "87. POSIX ERROR HANDLING"
624
625 compare_posix_output "command not found" 'nonexistent_cmd_xyz 2>/dev/null; echo $?'
626 compare_posix_output "file not found" 'cat /nonexistent_file_xyz 2>/dev/null; echo $?'
627 compare_posix_output "permission denied sim" 'test -r /etc/shadow 2>/dev/null; echo done'
628 compare_posix_output "syntax error handled" 'eval "if" 2>/dev/null; echo recovered'
629
630 section "88. POSIX CONDITIONAL CHAINS"
631
632 compare_posix_output "if and" 'if true && true; then echo yes; fi'
633 compare_posix_output "if or" 'if false || true; then echo yes; fi'
634 compare_posix_output "if not" 'if ! false; then echo yes; fi'
635 compare_posix_output "complex condition" 'X=5; if [ $X -gt 3 ] && [ $X -lt 10 ]; then echo range; fi'
636 compare_posix_output "elif chain long" 'X=4; if [ $X -eq 1 ]; then echo 1; elif [ $X -eq 2 ]; then echo 2; elif [ $X -eq 3 ]; then echo 3; elif [ $X -eq 4 ]; then echo 4; fi'
637
638 section "89. POSIX INPUT PROCESSING"
639
640 compare_posix_output "read line" 'echo "hello" | { read x; echo $x; }'
641 compare_posix_output "read words" 'echo "a b c" | { read x y z; echo $y; }'
642 compare_posix_output "read with IFS" 'echo "a:b:c" | { IFS=: read x y z; echo $y; }'
643 compare_posix_output "cat file" 'echo "test" > /tmp/read_test_$$; cat /tmp/read_test_$$; rm /tmp/read_test_$$'
644
645 section "90. POSIX OUTPUT FORMATTING"
646
647 compare_posix_output "printf string" 'printf "%s\n" "hello"'
648 compare_posix_output "printf number" 'printf "%d\n" 42'
649 compare_posix_output "printf hex" 'printf "%x\n" 255'
650 compare_posix_output "printf octal" 'printf "%o\n" 64'
651 compare_posix_output "printf char" 'printf "%c\n" A'
652 compare_posix_output "echo no newline" 'printf "no newline"'
653
654 # Summary
655 printf "\n"
656 printf "==========================================\n"
657 printf "EXTENDED POSIX COMPLIANCE TEST RESULTS ${TEST_PREFIX}\n"
658 printf "==========================================\n"
659 printf "${GREEN}Passed:${NC} %d\n" "$PASSED"
660 printf "${RED}Failed:${NC} %d\n" "$FAILED"
661 printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
662 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
663 printf "==========================================\n"
664
665 if [ $((PASSED + FAILED)) -gt 0 ]; then
666 PASS_RATE=$((PASSED * 100 / (PASSED + FAILED)))
667 printf "Pass rate: %d%%\n" "$PASS_RATE"
668 fi
669
670 if [ "$FAILED" -gt 0 ]; then
671 printf "\n${RED}Failed tests:${NC}\n"
672 printf "%b" "$FAILED_TESTS_LIST"
673 printf "==========================================\n"
674 fi
675
676 if [ "$FAILED" -eq 0 ]; then
677 printf "${GREEN}ALL EXTENDED POSIX TESTS PASSED!${NC} ✓\n"
678 exit 0
679 else
680 printf "${RED}SOME TESTS FAILED${NC} ✗\n"
681 exit 1
682 fi