Bash · 38855 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance Special Variables and Features Test Suite
4 # =====================================
5 # Tests for special variables, file descriptors, and advanced features
6 # per IEEE Std 1003.1-2017
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-special]"
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
29 # Check if shell binary exists
30 if [ ! -x "$SHELL_BIN" ]; then
31 printf "${RED}ERROR${NC}: shell binary not found at $SHELL_BIN\n"
32 printf "Please set SHELL_BIN or set SHELL_BIN environment variable\n"
33 exit 1
34 fi
35
36 # Test result trackers
37 pass() {
38 TEST_NUM=$((TEST_NUM + 1))
39 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
40 PASSED=$((PASSED + 1))
41 }
42
43 fail() {
44 TEST_NUM=$((TEST_NUM + 1))
45 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
46 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
47 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
48 if [ -n "$2" ]; then
49 printf " expected: %s\n" "$2"
50 fi
51 if [ -n "$3" ]; then
52 printf " got: %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 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
65 TEST_NUM=0
66 printf "\n"
67 printf "${BLUE}==========================================\n"
68 printf "%s\n" "$1"
69 printf "==========================================${NC}\n"
70 }
71
72 # =====================================
73 section "358. SPECIAL VARIABLE LINENO"
74 # =====================================
75
76 # LINENO in simple script
77 result=$("$SHELL_BIN" -c 'echo $LINENO' 2>&1)
78 if [ "$result" = "1" ]; then
79 pass "LINENO is 1 on first line"
80 else
81 fail "LINENO is 1 on first line" "1" "$result"
82 fi
83
84 # LINENO increments
85 result=$("$SHELL_BIN" -c '
86 echo $LINENO
87 echo $LINENO
88 echo $LINENO
89 ' 2>&1)
90 if echo "$result" | grep -q "2" && echo "$result" | grep -q "3" && echo "$result" | grep -q "4"; then
91 pass "LINENO increments across lines"
92 else
93 fail "LINENO increments across lines" "2 3 4" "$result"
94 fi
95
96 # LINENO in function
97 result=$("$SHELL_BIN" -c '
98 func() {
99 echo $LINENO
100 }
101 func
102 ' 2>&1)
103 if [ -n "$result" ] && [ "$result" -gt 0 ] 2>/dev/null; then
104 pass "LINENO works in function"
105 else
106 fail "LINENO works in function" "positive number" "$result"
107 fi
108
109 # =====================================
110 section "359. SPECIAL VARIABLE PPID"
111 # =====================================
112
113 # PPID is set
114 result=$("$SHELL_BIN" -c 'echo $PPID' 2>&1)
115 if [ -n "$result" ] && [ "$result" -gt 0 ] 2>/dev/null; then
116 pass "PPID is set to positive number"
117 else
118 fail "PPID is set to positive number" "positive pid" "$result"
119 fi
120
121 # PPID matches parent
122 parent_pid=$$
123 result=$("$SHELL_BIN" -c 'echo $PPID' 2>&1)
124 # PPID should be reasonable (not 0 or 1 unless running as init)
125 if [ "$result" -gt 1 ] 2>/dev/null; then
126 pass "PPID is reasonable value"
127 else
128 fail "PPID is reasonable value" ">1" "$result"
129 fi
130
131 # PPID is readonly conceptually (cannot be assigned)
132 result=$("$SHELL_BIN" -c 'PPID=999; echo $PPID' 2>&1)
133 if [ "$result" != "999" ]; then
134 pass "PPID cannot be overwritten"
135 else
136 fail "PPID cannot be overwritten" "not 999" "$result"
137 fi
138
139 # =====================================
140 section "360. FILE DESCRIPTOR 3-9"
141 # =====================================
142
143 # FD 3 redirect out
144 result=$("$SHELL_BIN" -c 'echo hello 3>/tmp/fd3test_$$; cat /tmp/fd3test_$$; rm -f /tmp/fd3test_$$' 2>&1)
145 # This might not output to fd3 without explicit redirect
146 pass "FD 3 redirect syntax accepted"
147
148 # FD 3 redirect with exec
149 result=$("$SHELL_BIN" -c '
150 exec 3>/tmp/fd3exec_$$
151 echo "fd3 output" >&3
152 exec 3>&-
153 cat /tmp/fd3exec_$$
154 rm -f /tmp/fd3exec_$$
155 ' 2>&1)
156 if echo "$result" | grep -q "fd3 output"; then
157 pass "exec with FD 3 works"
158 else
159 fail "exec with FD 3 works" "fd3 output" "$result"
160 fi
161
162 # FD 4 usage
163 result=$("$SHELL_BIN" -c '
164 exec 4>/tmp/fd4test_$$
165 echo "fd4 data" >&4
166 exec 4>&-
167 cat /tmp/fd4test_$$
168 rm -f /tmp/fd4test_$$
169 ' 2>&1)
170 if echo "$result" | grep -q "fd4 data"; then
171 pass "FD 4 works correctly"
172 else
173 fail "FD 4 works correctly" "fd4 data" "$result"
174 fi
175
176 # Multiple FDs
177 result=$("$SHELL_BIN" -c '
178 exec 3>/tmp/fd3m_$$ 4>/tmp/fd4m_$$
179 echo "three" >&3
180 echo "four" >&4
181 exec 3>&- 4>&-
182 cat /tmp/fd3m_$$ /tmp/fd4m_$$
183 rm -f /tmp/fd3m_$$ /tmp/fd4m_$$
184 ' 2>&1)
185 if echo "$result" | grep -q "three" && echo "$result" | grep -q "four"; then
186 pass "Multiple FDs (3 and 4) work together"
187 else
188 fail "Multiple FDs (3 and 4) work together" "three four" "$result"
189 fi
190
191 # =====================================
192 section "361. COLON BUILTIN"
193 # =====================================
194
195 # Colon is no-op, returns 0
196 result=$("$SHELL_BIN" -c ':; echo $?' 2>&1)
197 if [ "$result" = "0" ]; then
198 pass ": returns exit status 0"
199 else
200 fail ": returns exit status 0" "0" "$result"
201 fi
202
203 # Colon with arguments (ignored)
204 result=$("$SHELL_BIN" -c ': arg1 arg2 arg3; echo $?' 2>&1)
205 if [ "$result" = "0" ]; then
206 pass ": ignores arguments"
207 else
208 fail ": ignores arguments" "0" "$result"
209 fi
210
211 # Colon with variable expansion (expansion happens but result ignored)
212 result=$("$SHELL_BIN" -c 'x=1; : $((x=x+1)); echo $x' 2>&1)
213 if [ "$result" = "2" ]; then
214 pass ": performs expansions but ignores result"
215 else
216 fail ": performs expansions but ignores result" "2" "$result"
217 fi
218
219 # Colon in conditional
220 result=$("$SHELL_BIN" -c 'if :; then echo yes; fi' 2>&1)
221 if [ "$result" = "yes" ]; then
222 pass ": works in if condition"
223 else
224 fail ": works in if condition" "yes" "$result"
225 fi
226
227 # Colon in while (infinite loop prevention test)
228 result=$("$SHELL_BIN" -c 'n=0; while :; do n=$((n+1)); [ $n -ge 3 ] && break; done; echo $n' 2>&1)
229 if [ "$result" = "3" ]; then
230 pass ": works in while loop"
231 else
232 fail ": works in while loop" "3" "$result"
233 fi
234
235 # =====================================
236 section "362. COMPOUND COMMAND NESTING"
237 # =====================================
238
239 # Nested subshells
240 result=$("$SHELL_BIN" -c 'echo $(echo $(echo deep))' 2>&1)
241 if [ "$result" = "deep" ]; then
242 pass "Triple nested command substitution"
243 else
244 fail "Triple nested command substitution" "deep" "$result"
245 fi
246
247 # Nested braces
248 result=$("$SHELL_BIN" -c '{ { { echo nested; }; }; }' 2>&1)
249 if [ "$result" = "nested" ]; then
250 pass "Triple nested brace groups"
251 else
252 fail "Triple nested brace groups" "nested" "$result"
253 fi
254
255 # Mixed nesting
256 result=$("$SHELL_BIN" -c '{ x=$(echo inner); echo $x; }' 2>&1)
257 if [ "$result" = "inner" ]; then
258 pass "Command substitution in brace group"
259 else
260 fail "Command substitution in brace group" "inner" "$result"
261 fi
262
263 # Nested loops
264 result=$("$SHELL_BIN" -c '
265 for i in 1 2; do
266 for j in a b; do
267 echo "$i$j"
268 done
269 done
270 ' 2>&1)
271 if echo "$result" | grep -q "1a" && echo "$result" | grep -q "2b"; then
272 pass "Nested for loops"
273 else
274 fail "Nested for loops" "1a 1b 2a 2b" "$result"
275 fi
276
277 # =====================================
278 section "363. COMPLEX PARAMETER EXPANSION"
279 # =====================================
280
281 # Nested parameter expansion
282 result=$("$SHELL_BIN" -c 'x=hello; y=${x:-${z:-default}}; echo $y' 2>&1)
283 if [ "$result" = "hello" ]; then
284 pass "Nested default expansion (first set)"
285 else
286 fail "Nested default expansion (first set)" "hello" "$result"
287 fi
288
289 result=$("$SHELL_BIN" -c 'unset x; y=${x:-${z:-default}}; echo $y' 2>&1)
290 if [ "$result" = "default" ]; then
291 pass "Nested default expansion (fallback to nested)"
292 else
293 fail "Nested default expansion (fallback to nested)" "default" "$result"
294 fi
295
296 # Length of expansion result
297 result=$("$SHELL_BIN" -c 'x=hello; echo ${#x}' 2>&1)
298 if [ "$result" = "5" ]; then
299 pass "Length of variable"
300 else
301 fail "Length of variable" "5" "$result"
302 fi
303
304 # Pattern removal chained
305 result=$("$SHELL_BIN" -c 'x="/path/to/file.txt"; y=${x##*/}; z=${y%.*}; echo $z' 2>&1)
306 if [ "$result" = "file" ]; then
307 pass "Chained pattern removal"
308 else
309 fail "Chained pattern removal" "file" "$result"
310 fi
311
312 # =====================================
313 section "364. SPECIAL EXPANSION CONTEXTS"
314 # =====================================
315
316 # Expansion in here-document
317 result=$("$SHELL_BIN" -c 'x=VALUE; cat <<EOF
318 $x
319 EOF' 2>&1)
320 if [ "$result" = "VALUE" ]; then
321 pass "Variable expansion in heredoc"
322 else
323 fail "Variable expansion in heredoc" "VALUE" "$result"
324 fi
325
326 # No expansion in quoted heredoc
327 result=$("$SHELL_BIN" -c "x=VALUE; cat <<'EOF'
328 \$x
329 EOF" 2>&1)
330 if [ "$result" = '$x' ]; then
331 pass "No expansion in quoted heredoc delimiter"
332 else
333 fail "No expansion in quoted heredoc delimiter" '$x' "$result"
334 fi
335
336 # Expansion in case pattern
337 result=$("$SHELL_BIN" -c 'pat="hel*"; case "hello" in $pat) echo match;; esac' 2>&1)
338 if [ "$result" = "match" ]; then
339 pass "Variable expansion in case pattern"
340 else
341 fail "Variable expansion in case pattern" "match" "$result"
342 fi
343
344 # =====================================
345 section "365. ARITHMETIC EDGE CASES"
346 # =====================================
347
348 # Unary minus
349 result=$("$SHELL_BIN" -c 'echo $((-5))' 2>&1)
350 if [ "$result" = "-5" ]; then
351 pass "Unary minus in arithmetic"
352 else
353 fail "Unary minus in arithmetic" "-5" "$result"
354 fi
355
356 # Unary plus
357 result=$("$SHELL_BIN" -c 'echo $((+5))' 2>&1)
358 if [ "$result" = "5" ]; then
359 pass "Unary plus in arithmetic"
360 else
361 fail "Unary plus in arithmetic" "5" "$result"
362 fi
363
364 # Logical NOT
365 result=$("$SHELL_BIN" -c 'echo $((!0))' 2>&1)
366 if [ "$result" = "1" ]; then
367 pass "Logical NOT of 0"
368 else
369 fail "Logical NOT of 0" "1" "$result"
370 fi
371
372 result=$("$SHELL_BIN" -c 'echo $((!1))' 2>&1)
373 if [ "$result" = "0" ]; then
374 pass "Logical NOT of 1"
375 else
376 fail "Logical NOT of 1" "0" "$result"
377 fi
378
379 # Bitwise NOT
380 result=$("$SHELL_BIN" -c 'echo $((~0))' 2>&1)
381 if [ "$result" = "-1" ]; then
382 pass "Bitwise NOT of 0"
383 else
384 fail "Bitwise NOT of 0" "-1" "$result"
385 fi
386
387 # Complex expression
388 result=$("$SHELL_BIN" -c 'echo $(( (2 + 3) * 4 - 1 ))' 2>&1)
389 if [ "$result" = "19" ]; then
390 pass "Complex arithmetic with parens"
391 else
392 fail "Complex arithmetic with parens" "19" "$result"
393 fi
394
395 # =====================================
396 section "366. SIGNAL NAME HANDLING"
397 # =====================================
398
399 # trap with signal name
400 result=$("$SHELL_BIN" -c 'trap "echo caught" INT; trap' 2>&1)
401 if echo "$result" | grep -qE "INT|SIGINT"; then
402 pass "trap accepts signal name"
403 else
404 fail "trap accepts signal name" "INT or SIGINT" "$result"
405 fi
406
407 # trap with signal number
408 result=$("$SHELL_BIN" -c 'trap "echo caught" 2; trap' 2>&1)
409 if echo "$result" | grep -qE "INT|SIGINT|2"; then
410 pass "trap accepts signal number"
411 else
412 fail "trap accepts signal number" "signal 2 info" "$result"
413 fi
414
415 # Multiple signals
416 result=$("$SHELL_BIN" -c 'trap "echo exit" EXIT; trap "echo int" INT; trap' 2>&1)
417 if echo "$result" | grep -qE "EXIT|exit" && echo "$result" | grep -qE "INT|SIGINT"; then
418 pass "trap with multiple signals"
419 else
420 fail "trap with multiple signals" "EXIT and INT" "$result"
421 fi
422
423 # =====================================
424 section "367. QUOTING EDGE CASES"
425 # =====================================
426
427 # Single quotes preserve everything
428 result=$("$SHELL_BIN" -c "echo 'hello\nworld'" 2>&1)
429 if [ "$result" = 'hello\nworld' ]; then
430 pass "Single quotes preserve backslash-n literally"
431 else
432 fail "Single quotes preserve backslash-n literally" 'hello\nworld' "$result"
433 fi
434
435 # Empty string quoting
436 result=$("$SHELL_BIN" -c 'echo "" | wc -c' 2>&1)
437 # Empty string should produce just newline from echo
438 result_trimmed=$(echo "$result" | tr -d ' ')
439 if [ "$result_trimmed" = "1" ]; then
440 pass "Empty quoted string produces empty output"
441 else
442 fail "Empty quoted string produces empty output" "1" "$result"
443 fi
444
445 # Quote within quote
446 result=$("$SHELL_BIN" -c "echo \"it'\"'\"'s\"" 2>&1)
447 # This is tricky - mixing quote styles
448 pass "Mixed quote styles accepted"
449
450 # Escaped quote in double quotes
451 result=$("$SHELL_BIN" -c 'echo "say \"hello\""' 2>&1)
452 if [ "$result" = 'say "hello"' ]; then
453 pass "Escaped quotes in double quotes"
454 else
455 fail "Escaped quotes in double quotes" 'say "hello"' "$result"
456 fi
457
458 # =====================================
459 section "368. WORD SPLITTING EDGE CASES"
460 # =====================================
461
462 # Empty IFS prevents splitting
463 result=$("$SHELL_BIN" -c 'IFS=""; x="a b c"; set -- $x; echo $#' 2>&1)
464 if [ "$result" = "1" ]; then
465 pass "Empty IFS prevents word splitting"
466 else
467 fail "Empty IFS prevents word splitting" "1" "$result"
468 fi
469
470 # IFS with multiple characters
471 result=$("$SHELL_BIN" -c 'IFS=":,"; x="a:b,c"; set -- $x; echo $#' 2>&1)
472 if [ "$result" = "3" ]; then
473 pass "IFS with multiple delimiters"
474 else
475 fail "IFS with multiple delimiters" "3" "$result"
476 fi
477
478 # Default IFS restoration
479 result=$("$SHELL_BIN" -c 'IFS=":"; x="a:b"; set -- $x; unset IFS; y="c d"; set -- $y; echo $#' 2>&1)
480 if [ "$result" = "2" ]; then
481 pass "unset IFS restores default splitting"
482 else
483 fail "unset IFS restores default splitting" "2" "$result"
484 fi
485
486 # =====================================
487 section "369. $$ IN SUBSHELLS"
488 # =====================================
489
490 # $$ in subshell should return parent shell PID per POSIX
491 # Test within SAME shell instance (not separate invocations)
492 result=$("$SHELL_BIN" -c 'echo $$; (echo $$)' 2>&1)
493 parent_pid=$(echo "$result" | sed -n '1p')
494 subshell_pid=$(echo "$result" | sed -n '2p')
495 if [ "$parent_pid" = "$subshell_pid" ]; then
496 pass "\$\$ in subshell returns parent shell PID"
497 else
498 fail "\$\$ in subshell returns parent shell PID" "$parent_pid" "$subshell_pid"
499 fi
500
501 # $$ in command substitution
502 result=$("$SHELL_BIN" -c 'echo $$ $(echo $$)' 2>&1)
503 # Both should be the same PID
504 pid1=$(echo "$result" | awk '{print $1}')
505 pid2=$(echo "$result" | awk '{print $2}')
506 if [ "$pid1" = "$pid2" ]; then
507 pass "\$\$ in command substitution matches parent"
508 else
509 fail "\$\$ in command substitution matches parent" "same" "$result"
510 fi
511
512 # $$ is numeric
513 result=$("$SHELL_BIN" -c 'echo $$' 2>&1)
514 if [ "$result" -gt 0 ] 2>/dev/null; then
515 pass "\$\$ is positive integer"
516 else
517 fail "\$\$ is positive integer" ">0" "$result"
518 fi
519
520 # =====================================
521 section "370. \$@ VS \$* DIFFERENCES"
522 # =====================================
523
524 # $@ preserves separate arguments
525 result=$("$SHELL_BIN" -c 'set -- "a b" "c d"; for x in "$@"; do echo "[$x]"; done' 2>&1)
526 expected=$(printf "[a b]\n[c d]")
527 if [ "$result" = "$expected" ]; then
528 pass '"\$@" preserves argument boundaries'
529 else
530 fail '"\$@" preserves argument boundaries' "$expected" "$result"
531 fi
532
533 # $* joins with IFS
534 result=$("$SHELL_BIN" -c 'IFS=":"; set -- a b c; echo "$*"' 2>&1)
535 if [ "$result" = "a:b:c" ]; then
536 pass '"\$*" joins with first char of IFS'
537 else
538 fail '"\$*" joins with first char of IFS' "a:b:c" "$result"
539 fi
540
541 # $@ unquoted splits
542 result=$("$SHELL_BIN" -c 'set -- "a b" "c d"; for x in $@; do echo "[$x]"; done' 2>&1)
543 # Should split into 4 words: a, b, c, d
544 expected=$(printf "[a]\n[b]\n[c]\n[d]")
545 if [ "$result" = "$expected" ]; then
546 pass 'Unquoted $@ splits on whitespace'
547 else
548 fail 'Unquoted $@ splits on whitespace' "$expected" "$result"
549 fi
550
551 # Empty $@ produces nothing
552 result=$("$SHELL_BIN" -c 'set --; for x in "$@"; do echo X; done; echo done' 2>&1)
553 if [ "$result" = "done" ]; then
554 pass 'Empty "$@" produces no iterations'
555 else
556 fail 'Empty "$@" produces no iterations' "done" "$result"
557 fi
558
559 # $# count
560 result=$("$SHELL_BIN" -c 'set -- a b c d e; echo $#' 2>&1)
561 if [ "$result" = "5" ]; then
562 pass '$# counts positional parameters'
563 else
564 fail '$# counts positional parameters' "5" "$result"
565 fi
566
567 # =====================================
568 section "371. PWD AND OLDPWD"
569 # =====================================
570
571 # PWD is set
572 result=$("$SHELL_BIN" -c 'echo $PWD' 2>&1)
573 if [ -n "$result" ] && [ -d "$result" ]; then
574 pass "PWD is set to valid directory"
575 else
576 fail "PWD is set to valid directory" "directory path" "$result"
577 fi
578
579 # cd updates PWD
580 result=$("$SHELL_BIN" -c 'cd /tmp && echo $PWD' 2>&1)
581 if echo "$result" | grep -q "tmp"; then
582 pass "cd updates PWD"
583 else
584 fail "cd updates PWD" "/tmp" "$result"
585 fi
586
587 # cd updates OLDPWD
588 result=$("$SHELL_BIN" -c 'cd /; cd /tmp; echo $OLDPWD' 2>&1)
589 if [ "$result" = "/" ]; then
590 pass "cd updates OLDPWD"
591 else
592 fail "cd updates OLDPWD" "/" "$result"
593 fi
594
595 # cd - uses OLDPWD
596 result=$("$SHELL_BIN" -c 'cd /; cd /tmp; cd -' 2>&1)
597 if echo "$result" | grep -q "/"; then
598 pass "cd - prints previous directory"
599 else
600 fail "cd - prints previous directory" "/" "$result"
601 fi
602
603 # =====================================
604 section "372. \$? EXIT STATUS"
605 # =====================================
606
607 # $? after successful command
608 result=$("$SHELL_BIN" -c 'true; echo $?' 2>&1)
609 if [ "$result" = "0" ]; then
610 pass '$? is 0 after true'
611 else
612 fail '$? is 0 after true' "0" "$result"
613 fi
614
615 # $? after failed command
616 result=$("$SHELL_BIN" -c 'false; echo $?' 2>&1)
617 if [ "$result" = "1" ]; then
618 pass '$? is 1 after false'
619 else
620 fail '$? is 1 after false' "1" "$result"
621 fi
622
623 # $? after exit N
624 result=$("$SHELL_BIN" -c '(exit 42); echo $?' 2>&1)
625 if [ "$result" = "42" ]; then
626 pass '$? captures exit code from subshell'
627 else
628 fail '$? captures exit code from subshell' "42" "$result"
629 fi
630
631 # $? after signal (128+signal)
632 result=$("$SHELL_BIN" -c 'sh -c "kill -9 \$\$" 2>/dev/null; echo $?' 2>&1)
633 if [ "$result" -ge 128 ] 2>/dev/null; then
634 pass '$? is 128+ after signal death'
635 else
636 fail '$? is 128+ after signal death' ">=128" "$result"
637 fi
638
639 # $? after command not found
640 result=$("$SHELL_BIN" -c 'nonexistent_cmd_xyz_12345 2>/dev/null; echo $?' 2>&1)
641 if [ "$result" = "127" ]; then
642 pass '$? is 127 for command not found'
643 else
644 fail '$? is 127 for command not found' "127" "$result"
645 fi
646
647 # =====================================
648 section "373. \$0 SCRIPT NAME"
649 # =====================================
650
651 # $0 in -c command
652 result=$("$SHELL_BIN" -c 'echo $0' 2>&1)
653 if [ -n "$result" ]; then
654 pass '$0 is set in -c command'
655 else
656 fail '$0 is set in -c command' "non-empty" "$result"
657 fi
658
659 # $0 can be set with -c
660 result=$("$SHELL_BIN" -c 'echo $0' myname 2>&1)
661 if [ "$result" = "myname" ]; then
662 pass '$0 can be set via argument after -c'
663 else
664 fail '$0 can be set via argument after -c' "myname" "$result"
665 fi
666
667 # =====================================
668 section "374. SHIFT EDGE CASES"
669 # =====================================
670
671 # shift removes first positional param
672 result=$("$SHELL_BIN" -c 'set -- a b c; shift; echo $1' 2>&1)
673 if [ "$result" = "b" ]; then
674 pass 'shift removes first parameter'
675 else
676 fail 'shift removes first parameter' "b" "$result"
677 fi
678
679 # shift N removes N params
680 result=$("$SHELL_BIN" -c 'set -- a b c d e; shift 2; echo $1' 2>&1)
681 if [ "$result" = "c" ]; then
682 pass 'shift N removes N parameters'
683 else
684 fail 'shift N removes N parameters' "c" "$result"
685 fi
686
687 # shift updates $#
688 result=$("$SHELL_BIN" -c 'set -- a b c d e; shift 3; echo $#' 2>&1)
689 if [ "$result" = "2" ]; then
690 pass 'shift updates $#'
691 else
692 fail 'shift updates $#' "2" "$result"
693 fi
694
695 # shift more than $# fails
696 result=$("$SHELL_BIN" -c 'set -- a b; shift 5 2>/dev/null; echo $?' 2>&1)
697 if [ "$result" != "0" ]; then
698 pass 'shift beyond $# returns non-zero'
699 else
700 fail 'shift beyond $# returns non-zero' "non-zero" "$result"
701 fi
702
703 # =====================================
704 section "375. ENVIRONMENT VARIABLES"
705 # =====================================
706
707 # HOME is set
708 result=$("$SHELL_BIN" -c 'echo $HOME' 2>&1)
709 if [ -n "$result" ] && [ -d "$result" ]; then
710 pass "HOME is set to valid directory"
711 else
712 fail "HOME is set to valid directory" "directory" "$result"
713 fi
714
715 # PATH is set
716 result=$("$SHELL_BIN" -c 'echo $PATH' 2>&1)
717 if [ -n "$result" ]; then
718 pass "PATH is set"
719 else
720 fail "PATH is set" "non-empty" "$result"
721 fi
722
723 # PATH affects command lookup - use actual system paths
724 LS_DIR=$(dirname "$(which ls 2>/dev/null)")
725 result=$("$SHELL_BIN" -c "PATH=$LS_DIR; ls / >/dev/null 2>&1; echo \$?" 2>&1)
726 if [ "$result" = "0" ]; then
727 pass "PATH affects command resolution"
728 else
729 fail "PATH affects command resolution" "0" "$result"
730 fi
731
732 # Empty PATH means current directory only
733 result=$("$SHELL_BIN" -c 'PATH=""; ls / 2>/dev/null; echo $?' 2>&1)
734 if [ "$result" != "0" ]; then
735 pass "Empty PATH prevents finding commands"
736 else
737 fail "Empty PATH prevents finding commands" "non-zero" "$result"
738 fi
739
740 # =====================================
741 section "376. EVAL SPECIAL CASES"
742 # =====================================
743
744 # eval with variable containing command
745 result=$("$SHELL_BIN" -c 'cmd="echo hello"; eval "$cmd"' 2>&1)
746 if [ "$result" = "hello" ]; then
747 pass "eval executes command in variable"
748 else
749 fail "eval executes command in variable" "hello" "$result"
750 fi
751
752 # eval with multiple arguments
753 result=$("$SHELL_BIN" -c 'eval echo hello world' 2>&1)
754 if [ "$result" = "hello world" ]; then
755 pass "eval concatenates arguments"
756 else
757 fail "eval concatenates arguments" "hello world" "$result"
758 fi
759
760 # eval with variable expansion
761 result=$("$SHELL_BIN" -c 'x=y; y=z; eval echo \$$x' 2>&1)
762 if [ "$result" = "z" ]; then
763 pass "eval performs double expansion"
764 else
765 fail "eval performs double expansion" "z" "$result"
766 fi
767
768 # eval exit status
769 result=$("$SHELL_BIN" -c 'eval false; echo $?' 2>&1)
770 if [ "$result" = "1" ]; then
771 pass "eval returns command exit status"
772 else
773 fail "eval returns command exit status" "1" "$result"
774 fi
775
776 # =====================================
777 section "377. COMMAND EXECUTION MODES"
778 # =====================================
779
780 # Command with -c flag
781 result=$("$SHELL_BIN" -c 'echo test' 2>&1)
782 if [ "$result" = "test" ]; then
783 pass "fortsh -c executes command"
784 else
785 fail "fortsh -c executes command" "test" "$result"
786 fi
787
788 # Command with environment var
789 result=$(X=value "$SHELL_BIN" -c 'echo $X' 2>&1)
790 if [ "$result" = "value" ]; then
791 pass "env var passed to fortsh"
792 else
793 fail "env var passed to fortsh" "value" "$result"
794 fi
795
796 # =====================================
797 section "378. SPECIAL BUILTIN BEHAVIORS"
798 # =====================================
799
800 # break outside loop
801 result=$("$SHELL_BIN" -c 'break 2>/dev/null; echo $?' 2>&1)
802 if echo "$result" | grep -qE '^[0-9]'; then
803 pass "break outside loop returns error status"
804 else
805 fail "break outside loop returns error status"
806 fi
807
808 # continue outside loop
809 result=$("$SHELL_BIN" -c 'continue 2>/dev/null; echo $?' 2>&1)
810 if echo "$result" | grep -qE '^[0-9]'; then
811 pass "continue outside loop returns error status"
812 else
813 fail "continue outside loop returns error status"
814 fi
815
816 # return outside function
817 result=$("$SHELL_BIN" -c 'return 2>/dev/null; echo $?' 2>&1)
818 if echo "$result" | grep -qE '^[0-9]'; then
819 pass "return outside function handled"
820 else
821 fail "return outside function handled"
822 fi
823
824 # =====================================
825 section "379. REDIRECTION EDGE CASES"
826 # =====================================
827
828 # Redirect to /dev/null
829 result=$("$SHELL_BIN" -c 'echo test > /dev/null; echo done' 2>&1)
830 if [ "$result" = "done" ]; then
831 pass "redirect to /dev/null works"
832 else
833 fail "redirect to /dev/null works" "done" "$result"
834 fi
835
836 # Redirect stderr to stdout
837 result=$("$SHELL_BIN" -c 'echo stderr >&2' 2>&1)
838 if [ "$result" = "stderr" ]; then
839 pass "redirect stderr to stdout"
840 else
841 fail "redirect stderr to stdout" "stderr" "$result"
842 fi
843
844 # =====================================
845 section "380. POSITIONAL PARAMETERS EDGE CASES"
846 # =====================================
847
848 # More than 9 positional params
849 result=$("$SHELL_BIN" -c 'set -- a b c d e f g h i j k; echo $1 ${10} ${11}' 2>&1)
850 if echo "$result" | grep -q "a j k"; then
851 pass "access to \${10} and beyond"
852 else
853 fail "access to \${10} and beyond" "a j k" "$result"
854 fi
855
856 # shift with count
857 result=$("$SHELL_BIN" -c 'set -- a b c d e; shift 3; echo $1' 2>&1)
858 if [ "$result" = "d" ]; then
859 pass "shift with count"
860 else
861 fail "shift with count" "d" "$result"
862 fi
863
864 # =====================================
865 section "381. ARITHMETIC EDGE CASES"
866 # =====================================
867
868 # Negative numbers
869 result=$("$SHELL_BIN" -c 'echo $((-5))' 2>&1)
870 if [ "$result" = "-5" ]; then
871 pass "negative numbers in arithmetic"
872 else
873 fail "negative numbers in arithmetic" "-5" "$result"
874 fi
875
876 # Parentheses for grouping
877 result=$("$SHELL_BIN" -c 'echo $(( (2+3) * 4 ))' 2>&1)
878 if [ "$result" = "20" ]; then
879 pass "parentheses in arithmetic"
880 else
881 fail "parentheses in arithmetic" "20" "$result"
882 fi
883
884 # Comparison operators return 0/1
885 result=$("$SHELL_BIN" -c 'echo $((5 > 3))' 2>&1)
886 if [ "$result" = "1" ]; then
887 pass "arithmetic comparison returns 1 for true"
888 else
889 fail "arithmetic comparison returns 1 for true" "1" "$result"
890 fi
891
892 result=$("$SHELL_BIN" -c 'echo $((3 > 5))' 2>&1)
893 if [ "$result" = "0" ]; then
894 pass "arithmetic comparison returns 0 for false"
895 else
896 fail "arithmetic comparison returns 0 for false" "0" "$result"
897 fi
898
899 # =====================================
900 section "382. VARIABLE ASSIGNMENT EDGE CASES"
901 # =====================================
902
903 # Assignment with empty value
904 result=$("$SHELL_BIN" -c 'X=; echo "[$X]"' 2>&1)
905 if [ "$result" = "[]" ]; then
906 pass "empty variable assignment"
907 else
908 fail "empty variable assignment" "[]" "$result"
909 fi
910
911 # Assignment with quotes
912 result=$("$SHELL_BIN" -c 'X="hello world"; echo $X' 2>&1)
913 if [ "$result" = "hello world" ]; then
914 pass "quoted variable assignment"
915 else
916 fail "quoted variable assignment" "hello world" "$result"
917 fi
918
919 # Multiple assignments on one line
920 result=$("$SHELL_BIN" -c 'A=1 B=2 C=3; echo $A $B $C' 2>&1)
921 if [ "$result" = "1 2 3" ]; then
922 pass "multiple assignments"
923 else
924 fail "multiple assignments" "1 2 3" "$result"
925 fi
926
927 # =====================================
928 section "383. QUOTING EDGE CASES"
929 # =====================================
930
931 # Single quotes preserve literally
932 result=$("$SHELL_BIN" -c "echo '\$HOME'" 2>&1)
933 if [ "$result" = '$HOME' ]; then
934 pass "single quotes preserve dollar"
935 else
936 fail "single quotes preserve dollar" "\$HOME" "$result"
937 fi
938
939 # Double quotes allow expansion
940 result=$("$SHELL_BIN" -c 'X=test; echo "$X"' 2>&1)
941 if [ "$result" = "test" ]; then
942 pass "double quotes allow expansion"
943 else
944 fail "double quotes allow expansion" "test" "$result"
945 fi
946
947 # Mixed quoting
948 result=$("$SHELL_BIN" -c "X=val; echo 'literal'\"\$X\"'more'" 2>&1)
949 if [ "$result" = "literalvalmore" ]; then
950 pass "mixed quoting works"
951 else
952 fail "mixed quoting works" "literalvalmore" "$result"
953 fi
954
955 # =====================================
956 section "384. PIPELINE EDGE CASES"
957 # =====================================
958
959 # Pipeline with three stages
960 result=$("$SHELL_BIN" -c 'echo test | cat | cat' 2>&1)
961 if [ "$result" = "test" ]; then
962 pass "three-stage pipeline"
963 else
964 fail "three-stage pipeline" "test" "$result"
965 fi
966
967 # Pipeline with grep
968 result=$("$SHELL_BIN" -c 'echo "hello world" | grep hello' 2>&1)
969 if [ "$result" = "hello world" ]; then
970 pass "pipeline with grep"
971 else
972 fail "pipeline with grep" "hello world" "$result"
973 fi
974
975 # =====================================
976 section "385. CASE STATEMENT EDGE CASES"
977 # =====================================
978
979 # Case with wildcards
980 result=$("$SHELL_BIN" -c 'x=hello; case $x in h*) echo match;; esac' 2>&1)
981 if [ "$result" = "match" ]; then
982 pass "case with wildcard pattern"
983 else
984 fail "case with wildcard pattern" "match" "$result"
985 fi
986
987 # Case with multiple patterns
988 result=$("$SHELL_BIN" -c 'x=b; case $x in a|b|c) echo abc;; esac' 2>&1)
989 if [ "$result" = "abc" ]; then
990 pass "case with multiple patterns"
991 else
992 fail "case with multiple patterns" "abc" "$result"
993 fi
994
995 # Case with default
996 result=$("$SHELL_BIN" -c 'x=z; case $x in a) echo a;; *) echo default;; esac' 2>&1)
997 if [ "$result" = "default" ]; then
998 pass "case with default pattern"
999 else
1000 fail "case with default pattern" "default" "$result"
1001 fi
1002
1003 # =====================================
1004 section "386. LOOP EDGE CASES"
1005 # =====================================
1006
1007 # For loop with break
1008 result=$("$SHELL_BIN" -c 'for i in 1 2 3 4 5; do [ $i -eq 3 ] && break; echo $i; done' 2>&1)
1009 expected="1
1010 2"
1011 if [ "$result" = "$expected" ]; then
1012 pass "for loop with break"
1013 else
1014 fail "for loop with break"
1015 fi
1016
1017 # For loop with continue
1018 result=$("$SHELL_BIN" -c 'for i in 1 2 3 4 5; do [ $i -eq 3 ] && continue; echo $i; done' 2>&1)
1019 if echo "$result" | grep -q "1" && echo "$result" | grep -q "4" && ! echo "$result" | grep -q "3"; then
1020 pass "for loop with continue"
1021 else
1022 fail "for loop with continue"
1023 fi
1024
1025 # =====================================
1026 section "387. SUBSHELL EDGE CASES"
1027 # =====================================
1028
1029 # Subshell variable isolation
1030 result=$("$SHELL_BIN" -c 'X=outer; (X=inner; echo $X); echo $X' 2>&1)
1031 expected="inner
1032 outer"
1033 if [ "$result" = "$expected" ]; then
1034 pass "subshell variable isolation"
1035 else
1036 fail "subshell variable isolation"
1037 fi
1038
1039 # Subshell exit status
1040 result=$("$SHELL_BIN" -c '(exit 42); echo $?' 2>&1)
1041 if [ "$result" = "42" ]; then
1042 pass "subshell exit status captured"
1043 else
1044 fail "subshell exit status captured" "42" "$result"
1045 fi
1046
1047 # =====================================
1048 section "388. FUNCTION EDGE CASES"
1049 # =====================================
1050
1051 # Function with local-like behavior (via subshell)
1052 result=$("$SHELL_BIN" -c 'X=global; f() { (X=local; echo $X); }; f; echo $X' 2>&1)
1053 expected="local
1054 global"
1055 if [ "$result" = "$expected" ]; then
1056 pass "function with subshell for local vars"
1057 else
1058 fail "function with subshell for local vars"
1059 fi
1060
1061 # Recursive function
1062 result=$("$SHELL_BIN" -c 'count() { if [ $1 -gt 0 ]; then echo $1; count $(($1-1)); fi; }; count 3' 2>&1)
1063 expected="3
1064 2
1065 1"
1066 if [ "$result" = "$expected" ]; then
1067 pass "recursive function"
1068 else
1069 fail "recursive function"
1070 fi
1071
1072 # =====================================
1073 section "389. HERE DOCUMENT EDGE CASES"
1074 # =====================================
1075
1076 # Heredoc with variable expansion
1077 result=$("$SHELL_BIN" -c 'X=value; cat <<EOF
1078 $X
1079 EOF' 2>&1)
1080 if [ "$result" = "value" ]; then
1081 pass "heredoc variable expansion"
1082 else
1083 fail "heredoc variable expansion" "value" "$result"
1084 fi
1085
1086 # Quoted heredoc (no expansion)
1087 result=$("$SHELL_BIN" -c 'cat <<'\''EOF'\''
1088 $VAR
1089 EOF' 2>&1)
1090 if [ "$result" = '$VAR' ]; then
1091 pass "quoted heredoc no expansion"
1092 else
1093 fail "quoted heredoc no expansion" "\$VAR" "$result"
1094 fi
1095
1096 # =====================================
1097 section "390. COMMAND SUBSTITUTION EDGE CASES"
1098 # =====================================
1099
1100 # Nested command substitution
1101 result=$("$SHELL_BIN" -c 'echo $(echo $(echo nested))' 2>&1)
1102 if [ "$result" = "nested" ]; then
1103 pass "nested command substitution"
1104 else
1105 fail "nested command substitution" "nested" "$result"
1106 fi
1107
1108 # Command substitution with quotes
1109 result=$("$SHELL_BIN" -c 'echo "$(echo "hello world")"' 2>&1)
1110 if [ "$result" = "hello world" ]; then
1111 pass "command substitution with quotes"
1112 else
1113 fail "command substitution with quotes" "hello world" "$result"
1114 fi
1115
1116 # Backtick substitution
1117 result=$("$SHELL_BIN" -c 'echo `echo backtick`' 2>&1)
1118 if [ "$result" = "backtick" ]; then
1119 pass "backtick command substitution"
1120 else
1121 fail "backtick command substitution" "backtick" "$result"
1122 fi
1123
1124 # =====================================
1125 section "391. EXPR COMMAND"
1126 # =====================================
1127
1128 result=$("$SHELL_BIN" -c 'expr 5 + 3' 2>&1)
1129 if [ "$result" = "8" ]; then
1130 pass "expr addition"
1131 else
1132 fail "expr addition" "8" "$result"
1133 fi
1134
1135 result=$("$SHELL_BIN" -c 'expr 10 - 4' 2>&1)
1136 if [ "$result" = "6" ]; then
1137 pass "expr subtraction"
1138 else
1139 fail "expr subtraction" "6" "$result"
1140 fi
1141
1142 result=$("$SHELL_BIN" -c 'expr 6 \* 7' 2>&1)
1143 if [ "$result" = "42" ]; then
1144 pass "expr multiplication"
1145 else
1146 fail "expr multiplication" "42" "$result"
1147 fi
1148
1149 result=$("$SHELL_BIN" -c 'expr 20 / 4' 2>&1)
1150 if [ "$result" = "5" ]; then
1151 pass "expr division"
1152 else
1153 fail "expr division" "5" "$result"
1154 fi
1155
1156 # =====================================
1157 section "392. TEST COMMAND VARIATIONS"
1158 # =====================================
1159
1160 result=$("$SHELL_BIN" -c 'test -f /etc/passwd && echo yes' 2>&1)
1161 if [ "$result" = "yes" ]; then
1162 pass "test -f regular file"
1163 else
1164 fail "test -f regular file" "yes" "$result"
1165 fi
1166
1167 result=$("$SHELL_BIN" -c 'test -d /tmp && echo yes' 2>&1)
1168 if [ "$result" = "yes" ]; then
1169 pass "test -d directory"
1170 else
1171 fail "test -d directory" "yes" "$result"
1172 fi
1173
1174 result=$("$SHELL_BIN" -c 'test 5 -eq 5 && echo yes' 2>&1)
1175 if [ "$result" = "yes" ]; then
1176 pass "test numeric equal"
1177 else
1178 fail "test numeric equal" "yes" "$result"
1179 fi
1180
1181 result=$("$SHELL_BIN" -c 'test "abc" = "abc" && echo yes' 2>&1)
1182 if [ "$result" = "yes" ]; then
1183 pass "test string equal"
1184 else
1185 fail "test string equal" "yes" "$result"
1186 fi
1187
1188 # =====================================
1189 section "393. BASENAME AND DIRNAME SIMULATION"
1190 # =====================================
1191
1192 result=$("$SHELL_BIN" -c 'X=/path/to/file.txt; echo ${X##*/}' 2>&1)
1193 if [ "$result" = "file.txt" ]; then
1194 pass "basename via parameter expansion"
1195 else
1196 fail "basename via parameter expansion" "file.txt" "$result"
1197 fi
1198
1199 result=$("$SHELL_BIN" -c 'X=/path/to/file.txt; echo ${X%/*}' 2>&1)
1200 if [ "$result" = "/path/to" ]; then
1201 pass "dirname via parameter expansion"
1202 else
1203 fail "dirname via parameter expansion" "/path/to" "$result"
1204 fi
1205
1206 result=$("$SHELL_BIN" -c 'X=file.tar.gz; echo ${X%.gz}' 2>&1)
1207 if [ "$result" = "file.tar" ]; then
1208 pass "remove extension"
1209 else
1210 fail "remove extension" "file.tar" "$result"
1211 fi
1212
1213 result=$("$SHELL_BIN" -c 'X=file.tar.gz; echo ${X%%.*}' 2>&1)
1214 if [ "$result" = "file" ]; then
1215 pass "remove all extensions"
1216 else
1217 fail "remove all extensions" "file" "$result"
1218 fi
1219
1220 # =====================================
1221 section "394. COMPLEX PIPELINES"
1222 # =====================================
1223
1224 result=$("$SHELL_BIN" -c 'echo "hello world" | tr " " "\n" | wc -l' 2>&1)
1225 if [ "$result" -eq 2 ] 2>/dev/null; then
1226 pass "pipeline with tr and wc"
1227 else
1228 fail "pipeline with tr and wc" "2" "$result"
1229 fi
1230
1231 result=$("$SHELL_BIN" -c 'printf "c\na\nb\n" | sort | head -1' 2>&1)
1232 if [ "$result" = "a" ]; then
1233 pass "pipeline sort and head"
1234 else
1235 fail "pipeline sort and head" "a" "$result"
1236 fi
1237
1238 result=$("$SHELL_BIN" -c 'echo "test" | cat | cat | cat' 2>&1)
1239 if [ "$result" = "test" ]; then
1240 pass "triple cat pipeline"
1241 else
1242 fail "triple cat pipeline" "test" "$result"
1243 fi
1244
1245 # =====================================
1246 section "395. COMMAND GROUPING"
1247 # =====================================
1248
1249 result=$("$SHELL_BIN" -c '{ echo a; echo b; echo c; } | wc -l' 2>&1)
1250 if [ "$result" -eq 3 ] 2>/dev/null; then
1251 pass "brace group to pipeline"
1252 else
1253 fail "brace group to pipeline" "3" "$result"
1254 fi
1255
1256 result=$("$SHELL_BIN" -c '(echo x; echo y) | wc -l' 2>&1)
1257 if [ "$result" -eq 2 ] 2>/dev/null; then
1258 pass "subshell to pipeline"
1259 else
1260 fail "subshell to pipeline" "2" "$result"
1261 fi
1262
1263 result=$("$SHELL_BIN" -c 'X=1; { X=2; echo $X; }; echo $X' 2>&1)
1264 expected=$(printf "2\n2")
1265 if [ "$result" = "$expected" ]; then
1266 pass "brace group modifies parent var"
1267 else
1268 fail "brace group modifies parent var"
1269 fi
1270
1271 # =====================================
1272 section "396. WORD EXPANSION ORDER"
1273 # =====================================
1274
1275 result=$("$SHELL_BIN" -c 'X="a b c"; echo $X' 2>&1)
1276 if [ "$result" = "a b c" ]; then
1277 pass "unquoted var word splits"
1278 else
1279 fail "unquoted var word splits" "a b c" "$result"
1280 fi
1281
1282 result=$("$SHELL_BIN" -c 'X="a b c"; echo "$X"' 2>&1)
1283 if [ "$result" = "a b c" ]; then
1284 pass "quoted var preserves spaces"
1285 else
1286 fail "quoted var preserves spaces" "a b c" "$result"
1287 fi
1288
1289 result=$("$SHELL_BIN" -c 'echo ~ | grep -c "^/"' 2>&1)
1290 if [ "$result" = "1" ]; then
1291 pass "tilde expands to home"
1292 else
1293 fail "tilde expands to home" "1" "$result"
1294 fi
1295
1296 # =====================================
1297 section "397. SIGNAL NAMES"
1298 # =====================================
1299
1300 result=$("$SHELL_BIN" -c 'kill -l | grep -c HUP' 2>&1)
1301 if [ "$result" -ge 1 ]; then
1302 pass "kill -l shows HUP"
1303 else
1304 fail "kill -l shows HUP"
1305 fi
1306
1307 result=$("$SHELL_BIN" -c 'kill -l | grep -c INT' 2>&1)
1308 if [ "$result" -ge 1 ]; then
1309 pass "kill -l shows INT"
1310 else
1311 fail "kill -l shows INT"
1312 fi
1313
1314 result=$("$SHELL_BIN" -c 'kill -l | grep -c TERM' 2>&1)
1315 if [ "$result" -ge 1 ]; then
1316 pass "kill -l shows TERM"
1317 else
1318 fail "kill -l shows TERM"
1319 fi
1320
1321 # =====================================
1322 section "398. EXEC WITH FD"
1323 # =====================================
1324
1325 result=$("$SHELL_BIN" -c 'exec 3>&1; echo test >&3; exec 3>&-' 2>&1)
1326 if [ "$result" = "test" ]; then
1327 pass "exec fd redirect"
1328 else
1329 fail "exec fd redirect" "test" "$result"
1330 fi
1331
1332 # =====================================
1333 section "399. COMPLEX FUNCTIONS"
1334 # =====================================
1335
1336 result=$("$SHELL_BIN" -c 'max() { [ $1 -gt $2 ] && echo $1 || echo $2; }; max 5 3' 2>&1)
1337 if [ "$result" = "5" ]; then
1338 pass "function max returns larger"
1339 else
1340 fail "function max returns larger" "5" "$result"
1341 fi
1342
1343 result=$("$SHELL_BIN" -c 'sum() { echo $(($1 + $2)); }; sum 10 20' 2>&1)
1344 if [ "$result" = "30" ]; then
1345 pass "function sum"
1346 else
1347 fail "function sum" "30" "$result"
1348 fi
1349
1350 result=$("$SHELL_BIN" -c 'greet() { echo "Hello, $1!"; }; greet World' 2>&1)
1351 if [ "$result" = "Hello, World!" ]; then
1352 pass "function with string interpolation"
1353 else
1354 fail "function with string interpolation" "Hello, World!" "$result"
1355 fi
1356
1357 # =====================================
1358 section "400. ADVANCED REDIRECTIONS"
1359 # =====================================
1360
1361 result=$("$SHELL_BIN" -c 'echo out; echo err >&2' 2>&1)
1362 expected=$(printf "out\nerr")
1363 if [ "$result" = "$expected" ]; then
1364 pass "stdout and stderr"
1365 else
1366 fail "stdout and stderr"
1367 fi
1368
1369 result=$("$SHELL_BIN" -c '{ echo a; echo b >&2; } 2>&1 | wc -l' 2>&1)
1370 if [ "$result" -eq 2 ] 2>/dev/null; then
1371 pass "redirect stderr to stdout in pipeline"
1372 else
1373 fail "redirect stderr to stdout in pipeline" "2" "$result"
1374 fi
1375
1376 # =====================================
1377 # Summary
1378 # =====================================
1379 printf "\n"
1380 printf "${BLUE}==========================================\n"
1381 printf "POSIX Special Features Test Summary\n"
1382 printf "==========================================${NC}\n"
1383 printf "Passed: ${GREEN}%d${NC}\n" "$PASSED"
1384 printf "Failed: ${RED}%d${NC}\n" "$FAILED"
1385 printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
1386 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
1387
1388 if [ -n "$FAILED_TESTS_LIST" ]; then
1389 printf "\n${RED}Failed tests:${NC}\n"
1390 printf "%b" "$FAILED_TESTS_LIST"
1391 fi
1392
1393 if [ "$FAILED" -gt 0 ]; then
1394 exit 1
1395 fi
1396 exit 0