Bash · 38493 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 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 " 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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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=$("$RUSH_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 parent_pid=$("$RUSH_BIN" -c 'echo $$' 2>&1)
492 subshell_pid=$("$RUSH_BIN" -c '(echo $$)' 2>&1)
493 if [ "$parent_pid" = "$subshell_pid" ]; then
494 pass "\$\$ in subshell returns parent shell PID"
495 else
496 fail "\$\$ in subshell returns parent shell PID" "$parent_pid" "$subshell_pid"
497 fi
498
499 # $$ in command substitution
500 result=$("$RUSH_BIN" -c 'echo $$ $(echo $$)' 2>&1)
501 # Both should be the same PID
502 pid1=$(echo "$result" | awk '{print $1}')
503 pid2=$(echo "$result" | awk '{print $2}')
504 if [ "$pid1" = "$pid2" ]; then
505 pass "\$\$ in command substitution matches parent"
506 else
507 fail "\$\$ in command substitution matches parent" "same" "$result"
508 fi
509
510 # $$ is numeric
511 result=$("$RUSH_BIN" -c 'echo $$' 2>&1)
512 if [ "$result" -gt 0 ] 2>/dev/null; then
513 pass "\$\$ is positive integer"
514 else
515 fail "\$\$ is positive integer" ">0" "$result"
516 fi
517
518 # =====================================
519 section "370. \$@ VS \$* DIFFERENCES"
520 # =====================================
521
522 # $@ preserves separate arguments
523 result=$("$RUSH_BIN" -c 'set -- "a b" "c d"; for x in "$@"; do echo "[$x]"; done' 2>&1)
524 expected=$(printf "[a b]\n[c d]")
525 if [ "$result" = "$expected" ]; then
526 pass '"\$@" preserves argument boundaries'
527 else
528 fail '"\$@" preserves argument boundaries' "$expected" "$result"
529 fi
530
531 # $* joins with IFS
532 result=$("$RUSH_BIN" -c 'IFS=":"; set -- a b c; echo "$*"' 2>&1)
533 if [ "$result" = "a:b:c" ]; then
534 pass '"\$*" joins with first char of IFS'
535 else
536 fail '"\$*" joins with first char of IFS' "a:b:c" "$result"
537 fi
538
539 # $@ unquoted splits
540 result=$("$RUSH_BIN" -c 'set -- "a b" "c d"; for x in $@; do echo "[$x]"; done' 2>&1)
541 # Should split into 4 words: a, b, c, d
542 expected=$(printf "[a]\n[b]\n[c]\n[d]")
543 if [ "$result" = "$expected" ]; then
544 pass 'Unquoted $@ splits on whitespace'
545 else
546 fail 'Unquoted $@ splits on whitespace' "$expected" "$result"
547 fi
548
549 # Empty $@ produces nothing
550 result=$("$RUSH_BIN" -c 'set --; for x in "$@"; do echo X; done; echo done' 2>&1)
551 if [ "$result" = "done" ]; then
552 pass 'Empty "$@" produces no iterations'
553 else
554 fail 'Empty "$@" produces no iterations' "done" "$result"
555 fi
556
557 # $# count
558 result=$("$RUSH_BIN" -c 'set -- a b c d e; echo $#' 2>&1)
559 if [ "$result" = "5" ]; then
560 pass '$# counts positional parameters'
561 else
562 fail '$# counts positional parameters' "5" "$result"
563 fi
564
565 # =====================================
566 section "371. PWD AND OLDPWD"
567 # =====================================
568
569 # PWD is set
570 result=$("$RUSH_BIN" -c 'echo $PWD' 2>&1)
571 if [ -n "$result" ] && [ -d "$result" ]; then
572 pass "PWD is set to valid directory"
573 else
574 fail "PWD is set to valid directory" "directory path" "$result"
575 fi
576
577 # cd updates PWD
578 result=$("$RUSH_BIN" -c 'cd /tmp && echo $PWD' 2>&1)
579 if echo "$result" | grep -q "tmp"; then
580 pass "cd updates PWD"
581 else
582 fail "cd updates PWD" "/tmp" "$result"
583 fi
584
585 # cd updates OLDPWD
586 result=$("$RUSH_BIN" -c 'cd /; cd /tmp; echo $OLDPWD' 2>&1)
587 if [ "$result" = "/" ]; then
588 pass "cd updates OLDPWD"
589 else
590 fail "cd updates OLDPWD" "/" "$result"
591 fi
592
593 # cd - uses OLDPWD
594 result=$("$RUSH_BIN" -c 'cd /; cd /tmp; cd -' 2>&1)
595 if echo "$result" | grep -q "/"; then
596 pass "cd - prints previous directory"
597 else
598 fail "cd - prints previous directory" "/" "$result"
599 fi
600
601 # =====================================
602 section "372. \$? EXIT STATUS"
603 # =====================================
604
605 # $? after successful command
606 result=$("$RUSH_BIN" -c 'true; echo $?' 2>&1)
607 if [ "$result" = "0" ]; then
608 pass '$? is 0 after true'
609 else
610 fail '$? is 0 after true' "0" "$result"
611 fi
612
613 # $? after failed command
614 result=$("$RUSH_BIN" -c 'false; echo $?' 2>&1)
615 if [ "$result" = "1" ]; then
616 pass '$? is 1 after false'
617 else
618 fail '$? is 1 after false' "1" "$result"
619 fi
620
621 # $? after exit N
622 result=$("$RUSH_BIN" -c '(exit 42); echo $?' 2>&1)
623 if [ "$result" = "42" ]; then
624 pass '$? captures exit code from subshell'
625 else
626 fail '$? captures exit code from subshell' "42" "$result"
627 fi
628
629 # $? after signal (128+signal)
630 result=$("$RUSH_BIN" -c 'sh -c "kill -9 \$\$" 2>/dev/null; echo $?' 2>&1)
631 if [ "$result" -ge 128 ] 2>/dev/null; then
632 pass '$? is 128+ after signal death'
633 else
634 fail '$? is 128+ after signal death' ">=128" "$result"
635 fi
636
637 # $? after command not found
638 result=$("$RUSH_BIN" -c 'nonexistent_cmd_xyz_12345 2>/dev/null; echo $?' 2>&1)
639 if [ "$result" = "127" ]; then
640 pass '$? is 127 for command not found'
641 else
642 fail '$? is 127 for command not found' "127" "$result"
643 fi
644
645 # =====================================
646 section "373. \$0 SCRIPT NAME"
647 # =====================================
648
649 # $0 in -c command
650 result=$("$RUSH_BIN" -c 'echo $0' 2>&1)
651 if [ -n "$result" ]; then
652 pass '$0 is set in -c command'
653 else
654 fail '$0 is set in -c command' "non-empty" "$result"
655 fi
656
657 # $0 can be set with -c
658 result=$("$RUSH_BIN" -c 'echo $0' myname 2>&1)
659 if [ "$result" = "myname" ]; then
660 pass '$0 can be set via argument after -c'
661 else
662 fail '$0 can be set via argument after -c' "myname" "$result"
663 fi
664
665 # =====================================
666 section "374. SHIFT EDGE CASES"
667 # =====================================
668
669 # shift removes first positional param
670 result=$("$RUSH_BIN" -c 'set -- a b c; shift; echo $1' 2>&1)
671 if [ "$result" = "b" ]; then
672 pass 'shift removes first parameter'
673 else
674 fail 'shift removes first parameter' "b" "$result"
675 fi
676
677 # shift N removes N params
678 result=$("$RUSH_BIN" -c 'set -- a b c d e; shift 2; echo $1' 2>&1)
679 if [ "$result" = "c" ]; then
680 pass 'shift N removes N parameters'
681 else
682 fail 'shift N removes N parameters' "c" "$result"
683 fi
684
685 # shift updates $#
686 result=$("$RUSH_BIN" -c 'set -- a b c d e; shift 3; echo $#' 2>&1)
687 if [ "$result" = "2" ]; then
688 pass 'shift updates $#'
689 else
690 fail 'shift updates $#' "2" "$result"
691 fi
692
693 # shift more than $# fails
694 result=$("$RUSH_BIN" -c 'set -- a b; shift 5 2>/dev/null; echo $?' 2>&1)
695 if [ "$result" != "0" ]; then
696 pass 'shift beyond $# returns non-zero'
697 else
698 fail 'shift beyond $# returns non-zero' "non-zero" "$result"
699 fi
700
701 # =====================================
702 section "375. ENVIRONMENT VARIABLES"
703 # =====================================
704
705 # HOME is set
706 result=$("$RUSH_BIN" -c 'echo $HOME' 2>&1)
707 if [ -n "$result" ] && [ -d "$result" ]; then
708 pass "HOME is set to valid directory"
709 else
710 fail "HOME is set to valid directory" "directory" "$result"
711 fi
712
713 # PATH is set
714 result=$("$RUSH_BIN" -c 'echo $PATH' 2>&1)
715 if [ -n "$result" ]; then
716 pass "PATH is set"
717 else
718 fail "PATH is set" "non-empty" "$result"
719 fi
720
721 # PATH affects command lookup
722 result=$("$RUSH_BIN" -c 'PATH=/bin:/usr/bin; ls / >/dev/null 2>&1; echo $?' 2>&1)
723 if [ "$result" = "0" ]; then
724 pass "PATH affects command resolution"
725 else
726 fail "PATH affects command resolution" "0" "$result"
727 fi
728
729 # Empty PATH means current directory only
730 result=$("$RUSH_BIN" -c 'PATH=""; ls / 2>/dev/null; echo $?' 2>&1)
731 if [ "$result" != "0" ]; then
732 pass "Empty PATH prevents finding commands"
733 else
734 fail "Empty PATH prevents finding commands" "non-zero" "$result"
735 fi
736
737 # =====================================
738 section "376. EVAL SPECIAL CASES"
739 # =====================================
740
741 # eval with variable containing command
742 result=$("$RUSH_BIN" -c 'cmd="echo hello"; eval "$cmd"' 2>&1)
743 if [ "$result" = "hello" ]; then
744 pass "eval executes command in variable"
745 else
746 fail "eval executes command in variable" "hello" "$result"
747 fi
748
749 # eval with multiple arguments
750 result=$("$RUSH_BIN" -c 'eval echo hello world' 2>&1)
751 if [ "$result" = "hello world" ]; then
752 pass "eval concatenates arguments"
753 else
754 fail "eval concatenates arguments" "hello world" "$result"
755 fi
756
757 # eval with variable expansion
758 result=$("$RUSH_BIN" -c 'x=y; y=z; eval echo \$$x' 2>&1)
759 if [ "$result" = "z" ]; then
760 pass "eval performs double expansion"
761 else
762 fail "eval performs double expansion" "z" "$result"
763 fi
764
765 # eval exit status
766 result=$("$RUSH_BIN" -c 'eval false; echo $?' 2>&1)
767 if [ "$result" = "1" ]; then
768 pass "eval returns command exit status"
769 else
770 fail "eval returns command exit status" "1" "$result"
771 fi
772
773 # =====================================
774 section "377. COMMAND EXECUTION MODES"
775 # =====================================
776
777 # Command with -c flag
778 result=$("$RUSH_BIN" -c 'echo test' 2>&1)
779 if [ "$result" = "test" ]; then
780 pass "fortsh -c executes command"
781 else
782 fail "fortsh -c executes command" "test" "$result"
783 fi
784
785 # Command with environment var
786 result=$(X=value "$RUSH_BIN" -c 'echo $X' 2>&1)
787 if [ "$result" = "value" ]; then
788 pass "env var passed to fortsh"
789 else
790 fail "env var passed to fortsh" "value" "$result"
791 fi
792
793 # =====================================
794 section "378. SPECIAL BUILTIN BEHAVIORS"
795 # =====================================
796
797 # break outside loop
798 result=$("$RUSH_BIN" -c 'break 2>/dev/null; echo $?' 2>&1)
799 if echo "$result" | grep -qE '^[0-9]'; then
800 pass "break outside loop returns error status"
801 else
802 fail "break outside loop returns error status"
803 fi
804
805 # continue outside loop
806 result=$("$RUSH_BIN" -c 'continue 2>/dev/null; echo $?' 2>&1)
807 if echo "$result" | grep -qE '^[0-9]'; then
808 pass "continue outside loop returns error status"
809 else
810 fail "continue outside loop returns error status"
811 fi
812
813 # return outside function
814 result=$("$RUSH_BIN" -c 'return 2>/dev/null; echo $?' 2>&1)
815 if echo "$result" | grep -qE '^[0-9]'; then
816 pass "return outside function handled"
817 else
818 fail "return outside function handled"
819 fi
820
821 # =====================================
822 section "379. REDIRECTION EDGE CASES"
823 # =====================================
824
825 # Redirect to /dev/null
826 result=$("$RUSH_BIN" -c 'echo test > /dev/null; echo done' 2>&1)
827 if [ "$result" = "done" ]; then
828 pass "redirect to /dev/null works"
829 else
830 fail "redirect to /dev/null works" "done" "$result"
831 fi
832
833 # Redirect stderr to stdout
834 result=$("$RUSH_BIN" -c 'echo stderr >&2' 2>&1)
835 if [ "$result" = "stderr" ]; then
836 pass "redirect stderr to stdout"
837 else
838 fail "redirect stderr to stdout" "stderr" "$result"
839 fi
840
841 # =====================================
842 section "380. POSITIONAL PARAMETERS EDGE CASES"
843 # =====================================
844
845 # More than 9 positional params
846 result=$("$RUSH_BIN" -c 'set -- a b c d e f g h i j k; echo $1 ${10} ${11}' 2>&1)
847 if echo "$result" | grep -q "a j k"; then
848 pass "access to \${10} and beyond"
849 else
850 fail "access to \${10} and beyond" "a j k" "$result"
851 fi
852
853 # shift with count
854 result=$("$RUSH_BIN" -c 'set -- a b c d e; shift 3; echo $1' 2>&1)
855 if [ "$result" = "d" ]; then
856 pass "shift with count"
857 else
858 fail "shift with count" "d" "$result"
859 fi
860
861 # =====================================
862 section "381. ARITHMETIC EDGE CASES"
863 # =====================================
864
865 # Negative numbers
866 result=$("$RUSH_BIN" -c 'echo $((-5))' 2>&1)
867 if [ "$result" = "-5" ]; then
868 pass "negative numbers in arithmetic"
869 else
870 fail "negative numbers in arithmetic" "-5" "$result"
871 fi
872
873 # Parentheses for grouping
874 result=$("$RUSH_BIN" -c 'echo $(( (2+3) * 4 ))' 2>&1)
875 if [ "$result" = "20" ]; then
876 pass "parentheses in arithmetic"
877 else
878 fail "parentheses in arithmetic" "20" "$result"
879 fi
880
881 # Comparison operators return 0/1
882 result=$("$RUSH_BIN" -c 'echo $((5 > 3))' 2>&1)
883 if [ "$result" = "1" ]; then
884 pass "arithmetic comparison returns 1 for true"
885 else
886 fail "arithmetic comparison returns 1 for true" "1" "$result"
887 fi
888
889 result=$("$RUSH_BIN" -c 'echo $((3 > 5))' 2>&1)
890 if [ "$result" = "0" ]; then
891 pass "arithmetic comparison returns 0 for false"
892 else
893 fail "arithmetic comparison returns 0 for false" "0" "$result"
894 fi
895
896 # =====================================
897 section "382. VARIABLE ASSIGNMENT EDGE CASES"
898 # =====================================
899
900 # Assignment with empty value
901 result=$("$RUSH_BIN" -c 'X=; echo "[$X]"' 2>&1)
902 if [ "$result" = "[]" ]; then
903 pass "empty variable assignment"
904 else
905 fail "empty variable assignment" "[]" "$result"
906 fi
907
908 # Assignment with quotes
909 result=$("$RUSH_BIN" -c 'X="hello world"; echo $X' 2>&1)
910 if [ "$result" = "hello world" ]; then
911 pass "quoted variable assignment"
912 else
913 fail "quoted variable assignment" "hello world" "$result"
914 fi
915
916 # Multiple assignments on one line
917 result=$("$RUSH_BIN" -c 'A=1 B=2 C=3; echo $A $B $C' 2>&1)
918 if [ "$result" = "1 2 3" ]; then
919 pass "multiple assignments"
920 else
921 fail "multiple assignments" "1 2 3" "$result"
922 fi
923
924 # =====================================
925 section "383. QUOTING EDGE CASES"
926 # =====================================
927
928 # Single quotes preserve literally
929 result=$("$RUSH_BIN" -c "echo '\$HOME'" 2>&1)
930 if [ "$result" = '$HOME' ]; then
931 pass "single quotes preserve dollar"
932 else
933 fail "single quotes preserve dollar" "\$HOME" "$result"
934 fi
935
936 # Double quotes allow expansion
937 result=$("$RUSH_BIN" -c 'X=test; echo "$X"' 2>&1)
938 if [ "$result" = "test" ]; then
939 pass "double quotes allow expansion"
940 else
941 fail "double quotes allow expansion" "test" "$result"
942 fi
943
944 # Mixed quoting
945 result=$("$RUSH_BIN" -c "X=val; echo 'literal'\"\$X\"'more'" 2>&1)
946 if [ "$result" = "literalvalmore" ]; then
947 pass "mixed quoting works"
948 else
949 fail "mixed quoting works" "literalvalmore" "$result"
950 fi
951
952 # =====================================
953 section "384. PIPELINE EDGE CASES"
954 # =====================================
955
956 # Pipeline with three stages
957 result=$("$RUSH_BIN" -c 'echo test | cat | cat' 2>&1)
958 if [ "$result" = "test" ]; then
959 pass "three-stage pipeline"
960 else
961 fail "three-stage pipeline" "test" "$result"
962 fi
963
964 # Pipeline with grep
965 result=$("$RUSH_BIN" -c 'echo "hello world" | grep hello' 2>&1)
966 if [ "$result" = "hello world" ]; then
967 pass "pipeline with grep"
968 else
969 fail "pipeline with grep" "hello world" "$result"
970 fi
971
972 # =====================================
973 section "385. CASE STATEMENT EDGE CASES"
974 # =====================================
975
976 # Case with wildcards
977 result=$("$RUSH_BIN" -c 'x=hello; case $x in h*) echo match;; esac' 2>&1)
978 if [ "$result" = "match" ]; then
979 pass "case with wildcard pattern"
980 else
981 fail "case with wildcard pattern" "match" "$result"
982 fi
983
984 # Case with multiple patterns
985 result=$("$RUSH_BIN" -c 'x=b; case $x in a|b|c) echo abc;; esac' 2>&1)
986 if [ "$result" = "abc" ]; then
987 pass "case with multiple patterns"
988 else
989 fail "case with multiple patterns" "abc" "$result"
990 fi
991
992 # Case with default
993 result=$("$RUSH_BIN" -c 'x=z; case $x in a) echo a;; *) echo default;; esac' 2>&1)
994 if [ "$result" = "default" ]; then
995 pass "case with default pattern"
996 else
997 fail "case with default pattern" "default" "$result"
998 fi
999
1000 # =====================================
1001 section "386. LOOP EDGE CASES"
1002 # =====================================
1003
1004 # For loop with break
1005 result=$("$RUSH_BIN" -c 'for i in 1 2 3 4 5; do [ $i -eq 3 ] && break; echo $i; done' 2>&1)
1006 expected="1
1007 2"
1008 if [ "$result" = "$expected" ]; then
1009 pass "for loop with break"
1010 else
1011 fail "for loop with break"
1012 fi
1013
1014 # For loop with continue
1015 result=$("$RUSH_BIN" -c 'for i in 1 2 3 4 5; do [ $i -eq 3 ] && continue; echo $i; done' 2>&1)
1016 if echo "$result" | grep -q "1" && echo "$result" | grep -q "4" && ! echo "$result" | grep -q "3"; then
1017 pass "for loop with continue"
1018 else
1019 fail "for loop with continue"
1020 fi
1021
1022 # =====================================
1023 section "387. SUBSHELL EDGE CASES"
1024 # =====================================
1025
1026 # Subshell variable isolation
1027 result=$("$RUSH_BIN" -c 'X=outer; (X=inner; echo $X); echo $X' 2>&1)
1028 expected="inner
1029 outer"
1030 if [ "$result" = "$expected" ]; then
1031 pass "subshell variable isolation"
1032 else
1033 fail "subshell variable isolation"
1034 fi
1035
1036 # Subshell exit status
1037 result=$("$RUSH_BIN" -c '(exit 42); echo $?' 2>&1)
1038 if [ "$result" = "42" ]; then
1039 pass "subshell exit status captured"
1040 else
1041 fail "subshell exit status captured" "42" "$result"
1042 fi
1043
1044 # =====================================
1045 section "388. FUNCTION EDGE CASES"
1046 # =====================================
1047
1048 # Function with local-like behavior (via subshell)
1049 result=$("$RUSH_BIN" -c 'X=global; f() { (X=local; echo $X); }; f; echo $X' 2>&1)
1050 expected="local
1051 global"
1052 if [ "$result" = "$expected" ]; then
1053 pass "function with subshell for local vars"
1054 else
1055 fail "function with subshell for local vars"
1056 fi
1057
1058 # Recursive function
1059 result=$("$RUSH_BIN" -c 'count() { if [ $1 -gt 0 ]; then echo $1; count $(($1-1)); fi; }; count 3' 2>&1)
1060 expected="3
1061 2
1062 1"
1063 if [ "$result" = "$expected" ]; then
1064 pass "recursive function"
1065 else
1066 fail "recursive function"
1067 fi
1068
1069 # =====================================
1070 section "389. HERE DOCUMENT EDGE CASES"
1071 # =====================================
1072
1073 # Heredoc with variable expansion
1074 result=$("$RUSH_BIN" -c 'X=value; cat <<EOF
1075 $X
1076 EOF' 2>&1)
1077 if [ "$result" = "value" ]; then
1078 pass "heredoc variable expansion"
1079 else
1080 fail "heredoc variable expansion" "value" "$result"
1081 fi
1082
1083 # Quoted heredoc (no expansion)
1084 result=$("$RUSH_BIN" -c 'cat <<'\''EOF'\''
1085 $VAR
1086 EOF' 2>&1)
1087 if [ "$result" = '$VAR' ]; then
1088 pass "quoted heredoc no expansion"
1089 else
1090 fail "quoted heredoc no expansion" "\$VAR" "$result"
1091 fi
1092
1093 # =====================================
1094 section "390. COMMAND SUBSTITUTION EDGE CASES"
1095 # =====================================
1096
1097 # Nested command substitution
1098 result=$("$RUSH_BIN" -c 'echo $(echo $(echo nested))' 2>&1)
1099 if [ "$result" = "nested" ]; then
1100 pass "nested command substitution"
1101 else
1102 fail "nested command substitution" "nested" "$result"
1103 fi
1104
1105 # Command substitution with quotes
1106 result=$("$RUSH_BIN" -c 'echo "$(echo "hello world")"' 2>&1)
1107 if [ "$result" = "hello world" ]; then
1108 pass "command substitution with quotes"
1109 else
1110 fail "command substitution with quotes" "hello world" "$result"
1111 fi
1112
1113 # Backtick substitution
1114 result=$("$RUSH_BIN" -c 'echo `echo backtick`' 2>&1)
1115 if [ "$result" = "backtick" ]; then
1116 pass "backtick command substitution"
1117 else
1118 fail "backtick command substitution" "backtick" "$result"
1119 fi
1120
1121 # =====================================
1122 section "391. EXPR COMMAND"
1123 # =====================================
1124
1125 result=$("$RUSH_BIN" -c 'expr 5 + 3' 2>&1)
1126 if [ "$result" = "8" ]; then
1127 pass "expr addition"
1128 else
1129 fail "expr addition" "8" "$result"
1130 fi
1131
1132 result=$("$RUSH_BIN" -c 'expr 10 - 4' 2>&1)
1133 if [ "$result" = "6" ]; then
1134 pass "expr subtraction"
1135 else
1136 fail "expr subtraction" "6" "$result"
1137 fi
1138
1139 result=$("$RUSH_BIN" -c 'expr 6 \* 7' 2>&1)
1140 if [ "$result" = "42" ]; then
1141 pass "expr multiplication"
1142 else
1143 fail "expr multiplication" "42" "$result"
1144 fi
1145
1146 result=$("$RUSH_BIN" -c 'expr 20 / 4' 2>&1)
1147 if [ "$result" = "5" ]; then
1148 pass "expr division"
1149 else
1150 fail "expr division" "5" "$result"
1151 fi
1152
1153 # =====================================
1154 section "392. TEST COMMAND VARIATIONS"
1155 # =====================================
1156
1157 result=$("$RUSH_BIN" -c 'test -f /etc/passwd && echo yes' 2>&1)
1158 if [ "$result" = "yes" ]; then
1159 pass "test -f regular file"
1160 else
1161 fail "test -f regular file" "yes" "$result"
1162 fi
1163
1164 result=$("$RUSH_BIN" -c 'test -d /tmp && echo yes' 2>&1)
1165 if [ "$result" = "yes" ]; then
1166 pass "test -d directory"
1167 else
1168 fail "test -d directory" "yes" "$result"
1169 fi
1170
1171 result=$("$RUSH_BIN" -c 'test 5 -eq 5 && echo yes' 2>&1)
1172 if [ "$result" = "yes" ]; then
1173 pass "test numeric equal"
1174 else
1175 fail "test numeric equal" "yes" "$result"
1176 fi
1177
1178 result=$("$RUSH_BIN" -c 'test "abc" = "abc" && echo yes' 2>&1)
1179 if [ "$result" = "yes" ]; then
1180 pass "test string equal"
1181 else
1182 fail "test string equal" "yes" "$result"
1183 fi
1184
1185 # =====================================
1186 section "393. BASENAME AND DIRNAME SIMULATION"
1187 # =====================================
1188
1189 result=$("$RUSH_BIN" -c 'X=/path/to/file.txt; echo ${X##*/}' 2>&1)
1190 if [ "$result" = "file.txt" ]; then
1191 pass "basename via parameter expansion"
1192 else
1193 fail "basename via parameter expansion" "file.txt" "$result"
1194 fi
1195
1196 result=$("$RUSH_BIN" -c 'X=/path/to/file.txt; echo ${X%/*}' 2>&1)
1197 if [ "$result" = "/path/to" ]; then
1198 pass "dirname via parameter expansion"
1199 else
1200 fail "dirname via parameter expansion" "/path/to" "$result"
1201 fi
1202
1203 result=$("$RUSH_BIN" -c 'X=file.tar.gz; echo ${X%.gz}' 2>&1)
1204 if [ "$result" = "file.tar" ]; then
1205 pass "remove extension"
1206 else
1207 fail "remove extension" "file.tar" "$result"
1208 fi
1209
1210 result=$("$RUSH_BIN" -c 'X=file.tar.gz; echo ${X%%.*}' 2>&1)
1211 if [ "$result" = "file" ]; then
1212 pass "remove all extensions"
1213 else
1214 fail "remove all extensions" "file" "$result"
1215 fi
1216
1217 # =====================================
1218 section "394. COMPLEX PIPELINES"
1219 # =====================================
1220
1221 result=$("$RUSH_BIN" -c 'echo "hello world" | tr " " "\n" | wc -l' 2>&1)
1222 if [ "$result" = "2" ]; then
1223 pass "pipeline with tr and wc"
1224 else
1225 fail "pipeline with tr and wc" "2" "$result"
1226 fi
1227
1228 result=$("$RUSH_BIN" -c 'printf "c\na\nb\n" | sort | head -1' 2>&1)
1229 if [ "$result" = "a" ]; then
1230 pass "pipeline sort and head"
1231 else
1232 fail "pipeline sort and head" "a" "$result"
1233 fi
1234
1235 result=$("$RUSH_BIN" -c 'echo "test" | cat | cat | cat' 2>&1)
1236 if [ "$result" = "test" ]; then
1237 pass "triple cat pipeline"
1238 else
1239 fail "triple cat pipeline" "test" "$result"
1240 fi
1241
1242 # =====================================
1243 section "395. COMMAND GROUPING"
1244 # =====================================
1245
1246 result=$("$RUSH_BIN" -c '{ echo a; echo b; echo c; } | wc -l' 2>&1)
1247 if [ "$result" = "3" ]; then
1248 pass "brace group to pipeline"
1249 else
1250 fail "brace group to pipeline" "3" "$result"
1251 fi
1252
1253 result=$("$RUSH_BIN" -c '(echo x; echo y) | wc -l' 2>&1)
1254 if [ "$result" = "2" ]; then
1255 pass "subshell to pipeline"
1256 else
1257 fail "subshell to pipeline" "2" "$result"
1258 fi
1259
1260 result=$("$RUSH_BIN" -c 'X=1; { X=2; echo $X; }; echo $X' 2>&1)
1261 expected=$(printf "2\n2")
1262 if [ "$result" = "$expected" ]; then
1263 pass "brace group modifies parent var"
1264 else
1265 fail "brace group modifies parent var"
1266 fi
1267
1268 # =====================================
1269 section "396. WORD EXPANSION ORDER"
1270 # =====================================
1271
1272 result=$("$RUSH_BIN" -c 'X="a b c"; echo $X' 2>&1)
1273 if [ "$result" = "a b c" ]; then
1274 pass "unquoted var word splits"
1275 else
1276 fail "unquoted var word splits" "a b c" "$result"
1277 fi
1278
1279 result=$("$RUSH_BIN" -c 'X="a b c"; echo "$X"' 2>&1)
1280 if [ "$result" = "a b c" ]; then
1281 pass "quoted var preserves spaces"
1282 else
1283 fail "quoted var preserves spaces" "a b c" "$result"
1284 fi
1285
1286 result=$("$RUSH_BIN" -c 'echo ~ | grep -c "^/"' 2>&1)
1287 if [ "$result" = "1" ]; then
1288 pass "tilde expands to home"
1289 else
1290 fail "tilde expands to home" "1" "$result"
1291 fi
1292
1293 # =====================================
1294 section "397. SIGNAL NAMES"
1295 # =====================================
1296
1297 result=$("$RUSH_BIN" -c 'kill -l | grep -c HUP' 2>&1)
1298 if [ "$result" -ge 1 ]; then
1299 pass "kill -l shows HUP"
1300 else
1301 fail "kill -l shows HUP"
1302 fi
1303
1304 result=$("$RUSH_BIN" -c 'kill -l | grep -c INT' 2>&1)
1305 if [ "$result" -ge 1 ]; then
1306 pass "kill -l shows INT"
1307 else
1308 fail "kill -l shows INT"
1309 fi
1310
1311 result=$("$RUSH_BIN" -c 'kill -l | grep -c TERM' 2>&1)
1312 if [ "$result" -ge 1 ]; then
1313 pass "kill -l shows TERM"
1314 else
1315 fail "kill -l shows TERM"
1316 fi
1317
1318 # =====================================
1319 section "398. EXEC WITH FD"
1320 # =====================================
1321
1322 result=$("$RUSH_BIN" -c 'exec 3>&1; echo test >&3; exec 3>&-' 2>&1)
1323 if [ "$result" = "test" ]; then
1324 pass "exec fd redirect"
1325 else
1326 fail "exec fd redirect" "test" "$result"
1327 fi
1328
1329 # =====================================
1330 section "399. COMPLEX FUNCTIONS"
1331 # =====================================
1332
1333 result=$("$RUSH_BIN" -c 'max() { [ $1 -gt $2 ] && echo $1 || echo $2; }; max 5 3' 2>&1)
1334 if [ "$result" = "5" ]; then
1335 pass "function max returns larger"
1336 else
1337 fail "function max returns larger" "5" "$result"
1338 fi
1339
1340 result=$("$RUSH_BIN" -c 'sum() { echo $(($1 + $2)); }; sum 10 20' 2>&1)
1341 if [ "$result" = "30" ]; then
1342 pass "function sum"
1343 else
1344 fail "function sum" "30" "$result"
1345 fi
1346
1347 result=$("$RUSH_BIN" -c 'greet() { echo "Hello, $1!"; }; greet World' 2>&1)
1348 if [ "$result" = "Hello, World!" ]; then
1349 pass "function with string interpolation"
1350 else
1351 fail "function with string interpolation" "Hello, World!" "$result"
1352 fi
1353
1354 # =====================================
1355 section "400. ADVANCED REDIRECTIONS"
1356 # =====================================
1357
1358 result=$("$RUSH_BIN" -c 'echo out; echo err >&2' 2>&1)
1359 expected=$(printf "out\nerr")
1360 if [ "$result" = "$expected" ]; then
1361 pass "stdout and stderr"
1362 else
1363 fail "stdout and stderr"
1364 fi
1365
1366 result=$("$RUSH_BIN" -c '{ echo a; echo b >&2; } 2>&1 | wc -l' 2>&1)
1367 if [ "$result" = "2" ]; then
1368 pass "redirect stderr to stdout in pipeline"
1369 else
1370 fail "redirect stderr to stdout in pipeline" "2" "$result"
1371 fi
1372
1373 # =====================================
1374 # Summary
1375 # =====================================
1376 printf "\n"
1377 printf "${BLUE}==========================================\n"
1378 printf "POSIX Special Features Test Summary\n"
1379 printf "==========================================${NC}\n"
1380 printf "Passed: ${GREEN}%d${NC}\n" "$PASSED"
1381 printf "Failed: ${RED}%d${NC}\n" "$FAILED"
1382 printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
1383 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
1384
1385 if [ -n "$FAILED_TESTS_LIST" ]; then
1386 printf "\n${RED}Failed tests:${NC}\n"
1387 printf "%b" "$FAILED_TESTS_LIST"
1388 fi
1389
1390 if [ "$FAILED" -gt 0 ]; then
1391 exit 1
1392 fi
1393 exit 0