Bash · 36740 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Compliance Control Flow and Grouping Test Suite for fortsh
4 # =====================================
5 # Tests control flow, subshells, brace groups per IEEE Std 1003.1-2017
6 # Section: Shell Command Language - Compound Commands
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-control]"
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 FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
28 BASH_REF="${BASH_REF:-bash}"
29
30 # Check if fortsh exists
31 if [ ! -x "$FORTSH_BIN" ]; then
32 printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
33 printf "Please run 'make' first or set FORTSH_BIN environment variable\n"
34 exit 1
35 fi
36
37 # Test result trackers
38 pass() {
39 TEST_NUM=$((TEST_NUM + 1))
40 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
41 PASSED=$((PASSED + 1))
42 }
43
44 fail() {
45 TEST_NUM=$((TEST_NUM + 1))
46 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
47 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
48 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
49 if [ -n "$2" ]; then
50 printf " expected: %s\n" "$2"
51 fi
52 if [ -n "$3" ]; then
53 printf " got: %s\n" "$3"
54 fi
55 FAILED=$((FAILED + 1))
56 }
57
58 skip() {
59 TEST_NUM=$((TEST_NUM + 1))
60 printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
61 SKIPPED=$((SKIPPED + 1))
62 }
63
64 section() {
65 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
66 TEST_NUM=0
67 printf "\n"
68 printf "${BLUE}==========================================\n"
69 printf "%s\n" "$1"
70 printf "==========================================${NC}\n"
71 }
72
73 # =====================================
74 section "411. SUBSHELL EXECUTION ( )"
75 # =====================================
76
77 result=$("$FORTSH_BIN" -c '(echo hello)' 2>&1)
78 if [ "$result" = "hello" ]; then
79 pass "Basic subshell execution"
80 else
81 fail "Basic subshell execution" "hello" "$result"
82 fi
83
84 result=$("$FORTSH_BIN" -c 'x=outer; (x=inner; echo $x); echo $x' 2>&1)
85 expected=$(printf "inner\nouter")
86 if [ "$result" = "$expected" ]; then
87 pass "Subshell variable isolation"
88 else
89 fail "Subshell variable isolation" "$expected" "$result"
90 fi
91
92 result=$("$FORTSH_BIN" -c '(cd /tmp; pwd); pwd' 2>&1)
93 # Should show /tmp then original dir
94 if echo "$result" | head -1 | grep -q "/tmp"; then
95 pass "Subshell cd isolation"
96 else
97 fail "Subshell cd isolation" "/tmp then original" "$result"
98 fi
99
100 result=$("$FORTSH_BIN" -c '(exit 42); echo $?' 2>&1)
101 if [ "$result" = "42" ]; then
102 pass "Subshell exit status propagation"
103 else
104 fail "Subshell exit status propagation" "42" "$result"
105 fi
106
107 result=$("$FORTSH_BIN" -c '(echo a; echo b; echo c)' 2>&1)
108 expected=$(printf "a\nb\nc")
109 if [ "$result" = "$expected" ]; then
110 pass "Subshell with multiple commands"
111 else
112 fail "Subshell with multiple commands" "$expected" "$result"
113 fi
114
115 # =====================================
116 section "412. BRACE GROUP { }"
117 # =====================================
118
119 result=$("$FORTSH_BIN" -c '{ echo hello; }' 2>&1)
120 if [ "$result" = "hello" ]; then
121 pass "Basic brace group"
122 else
123 fail "Basic brace group" "hello" "$result"
124 fi
125
126 result=$("$FORTSH_BIN" -c 'x=outer; { x=inner; echo $x; }; echo $x' 2>&1)
127 expected=$(printf "inner\ninner")
128 if [ "$result" = "$expected" ]; then
129 pass "Brace group shares variable scope"
130 else
131 fail "Brace group shares variable scope" "$expected" "$result"
132 fi
133
134 result=$("$FORTSH_BIN" -c '{ echo a; echo b; echo c; }' 2>&1)
135 expected=$(printf "a\nb\nc")
136 if [ "$result" = "$expected" ]; then
137 pass "Brace group with multiple commands"
138 else
139 fail "Brace group with multiple commands" "$expected" "$result"
140 fi
141
142 result=$("$FORTSH_BIN" -c '{ false; }; echo $?' 2>&1)
143 if [ "$result" = "1" ]; then
144 pass "Brace group exit status"
145 else
146 fail "Brace group exit status" "1" "$result"
147 fi
148
149 # =====================================
150 section "413. BREAK WITH COUNT"
151 # =====================================
152
153 result=$("$FORTSH_BIN" -c '
154 for i in 1 2 3; do
155 for j in a b c; do
156 if [ "$j" = "b" ]; then
157 break
158 fi
159 echo "$i$j"
160 done
161 done' 2>&1)
162 expected=$(printf "1a\n2a\n3a")
163 if [ "$result" = "$expected" ]; then
164 pass "break exits inner loop"
165 else
166 fail "break exits inner loop" "$expected" "$result"
167 fi
168
169 result=$("$FORTSH_BIN" -c '
170 for i in 1 2 3; do
171 for j in a b c; do
172 if [ "$j" = "b" ]; then
173 break 2
174 fi
175 echo "$i$j"
176 done
177 done' 2>&1)
178 if [ "$result" = "1a" ]; then
179 pass "break 2 exits both loops"
180 else
181 fail "break 2 exits both loops" "1a" "$result"
182 fi
183
184 result=$("$FORTSH_BIN" -c '
185 for i in 1 2 3; do
186 break
187 echo "not reached"
188 done
189 echo "done"' 2>&1)
190 if [ "$result" = "done" ]; then
191 pass "break stops loop immediately"
192 else
193 fail "break stops loop immediately" "done" "$result"
194 fi
195
196 # =====================================
197 section "414. CONTINUE WITH COUNT"
198 # =====================================
199
200 result=$("$FORTSH_BIN" -c '
201 for i in 1 2 3; do
202 if [ "$i" = "2" ]; then
203 continue
204 fi
205 echo $i
206 done' 2>&1)
207 expected=$(printf "1\n3")
208 if [ "$result" = "$expected" ]; then
209 pass "continue skips iteration"
210 else
211 fail "continue skips iteration" "$expected" "$result"
212 fi
213
214 result=$("$FORTSH_BIN" -c '
215 for i in 1 2; do
216 for j in a b c; do
217 if [ "$j" = "b" ]; then
218 continue 2
219 fi
220 echo "$i$j"
221 done
222 echo "inner done"
223 done' 2>&1)
224 expected=$(printf "1a\n2a")
225 if [ "$result" = "$expected" ]; then
226 pass "continue 2 continues outer loop"
227 else
228 fail "continue 2 continues outer loop" "$expected" "$result"
229 fi
230
231 # =====================================
232 section "415. WHILE LOOP"
233 # =====================================
234
235 result=$("$FORTSH_BIN" -c 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done' 2>&1)
236 expected=$(printf "0\n1\n2")
237 if [ "$result" = "$expected" ]; then
238 pass "Basic while loop"
239 else
240 fail "Basic while loop" "$expected" "$result"
241 fi
242
243 result=$("$FORTSH_BIN" -c 'while false; do echo no; done; echo done' 2>&1)
244 if [ "$result" = "done" ]; then
245 pass "While with false condition"
246 else
247 fail "While with false condition" "done" "$result"
248 fi
249
250 result=$("$FORTSH_BIN" -c '
251 i=0
252 while [ $i -lt 5 ]; do
253 i=$((i+1))
254 if [ $i -eq 3 ]; then continue; fi
255 echo $i
256 done' 2>&1)
257 expected=$(printf "1\n2\n4\n5")
258 if [ "$result" = "$expected" ]; then
259 pass "While with continue"
260 else
261 fail "While with continue" "$expected" "$result"
262 fi
263
264 # =====================================
265 section "416. UNTIL LOOP"
266 # =====================================
267
268 result=$("$FORTSH_BIN" -c 'i=0; until [ $i -ge 3 ]; do echo $i; i=$((i+1)); done' 2>&1)
269 expected=$(printf "0\n1\n2")
270 if [ "$result" = "$expected" ]; then
271 pass "Basic until loop"
272 else
273 fail "Basic until loop" "$expected" "$result"
274 fi
275
276 result=$("$FORTSH_BIN" -c 'until true; do echo no; done; echo done' 2>&1)
277 if [ "$result" = "done" ]; then
278 pass "Until with true condition"
279 else
280 fail "Until with true condition" "done" "$result"
281 fi
282
283 # =====================================
284 section "417. PIPELINE EXIT STATUS"
285 # =====================================
286
287 result=$("$FORTSH_BIN" -c 'true | false; echo $?' 2>&1)
288 if [ "$result" = "1" ]; then
289 pass "Pipeline exit status is last command"
290 else
291 fail "Pipeline exit status is last command" "1" "$result"
292 fi
293
294 result=$("$FORTSH_BIN" -c 'false | true; echo $?' 2>&1)
295 if [ "$result" = "0" ]; then
296 pass "Pipeline with false then true"
297 else
298 fail "Pipeline with false then true" "0" "$result"
299 fi
300
301 result=$("$FORTSH_BIN" -c 'echo hello | cat | cat; echo $?' 2>&1)
302 expected=$(printf "hello\n0")
303 if [ "$result" = "$expected" ]; then
304 pass "Multi-stage pipeline"
305 else
306 fail "Multi-stage pipeline" "$expected" "$result"
307 fi
308
309 # =====================================
310 section "418. AND-OR LISTS"
311 # =====================================
312
313 result=$("$FORTSH_BIN" -c 'true && echo yes' 2>&1)
314 if [ "$result" = "yes" ]; then
315 pass "&& executes on success"
316 else
317 fail "&& executes on success" "yes" "$result"
318 fi
319
320 result=$("$FORTSH_BIN" -c 'false && echo yes; echo done' 2>&1)
321 if [ "$result" = "done" ]; then
322 pass "&& skips on failure"
323 else
324 fail "&& skips on failure" "done" "$result"
325 fi
326
327 result=$("$FORTSH_BIN" -c 'false || echo fallback' 2>&1)
328 if [ "$result" = "fallback" ]; then
329 pass "|| executes on failure"
330 else
331 fail "|| executes on failure" "fallback" "$result"
332 fi
333
334 result=$("$FORTSH_BIN" -c 'true || echo no; echo done' 2>&1)
335 if [ "$result" = "done" ]; then
336 pass "|| skips on success"
337 else
338 fail "|| skips on success" "done" "$result"
339 fi
340
341 result=$("$FORTSH_BIN" -c 'true && echo a && echo b' 2>&1)
342 expected=$(printf "a\nb")
343 if [ "$result" = "$expected" ]; then
344 pass "Chained && operators"
345 else
346 fail "Chained && operators" "$expected" "$result"
347 fi
348
349 result=$("$FORTSH_BIN" -c 'false || false || echo third' 2>&1)
350 if [ "$result" = "third" ]; then
351 pass "Chained || operators"
352 else
353 fail "Chained || operators" "third" "$result"
354 fi
355
356 result=$("$FORTSH_BIN" -c 'false || true && echo mixed' 2>&1)
357 if [ "$result" = "mixed" ]; then
358 pass "Mixed && and ||"
359 else
360 fail "Mixed && and ||" "mixed" "$result"
361 fi
362
363 # =====================================
364 section "419. NEGATION WITH !"
365 # =====================================
366
367 result=$("$FORTSH_BIN" -c '! false; echo $?' 2>&1)
368 if [ "$result" = "0" ]; then
369 pass "! false returns 0"
370 else
371 fail "! false returns 0" "0" "$result"
372 fi
373
374 result=$("$FORTSH_BIN" -c '! true; echo $?' 2>&1)
375 if [ "$result" = "1" ]; then
376 pass "! true returns 1"
377 else
378 fail "! true returns 1" "1" "$result"
379 fi
380
381 result=$("$FORTSH_BIN" -c '! echo hello >/dev/null; echo $?' 2>&1)
382 if [ "$result" = "1" ]; then
383 pass "! negates command exit status"
384 else
385 fail "! negates command exit status" "1" "$result"
386 fi
387
388 # =====================================
389 section "420. DOT (SOURCE) COMMAND"
390 # =====================================
391
392 TEST_DIR="/tmp/fortsh_control_$$"
393 mkdir -p "$TEST_DIR"
394
395 # Create a file to source
396 echo 'SOURCED_VAR=hello' > "$TEST_DIR/sourceme.sh"
397 echo 'sourced_func() { echo "from func"; }' >> "$TEST_DIR/sourceme.sh"
398
399 result=$("$FORTSH_BIN" -c '. '"$TEST_DIR"'/sourceme.sh; echo $SOURCED_VAR' 2>&1)
400 if [ "$result" = "hello" ]; then
401 pass ". sources file and sets variable"
402 else
403 fail ". sources file and sets variable" "hello" "$result"
404 fi
405
406 result=$("$FORTSH_BIN" -c '. '"$TEST_DIR"'/sourceme.sh; sourced_func' 2>&1)
407 if echo "$result" | grep -q "from func"; then
408 pass ". sources file and defines function"
409 else
410 fail ". sources file and defines function" "from func" "$result"
411 fi
412
413 # Test source with arguments
414 echo 'echo "arg1=$1 arg2=$2"' > "$TEST_DIR/withargs.sh"
415 result=$("$FORTSH_BIN" -c '. '"$TEST_DIR"'/withargs.sh foo bar' 2>&1)
416 if [ "$result" = "arg1=foo arg2=bar" ]; then
417 pass ". passes arguments to sourced script"
418 else
419 fail ". passes arguments to sourced script" "arg1=foo arg2=bar" "$result"
420 fi
421
422 rm -rf "$TEST_DIR"
423
424 # =====================================
425 section "421. EXEC BUILTIN"
426 # =====================================
427
428 result=$("$FORTSH_BIN" -c 'exec echo replaced' 2>&1)
429 if [ "$result" = "replaced" ]; then
430 pass "exec replaces shell with command"
431 else
432 fail "exec replaces shell with command" "replaced" "$result"
433 fi
434
435 # exec without command just does redirections
436 result=$("$FORTSH_BIN" -c 'exec 2>/dev/null; echo hello' 2>&1)
437 if [ "$result" = "hello" ]; then
438 pass "exec without command continues shell"
439 else
440 fail "exec without command continues shell" "hello" "$result"
441 fi
442
443 # =====================================
444 section "422. EVAL BUILTIN"
445 # =====================================
446
447 result=$("$FORTSH_BIN" -c 'cmd="echo hello"; eval $cmd' 2>&1)
448 if [ "$result" = "hello" ]; then
449 pass "eval executes string as command"
450 else
451 fail "eval executes string as command" "hello" "$result"
452 fi
453
454 result=$("$FORTSH_BIN" -c 'x=world; eval echo "hello $x"' 2>&1)
455 if [ "$result" = "hello world" ]; then
456 pass "eval with variable expansion"
457 else
458 fail "eval with variable expansion" "hello world" "$result"
459 fi
460
461 result=$("$FORTSH_BIN" -c 'eval "x=5; echo \$x"' 2>&1)
462 if [ "$result" = "5" ]; then
463 pass "eval with multiple commands"
464 else
465 fail "eval with multiple commands" "5" "$result"
466 fi
467
468 result=$("$FORTSH_BIN" -c 'var=x; eval "$var=42"; echo $x' 2>&1)
469 if [ "$result" = "42" ]; then
470 pass "eval for indirect assignment"
471 else
472 fail "eval for indirect assignment" "42" "$result"
473 fi
474
475 # =====================================
476 section "423. RETURN FROM FUNCTION"
477 # =====================================
478
479 result=$("$FORTSH_BIN" -c 'f() { return 0; echo no; }; f; echo $?' 2>&1)
480 if [ "$result" = "0" ]; then
481 pass "return 0 from function"
482 else
483 fail "return 0 from function" "0" "$result"
484 fi
485
486 result=$("$FORTSH_BIN" -c 'f() { return 42; }; f; echo $?' 2>&1)
487 if [ "$result" = "42" ]; then
488 pass "return with status code"
489 else
490 fail "return with status code" "42" "$result"
491 fi
492
493 result=$("$FORTSH_BIN" -c 'f() { echo before; return; echo after; }; f' 2>&1)
494 if [ "$result" = "before" ]; then
495 pass "return without value"
496 else
497 fail "return without value" "before" "$result"
498 fi
499
500 # =====================================
501 section "424. FUNCTION LOCAL VARIABLES"
502 # =====================================
503
504 result=$("$FORTSH_BIN" -c 'x=global; f() { local x=local; echo $x; }; f; echo $x' 2>&1)
505 expected=$(printf "local\nglobal")
506 if [ "$result" = "$expected" ]; then
507 pass "local variable in function"
508 else
509 fail "local variable in function" "$expected" "$result"
510 fi
511
512 result=$("$FORTSH_BIN" -c 'f() { local x=1; g() { echo $x; }; g; }; f' 2>&1)
513 if [ "$result" = "1" ]; then
514 pass "local visible in nested function"
515 else
516 fail "local visible in nested function" "1" "$result"
517 fi
518
519 result=$("$FORTSH_BIN" -c 'f() { local a=1 b=2; echo $a $b; }; f' 2>&1)
520 if [ "$result" = "1 2" ]; then
521 pass "local multiple variables"
522 else
523 fail "local multiple variables" "1 2" "$result"
524 fi
525
526 result=$("$FORTSH_BIN" -c 'f() { local x; x=set; echo $x; }; f' 2>&1)
527 if [ "$result" = "set" ]; then
528 pass "local without initial value"
529 else
530 fail "local without initial value" "set" "$result"
531 fi
532
533 # =====================================
534 section "425. FUNCTION RECURSION"
535 # =====================================
536
537 result=$("$FORTSH_BIN" -c '
538 factorial() {
539 if [ $1 -le 1 ]; then
540 echo 1
541 else
542 prev=$(factorial $(($1 - 1)))
543 echo $(($1 * prev))
544 fi
545 }
546 factorial 5' 2>&1)
547 if [ "$result" = "120" ]; then
548 pass "Recursive function (factorial)"
549 else
550 fail "Recursive function (factorial)" "120" "$result"
551 fi
552
553 result=$("$FORTSH_BIN" -c '
554 count=0
555 recurse() {
556 count=$((count + 1))
557 if [ $count -lt 5 ]; then
558 recurse
559 fi
560 echo $count
561 }
562 recurse | tail -1' 2>&1)
563 if [ "$result" = "5" ]; then
564 pass "Recursive function with global state"
565 else
566 fail "Recursive function with global state" "5" "$result"
567 fi
568
569 # =====================================
570 section "426. FUNCTION ARGUMENTS"
571 # =====================================
572
573 result=$("$FORTSH_BIN" -c 'f() { echo $1 $2 $3; }; f a b c' 2>&1)
574 if [ "$result" = "a b c" ]; then
575 pass "Function positional parameters"
576 else
577 fail "Function positional parameters" "a b c" "$result"
578 fi
579
580 result=$("$FORTSH_BIN" -c 'f() { echo $#; }; f a b c d e' 2>&1)
581 if [ "$result" = "5" ]; then
582 pass "Function \$# count"
583 else
584 fail "Function \$# count" "5" "$result"
585 fi
586
587 result=$("$FORTSH_BIN" -c 'f() { echo "$@"; }; f "a b" c' 2>&1)
588 if [ "$result" = "a b c" ]; then
589 pass "Function \$@ expansion"
590 else
591 fail "Function \$@ expansion" "a b c" "$result"
592 fi
593
594 result=$("$FORTSH_BIN" -c 'f() { shift; echo $1; }; f a b c' 2>&1)
595 if [ "$result" = "b" ]; then
596 pass "shift in function"
597 else
598 fail "shift in function" "b" "$result"
599 fi
600
601 result=$("$FORTSH_BIN" -c 'f() { set -- x y z; echo $1; }; f a b; echo done' 2>&1)
602 expected=$(printf "x\ndone")
603 if [ "$result" = "$expected" ]; then
604 pass "set -- in function is local"
605 else
606 fail "set -- in function is local" "$expected" "$result"
607 fi
608
609 # =====================================
610 section "427. FUNCTION OVERRIDE"
611 # =====================================
612
613 result=$("$FORTSH_BIN" -c 'f() { echo first; }; f() { echo second; }; f' 2>&1)
614 if [ "$result" = "second" ]; then
615 pass "Function redefinition"
616 else
617 fail "Function redefinition" "second" "$result"
618 fi
619
620 result=$("$FORTSH_BIN" -c 'f() { echo orig; }; g() { f; }; f() { echo new; }; g' 2>&1)
621 if [ "$result" = "new" ]; then
622 pass "Function sees redefined function"
623 else
624 fail "Function sees redefined function" "new" "$result"
625 fi
626
627 # =====================================
628 section "428. UNSET FUNCTION"
629 # =====================================
630
631 result=$("$FORTSH_BIN" -c 'f() { echo hi; }; unset -f f; f 2>/dev/null; echo $?' 2>&1)
632 if echo "$result" | grep -qE "127|1"; then
633 pass "unset -f removes function"
634 else
635 fail "unset -f removes function" "non-zero exit" "$result"
636 fi
637
638 # =====================================
639 section "429. COLON BUILTIN"
640 # =====================================
641
642 result=$("$FORTSH_BIN" -c ':; echo $?' 2>&1)
643 if [ "$result" = "0" ]; then
644 pass ": returns 0"
645 else
646 fail ": returns 0" "0" "$result"
647 fi
648
649 result=$("$FORTSH_BIN" -c ': this is ignored; echo ok' 2>&1)
650 if [ "$result" = "ok" ]; then
651 pass ": ignores arguments"
652 else
653 fail ": ignores arguments" "ok" "$result"
654 fi
655
656 result=$("$FORTSH_BIN" -c ': ${x:=default}; echo $x' 2>&1)
657 if [ "$result" = "default" ]; then
658 pass ": for side effects in expansion"
659 else
660 fail ": for side effects in expansion" "default" "$result"
661 fi
662
663 # =====================================
664 section "426. TRUE AND FALSE BUILTINS"
665 # =====================================
666
667 result=$("$FORTSH_BIN" -c 'true; echo $?' 2>&1)
668 if [ "$result" = "0" ]; then
669 pass "true returns 0"
670 else
671 fail "true returns 0" "0" "$result"
672 fi
673
674 result=$("$FORTSH_BIN" -c 'false; echo $?' 2>&1)
675 if [ "$result" = "1" ]; then
676 pass "false returns 1"
677 else
678 fail "false returns 1" "1" "$result"
679 fi
680
681 # =====================================
682 section "427. EXIT BUILTIN"
683 # =====================================
684
685 result=$("$FORTSH_BIN" -c 'exit 0; echo no' 2>&1)
686 if [ -z "$result" ]; then
687 pass "exit 0 terminates immediately"
688 else
689 fail "exit 0 terminates immediately" "(empty)" "$result"
690 fi
691
692 result=$("$FORTSH_BIN" -c 'exit 42' 2>&1)
693 code=$?
694 if [ $code -eq 42 ]; then
695 pass "exit with specific code"
696 else
697 fail "exit with specific code" "42" "$code"
698 fi
699
700 result=$("$FORTSH_BIN" -c 'false; exit' 2>&1)
701 code=$?
702 if [ $code -eq 1 ]; then
703 pass "exit without arg uses last status"
704 else
705 fail "exit without arg uses last status" "1" "$code"
706 fi
707
708 # =====================================
709 section "430. CASE STATEMENT PATTERNS"
710 # =====================================
711
712 # Simple exact match
713 result=$("$FORTSH_BIN" -c 'case "hello" in hello) echo yes;; esac' 2>&1)
714 if [ "$result" = "yes" ]; then
715 pass "case exact match"
716 else
717 fail "case exact match" "yes" "$result"
718 fi
719
720 # Glob pattern match
721 result=$("$FORTSH_BIN" -c 'case "hello" in h*) echo yes;; esac' 2>&1)
722 if [ "$result" = "yes" ]; then
723 pass "case glob pattern"
724 else
725 fail "case glob pattern" "yes" "$result"
726 fi
727
728 # Question mark match
729 result=$("$FORTSH_BIN" -c 'case "cat" in c?t) echo yes;; esac' 2>&1)
730 if [ "$result" = "yes" ]; then
731 pass "case ? pattern"
732 else
733 fail "case ? pattern" "yes" "$result"
734 fi
735
736 # Bracket pattern
737 result=$("$FORTSH_BIN" -c 'case "b" in [abc]) echo yes;; esac' 2>&1)
738 if [ "$result" = "yes" ]; then
739 pass "case [abc] bracket pattern"
740 else
741 fail "case [abc] bracket pattern" "yes" "$result"
742 fi
743
744 # Multiple patterns with |
745 result=$("$FORTSH_BIN" -c 'case "two" in one|two|three) echo yes;; esac' 2>&1)
746 if [ "$result" = "yes" ]; then
747 pass "case multiple patterns with |"
748 else
749 fail "case multiple patterns with |" "yes" "$result"
750 fi
751
752 # Default pattern *
753 result=$("$FORTSH_BIN" -c 'case "xyz" in abc) echo no;; *) echo default;; esac' 2>&1)
754 if [ "$result" = "default" ]; then
755 pass "case * default pattern"
756 else
757 fail "case * default pattern" "default" "$result"
758 fi
759
760 # No match produces nothing
761 result=$("$FORTSH_BIN" -c 'case "x" in y) echo no;; z) echo also no;; esac; echo done' 2>&1)
762 if [ "$result" = "done" ]; then
763 pass "case no match produces empty"
764 else
765 fail "case no match produces empty" "done" "$result"
766 fi
767
768 # Variable in word
769 result=$("$FORTSH_BIN" -c 'x=hello; case "$x" in hello) echo yes;; esac' 2>&1)
770 if [ "$result" = "yes" ]; then
771 pass "case with variable in word"
772 else
773 fail "case with variable in word" "yes" "$result"
774 fi
775
776 # Variable in pattern
777 result=$("$FORTSH_BIN" -c 'pat="hel*"; case "hello" in $pat) echo yes;; *) echo no;; esac' 2>&1)
778 if [ "$result" = "yes" ]; then
779 pass "case with variable in pattern"
780 else
781 fail "case with variable in pattern" "yes" "$result"
782 fi
783
784 # Quoted pattern (literal)
785 result=$("$FORTSH_BIN" -c 'case "h*" in "h*") echo yes;; esac' 2>&1)
786 if [ "$result" = "yes" ]; then
787 pass "case quoted pattern is literal"
788 else
789 fail "case quoted pattern is literal" "yes" "$result"
790 fi
791
792 # =====================================
793 section "431. NESTED CONTROL STRUCTURES"
794 # =====================================
795
796 # if inside for
797 result=$("$FORTSH_BIN" -c 'for i in 1 2 3; do if [ $i -eq 2 ]; then echo found; fi; done' 2>&1)
798 if [ "$result" = "found" ]; then
799 pass "if inside for loop"
800 else
801 fail "if inside for loop" "found" "$result"
802 fi
803
804 # case inside while
805 result=$("$FORTSH_BIN" -c 'i=0; while [ $i -lt 3 ]; do case $i in 1) echo one;; esac; i=$((i+1)); done' 2>&1)
806 if [ "$result" = "one" ]; then
807 pass "case inside while loop"
808 else
809 fail "case inside while loop" "one" "$result"
810 fi
811
812 # for inside if
813 result=$("$FORTSH_BIN" -c 'if true; then for i in a b; do echo $i; done; fi' 2>&1)
814 expected=$(printf "a\nb")
815 if [ "$result" = "$expected" ]; then
816 pass "for inside if"
817 else
818 fail "for inside if" "$expected" "$result"
819 fi
820
821 # Deeply nested
822 result=$("$FORTSH_BIN" -c '
823 for i in 1; do
824 for j in 2; do
825 for k in 3; do
826 echo "$i$j$k"
827 done
828 done
829 done' 2>&1)
830 if [ "$result" = "123" ]; then
831 pass "Triple nested for loops"
832 else
833 fail "Triple nested for loops" "123" "$result"
834 fi
835
836 # =====================================
837 section "432. WHILE AND UNTIL LOOPS"
838 # =====================================
839
840 # while with counter
841 result=$("$FORTSH_BIN" -c 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done' 2>&1)
842 expected=$(printf "0\n1\n2")
843 if [ "$result" = "$expected" ]; then
844 pass "while loop with counter"
845 else
846 fail "while loop with counter" "$expected" "$result"
847 fi
848
849 # until loop
850 result=$("$FORTSH_BIN" -c 'i=0; until [ $i -ge 3 ]; do echo $i; i=$((i+1)); done' 2>&1)
851 expected=$(printf "0\n1\n2")
852 if [ "$result" = "$expected" ]; then
853 pass "until loop"
854 else
855 fail "until loop" "$expected" "$result"
856 fi
857
858 # while with compound condition
859 result=$("$FORTSH_BIN" -c 'i=0; while [ $i -lt 5 ] && [ $i -ne 3 ]; do echo $i; i=$((i+1)); done' 2>&1)
860 expected=$(printf "0\n1\n2")
861 if [ "$result" = "$expected" ]; then
862 pass "while with compound condition"
863 else
864 fail "while with compound condition" "$expected" "$result"
865 fi
866
867 # Empty while body (using :)
868 result=$("$FORTSH_BIN" -c 'i=0; while [ $i -lt 3 ]; do : ; i=$((i+1)); done; echo $i' 2>&1)
869 if [ "$result" = "3" ]; then
870 pass "while with empty body (colon)"
871 else
872 fail "while with empty body (colon)" "3" "$result"
873 fi
874
875 # =====================================
876 section "433. BREAK AND CONTINUE"
877 # =====================================
878
879 # break in while
880 result=$("$FORTSH_BIN" -c 'i=0; while true; do i=$((i+1)); [ $i -ge 3 ] && break; done; echo $i' 2>&1)
881 if [ "$result" = "3" ]; then
882 pass "break in while loop"
883 else
884 fail "break in while loop" "3" "$result"
885 fi
886
887 # continue in for
888 result=$("$FORTSH_BIN" -c 'for i in 1 2 3 4 5; do [ $i -eq 3 ] && continue; echo $i; done' 2>&1)
889 expected=$(printf "1\n2\n4\n5")
890 if [ "$result" = "$expected" ]; then
891 pass "continue in for loop"
892 else
893 fail "continue in for loop" "$expected" "$result"
894 fi
895
896 # break N for nested loops
897 result=$("$FORTSH_BIN" -c '
898 for i in 1 2; do
899 for j in a b; do
900 echo "$i$j"
901 [ "$j" = "a" ] && break 2
902 done
903 done
904 echo done' 2>&1)
905 expected=$(printf "1a\ndone")
906 if [ "$result" = "$expected" ]; then
907 pass "break 2 exits outer loop"
908 else
909 fail "break 2 exits outer loop" "$expected" "$result"
910 fi
911
912 # continue N for nested loops
913 result=$("$FORTSH_BIN" -c '
914 for i in 1 2; do
915 for j in a b; do
916 [ "$i$j" = "1a" ] && continue 2
917 echo "$i$j"
918 done
919 done' 2>&1)
920 expected=$(printf "2a\n2b")
921 if [ "$result" = "$expected" ]; then
922 pass "continue 2 continues outer loop"
923 else
924 fail "continue 2 continues outer loop" "$expected" "$result"
925 fi
926
927 # =====================================
928 section "434. FOR LOOP VARIATIONS"
929 # =====================================
930
931 # for without in (uses positional params)
932 result=$("$FORTSH_BIN" -c 'set -- a b c; for x; do echo $x; done' 2>&1)
933 expected=$(printf "a\nb\nc")
934 if [ "$result" = "$expected" ]; then
935 pass "for without 'in' uses \$@"
936 else
937 fail "for without 'in' uses \$@" "$expected" "$result"
938 fi
939
940 # for with glob pattern
941 # Create temp files for glob test
942 result=$("$FORTSH_BIN" -c 'cd /tmp && touch _testglob_a _testglob_b && for f in _testglob_*; do echo $f; done | wc -l && rm -f _testglob_*' 2>&1 | head -1)
943 if [ "$result" -eq 2 ] 2>/dev/null; then
944 pass "for with glob expansion"
945 else
946 fail "for with glob expansion" "2" "$result"
947 fi
948
949 # for with command substitution
950 result=$("$FORTSH_BIN" -c 'for x in $(echo a b c); do echo $x; done' 2>&1)
951 expected=$(printf "a\nb\nc")
952 if [ "$result" = "$expected" ]; then
953 pass "for with command substitution"
954 else
955 fail "for with command substitution" "$expected" "$result"
956 fi
957
958 # for with quoted string (single iteration)
959 result=$("$FORTSH_BIN" -c 'for x in "a b c"; do echo "[$x]"; done' 2>&1)
960 if [ "$result" = "[a b c]" ]; then
961 pass "for with quoted string (single iteration)"
962 else
963 fail "for with quoted string (single iteration)" "[a b c]" "$result"
964 fi
965
966 # =====================================
967 section "435. WHILE LOOP VARIATIONS"
968 # =====================================
969
970 # while with pipeline
971 result=$("$FORTSH_BIN" -c 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done | wc -l' 2>&1)
972 if [ "$result" -eq 3 ] 2>/dev/null; then
973 pass "while loop with pipeline"
974 else
975 fail "while loop with pipeline" "3" "$result"
976 fi
977
978 # while with command substitution condition
979 result=$("$FORTSH_BIN" -c 'X=yes; while [ "$X" = "yes" ]; do echo once; X=no; done' 2>&1)
980 if [ "$result" = "once" ]; then
981 pass "while with variable condition"
982 else
983 fail "while with variable condition" "once" "$result"
984 fi
985
986 # infinite while with break
987 result=$("$FORTSH_BIN" -c 'i=0; while true; do i=$((i+1)); [ $i -ge 5 ] && break; done; echo $i' 2>&1)
988 if [ "$result" = "5" ]; then
989 pass "infinite while with break"
990 else
991 fail "infinite while with break" "5" "$result"
992 fi
993
994 # =====================================
995 section "436. UNTIL LOOP VARIATIONS"
996 # =====================================
997
998 # until basic
999 result=$("$FORTSH_BIN" -c 'i=0; until [ $i -ge 3 ]; do echo $i; i=$((i+1)); done' 2>&1)
1000 expected=$(printf "0\n1\n2")
1001 if [ "$result" = "$expected" ]; then
1002 pass "until loop basic"
1003 else
1004 fail "until loop basic"
1005 fi
1006
1007 # until with break
1008 result=$("$FORTSH_BIN" -c 'i=0; until false; do i=$((i+1)); [ $i -ge 3 ] && break; done; echo $i' 2>&1)
1009 if [ "$result" = "3" ]; then
1010 pass "until with break"
1011 else
1012 fail "until with break" "3" "$result"
1013 fi
1014
1015 # =====================================
1016 section "437. IF STATEMENT VARIATIONS"
1017 # =====================================
1018
1019 # if with command
1020 result=$("$FORTSH_BIN" -c 'if true; then echo yes; fi' 2>&1)
1021 if [ "$result" = "yes" ]; then
1022 pass "if with true command"
1023 else
1024 fail "if with true command" "yes" "$result"
1025 fi
1026
1027 # if with test
1028 result=$("$FORTSH_BIN" -c 'X=5; if [ $X -gt 3 ]; then echo big; fi' 2>&1)
1029 if [ "$result" = "big" ]; then
1030 pass "if with test condition"
1031 else
1032 fail "if with test condition" "big" "$result"
1033 fi
1034
1035 # if-else
1036 result=$("$FORTSH_BIN" -c 'if false; then echo no; else echo yes; fi' 2>&1)
1037 if [ "$result" = "yes" ]; then
1038 pass "if-else statement"
1039 else
1040 fail "if-else statement" "yes" "$result"
1041 fi
1042
1043 # if-elif-else
1044 result=$("$FORTSH_BIN" -c 'X=2; if [ $X -eq 1 ]; then echo one; elif [ $X -eq 2 ]; then echo two; else echo other; fi' 2>&1)
1045 if [ "$result" = "two" ]; then
1046 pass "if-elif-else statement"
1047 else
1048 fail "if-elif-else statement" "two" "$result"
1049 fi
1050
1051 # nested if
1052 result=$("$FORTSH_BIN" -c 'X=1; Y=2; if [ $X -eq 1 ]; then if [ $Y -eq 2 ]; then echo both; fi; fi' 2>&1)
1053 if [ "$result" = "both" ]; then
1054 pass "nested if statement"
1055 else
1056 fail "nested if statement" "both" "$result"
1057 fi
1058
1059 # =====================================
1060 section "438. CASE STATEMENT VARIATIONS"
1061 # =====================================
1062
1063 # case with character class
1064 result=$("$FORTSH_BIN" -c 'x=5; case $x in [0-9]) echo digit;; esac' 2>&1)
1065 if [ "$result" = "digit" ]; then
1066 pass "case with character class"
1067 else
1068 fail "case with character class" "digit" "$result"
1069 fi
1070
1071 # case with negation
1072 result=$("$FORTSH_BIN" -c 'x=x; case $x in [!0-9]) echo not_digit;; esac' 2>&1)
1073 if [ "$result" = "not_digit" ]; then
1074 pass "case with negation"
1075 else
1076 fail "case with negation" "not_digit" "$result"
1077 fi
1078
1079 # case with question mark
1080 result=$("$FORTSH_BIN" -c 'x=ab; case $x in ??) echo two_chars;; esac' 2>&1)
1081 if [ "$result" = "two_chars" ]; then
1082 pass "case with question mark pattern"
1083 else
1084 fail "case with question mark pattern" "two_chars" "$result"
1085 fi
1086
1087 # case fall-through (first match wins)
1088 result=$("$FORTSH_BIN" -c 'x=a; case $x in a) echo first;; a) echo second;; esac' 2>&1)
1089 if [ "$result" = "first" ]; then
1090 pass "case first match wins"
1091 else
1092 fail "case first match wins" "first" "$result"
1093 fi
1094
1095 # =====================================
1096 section "439. BRACE GROUP VARIATIONS"
1097 # =====================================
1098
1099 # brace group in pipeline
1100 result=$("$FORTSH_BIN" -c '{ echo a; echo b; } | wc -l' 2>&1)
1101 if [ "$result" -eq 2 ] 2>/dev/null; then
1102 pass "brace group in pipeline"
1103 else
1104 fail "brace group in pipeline" "2" "$result"
1105 fi
1106
1107 # brace group with redirection
1108 result=$("$FORTSH_BIN" -c '{ echo test; } > /tmp/brace_test_$$; cat /tmp/brace_test_$$; rm /tmp/brace_test_$$' 2>&1)
1109 if [ "$result" = "test" ]; then
1110 pass "brace group with redirection"
1111 else
1112 fail "brace group with redirection" "test" "$result"
1113 fi
1114
1115 # brace group preserves variables
1116 result=$("$FORTSH_BIN" -c 'X=1; { X=2; }; echo $X' 2>&1)
1117 if [ "$result" = "2" ]; then
1118 pass "brace group preserves variable changes"
1119 else
1120 fail "brace group preserves variable changes" "2" "$result"
1121 fi
1122
1123 # =====================================
1124 section "440. SUBSHELL VARIATIONS"
1125 # =====================================
1126
1127 # subshell in pipeline
1128 result=$("$FORTSH_BIN" -c '(echo a; echo b) | wc -l' 2>&1)
1129 if [ "$result" -eq 2 ] 2>/dev/null; then
1130 pass "subshell in pipeline"
1131 else
1132 fail "subshell in pipeline" "2" "$result"
1133 fi
1134
1135 # subshell isolates variables
1136 result=$("$FORTSH_BIN" -c 'X=1; (X=2); echo $X' 2>&1)
1137 if [ "$result" = "1" ]; then
1138 pass "subshell isolates variable changes"
1139 else
1140 fail "subshell isolates variable changes" "1" "$result"
1141 fi
1142
1143 # Note: (( )) is arithmetic syntax in bash, not nested subshell
1144 # Both bash and fortsh correctly treat this as arithmetic and error
1145 "$FORTSH_BIN" -c '((echo deep))' >/dev/null 2>&1
1146 fortsh_exit=$?
1147 "$BASH_REF" -c '((echo deep))' >/dev/null 2>&1
1148 bash_exit=$?
1149 if [ "$fortsh_exit" -eq "$bash_exit" ]; then
1150 pass "double paren arithmetic exit"
1151 else
1152 fail "double paren arithmetic exit" "exit=$bash_exit" "exit=$fortsh_exit"
1153 fi
1154
1155 # =====================================
1156 section "441. COMPOUND LIST VARIATIONS"
1157 # =====================================
1158
1159 # semicolon separated
1160 result=$("$FORTSH_BIN" -c 'echo a; echo b; echo c' 2>&1)
1161 expected=$(printf "a\nb\nc")
1162 if [ "$result" = "$expected" ]; then
1163 pass "semicolon separated commands"
1164 else
1165 fail "semicolon separated commands"
1166 fi
1167
1168 # newline separated
1169 result=$("$FORTSH_BIN" -c 'echo a
1170 echo b
1171 echo c' 2>&1)
1172 expected=$(printf "a\nb\nc")
1173 if [ "$result" = "$expected" ]; then
1174 pass "newline separated commands"
1175 else
1176 fail "newline separated commands"
1177 fi
1178
1179 # =====================================
1180 section "442. LOGICAL OPERATORS VARIATIONS"
1181 # =====================================
1182
1183 # && chain
1184 result=$("$FORTSH_BIN" -c 'true && true && echo success' 2>&1)
1185 if [ "$result" = "success" ]; then
1186 pass "&& chain all true"
1187 else
1188 fail "&& chain all true" "success" "$result"
1189 fi
1190
1191 # && short-circuit
1192 result=$("$FORTSH_BIN" -c 'false && echo never' 2>&1)
1193 if [ -z "$result" ]; then
1194 pass "&& short-circuits on false"
1195 else
1196 fail "&& short-circuits on false" "empty" "$result"
1197 fi
1198
1199 # || chain
1200 result=$("$FORTSH_BIN" -c 'false || false || echo fallback' 2>&1)
1201 if [ "$result" = "fallback" ]; then
1202 pass "|| chain with fallback"
1203 else
1204 fail "|| chain with fallback" "fallback" "$result"
1205 fi
1206
1207 # || short-circuit
1208 result=$("$FORTSH_BIN" -c 'true || echo never' 2>&1)
1209 if [ -z "$result" ]; then
1210 pass "|| short-circuits on true"
1211 else
1212 fail "|| short-circuits on true" "empty" "$result"
1213 fi
1214
1215 # mixed && and ||
1216 result=$("$FORTSH_BIN" -c 'false && echo no || echo yes' 2>&1)
1217 if [ "$result" = "yes" ]; then
1218 pass "mixed && and ||"
1219 else
1220 fail "mixed && and ||" "yes" "$result"
1221 fi
1222
1223 # =====================================
1224 section "443. NEGATION WITH !"
1225 # =====================================
1226
1227 # ! negates exit status
1228 result=$("$FORTSH_BIN" -c '! false && echo success' 2>&1)
1229 if [ "$result" = "success" ]; then
1230 pass "! negates false to true"
1231 else
1232 fail "! negates false to true" "success" "$result"
1233 fi
1234
1235 result=$("$FORTSH_BIN" -c '! true || echo failed' 2>&1)
1236 if [ "$result" = "failed" ]; then
1237 pass "! negates true to false"
1238 else
1239 fail "! negates true to false" "failed" "$result"
1240 fi
1241
1242 # ! with pipeline
1243 result=$("$FORTSH_BIN" -c '! echo test | grep -q nomatch && echo ok' 2>&1)
1244 if [ "$result" = "ok" ]; then
1245 pass "! with pipeline"
1246 else
1247 fail "! with pipeline" "ok" "$result"
1248 fi
1249
1250 # =====================================
1251 section "444. FUNCTION DEFINITIONS"
1252 # =====================================
1253
1254 # function with return
1255 result=$("$FORTSH_BIN" -c 'f() { return 42; }; f; echo $?' 2>&1)
1256 if [ "$result" = "42" ]; then
1257 pass "function with return value"
1258 else
1259 fail "function with return value" "42" "$result"
1260 fi
1261
1262 # function with arguments
1263 result=$("$FORTSH_BIN" -c 'greet() { echo "Hello, $1"; }; greet World' 2>&1)
1264 if [ "$result" = "Hello, World" ]; then
1265 pass "function with arguments"
1266 else
1267 fail "function with arguments" "Hello, World" "$result"
1268 fi
1269
1270 # function calling function
1271 result=$("$FORTSH_BIN" -c 'a() { b; }; b() { echo called; }; a' 2>&1)
1272 if [ "$result" = "called" ]; then
1273 pass "function calling function"
1274 else
1275 fail "function calling function" "called" "$result"
1276 fi
1277
1278 # =====================================
1279 section "445. EXIT AND RETURN"
1280 # =====================================
1281
1282 # exit from subshell
1283 result=$("$FORTSH_BIN" -c '(exit 7); echo $?' 2>&1)
1284 if [ "$result" = "7" ]; then
1285 pass "exit from subshell"
1286 else
1287 fail "exit from subshell" "7" "$result"
1288 fi
1289
1290 # return from function
1291 result=$("$FORTSH_BIN" -c 'f() { echo before; return; echo after; }; f' 2>&1)
1292 if [ "$result" = "before" ]; then
1293 pass "return stops function execution"
1294 else
1295 fail "return stops function execution" "before" "$result"
1296 fi
1297
1298 # return default value
1299 result=$("$FORTSH_BIN" -c 'f() { true; return; }; f; echo $?' 2>&1)
1300 if [ "$result" = "0" ]; then
1301 pass "return with no value uses last exit status"
1302 else
1303 fail "return with no value uses last exit status" "0" "$result"
1304 fi
1305
1306 # =====================================
1307 # Summary
1308 # =====================================
1309 printf "\n"
1310 printf "${BLUE}==========================================\n"
1311 printf "POSIX Control Flow and Grouping Summary\n"
1312 printf "==========================================${NC}\n"
1313 printf "Passed: ${GREEN}%d${NC}\n" "$PASSED"
1314 printf "Failed: ${RED}%d${NC}\n" "$FAILED"
1315 printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
1316 printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))"
1317
1318 if [ -n "$FAILED_TESTS_LIST" ]; then
1319 printf "\n${RED}Failed tests:${NC}\n"
1320 printf "%b" "$FAILED_TESTS_LIST"
1321 fi
1322
1323 if [ "$FAILED" -gt 0 ]; then
1324 exit 1
1325 fi
1326 exit 0