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