Bash · 12579 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Control Flow Gap Tests
4 # =====================================
5 # Tests for POSIX control flow constructs
6 # Split from posix_compliance_gaps.sh for better organization
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="[gaps-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 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 printf " posix: %s\n" "$2"; fi
49 if [ -n "$3" ]; then printf " fortsh: %s\n" "$3"; fi
50 FAILED=$((FAILED + 1))
51 }
52
53 section() {
54 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
55 TEST_NUM=0
56 printf "\n${BLUE}==========================================\n%s\n==========================================${NC}\n" "$1"
57 }
58
59 normalize_output() { sed -e 's/^bash: /sh: /' -e 's/line [0-9]*: //'; }
60
61 compare_posix_output() {
62 test_name="$1"; command="$2"
63 posix_out=$("$BASH_REF" -c "$command" 2>&1 | normalize_output)
64 fortsh_out=$("$FORTSH_BIN" -c "$command" 2>&1 | normalize_output)
65 if [ "$posix_out" = "$fortsh_out" ]; then pass "$test_name"
66 else fail "$test_name" "$posix_out" "$fortsh_out"; fi
67 }
68
69 # ============================================================================
70 # IF STATEMENTS
71 # ============================================================================
72
73 section "1. IF BASIC"
74 compare_posix_output "if true" 'if true; then echo yes; fi'
75 compare_posix_output "if false" 'if false; then echo yes; fi; echo done'
76 compare_posix_output "if else" 'if false; then echo no; else echo yes; fi'
77 compare_posix_output "if elif" 'if false; then echo 1; elif true; then echo 2; fi'
78 compare_posix_output "if elif else" 'if false; then echo 1; elif false; then echo 2; else echo 3; fi'
79 compare_posix_output "if nested" 'if true; then if true; then echo deep; fi; fi'
80
81 section "2. IF CONDITIONS"
82 compare_posix_output "if and" 'if true && true; then echo yes; fi'
83 compare_posix_output "if or" 'if false || true; then echo yes; fi'
84 compare_posix_output "if not" 'if ! false; then echo yes; fi'
85
86 # ============================================================================
87 # FOR LOOPS
88 # ============================================================================
89
90 section "3. FOR BASIC"
91 compare_posix_output "for simple" 'for i in a b c; do echo $i; done'
92 compare_posix_output "for numbers" 'for i in 1 2 3; do echo $i; done | wc -l'
93 compare_posix_output "for empty" 'for i in; do echo $i; done; echo done'
94 compare_posix_output "for single" "for i in one; do echo \$i; done"
95
96 section "4. FOR EDGE CASES"
97 compare_posix_output "for with break" 'for i in 1 2 3; do echo $i; break; done'
98 compare_posix_output "for with continue" 'for i in 1 2 3; do if [ $i = 2 ]; then continue; fi; echo $i; done'
99 # Inline test with debug output for CI diagnosis
100 _glob_cmd="touch /tmp/posix_gaps_for1_$$.txt /tmp/posix_gaps_for2_$$.txt /tmp/posix_gaps_for3_$$.txt 2>/dev/null; for f in /tmp/posix_gaps_for*_$$.txt; do test -f \$f && echo yes; done | head -1; rm -f /tmp/posix_gaps_for*_$$.txt"
101 _glob_posix=$("$BASH_REF" -c "$_glob_cmd" 2>&1)
102 _glob_fortsh=$("$FORTSH_BIN" -c "$_glob_cmd" 2>&1)
103 _glob_debug="[DEBUG glob] posix_hex=[$(printf '%s' "$_glob_posix" | od -A n -t x1 | tr -d '\n')] fortsh_hex=[$(printf '%s' "$_glob_fortsh" | od -A n -t x1 | tr -d '\n')] posix_raw=[$_glob_posix] fortsh_raw=[$_glob_fortsh]"
104 _glob_posix_n=$(printf '%s' "$_glob_posix" | normalize_output)
105 _glob_fortsh_n=$(printf '%s' "$_glob_fortsh" | normalize_output)
106 if [ "$_glob_posix_n" = "$_glob_fortsh_n" ]; then pass "for glob expansion"
107 else fail "for glob expansion" "$_glob_posix_n" "$_glob_fortsh_n"; fi
108 compare_posix_output "for preserves IFS" "IFS=:; for i in a b c; do echo \$i; done; echo \$IFS | od -A n -t x1 | grep -c 3a"
109
110 # ============================================================================
111 # WHILE LOOPS
112 # ============================================================================
113
114 section "5. WHILE BASIC"
115 compare_posix_output "while basic" 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done'
116 compare_posix_output "while false" 'while false; do echo no; done; echo done'
117 compare_posix_output "while break" 'while true; do echo once; break; done'
118 compare_posix_output "while count" 'n=3; while [ $n -gt 0 ]; do n=$((n-1)); done; echo $n'
119
120 section "6. WHILE EDGE CASES"
121 compare_posix_output "while true with break" "i=0; while true; do i=\$((i+1)); test \$i -eq 3 && break; done; echo \$i"
122 compare_posix_output "while with exit status" "i=5; while [ \$i -gt 0 ]; do i=\$((i-1)); done; echo \$?"
123
124 # ============================================================================
125 # UNTIL LOOPS
126 # ============================================================================
127
128 section "7. UNTIL BASIC"
129 compare_posix_output "until basic" 'i=0; until [ $i -ge 3 ]; do echo $i; i=$((i+1)); done'
130 compare_posix_output "until true" 'until true; do echo no; done; echo done'
131 compare_posix_output "until count" 'n=0; until [ $n -eq 3 ]; do n=$((n+1)); done; echo $n'
132
133 section "8. UNTIL EDGE CASES"
134 compare_posix_output "until false with break" "i=0; until false; do i=\$((i+1)); test \$i -eq 3 && break; done; echo \$i"
135 compare_posix_output "until complex condition" "i=0; until [ \$i -gt 3 ] && [ \$i -lt 10 ]; do i=\$((i+1)); done; echo \$i"
136
137 # ============================================================================
138 # CASE STATEMENTS
139 # ============================================================================
140
141 section "9. CASE BASIC"
142 compare_posix_output "case single" 'case x in x) echo yes;; esac'
143 compare_posix_output "case default" 'case y in x) echo no;; *) echo yes;; esac'
144 compare_posix_output "case multi" 'case b in a|b|c) echo abc;; esac'
145 compare_posix_output "case glob" 'case abc in a*) echo yes;; esac'
146 compare_posix_output "case nested" 'case x in x) case y in y) echo deep;; esac;; esac'
147
148 section "10. CASE PATTERNS"
149 compare_posix_output "case glob star" 'case abc in a*) echo star;; esac'
150 compare_posix_output "case glob question" 'case abc in a?c) echo match;; esac'
151 compare_posix_output "case glob bracket" 'case abc in [a-z]*) echo match;; esac'
152 compare_posix_output "case no match" 'case x in y) echo no;; esac; echo done'
153 compare_posix_output "case empty" 'case "" in "") echo empty;; esac'
154 compare_posix_output "case with var" 'x=test; case $x in test) echo yes;; esac'
155 compare_posix_output "case quoted" 'case "a b" in "a b") echo space;; esac'
156
157 section "11. CASE EDGE CASES"
158 compare_posix_output "case empty pattern" "x=''; case \$x in '') echo empty;; esac"
159 compare_posix_output "case with quotes" "x='a b'; case \$x in 'a b') echo match;; esac"
160 compare_posix_output "case glob vs literal" "x='*'; case \$x in '*') echo literal;; esac"
161 compare_posix_output "case bracket range" "x=b; case \$x in [a-c]) echo range;; esac"
162 compare_posix_output "case multiple patterns order" "x=a; case \$x in a|b) echo first;; a) echo second;; esac"
163 compare_posix_output "case with variable pattern" "P='a*'; x=abc; case \$x in \$P) echo var_pattern;; esac"
164
165 # ============================================================================
166 # BREAK AND CONTINUE
167 # ============================================================================
168
169 section "12. BREAK"
170 compare_posix_output "break simple" 'for i in 1 2 3; do echo $i; break; done'
171 compare_posix_output "break nested" 'for i in 1 2; do for j in a b; do echo $i$j; break; done; done'
172 compare_posix_output "break 2" 'for i in 1 2; do for j in a b; do echo $i$j; break 2; done; done'
173
174 section "13. CONTINUE"
175 compare_posix_output "continue simple" 'for i in 1 2 3; do if [ $i = 2 ]; then continue; fi; echo $i; done'
176 compare_posix_output "continue 2" 'for i in 1 2; do for j in a b; do if [ $j = a ]; then continue 2; fi; echo $i$j; done; done'
177
178 section "14. BREAK/CONTINUE EDGE CASES"
179 compare_posix_output "break with level 0" "for i in 1 2; do break 0 2>/dev/null || break; echo \$i; done || echo ok"
180 compare_posix_output "break level too high" "for i in 1 2; do break 10 2>/dev/null || break; echo \$i; done || echo done"
181 compare_posix_output "continue with level" "for i in 1 2; do for j in a b; do continue 2 2>/dev/null || continue; echo \$i\$j; done; done || echo ok"
182 compare_posix_output "break outside loop" "break 2>/dev/null || echo ok"
183 compare_posix_output "continue outside loop" "continue 2>/dev/null || echo ok"
184
185 # ============================================================================
186 # FUNCTIONS
187 # ============================================================================
188
189 section "15. FUNCTION DEFINITION"
190 compare_posix_output "func basic" 'f() { echo hello; }; f'
191 compare_posix_output "func args" 'f() { echo $1 $2; }; f a b'
192 compare_posix_output "func return" 'f() { return 5; }; f; echo $?'
193 compare_posix_output "func positional" 'f() { echo $# $@; }; f a b c'
194
195 section "16. RETURN AND EXIT"
196 compare_posix_output "return basic" 'f() { return; }; f; echo $?'
197 compare_posix_output "return code" 'f() { return 5; }; f; echo $?'
198 compare_posix_output "exit subshell" '(exit 5); echo $?'
199 compare_posix_output "exit code" '(exit 42); echo $?'
200
201 # ============================================================================
202 # SUBSHELL AND BRACE GROUPS
203 # ============================================================================
204
205 section "17. SUBSHELL"
206 compare_posix_output "subshell basic" '(echo hello)'
207 compare_posix_output "subshell var" 'x=1; (x=2); echo $x'
208 compare_posix_output "subshell exit" '(exit 5); echo $?'
209
210 section "18. SUBSHELL VARIABLE ISOLATION"
211 compare_posix_output "subshell doesnt modify parent" "(X=inner); echo \${X:-unset}"
212 compare_posix_output "subshell inherits variables" "X=outer; (echo \$X)"
213 compare_posix_output "nested subshells" "X=1; (X=2; (X=3; echo \$X); echo \$X); echo \$X"
214 compare_posix_output "subshell with exports" "export X=exp; (X=inner; echo \$X); echo \$X"
215
216 section "19. BRACE GROUPS"
217 compare_posix_output "brace basic" '{ echo hello; }'
218 compare_posix_output "brace var" 'x=1; { x=2; }; echo $x'
219 compare_posix_output "brace redir" '{ echo a; echo b; } > /tmp/b$$; wc -l < /tmp/b$$; rm /tmp/b$$'
220 compare_posix_output "brace group modifies parent" "X=1; { X=2; }; echo \$X"
221 compare_posix_output "brace group with redirects" "{ echo a; echo b; } | wc -l"
222 compare_posix_output "nested brace groups" "X=1; { { X=2; }; echo \$X; }; echo \$X"
223
224 # ============================================================================
225 # LOGICAL OPERATORS
226 # ============================================================================
227
228 section "20. LOGICAL OPERATORS"
229 compare_posix_output "and both true" 'true && echo yes'
230 compare_posix_output "and first false" 'false && echo no; echo done'
231 compare_posix_output "or first true" 'true || echo no; echo done'
232 compare_posix_output "or first false" 'false || echo yes'
233 compare_posix_output "not true" '! true; echo $?'
234 compare_posix_output "not false" '! false; echo $?'
235
236 # ============================================================================
237 # NESTED LOOPS
238 # ============================================================================
239
240 section "21. NESTED LOOPS"
241 compare_posix_output "nested for" 'for i in 1 2; do for j in a b; do echo $i$j; done; done | wc -l'
242 compare_posix_output "loop with pipe" 'for i in 1 2 3; do echo $i; done | head -2'
243
244 # Summary
245 printf "\n==========================================\n"
246 if [ -n "$_glob_debug" ]; then printf "%s\n" "$_glob_debug"; fi
247 printf "CONTROL FLOW GAP TEST RESULTS\n"
248 printf "==========================================\n"
249 printf "${GREEN}Passed:${NC} %d\n" "$PASSED"
250 printf "${RED}Failed:${NC} %d\n" "$FAILED"
251 printf "Total: %d\n" "$((PASSED + FAILED))"
252 if [ "$FAILED" -gt 0 ]; then
253 printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
254 exit 1
255 fi
256 exit 0