Bash · 33828 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance Redirection and Pipeline Test Suite for rush
4 # =====================================
5 # Tests redirections and pipelines per IEEE Std 1003.1-2017
6 # Section: Shell Command Language - Redirection
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-redirect]"
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 TEST_DIR="/tmp/fortsh_redirect_$$"
73 mkdir -p "$TEST_DIR"
74
75 cleanup() {
76 rm -rf "$TEST_DIR"
77 }
78 trap cleanup EXIT
79
80 # =====================================
81 section "428. OUTPUT REDIRECTION >"
82 # =====================================
83
84 result=$("$RUSH_BIN" -c 'echo hello > '"$TEST_DIR"'/out1.txt; cat '"$TEST_DIR"'/out1.txt' 2>&1)
85 if [ "$result" = "hello" ]; then
86 pass "> redirects stdout to file"
87 else
88 fail "> redirects stdout to file" "hello" "$result"
89 fi
90
91 result=$("$RUSH_BIN" -c 'echo first > '"$TEST_DIR"'/out2.txt; echo second > '"$TEST_DIR"'/out2.txt; cat '"$TEST_DIR"'/out2.txt' 2>&1)
92 if [ "$result" = "second" ]; then
93 pass "> overwrites existing file"
94 else
95 fail "> overwrites existing file" "second" "$result"
96 fi
97
98 result=$("$RUSH_BIN" -c 'echo "multi word" > '"$TEST_DIR"'/out3.txt; cat '"$TEST_DIR"'/out3.txt' 2>&1)
99 if [ "$result" = "multi word" ]; then
100 pass "> with quoted string"
101 else
102 fail "> with quoted string" "multi word" "$result"
103 fi
104
105 # =====================================
106 section "429. APPEND REDIRECTION >>"
107 # =====================================
108
109 result=$("$RUSH_BIN" -c 'echo first > '"$TEST_DIR"'/append.txt; echo second >> '"$TEST_DIR"'/append.txt; cat '"$TEST_DIR"'/append.txt' 2>&1)
110 expected=$(printf "first\nsecond")
111 if [ "$result" = "$expected" ]; then
112 pass ">> appends to file"
113 else
114 fail ">> appends to file" "$expected" "$result"
115 fi
116
117 result=$("$RUSH_BIN" -c 'echo line1 >> '"$TEST_DIR"'/new.txt; cat '"$TEST_DIR"'/new.txt' 2>&1)
118 if [ "$result" = "line1" ]; then
119 pass ">> creates file if not exists"
120 else
121 fail ">> creates file if not exists" "line1" "$result"
122 fi
123
124 # =====================================
125 section "430. INPUT REDIRECTION <"
126 # =====================================
127
128 echo "test input" > "$TEST_DIR/input.txt"
129
130 result=$("$RUSH_BIN" -c 'cat < '"$TEST_DIR"'/input.txt' 2>&1)
131 if [ "$result" = "test input" ]; then
132 pass "< redirects file to stdin"
133 else
134 fail "< redirects file to stdin" "test input" "$result"
135 fi
136
137 result=$("$RUSH_BIN" -c 'read line < '"$TEST_DIR"'/input.txt; echo "$line"' 2>&1)
138 if [ "$result" = "test input" ]; then
139 pass "< with read builtin"
140 else
141 fail "< with read builtin" "test input" "$result"
142 fi
143
144 # =====================================
145 section "431. STDERR REDIRECTION 2>"
146 # =====================================
147
148 result=$("$RUSH_BIN" -c 'ls /nonexistent 2> '"$TEST_DIR"'/err.txt; cat '"$TEST_DIR"'/err.txt' 2>&1)
149 if echo "$result" | grep -qi "no such\|not found\|cannot"; then
150 pass "2> redirects stderr to file"
151 else
152 fail "2> redirects stderr to file" "error message" "$result"
153 fi
154
155 result=$("$RUSH_BIN" -c 'echo stdout; ls /nonexistent 2>/dev/null' 2>&1)
156 if [ "$result" = "stdout" ]; then
157 pass "2>/dev/null suppresses stderr"
158 else
159 fail "2>/dev/null suppresses stderr" "stdout" "$result"
160 fi
161
162 # =====================================
163 section "432. REDIRECT STDERR TO STDOUT 2>&1"
164 # =====================================
165
166 result=$("$RUSH_BIN" -c '{ echo out; ls /nonexistent; } 2>&1 | head -2' 2>&1)
167 if echo "$result" | grep -q "out"; then
168 pass "2>&1 merges stderr to stdout"
169 else
170 fail "2>&1 merges stderr to stdout" "both streams" "$result"
171 fi
172
173 result=$("$RUSH_BIN" -c 'ls /nonexistent 2>&1 | cat' 2>&1)
174 if echo "$result" | grep -qi "no such\|not found\|cannot"; then
175 pass "2>&1 stderr goes through pipe"
176 else
177 fail "2>&1 stderr goes through pipe" "error in pipe" "$result"
178 fi
179
180 # =====================================
181 section "433. REDIRECT STDOUT TO STDERR 1>&2"
182 # =====================================
183
184 result=$("$RUSH_BIN" -c 'echo error >&2' 2>&1)
185 if [ "$result" = "error" ]; then
186 pass ">&2 redirects stdout to stderr"
187 else
188 fail ">&2 redirects stdout to stderr" "error" "$result"
189 fi
190
191 result=$("$RUSH_BIN" -c 'echo error 1>&2 2>/dev/null' 2>&1)
192 if [ -z "$result" ]; then
193 pass "1>&2 with stderr suppressed"
194 else
195 fail "1>&2 with stderr suppressed" "(empty)" "$result"
196 fi
197
198 # =====================================
199 section "434. COMBINED REDIRECTIONS"
200 # =====================================
201
202 result=$("$RUSH_BIN" -c 'echo out > '"$TEST_DIR"'/combined.txt 2>&1; cat '"$TEST_DIR"'/combined.txt' 2>&1)
203 if [ "$result" = "out" ]; then
204 pass "> file 2>&1 combination"
205 else
206 fail "> file 2>&1 combination" "out" "$result"
207 fi
208
209 result=$("$RUSH_BIN" -c '{ echo out; echo err >&2; } > '"$TEST_DIR"'/both.txt 2>&1; cat '"$TEST_DIR"'/both.txt' 2>&1)
210 expected=$(printf "out\nerr")
211 if [ "$result" = "$expected" ]; then
212 pass "Both streams to same file"
213 else
214 fail "Both streams to same file" "$expected" "$result"
215 fi
216
217 # =====================================
218 section "435. NOCLOBBER WITH >|"
219 # =====================================
220
221 echo "original" > "$TEST_DIR/noclobber.txt"
222
223 result=$("$RUSH_BIN" -c 'set -C; echo new >| '"$TEST_DIR"'/noclobber.txt; cat '"$TEST_DIR"'/noclobber.txt' 2>&1)
224 if [ "$result" = "new" ]; then
225 pass ">| overrides noclobber"
226 else
227 fail ">| overrides noclobber" "new" "$result"
228 fi
229
230 # =====================================
231 section "436. SIMPLE PIPELINE"
232 # =====================================
233
234 result=$("$RUSH_BIN" -c 'echo hello | cat' 2>&1)
235 if [ "$result" = "hello" ]; then
236 pass "Simple two-stage pipeline"
237 else
238 fail "Simple two-stage pipeline" "hello" "$result"
239 fi
240
241 result=$("$RUSH_BIN" -c 'echo hello world | wc -w' 2>&1)
242 if echo "$result" | grep -q "2"; then
243 pass "Pipeline with wc"
244 else
245 fail "Pipeline with wc" "2" "$result"
246 fi
247
248 result=$("$RUSH_BIN" -c 'printf "c\na\nb\n" | sort' 2>&1)
249 expected=$(printf "a\nb\nc")
250 if [ "$result" = "$expected" ]; then
251 pass "Pipeline with sort"
252 else
253 fail "Pipeline with sort" "$expected" "$result"
254 fi
255
256 # =====================================
257 section "437. MULTI-STAGE PIPELINE"
258 # =====================================
259
260 result=$("$RUSH_BIN" -c 'echo hello | cat | cat | cat' 2>&1)
261 if [ "$result" = "hello" ]; then
262 pass "Four-stage pipeline"
263 else
264 fail "Four-stage pipeline" "hello" "$result"
265 fi
266
267 result=$("$RUSH_BIN" -c 'printf "b\na\nc\nb\na\n" | sort | uniq' 2>&1)
268 expected=$(printf "a\nb\nc")
269 if [ "$result" = "$expected" ]; then
270 pass "sort | uniq pipeline"
271 else
272 fail "sort | uniq pipeline" "$expected" "$result"
273 fi
274
275 result=$("$RUSH_BIN" -c 'echo "hello world" | tr " " "\n" | wc -l' 2>&1)
276 if echo "$result" | grep -q "2"; then
277 pass "tr | wc pipeline"
278 else
279 fail "tr | wc pipeline" "2" "$result"
280 fi
281
282 # =====================================
283 section "438. PIPELINE WITH REDIRECTION"
284 # =====================================
285
286 result=$("$RUSH_BIN" -c 'echo hello | cat > '"$TEST_DIR"'/pipe_out.txt; cat '"$TEST_DIR"'/pipe_out.txt' 2>&1)
287 if [ "$result" = "hello" ]; then
288 pass "Pipeline with output redirect"
289 else
290 fail "Pipeline with output redirect" "hello" "$result"
291 fi
292
293 echo "from file" > "$TEST_DIR/pipe_in.txt"
294 result=$("$RUSH_BIN" -c 'cat < '"$TEST_DIR"'/pipe_in.txt | tr "a-z" "A-Z"' 2>&1)
295 if [ "$result" = "FROM FILE" ]; then
296 pass "Input redirect into pipeline"
297 else
298 fail "Input redirect into pipeline" "FROM FILE" "$result"
299 fi
300
301 # =====================================
302 section "439. PIPELINE SUBSHELL"
303 # =====================================
304
305 result=$("$RUSH_BIN" -c 'echo test | { read x; echo "got: $x"; }' 2>&1)
306 if [ "$result" = "got: test" ]; then
307 pass "Pipeline to brace group"
308 else
309 fail "Pipeline to brace group" "got: test" "$result"
310 fi
311
312 result=$("$RUSH_BIN" -c 'echo test | (cat; echo done)' 2>&1)
313 expected=$(printf "test\ndone")
314 if [ "$result" = "$expected" ]; then
315 pass "Pipeline to subshell"
316 else
317 fail "Pipeline to subshell" "$expected" "$result"
318 fi
319
320 # =====================================
321 section "440. COMMAND SUBSTITUTION IN PIPELINE"
322 # =====================================
323
324 result=$("$RUSH_BIN" -c 'echo $(echo hello | tr "a-z" "A-Z")' 2>&1)
325 if [ "$result" = "HELLO" ]; then
326 pass "Command substitution with pipeline"
327 else
328 fail "Command substitution with pipeline" "HELLO" "$result"
329 fi
330
331 result=$("$RUSH_BIN" -c 'x=$(printf "a\nb\nc\n" | wc -l); echo $x' 2>&1)
332 if echo "$result" | grep -q "3"; then
333 pass "Variable from pipeline in command sub"
334 else
335 fail "Variable from pipeline in command sub" "3" "$result"
336 fi
337
338 # =====================================
339 section "441. REDIRECTION ORDER"
340 # =====================================
341
342 # The order matters: 2>&1 before > vs after
343 result=$("$RUSH_BIN" -c '{ echo out; echo err >&2; } > '"$TEST_DIR"'/order1.txt 2>&1; cat '"$TEST_DIR"'/order1.txt | wc -l' 2>&1)
344 if echo "$result" | grep -q "2"; then
345 pass "> file 2>&1 captures both"
346 else
347 fail "> file 2>&1 captures both" "2 lines" "$result"
348 fi
349
350 # =====================================
351 section "442. /dev/null REDIRECTION"
352 # =====================================
353
354 result=$("$RUSH_BIN" -c 'echo hello > /dev/null; echo done' 2>&1)
355 if [ "$result" = "done" ]; then
356 pass "> /dev/null discards output"
357 else
358 fail "> /dev/null discards output" "done" "$result"
359 fi
360
361 result=$("$RUSH_BIN" -c 'cat < /dev/null; echo empty' 2>&1)
362 if [ "$result" = "empty" ]; then
363 pass "< /dev/null provides empty input"
364 else
365 fail "< /dev/null provides empty input" "empty" "$result"
366 fi
367
368 result=$("$RUSH_BIN" -c 'ls /nonexistent 2>/dev/null; echo $?' 2>&1)
369 # Should show non-zero exit but no error output
370 if echo "$result" | grep -qE "^[12]$"; then
371 pass "2>/dev/null with failing command"
372 else
373 fail "2>/dev/null with failing command" "exit code only" "$result"
374 fi
375
376 # =====================================
377 section "443. PROCESS SUBSTITUTION STYLE"
378 # =====================================
379
380 # Note: <() is a bash extension, but we test if basic redirects work with commands
381 result=$("$RUSH_BIN" -c 'diff <(echo a) <(echo a) 2>/dev/null; echo $?' 2>&1)
382 # This may not be supported - just document if it works
383 if [ "$result" = "0" ]; then
384 pass "Process substitution <() works"
385 else
386 skip "Process substitution <() works" "bash extension"
387 fi
388
389 # =====================================
390 section "444. CLOSING FILE DESCRIPTORS"
391 # =====================================
392
393 result=$("$RUSH_BIN" -c 'echo hello >&-; echo done 2>/dev/null' 2>&1)
394 # Closing stdout then trying to echo should either error or succeed
395 if echo "$result" | grep -q "done"; then
396 pass ">&- closes stdout (recovered)"
397 else
398 pass ">&- closes stdout (caused error)"
399 fi
400
401 # =====================================
402 section "445. DUPLICATING INPUT"
403 # =====================================
404
405 result=$("$RUSH_BIN" -c 'echo test | { cat; } 0<&0' 2>&1)
406 if [ "$result" = "test" ]; then
407 pass "0<&0 duplicates stdin"
408 else
409 fail "0<&0 duplicates stdin" "test" "$result"
410 fi
411
412 # =====================================
413 section "446. PIPELINE EXIT STATUS"
414 # =====================================
415
416 # Last command determines exit status
417 result=$("$RUSH_BIN" -c 'false | true; echo $?' 2>&1)
418 if [ "$result" = "0" ]; then
419 pass "Pipeline exit is last command (false|true=0)"
420 else
421 fail "Pipeline exit is last command (false|true=0)" "0" "$result"
422 fi
423
424 result=$("$RUSH_BIN" -c 'true | false; echo $?' 2>&1)
425 if [ "$result" = "1" ]; then
426 pass "Pipeline exit is last command (true|false=1)"
427 else
428 fail "Pipeline exit is last command (true|false=1)" "1" "$result"
429 fi
430
431 # Multi-stage pipeline
432 result=$("$RUSH_BIN" -c 'echo a | cat | cat | cat; echo $?' 2>&1)
433 if echo "$result" | grep -q "0"; then
434 pass "Multi-stage pipeline success"
435 else
436 fail "Multi-stage pipeline success" "0" "$result"
437 fi
438
439 # Pipeline with negation
440 result=$("$RUSH_BIN" -c '! false | true; echo $?' 2>&1)
441 if [ "$result" = "1" ]; then
442 pass "! negates pipeline exit"
443 else
444 fail "! negates pipeline exit" "1" "$result"
445 fi
446
447 # =====================================
448 section "447. EXEC REDIRECTIONS"
449 # =====================================
450
451 # exec without command modifies shell FDs
452 result=$("$RUSH_BIN" -c '
453 exec 3>"'"$TEST_DIR"'/exec_fd3.txt"
454 echo "fd3 data" >&3
455 exec 3>&-
456 cat "'"$TEST_DIR"'/exec_fd3.txt"
457 ' 2>&1)
458 if [ "$result" = "fd3 data" ]; then
459 pass "exec opens FD 3 for writing"
460 else
461 fail "exec opens FD 3 for writing" "fd3 data" "$result"
462 fi
463
464 # exec read/write mode
465 result=$("$RUSH_BIN" -c '
466 echo "initial" > "'"$TEST_DIR"'/rw_test.txt"
467 exec 4<>"'"$TEST_DIR"'/rw_test.txt"
468 read line <&4
469 echo "read: $line"
470 exec 4>&-
471 ' 2>&1)
472 if [ "$result" = "read: initial" ]; then
473 pass "exec <> opens read-write"
474 else
475 fail "exec <> opens read-write" "read: initial" "$result"
476 fi
477
478 # =====================================
479 section "448. COMPOUND REDIRECTIONS"
480 # =====================================
481
482 # Redirect entire loop
483 result=$("$RUSH_BIN" -c '
484 for i in 1 2 3; do
485 echo $i
486 done > "'"$TEST_DIR"'/loop_out.txt"
487 cat "'"$TEST_DIR"'/loop_out.txt"
488 ' 2>&1)
489 expected=$(printf "1\n2\n3")
490 if [ "$result" = "$expected" ]; then
491 pass "Redirect entire for loop"
492 else
493 fail "Redirect entire for loop" "$expected" "$result"
494 fi
495
496 # Redirect if statement
497 result=$("$RUSH_BIN" -c '
498 if true; then
499 echo inside
500 fi > "'"$TEST_DIR"'/if_out.txt"
501 cat "'"$TEST_DIR"'/if_out.txt"
502 ' 2>&1)
503 if [ "$result" = "inside" ]; then
504 pass "Redirect entire if statement"
505 else
506 fail "Redirect entire if statement" "inside" "$result"
507 fi
508
509 # Redirect case statement
510 result=$("$RUSH_BIN" -c '
511 case "x" in
512 x) echo matched;;
513 esac > "'"$TEST_DIR"'/case_out.txt"
514 cat "'"$TEST_DIR"'/case_out.txt"
515 ' 2>&1)
516 if [ "$result" = "matched" ]; then
517 pass "Redirect entire case statement"
518 else
519 fail "Redirect entire case statement" "matched" "$result"
520 fi
521
522 # =====================================
523 section "449. INPUT REDIRECTION VARIATIONS"
524 # =====================================
525
526 # cat multiple files
527 echo "file1" > "$TEST_DIR/cat1.txt"
528 echo "file2" > "$TEST_DIR/cat2.txt"
529 result=$("$RUSH_BIN" -c 'cat "'"$TEST_DIR"'/cat1.txt" "'"$TEST_DIR"'/cat2.txt"' 2>&1)
530 expected=$(printf "file1\nfile2")
531 if [ "$result" = "$expected" ]; then
532 pass "cat multiple files"
533 else
534 fail "cat multiple files" "$expected" "$result"
535 fi
536
537 # Input from file
538 echo "from file" > "$TEST_DIR/input.txt"
539 result=$("$RUSH_BIN" -c 'cat < "'"$TEST_DIR"'/input.txt"' 2>&1)
540 if [ "$result" = "from file" ]; then
541 pass "< redirects stdin from file"
542 else
543 fail "< redirects stdin from file" "from file" "$result"
544 fi
545
546 # =====================================
547 section "450. OUTPUT APPEND BEHAVIOR"
548 # =====================================
549
550 # >> creates file if not exists
551 rm -f "$TEST_DIR/append_new.txt"
552 result=$("$RUSH_BIN" -c 'echo first >> "'"$TEST_DIR"'/append_new.txt"; cat "'"$TEST_DIR"'/append_new.txt"' 2>&1)
553 if [ "$result" = "first" ]; then
554 pass ">> creates file if not exists"
555 else
556 fail ">> creates file if not exists" "first" "$result"
557 fi
558
559 # >> appends to existing
560 echo "line1" > "$TEST_DIR/append_exist.txt"
561 result=$("$RUSH_BIN" -c 'echo line2 >> "'"$TEST_DIR"'/append_exist.txt"; cat "'"$TEST_DIR"'/append_exist.txt"' 2>&1)
562 expected=$(printf "line1\nline2")
563 if [ "$result" = "$expected" ]; then
564 pass ">> appends to existing file"
565 else
566 fail ">> appends to existing file" "$expected" "$result"
567 fi
568
569 # Multiple appends
570 rm -f "$TEST_DIR/multi_append.txt"
571 result=$("$RUSH_BIN" -c '
572 echo a >> "'"$TEST_DIR"'/multi_append.txt"
573 echo b >> "'"$TEST_DIR"'/multi_append.txt"
574 echo c >> "'"$TEST_DIR"'/multi_append.txt"
575 cat "'"$TEST_DIR"'/multi_append.txt"
576 ' 2>&1)
577 expected=$(printf "a\nb\nc")
578 if [ "$result" = "$expected" ]; then
579 pass "Multiple sequential appends"
580 else
581 fail "Multiple sequential appends" "$expected" "$result"
582 fi
583
584 # =====================================
585 section "451. REDIRECT WITH ASSIGNMENTS"
586 # =====================================
587
588 # Assignment with redirect
589 result=$("$RUSH_BIN" -c 'x=$(cat < /dev/null); echo "empty:[$x]"' 2>&1)
590 if [ "$result" = "empty:[]" ]; then
591 pass "Assignment from empty file"
592 else
593 fail "Assignment from empty file" "empty:[]" "$result"
594 fi
595
596 # Assignment captures command output despite redirects
597 result=$("$RUSH_BIN" -c 'x=$(echo hello 2>/dev/null); echo $x' 2>&1)
598 if [ "$result" = "hello" ]; then
599 pass "Assignment captures stdout"
600 else
601 fail "Assignment captures stdout" "hello" "$result"
602 fi
603
604 # =====================================
605 section "452. HEREDOC VARIATIONS"
606 # =====================================
607
608 # Basic heredoc
609 result=$("$RUSH_BIN" -c 'cat <<END
610 hello
611 END' 2>&1)
612 if [ "$result" = "hello" ]; then
613 pass "Basic heredoc"
614 else
615 fail "Basic heredoc" "hello" "$result"
616 fi
617
618 # Heredoc with variable expansion
619 result=$("$RUSH_BIN" -c 'X=world; cat <<END
620 hello $X
621 END' 2>&1)
622 if [ "$result" = "hello world" ]; then
623 pass "Heredoc with variable expansion"
624 else
625 fail "Heredoc with variable expansion" "hello world" "$result"
626 fi
627
628 # Quoted heredoc prevents expansion
629 result=$("$RUSH_BIN" -c 'cat <<'\''END'\''
630 $VAR
631 END' 2>&1)
632 if [ "$result" = '$VAR' ]; then
633 pass "Quoted heredoc prevents expansion"
634 else
635 fail "Quoted heredoc prevents expansion" "\$VAR" "$result"
636 fi
637
638 # =====================================
639 section "453. PIPELINE VARIATIONS"
640 # =====================================
641
642 # Long pipeline
643 result=$("$RUSH_BIN" -c 'echo test | cat | cat | cat | cat' 2>&1)
644 if [ "$result" = "test" ]; then
645 pass "Long pipeline with multiple cats"
646 else
647 fail "Long pipeline with multiple cats" "test" "$result"
648 fi
649
650 # Pipeline with head
651 result=$("$RUSH_BIN" -c 'printf "a\nb\nc\n" | head -1' 2>&1)
652 if [ "$result" = "a" ]; then
653 pass "Pipeline with head"
654 else
655 fail "Pipeline with head" "a" "$result"
656 fi
657
658 # Pipeline with tail
659 result=$("$RUSH_BIN" -c 'printf "a\nb\nc\n" | tail -1' 2>&1)
660 if [ "$result" = "c" ]; then
661 pass "Pipeline with tail"
662 else
663 fail "Pipeline with tail" "c" "$result"
664 fi
665
666 # Pipeline with sort
667 result=$("$RUSH_BIN" -c 'printf "c\na\nb\n" | sort | head -1' 2>&1)
668 if [ "$result" = "a" ]; then
669 pass "Pipeline with sort"
670 else
671 fail "Pipeline with sort" "a" "$result"
672 fi
673
674 # =====================================
675 section "454. FILE DESCRIPTOR OPERATIONS"
676 # =====================================
677
678 # Dup stdout to stderr
679 result=$("$RUSH_BIN" -c 'echo test >&2' 2>&1)
680 if [ "$result" = "test" ]; then
681 pass "Redirect stdout to stderr"
682 else
683 fail "Redirect stdout to stderr" "test" "$result"
684 fi
685
686 # Close stdout (write to stderr)
687 result=$("$RUSH_BIN" -c 'echo test >&2 1>&-' 2>&1)
688 if [ "$result" = "test" ]; then
689 pass "Close stdout, write to stderr"
690 else
691 fail "Close stdout, write to stderr" "test" "$result"
692 fi
693
694 # =====================================
695 section "455. INPUT REDIRECTION VARIATIONS"
696 # =====================================
697
698 # Input from file
699 echo "content" > "$TEST_DIR/input_test.txt"
700 result=$("$RUSH_BIN" -c 'cat < "'"$TEST_DIR"'/input_test.txt"' 2>&1)
701 if [ "$result" = "content" ]; then
702 pass "Input redirection from file"
703 else
704 fail "Input redirection from file" "content" "$result"
705 fi
706
707 # Input from /dev/null
708 result=$("$RUSH_BIN" -c 'cat < /dev/null | wc -c' 2>&1)
709 if [ "$result" = "0" ]; then
710 pass "Input from /dev/null is empty"
711 else
712 fail "Input from /dev/null is empty" "0" "$result"
713 fi
714
715 # =====================================
716 section "456. OUTPUT TO /dev/null"
717 # =====================================
718
719 # Stdout to /dev/null
720 result=$("$RUSH_BIN" -c 'echo hidden > /dev/null; echo visible' 2>&1)
721 if [ "$result" = "visible" ]; then
722 pass "Stdout to /dev/null suppresses output"
723 else
724 fail "Stdout to /dev/null suppresses output" "visible" "$result"
725 fi
726
727 # Stderr to /dev/null
728 result=$("$RUSH_BIN" -c 'ls /nonexistent 2>/dev/null; echo done' 2>&1)
729 if [ "$result" = "done" ]; then
730 pass "Stderr to /dev/null suppresses errors"
731 else
732 fail "Stderr to /dev/null suppresses errors" "done" "$result"
733 fi
734
735 # Both to /dev/null
736 result=$("$RUSH_BIN" -c 'ls /nonexistent >/dev/null 2>&1; echo status=$?' 2>&1)
737 if echo "$result" | grep -q "status="; then
738 pass "Both stdout and stderr to /dev/null"
739 else
740 fail "Both stdout and stderr to /dev/null"
741 fi
742
743 # =====================================
744 section "457. NOCLOBBER BEHAVIOR"
745 # =====================================
746
747 # Normal overwrite
748 echo "old" > "$TEST_DIR/clobber_test.txt"
749 result=$("$RUSH_BIN" -c 'echo new > "'"$TEST_DIR"'/clobber_test.txt"; cat "'"$TEST_DIR"'/clobber_test.txt"' 2>&1)
750 if [ "$result" = "new" ]; then
751 pass "Normal > overwrites file"
752 else
753 fail "Normal > overwrites file" "new" "$result"
754 fi
755
756 # =====================================
757 section "458. COMPOUND REDIRECTIONS"
758 # =====================================
759
760 # Redirect in if statement
761 result=$("$RUSH_BIN" -c 'if true; then echo yes; fi > "'"$TEST_DIR"'/if_redir.txt"; cat "'"$TEST_DIR"'/if_redir.txt"' 2>&1)
762 if [ "$result" = "yes" ]; then
763 pass "Redirect if statement output"
764 else
765 fail "Redirect if statement output" "yes" "$result"
766 fi
767
768 # Redirect in while loop
769 result=$("$RUSH_BIN" -c 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done > "'"$TEST_DIR"'/while_redir.txt"; wc -l < "'"$TEST_DIR"'/while_redir.txt"' 2>&1)
770 if [ "$result" = "3" ]; then
771 pass "Redirect while loop output"
772 else
773 fail "Redirect while loop output" "3" "$result"
774 fi
775
776 # Redirect in for loop
777 result=$("$RUSH_BIN" -c 'for x in a b c; do echo $x; done > "'"$TEST_DIR"'/for_redir.txt"; wc -l < "'"$TEST_DIR"'/for_redir.txt"' 2>&1)
778 if [ "$result" = "3" ]; then
779 pass "Redirect for loop output"
780 else
781 fail "Redirect for loop output" "3" "$result"
782 fi
783
784 # =====================================
785 section "459. PIPELINE EXIT STATUS"
786 # =====================================
787
788 # Last command determines exit
789 result=$("$RUSH_BIN" -c 'true | false; echo $?' 2>&1)
790 if [ "$result" = "1" ]; then
791 pass "Pipeline exit from last command (false)"
792 else
793 fail "Pipeline exit from last command (false)" "1" "$result"
794 fi
795
796 result=$("$RUSH_BIN" -c 'false | true; echo $?' 2>&1)
797 if [ "$result" = "0" ]; then
798 pass "Pipeline exit from last command (true)"
799 else
800 fail "Pipeline exit from last command (true)" "0" "$result"
801 fi
802
803 # =====================================
804 section "460. MULTIPLE REDIRECTIONS"
805 # =====================================
806
807 # Both input and output
808 echo "input" > "$TEST_DIR/multi_in.txt"
809 result=$("$RUSH_BIN" -c 'cat < "'"$TEST_DIR"'/multi_in.txt" > "'"$TEST_DIR"'/multi_out.txt"; cat "'"$TEST_DIR"'/multi_out.txt"' 2>&1)
810 if [ "$result" = "input" ]; then
811 pass "Both input and output redirect"
812 else
813 fail "Both input and output redirect" "input" "$result"
814 fi
815
816 # Order of redirections
817 result=$("$RUSH_BIN" -c 'echo test > "'"$TEST_DIR"'/order1.txt" 2>&1; cat "'"$TEST_DIR"'/order1.txt"' 2>&1)
818 if [ "$result" = "test" ]; then
819 pass "Redirect order: > then 2>&1"
820 else
821 fail "Redirect order: > then 2>&1" "test" "$result"
822 fi
823
824 # =====================================
825 section "461. PIPELINE WITH SUBSHELL"
826 # =====================================
827
828 # Subshell in pipeline
829 result=$("$RUSH_BIN" -c '(echo a; echo b) | wc -l' 2>&1)
830 if [ "$result" = "2" ]; then
831 pass "Subshell in pipeline"
832 else
833 fail "Subshell in pipeline" "2" "$result"
834 fi
835
836 # Brace group in pipeline
837 result=$("$RUSH_BIN" -c '{ echo x; echo y; } | wc -l' 2>&1)
838 if [ "$result" = "2" ]; then
839 pass "Brace group in pipeline"
840 else
841 fail "Brace group in pipeline" "2" "$result"
842 fi
843
844 # =====================================
845 section "462. REDIRECT AND PIPELINE COMBO"
846 # =====================================
847
848 # Pipeline with final redirect
849 result=$("$RUSH_BIN" -c 'echo test | cat > "'"$TEST_DIR"'/pipe_redir.txt"; cat "'"$TEST_DIR"'/pipe_redir.txt"' 2>&1)
850 if [ "$result" = "test" ]; then
851 pass "Pipeline with final redirect"
852 else
853 fail "Pipeline with final redirect" "test" "$result"
854 fi
855
856 # Redirect within pipeline
857 result=$("$RUSH_BIN" -c 'echo test 2>/dev/null | cat' 2>&1)
858 if [ "$result" = "test" ]; then
859 pass "Redirect within pipeline stage"
860 else
861 fail "Redirect within pipeline stage" "test" "$result"
862 fi
863
864 # =====================================
865 section "463. HERE STRING ALTERNATIVES"
866 # =====================================
867
868 # Echo pipe as here-string alternative
869 result=$("$RUSH_BIN" -c 'echo "test" | cat' 2>&1)
870 if [ "$result" = "test" ]; then
871 pass "Echo pipe as here-string alternative"
872 else
873 fail "Echo pipe as here-string alternative" "test" "$result"
874 fi
875
876 # Printf pipe
877 result=$("$RUSH_BIN" -c 'printf "%s" "test" | cat' 2>&1)
878 if [ "$result" = "test" ]; then
879 pass "Printf pipe"
880 else
881 fail "Printf pipe" "test" "$result"
882 fi
883
884 # =====================================
885 section "464. REDIRECTION EDGE CASES"
886 # =====================================
887
888 # Redirect to same file
889 result=$("$RUSH_BIN" -c 'echo original > "'"$TEST_DIR"'/same.txt"; echo new > "'"$TEST_DIR"'/same.txt"; cat "'"$TEST_DIR"'/same.txt"' 2>&1)
890 if [ "$result" = "new" ]; then
891 pass "Multiple redirects to same file"
892 else
893 fail "Multiple redirects to same file" "new" "$result"
894 fi
895
896 # Empty redirect (creates empty file)
897 rm -f "$TEST_DIR/empty_redir.txt"
898 result=$("$RUSH_BIN" -c '> "'"$TEST_DIR"'/empty_redir.txt"; [ -f "'"$TEST_DIR"'/empty_redir.txt" ] && echo exists' 2>&1)
899 if [ "$result" = "exists" ]; then
900 pass "Empty redirect creates file"
901 else
902 fail "Empty redirect creates file" "exists" "$result"
903 fi
904
905 # =====================================
906 section "465. TPYE WITH REDIRECTION"
907 # =====================================
908
909 # Pipeline preserves data integrity
910 result=$("$RUSH_BIN" -c 'echo "hello world" | cat | cat | cat' 2>&1)
911 if [ "$result" = "hello world" ]; then
912 pass "Pipeline preserves data integrity"
913 else
914 fail "Pipeline preserves data integrity" "hello world" "$result"
915 fi
916
917 # Multiple pipes with tr
918 result=$("$RUSH_BIN" -c 'echo abc | tr a X | tr b Y | tr c Z' 2>&1)
919 if [ "$result" = "XYZ" ]; then
920 pass "Multiple pipes with tr"
921 else
922 fail "Multiple pipes with tr" "XYZ" "$result"
923 fi
924
925 # =====================================
926 section "466. ADVANCED HEREDOCS"
927 # =====================================
928
929 # Heredoc with indented delimiter
930 result=$("$RUSH_BIN" -c 'cat <<-EOF
931 indented
932 EOF' 2>&1)
933 if echo "$result" | grep -q "indented"; then
934 pass "Heredoc with tab stripping"
935 else
936 fail "Heredoc with tab stripping"
937 fi
938
939 # Multiple heredocs
940 result=$("$RUSH_BIN" -c 'cat <<EOF1; cat <<EOF2
941 first
942 EOF1
943 second
944 EOF2' 2>&1)
945 if echo "$result" | grep -q "first" && echo "$result" | grep -q "second"; then
946 pass "Multiple sequential heredocs"
947 else
948 fail "Multiple sequential heredocs"
949 fi
950
951 # =====================================
952 section "467. FD MANIPULATION"
953 # =====================================
954
955 # Close and reopen
956 result=$("$RUSH_BIN" -c 'exec 3>&1; echo via3 >&3; exec 3>&-' 2>&1)
957 if [ "$result" = "via3" ]; then
958 pass "exec fd open, write, close"
959 else
960 fail "exec fd open, write, close" "via3" "$result"
961 fi
962
963 # Stderr to file
964 result=$("$RUSH_BIN" -c 'ls /nonexistent 2>"'"$TEST_DIR"'/stderr.txt"; cat "'"$TEST_DIR"'/stderr.txt" | wc -l' 2>&1)
965 if [ "$result" -ge 0 ]; then
966 pass "stderr redirect to file"
967 else
968 fail "stderr redirect to file"
969 fi
970
971 # =====================================
972 section "468. PROCESS SUBSTITUTION ALTERNATIVES"
973 # =====================================
974
975 # Using temp files
976 result=$("$RUSH_BIN" -c 'echo test > /tmp/proc_sub_$$; cat /tmp/proc_sub_$$; rm /tmp/proc_sub_$$' 2>&1)
977 if [ "$result" = "test" ]; then
978 pass "temp file as process sub"
979 else
980 fail "temp file as process sub" "test" "$result"
981 fi
982
983 # =====================================
984 section "469. COMPLEX REDIRECT CHAINS"
985 # =====================================
986
987 # Redirect chain
988 result=$("$RUSH_BIN" -c 'echo test > "'"$TEST_DIR"'/chain1.txt"; cat "'"$TEST_DIR"'/chain1.txt" > "'"$TEST_DIR"'/chain2.txt"; cat "'"$TEST_DIR"'/chain2.txt"; rm "'"$TEST_DIR"'/chain1.txt" "'"$TEST_DIR"'/chain2.txt"' 2>&1)
989 if [ "$result" = "test" ]; then
990 pass "redirect file chain"
991 else
992 fail "redirect file chain" "test" "$result"
993 fi
994
995 # Pipeline to file
996 result=$("$RUSH_BIN" -c 'echo abc | tr a-z A-Z > "'"$TEST_DIR"'/pipe_out.txt"; cat "'"$TEST_DIR"'/pipe_out.txt"; rm "'"$TEST_DIR"'/pipe_out.txt"' 2>&1)
997 if [ "$result" = "ABC" ]; then
998 pass "pipeline output to file"
999 else
1000 fail "pipeline output to file" "ABC" "$result"
1001 fi
1002
1003 # =====================================
1004 section "470. INPUT VARIATIONS"
1005 # =====================================
1006
1007 # Read from pipeline
1008 result=$("$RUSH_BIN" -c 'echo "hello" | { read x; echo got $x; }' 2>&1)
1009 if [ "$result" = "got hello" ]; then
1010 pass "read from pipeline"
1011 else
1012 fail "read from pipeline" "got hello" "$result"
1013 fi
1014
1015 # Cat from stdin
1016 result=$("$RUSH_BIN" -c 'echo test | cat' 2>&1)
1017 if [ "$result" = "test" ]; then
1018 pass "cat from stdin"
1019 else
1020 fail "cat from stdin" "test" "$result"
1021 fi
1022
1023 # =====================================
1024 section "471. OUTPUT VARIATIONS"
1025 # =====================================
1026
1027 # Printf to file
1028 result=$("$RUSH_BIN" -c 'printf "formatted\n" > "'"$TEST_DIR"'/printf_out.txt"; cat "'"$TEST_DIR"'/printf_out.txt"; rm "'"$TEST_DIR"'/printf_out.txt"' 2>&1)
1029 if [ "$result" = "formatted" ]; then
1030 pass "printf to file"
1031 else
1032 fail "printf to file" "formatted" "$result"
1033 fi
1034
1035 # Echo -n equivalent
1036 result=$("$RUSH_BIN" -c 'printf "no newline"' 2>&1)
1037 if [ "$result" = "no newline" ]; then
1038 pass "printf without newline"
1039 else
1040 fail "printf without newline" "no newline" "$result"
1041 fi
1042
1043 # =====================================
1044 section "472. MIXED REDIRECTIONS"
1045 # =====================================
1046
1047 # stdout and stderr to different files
1048 result=$("$RUSH_BIN" -c 'echo out > "'"$TEST_DIR"'/mix_out.txt" 2>"'"$TEST_DIR"'/mix_err.txt"; cat "'"$TEST_DIR"'/mix_out.txt"; rm "'"$TEST_DIR"'/mix_out.txt" "'"$TEST_DIR"'/mix_err.txt"' 2>&1)
1049 if [ "$result" = "out" ]; then
1050 pass "stdout and stderr to different files"
1051 else
1052 fail "stdout and stderr to different files" "out" "$result"
1053 fi
1054
1055 # =====================================
1056 section "473. PIPELINE EDGE CASES"
1057 # =====================================
1058
1059 # Empty pipeline input
1060 result=$("$RUSH_BIN" -c 'cat /dev/null | wc -c' 2>&1)
1061 if [ "$result" = "0" ]; then
1062 pass "empty pipeline input"
1063 else
1064 fail "empty pipeline input" "0" "$result"
1065 fi
1066
1067 # Very long pipeline
1068 result=$("$RUSH_BIN" -c 'echo x | cat | cat | cat | cat | cat | cat | cat | cat' 2>&1)
1069 if [ "$result" = "x" ]; then
1070 pass "very long pipeline"
1071 else
1072 fail "very long pipeline" "x" "$result"
1073 fi
1074
1075 # =====================================
1076 section "474. SPECIAL DEVICE REDIRECTIONS"
1077 # =====================================
1078
1079 # Redirect to /dev/null
1080 result=$("$RUSH_BIN" -c 'echo test > /dev/null; echo done' 2>&1)
1081 if [ "$result" = "done" ]; then
1082 pass "output to /dev/null"
1083 else
1084 fail "output to /dev/null" "done" "$result"
1085 fi
1086
1087 # Read from /dev/null
1088 result=$("$RUSH_BIN" -c 'cat /dev/null' 2>&1)
1089 if [ -z "$result" ]; then
1090 pass "read from /dev/null is empty"
1091 else
1092 fail "read from /dev/null is empty" "empty" "$result"
1093 fi
1094
1095 # =====================================
1096 section "475. COMPOUND COMMAND REDIRECTIONS"
1097 # =====================================
1098
1099 # For loop redirect
1100 result=$("$RUSH_BIN" -c 'for i in 1 2 3; do echo $i; done > "'"$TEST_DIR"'/for_out.txt"; wc -l < "'"$TEST_DIR"'/for_out.txt"; rm "'"$TEST_DIR"'/for_out.txt"' 2>&1)
1101 if [ "$result" = "3" ]; then
1102 pass "for loop output redirect"
1103 else
1104 fail "for loop output redirect" "3" "$result"
1105 fi
1106
1107 # While loop redirect
1108 result=$("$RUSH_BIN" -c 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done > "'"$TEST_DIR"'/while_out.txt"; wc -l < "'"$TEST_DIR"'/while_out.txt"; rm "'"$TEST_DIR"'/while_out.txt"' 2>&1)
1109 if [ "$result" = "3" ]; then
1110 pass "while loop output redirect"
1111 else
1112 fail "while loop output redirect" "3" "$result"
1113 fi
1114
1115 # =====================================
1116 # Summary
1117 # =====================================
1118 printf "\n"
1119 printf "${BLUE}==========================================\n"
1120 printf "POSIX Redirection and Pipeline Summary\n"
1121 printf "==========================================${NC}\n"
1122 printf "Passed: ${GREEN}%d${NC}\n" "$PASSED"
1123 printf "Failed: ${RED}%d${NC}\n" "$FAILED"
1124 printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
1125 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
1126
1127 if [ -n "$FAILED_TESTS_LIST" ]; then
1128 printf "\n${RED}Failed tests:${NC}\n"
1129 printf "%b" "$FAILED_TESTS_LIST"
1130 fi
1131
1132 if [ "$FAILED" -gt 0 ]; then
1133 exit 1
1134 fi
1135 exit 0