tenseleyflow/bensch / 8b63983

Browse files

extract POSIX compliance shell scripts

17 posix_compliance_*.sh + 8 posix_gaps_*.sh + run_posix_tests.sh
~19,450 lines, ~3800 test assertions.

Verbatim copies. FORTSH_BIN references will be replaced in Phase 4.

Source: fortsh/tests/posix_*.sh, fortsh/tests/run_posix_tests.sh
Authored by espadonne
SHA
8b639836127f329b5b96162c873cb3fd71f02296
Parents
431107b
Tree
f757358

26 changed files

StatusFile+-
A suites/posix/posix_compliance_advanced.sh 621 0
A suites/posix/posix_compliance_builtins.sh 658 0
A suites/posix/posix_compliance_charclass.sh 407 0
A suites/posix/posix_compliance_control.sh 1326 0
A suites/posix/posix_compliance_coverage.sh 480 0
A suites/posix/posix_compliance_extended.sh 682 0
A suites/posix/posix_compliance_filetest.sh 1108 0
A suites/posix/posix_compliance_gaps.sh 2802 0
A suites/posix/posix_compliance_heredoc.sh 758 0
A suites/posix/posix_compliance_jobcontrol.sh 258 0
A suites/posix/posix_compliance_options.sh 1248 0
A suites/posix/posix_compliance_printf.sh 428 0
A suites/posix/posix_compliance_quoting.sh 959 0
A suites/posix/posix_compliance_redirect.sh 950 0
A suites/posix/posix_compliance_special.sh 1396 0
A suites/posix/posix_compliance_test.sh 448 0
A suites/posix/posix_compliance_untested.sh 282 0
A suites/posix/posix_gaps_arithmetic.sh 169 0
A suites/posix/posix_gaps_builtins.sh 351 0
A suites/posix/posix_gaps_charclass.sh 162 0
A suites/posix/posix_gaps_control.sh 256 0
A suites/posix/posix_gaps_heredoc.sh 161 0
A suites/posix/posix_gaps_quoting.sh 127 0
A suites/posix/posix_gaps_redirect.sh 156 0
A suites/posix/posix_gaps_special.sh 225 0
A suites/posix/run_posix_tests.sh 118 0
suites/posix/posix_compliance_advanced.shadded
@@ -0,0 +1,621 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Advanced Test Suite for fortsh
4
+# =====================================
5
+# Additional coverage based on OpenGroup POSIX.1-2017 specification
6
+# Fills gaps not covered in basic, extended, and builtins test suites
7
+#
8
+# Coverage areas:
9
+# - Advanced control flow (break/continue with levels)
10
+# - Additional special built-ins (exec, command)
11
+# - Extended set options (-f, -x, -v, -a)
12
+# - File descriptor operations
13
+# - Advanced arithmetic operators
14
+# - Signal handling extensions
15
+# - Pathname expansion edge cases
16
+# - IFS edge cases
17
+# - Function recursion and advanced usage
18
+
19
+# Colors (POSIX-compliant way)
20
+RED='\033[0;31m'
21
+GREEN='\033[0;32m'
22
+YELLOW='\033[1;33m'
23
+BLUE='\033[0;34m'
24
+NC='\033[0m'
25
+
26
+# Test identification
27
+TEST_PREFIX="[posix-advanced]"
28
+CURRENT_SECTION=""
29
+TEST_NUM=0
30
+
31
+PASSED=0
32
+FAILED=0
33
+SKIPPED=0
34
+FAILED_TESTS_LIST=""
35
+
36
+# Get script directory (POSIX way)
37
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
38
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
39
+BASH_REF="${BASH_REF:-bash}"
40
+
41
+# Check if fortsh exists
42
+if [ ! -x "$FORTSH_BIN" ]; then
43
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
44
+    printf "Please run 'make' first or set FORTSH_BIN environment variable\n"
45
+    exit 1
46
+fi
47
+
48
+# Test result trackers
49
+pass() {
50
+    TEST_NUM=$((TEST_NUM + 1))
51
+    printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
52
+    PASSED=$((PASSED + 1))
53
+}
54
+
55
+fail() {
56
+    TEST_NUM=$((TEST_NUM + 1))
57
+    TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
58
+    printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
59
+    FAILED_TESTS_LIST="${FAILED_TESTS_LIST}  ${TEST_ID}: $1\n"
60
+    if [ -n "$2" ]; then
61
+        printf "  posix:  %s\n" "$2"
62
+    fi
63
+    if [ -n "$3" ]; then
64
+        printf "  fortsh: %s\n" "$3"
65
+    fi
66
+    FAILED=$((FAILED + 1))
67
+}
68
+
69
+skip() {
70
+    TEST_NUM=$((TEST_NUM + 1))
71
+    printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
72
+    SKIPPED=$((SKIPPED + 1))
73
+}
74
+
75
+section() {
76
+    # Extract section number from header like "51. BREAK CONTINUE"
77
+    CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
78
+    TEST_NUM=0
79
+    printf "\n"
80
+    printf "${BLUE}==========================================\n"
81
+    printf "%s\n" "$1"
82
+    printf "==========================================${NC}\n"
83
+}
84
+
85
+# Normalize shell error messages by stripping shell name and "line N: " prefix
86
+normalize_output() {
87
+    sed -e 's|^[^ ]*bash: |sh: |' -e 's|^[^ ]*fortsh: |sh: |' -e 's/line [0-9]*: //'
88
+}
89
+
90
+# Helper function to run command in both shells and compare
91
+compare_posix_output() {
92
+    test_name="$1"
93
+    command="$2"
94
+    posix_file="/tmp/posix_adv_$$_posix"
95
+    fortsh_file="/tmp/posix_adv_$$_fortsh"
96
+
97
+    # Run in POSIX shell (sh)
98
+    "$BASH_REF" -c "$command" 2>&1 | normalize_output > "$posix_file" || true
99
+
100
+    # Run in fortsh
101
+    "$FORTSH_BIN" -c "$command" 2>&1 | normalize_output > "$fortsh_file" || true
102
+
103
+    # Compare outputs
104
+    if diff -q "$posix_file" "$fortsh_file" > /dev/null 2>&1; then
105
+        pass "$test_name"
106
+    else
107
+        fail "$test_name" "$(cat "$posix_file")" "$(cat "$fortsh_file")"
108
+    fi
109
+
110
+    rm -f "$posix_file" "$fortsh_file"
111
+}
112
+
113
+# Helper function to compare exit codes
114
+compare_posix_exit_code() {
115
+    test_name="$1"
116
+    command="$2"
117
+
118
+    "$BASH_REF" -c "$command" > /dev/null 2>&1
119
+    posix_exit=$?
120
+
121
+    "$FORTSH_BIN" -c "$command" > /dev/null 2>&1
122
+    fortsh_exit=$?
123
+
124
+    if [ "$posix_exit" -eq "$fortsh_exit" ]; then
125
+        pass "$test_name"
126
+    else
127
+        fail "$test_name" "exit=$posix_exit" "exit=$fortsh_exit"
128
+    fi
129
+}
130
+
131
+# Cleanup
132
+cleanup() {
133
+    rm -f /tmp/posix_adv_$$_* 2>/dev/null
134
+    rm -rf /tmp/posix_advanced_test_* 2>/dev/null
135
+}
136
+trap cleanup EXIT INT TERM
137
+
138
+section "51. BREAK AND CONTINUE IN LOOPS"
139
+
140
+compare_posix_output "break in for loop" 'for i in 1 2 3 4 5; do echo $i; if [ $i -eq 3 ]; then break; fi; done'
141
+compare_posix_output "continue in for loop" 'for i in 1 2 3 4 5; do if [ $i -eq 3 ]; then continue; fi; echo $i; done'
142
+compare_posix_output "break in while loop" 'i=0; while [ $i -lt 5 ]; do i=$((i+1)); echo $i; if [ $i -eq 3 ]; then break; fi; done'
143
+compare_posix_output "continue in while loop" 'i=0; while [ $i -lt 5 ]; do i=$((i+1)); if [ $i -eq 3 ]; then continue; fi; echo $i; done'
144
+compare_posix_output "break in until loop" 'i=0; until [ $i -ge 5 ]; do i=$((i+1)); echo $i; if [ $i -eq 3 ]; then break; fi; done'
145
+
146
+section "52. NESTED LOOPS WITH BREAK/CONTINUE"
147
+
148
+compare_posix_output "break inner loop" 'for i in 1 2; do for j in a b c; do echo $i$j; if [ $j = b ]; then break; fi; done; done'
149
+compare_posix_output "break with level" 'for i in 1 2; do for j in a b; do echo $i$j; if [ $j = b ]; then break 2; fi; done; done'
150
+compare_posix_output "continue outer loop" 'for i in 1 2 3; do for j in a b; do echo $i$j; if [ $j = a ]; then continue 2; fi; done; done'
151
+
152
+section "53. EXEC BUILTIN"
153
+
154
+compare_posix_output "exec with redirect" 'exec 3>&1; echo test >&3; exec 3>&-; echo done'
155
+compare_posix_exit_code "exec replace shell" '(exec true); echo $?'
156
+compare_posix_output "exec without command" 'exec 2>&1; pwd >/dev/null'
157
+
158
+section "54. COMMAND BUILTIN"
159
+
160
+compare_posix_output "command -v test" 'command -v test | grep -c test'
161
+compare_posix_output "command -v echo" 'command -v echo | grep -c echo'
162
+compare_posix_exit_code "command -v nonexistent" '! command -v nonexistent_cmd_xyz'
163
+
164
+section "55. READ BUILTIN - THOROUGH TESTING"
165
+
166
+compare_posix_output "read single var" 'echo hello | read VAR 2>/dev/null || VAR=hello; echo $VAR'
167
+compare_posix_output "read multiple vars" 'echo one two three | { read A B C; echo $A $B $C; }'
168
+compare_posix_output "read with IFS" 'IFS=:; echo a:b:c | { read X Y Z; echo $X $Y $Z; }'
169
+compare_posix_output "read remaining to last" 'echo a b c d e | { read X Y Z; echo "$Z"; }'
170
+
171
+section "56. FILE DESCRIPTOR DUPLICATION"
172
+
173
+compare_posix_output "dup stdout to fd3" 'exec 3>&1; echo test >&3'
174
+compare_posix_output "dup stdin from fd" 'exec 3</dev/null; cat <&3; exec 3<&-; echo ok'
175
+compare_posix_exit_code "close stdout" '(exec 1>&-; echo test 2>/dev/null); echo $?'
176
+
177
+section "57. SET -F (NOGLOB)"
178
+
179
+compare_posix_output "noglob disables expansion" 'set -f; echo /tmp/*.xyz; set +f'
180
+compare_posix_output "noglob with literal star" 'set -f; VAR="a * b"; echo $VAR; set +f'
181
+compare_posix_output "glob after set +f" 'set -f; set +f; echo /tmp 2>/dev/null | grep -c tmp'
182
+
183
+section "58. SET -X (XTRACE)"
184
+
185
+compare_posix_output "xtrace shows commands" 'set -x; echo test 2>/tmp/xtrace_$$; set +x; grep -c echo /tmp/xtrace_$$; rm -f /tmp/xtrace_$$'
186
+compare_posix_output "xtrace in function" 'f() { set -x; echo inner 2>/tmp/xtrace_fn_$$; set +x; }; f; grep -c echo /tmp/xtrace_fn_$$; rm -f /tmp/xtrace_fn_$$'
187
+
188
+section "59. SET -V (VERBOSE)"
189
+
190
+compare_posix_output "verbose shows input" 'set -v; : test 2>&1 | grep -c test; set +v'
191
+
192
+section "60. SET -A (ALLEXPORT)"
193
+
194
+compare_posix_output "allexport exports vars" 'set -a; TEST_VAR=value; sh -c "echo \$TEST_VAR"'
195
+compare_posix_output "allexport off" 'set +a; TEST2=val; sh -c "echo \${TEST2:-empty}"'
196
+
197
+section "61. TRAP WITH SIGNALS"
198
+
199
+compare_posix_output "trap INT signal" 'trap "echo caught" INT; trap | grep INT'
200
+compare_posix_output "trap TERM signal" 'trap "echo term" TERM; trap | grep TERM'
201
+compare_posix_output "trap HUP signal" 'trap "echo hup" HUP; trap | grep HUP'
202
+compare_posix_output "trap with number" 'trap "echo sig15" 15; trap | grep -c 15'
203
+
204
+section "62. TRAP INHERITANCE IN SUBSHELLS"
205
+
206
+compare_posix_output "trap not inherited" 'trap "echo parent" EXIT; (trap | grep -c EXIT || echo 0)'
207
+compare_posix_output "subshell can set trap" '(trap "echo sub" EXIT; exit 0)'
208
+
209
+section "63. PARAMETER EXPANSION :? ERROR"
210
+
211
+compare_posix_exit_code "error if unset" 'unset VAR; echo ${VAR:?error} 2>/dev/null'
212
+compare_posix_exit_code "error if null" 'VAR=; echo ${VAR:?null} 2>/dev/null'
213
+compare_posix_output "no error if set" 'VAR=test; echo ${VAR:?error}'
214
+
215
+section "64. PARAMETER EXPANSION WITH SPECIAL PARAMS"
216
+
217
+compare_posix_output "length of positional params" 'set -- a b c; echo ${#@}'
218
+compare_posix_output "length of $*" 'set -- x y; echo ${#*}'
219
+compare_posix_output "length of $1" 'set -- hello; echo ${#1}'
220
+
221
+section "65. WAIT WITH ARGUMENTS"
222
+
223
+compare_posix_output "wait specific PID" 'sleep 0.1 & pid=$!; wait $pid; echo $?'
224
+compare_posix_output "wait all background" '(sleep 0.1 &); wait; echo ok'
225
+compare_posix_exit_code "wait nonexistent PID" 'wait 999999'
226
+
227
+section "66. IFS EDGE CASES"
228
+
229
+compare_posix_output "empty IFS" 'IFS=; VAR="a b c"; set -- $VAR; echo $#'
230
+compare_posix_output "IFS whitespace only" 'IFS=" \t\n"; VAR="a  b"; set -- $VAR; echo $# $1 $2'
231
+compare_posix_output "IFS custom delimiter" 'IFS=,; VAR="a,b,c"; set -- $VAR; echo $2'
232
+compare_posix_output "IFS leading delimiters" 'IFS=:; VAR=":a:b"; set -- $VAR; echo $#'
233
+
234
+section "67. ARITHMETIC BITWISE OPERATORS"
235
+
236
+compare_posix_output "bitwise AND" 'echo $((12 & 10))'
237
+compare_posix_output "bitwise OR" 'echo $((12 | 10))'
238
+compare_posix_output "bitwise XOR" 'echo $((12 ^ 10))'
239
+compare_posix_output "bitwise NOT" 'echo $((~5))'
240
+compare_posix_output "left shift" 'echo $((3 << 2))'
241
+compare_posix_output "right shift" 'echo $((12 >> 2))'
242
+
243
+section "68. ARITHMETIC ASSIGNMENT OPERATORS"
244
+
245
+compare_posix_output "add assign" 'X=5; echo $((X += 3)); echo $X'
246
+compare_posix_output "subtract assign" 'X=10; echo $((X -= 3)); echo $X'
247
+compare_posix_output "multiply assign" 'X=4; echo $((X *= 2)); echo $X'
248
+compare_posix_output "divide assign" 'X=20; echo $((X /= 4)); echo $X'
249
+compare_posix_output "modulo assign" 'X=17; echo $((X %= 5)); echo $X'
250
+
251
+section "69. ARITHMETIC INCREMENT/DECREMENT"
252
+
253
+compare_posix_output "post increment" 'X=5; echo $((X++)); echo $X'
254
+compare_posix_output "pre increment" 'X=5; echo $((++X)); echo $X'
255
+compare_posix_output "post decrement" 'X=5; echo $((X--)); echo $X'
256
+compare_posix_output "pre decrement" 'X=5; echo $((--X)); echo $X'
257
+
258
+section "70. ARITHMETIC TERNARY OPERATOR"
259
+
260
+compare_posix_output "ternary true" 'echo $((5 > 3 ? 10 : 20))'
261
+compare_posix_output "ternary false" 'echo $((5 < 3 ? 10 : 20))'
262
+compare_posix_output "ternary nested" 'echo $((1 ? 2 : 3 ? 4 : 5))'
263
+
264
+section "71. PIPELINE NEGATION"
265
+
266
+compare_posix_exit_code "negate true" '! true'
267
+compare_posix_exit_code "negate false" '! false'
268
+compare_posix_output "negate pipeline" '! echo test | grep -q xyz; echo $?'
269
+compare_posix_exit_code "negate compound" '! (exit 0)'
270
+
271
+section "72. THREE-STAGE PIPELINES"
272
+
273
+compare_posix_output "three stage pipe" 'echo abc | tr a A | tr b B'
274
+compare_posix_output "four stage pipe" 'echo test | cat | cat | wc -l'
275
+compare_posix_exit_code "multi-stage exit" 'true | true | true'
276
+
277
+section "73. CDPATH VARIABLE"
278
+
279
+compare_posix_output "CDPATH usage" 'mkdir -p /tmp/posix_cdpath_test/subdir; CDPATH=/tmp/posix_cdpath_test; (cd subdir 2>/dev/null && pwd) | grep -c subdir; rm -rf /tmp/posix_cdpath_test'
280
+
281
+section "74. OLDPWD VARIABLE"
282
+
283
+compare_posix_output "OLDPWD set" 'OLD=/tmp; cd /tmp >/dev/null; cd / >/dev/null; echo $OLDPWD | grep -c tmp'
284
+compare_posix_output "cd - uses OLDPWD" 'cd /tmp >/dev/null; cd / >/dev/null; cd - >/dev/null; pwd | grep -c tmp'
285
+
286
+section "75. PATHNAME EXPANSION - BRACKET EXPRESSIONS"
287
+
288
+mkdir -p /tmp/posix_advanced_test_bracket
289
+touch /tmp/posix_advanced_test_bracket/a1.txt /tmp/posix_advanced_test_bracket/a2.txt
290
+touch /tmp/posix_advanced_test_bracket/b1.txt /tmp/posix_advanced_test_bracket/b2.txt
291
+touch /tmp/posix_advanced_test_bracket/c1.txt
292
+
293
+compare_posix_output "bracket range" 'ls /tmp/posix_advanced_test_bracket/[a-b]1.txt 2>/dev/null | wc -l'
294
+compare_posix_output "bracket negation" 'ls /tmp/posix_advanced_test_bracket/[!a]*.txt 2>/dev/null | wc -l'
295
+compare_posix_output "bracket char class" 'ls /tmp/posix_advanced_test_bracket/[[:lower:]]1.txt 2>/dev/null | wc -l'
296
+
297
+rm -rf /tmp/posix_advanced_test_bracket
298
+
299
+section "76. PATHNAME EXPANSION - DOTFILE MATCHING"
300
+
301
+mkdir -p /tmp/posix_advanced_test_dotfile
302
+touch /tmp/posix_advanced_test_dotfile/.hidden
303
+touch /tmp/posix_advanced_test_dotfile/visible
304
+
305
+compare_posix_output "star no dotfiles" 'ls /tmp/posix_advanced_test_dotfile/* 2>/dev/null | wc -l'
306
+compare_posix_output "explicit dot match" 'ls /tmp/posix_advanced_test_dotfile/.* 2>/dev/null | grep -c hidden'
307
+
308
+rm -rf /tmp/posix_advanced_test_dotfile
309
+
310
+section "77. FOR LOOP VARIATIONS"
311
+
312
+compare_posix_output "for with glob" 'touch /tmp/posix_for_1.txt /tmp/posix_for_2.txt /tmp/posix_for_3.txt; for f in /tmp/posix_for_*.txt; do echo $f; done | wc -l; rm /tmp/posix_for_*.txt'
313
+compare_posix_output "for with no items" 'for x in; do echo $x; done; echo empty'
314
+compare_posix_output "for with quoted items" 'for i in "a b" "c d"; do echo $i; done | wc -l'
315
+
316
+section "78. CASE PATTERN MATCHING"
317
+
318
+compare_posix_output "case multiple pattern" 'x=b; case $x in a|b|c) echo match;; *) echo no;; esac'
319
+compare_posix_output "case glob pattern" 'x=hello; case $x in h*) echo prefix;; esac'
320
+compare_posix_output "case question mark" 'x=ab; case $x in ??) echo two;; esac'
321
+compare_posix_output "case bracket" 'x=a; case $x in [abc]) echo bracket;; esac'
322
+
323
+section "79. FUNCTION VARIATIONS"
324
+
325
+compare_posix_output "function recursion" 'fact() { if [ $1 -le 1 ]; then echo 1; else echo $(($1 * $(fact $(($1 - 1))))); fi; }; fact 5'
326
+compare_posix_output "function unset" 'f() { echo test; }; f; unset -f f; command -v f >/dev/null 2>&1; echo $?'
327
+compare_posix_output "nested return" 'a() { b; echo $?; }; b() { return 42; }; a'
328
+
329
+section "80. EXPANSION IN DIFFERENT CONTEXTS"
330
+
331
+compare_posix_output "tilde in assignment" 'VAR=~; echo $VAR | grep -c ^/'
332
+compare_posix_output "expansion in case" 'VAR=test; case $VAR in test) echo match;; esac'
333
+compare_posix_output "no expansion in quotes" 'echo "~" | grep -c "~"'
334
+
335
+section "81. REDIRECTION ORDER AND PRECEDENCE"
336
+
337
+compare_posix_output "multiple redirects" 'echo test >/tmp/redir1 2>&1 >/tmp/redir2; cat /tmp/redir1 /tmp/redir2 2>/dev/null | wc -l; rm -f /tmp/redir1 /tmp/redir2'
338
+compare_posix_output "redirect compound cmd" '{ echo a; echo b; } >/tmp/redir_compound; wc -l < /tmp/redir_compound; rm -f /tmp/redir_compound'
339
+
340
+section "82. ERROR HANDLING IN EXPANSIONS"
341
+
342
+compare_posix_exit_code "division by zero" 'echo $((5 / 0)) 2>/dev/null'
343
+compare_posix_exit_code "expansion error" 'set -u; echo $UNDEFINED_VAR_XYZ 2>/dev/null'
344
+
345
+section "83. SPECIAL PARAMETERS - ADDITIONAL"
346
+
347
+compare_posix_output "PPID is numeric" 'echo $PPID | grep -c "^[0-9]*$"'
348
+compare_posix_output "LINENO exists" 'echo $LINENO | grep -c "^[0-9]*$"'
349
+
350
+section "84. ALIAS AND UNALIAS"
351
+
352
+compare_posix_output "alias definition" 'alias ll="ls -l"; alias ll | grep -c "ls -l"'
353
+compare_posix_output "unalias removes" 'alias test_alias=echo; unalias test_alias; alias test_alias 2>&1 | grep -c "not found"'
354
+
355
+section "85. HASH BUILTIN OPERATIONS"
356
+
357
+compare_posix_output "hash -r clears" 'hash -r; echo $?'
358
+compare_posix_exit_code "hash command" 'hash echo 2>/dev/null'
359
+
360
+section "86. MULTIPLE SEMICOLONS AND EDGE CASES"
361
+
362
+compare_posix_output "multiple semicolons" 'echo a;; echo b'
363
+compare_posix_output "semicolon at start" '; echo test'
364
+compare_posix_exit_code "empty command" ''
365
+
366
+section "87. BACKSLASH LINE CONTINUATION"
367
+
368
+compare_posix_output "backslash continuation" 'echo hel\
369
+lo'
370
+compare_posix_output "backslash in command" 'ec\
371
+ho test'
372
+
373
+section "88. COMMENT HANDLING"
374
+
375
+compare_posix_output "comment after command" 'echo visible # this is comment'
376
+compare_posix_output "comment alone" '# comment
377
+echo after'
378
+
379
+section "89. QUOTING EDGE CASES"
380
+
381
+compare_posix_output "empty string quotes" 'echo "" | wc -l'
382
+compare_posix_output "adjacent quotes" 'echo "a"b"c"'
383
+compare_posix_output "quote within quote" "echo \"it's\" | grep -c \"'\""
384
+
385
+section "90. COMPOUND COMMAND REDIRECTION"
386
+
387
+compare_posix_output "subshell with redirect" '(echo a; echo b) | wc -l'
388
+compare_posix_output "brace group redirect" '{ echo x; echo y; } | wc -l'
389
+compare_posix_output "if statement redirect" 'if true; then echo yes; fi | cat'
390
+
391
+section "91. POSIX PARAMETER LENGTH"
392
+
393
+compare_posix_output "length of empty" 'X=""; echo ${#X}'
394
+compare_posix_output "length with spaces" 'X="a b c"; echo ${#X}'
395
+compare_posix_output "length special chars" 'X="$@#!"; echo ${#X}'
396
+compare_posix_output "length unicode" 'X="abc"; echo ${#X}'
397
+
398
+section "92. POSIX DEFAULT VALUES VARIANTS"
399
+
400
+compare_posix_output ":-colon vs dash" 'X=""; echo "${X:-default}" "${X-default}"'
401
+compare_posix_output ":=colon vs equals" 'unset Y; echo "${Y:=def}"; echo $Y'
402
+compare_posix_output ":+colon vs plus" 'X=val; echo "${X:+alt}" "${X+alt}"'
403
+compare_posix_output ":?colon vs question" 'X=val; echo "${X:?err}" 2>/dev/null'
404
+
405
+section "93. POSIX PATTERN REMOVAL EDGE CASES"
406
+
407
+compare_posix_output "remove empty pattern" 'X=test; echo "${X#}"'
408
+compare_posix_output "remove star pattern" 'X=test; echo "${X#*}"'
409
+compare_posix_output "remove all with ##" 'X=test; echo "${X##*}"'
410
+compare_posix_output "suffix empty" 'X=test; echo "${X%}"'
411
+compare_posix_output "suffix star" 'X=test; echo "${X%*}"'
412
+compare_posix_output "suffix all" 'X=test; echo "${X%%*}"'
413
+
414
+section "94. POSIX ARITHMETIC OPERATORS"
415
+
416
+compare_posix_output "arithmetic modulo" 'echo $((17 % 5))'
417
+compare_posix_output "arithmetic negative" 'echo $((-5 + 3))'
418
+compare_posix_output "arithmetic parens" 'echo $(((2 + 3) * 4))'
419
+compare_posix_output "arithmetic bitwise and" 'echo $((12 & 10))'
420
+compare_posix_output "arithmetic bitwise or" 'echo $((12 | 10))'
421
+compare_posix_output "arithmetic bitwise xor" 'echo $((12 ^ 10))'
422
+compare_posix_output "arithmetic shift left" 'echo $((1 << 4))'
423
+compare_posix_output "arithmetic shift right" 'echo $((16 >> 2))'
424
+
425
+section "95. POSIX ARITHMETIC COMPARISONS"
426
+
427
+compare_posix_output "arith less than" 'echo $((3 < 5))'
428
+compare_posix_output "arith greater than" 'echo $((5 > 3))'
429
+compare_posix_output "arith less equal" 'echo $((3 <= 3))'
430
+compare_posix_output "arith greater equal" 'echo $((3 >= 3))'
431
+compare_posix_output "arith equal" 'echo $((5 == 5))'
432
+compare_posix_output "arith not equal" 'echo $((5 != 3))'
433
+compare_posix_output "arith logical and" 'echo $((1 && 1))'
434
+compare_posix_output "arith logical or" 'echo $((0 || 1))'
435
+
436
+section "96. POSIX COMMAND SUBSTITUTION EDGE CASES"
437
+
438
+compare_posix_output "cmd sub with quotes" 'echo $(echo "hello world")'
439
+compare_posix_output "cmd sub trailing newlines" 'echo "$(printf "a\n\n\n")"b'
440
+compare_posix_output "cmd sub with pipe" 'echo $(echo test | tr t T)'
441
+compare_posix_output "cmd sub with redirect" 'echo $(cat </dev/null)'
442
+compare_posix_output "backtick with quotes" 'echo `echo "hello"`'
443
+
444
+section "97. POSIX SUBSHELL VARIABLE ISOLATION"
445
+
446
+compare_posix_output "subshell no export" 'X=1; (X=2; echo $X); echo $X'
447
+compare_posix_output "subshell unset" 'X=1; (unset X; echo ${X:-empty}); echo $X'
448
+# Note: (( expr )) is arithmetic syntax in bash, not nested subshell. Compare exit codes.
449
+compare_posix_exit_code "double paren arithmetic" '(( echo nested ))'
450
+compare_posix_output "subshell exit" '(exit 5); echo $?'
451
+
452
+section "98. POSIX BRACE GROUP VS SUBSHELL"
453
+
454
+compare_posix_output "brace group modifies" 'X=1; { X=2; }; echo $X'
455
+compare_posix_output "subshell isolates" 'X=1; (X=2); echo $X'
456
+compare_posix_output "brace with semicolon" '{ echo a; echo b; }'
457
+compare_posix_output "brace in pipeline" '{ echo test; } | cat'
458
+
459
+section "99. POSIX HERE-STRING ALTERNATIVES"
460
+
461
+compare_posix_output "echo pipe vs redirect" 'echo test | cat'
462
+compare_posix_output "printf to stdin" 'printf "test\n" | read X; echo $X'
463
+
464
+section "100. POSIX PIPELINE EXIT STATUS"
465
+
466
+compare_posix_output "pipe success" 'true | true; echo $?'
467
+compare_posix_output "pipe last fails" 'true | false; echo $?'
468
+compare_posix_output "pipe first fails" 'false | true; echo $?'
469
+compare_posix_output "long pipeline" 'echo a | cat | cat | cat; echo $?'
470
+
471
+section "101. POSIX PROCESS SUBSTITUTION ALTERNATIVES"
472
+
473
+compare_posix_output "temp file pattern" 'echo test > /tmp/psub$$; cat /tmp/psub$$; rm /tmp/psub$$'
474
+compare_posix_output "named pipe simulation" 'mkfifo /tmp/pfifo$$ 2>/dev/null || true; rm -f /tmp/pfifo$$; echo ok'
475
+
476
+section "102. POSIX TEST BRACKETS"
477
+
478
+compare_posix_output "test vs bracket" 'test 1 -eq 1 && [ 1 -eq 1 ] && echo ok'
479
+compare_posix_output "bracket spacing" '[ "a" = "a" ] && echo match'
480
+compare_posix_output "empty bracket test" '[ "" ] || echo empty'
481
+compare_posix_output "nonempty test" '[ "x" ] && echo nonempty'
482
+
483
+section "103. POSIX GETOPTS ADVANCED"
484
+
485
+compare_posix_output "getopts multiple" 'while getopts "ab:c" opt 2>/dev/null; do echo $opt; done <<< "-a -b val -c"'
486
+compare_posix_output "getopts OPTARG" 'getopts "a:" opt <<< "-a value" 2>/dev/null; echo $OPTARG'
487
+compare_posix_output "getopts OPTIND" 'OPTIND=1; getopts "a" opt <<< "-a" 2>/dev/null; echo $OPTIND'
488
+
489
+section "104. POSIX READ BUILTIN"
490
+
491
+compare_posix_output "read single var" 'echo "hello" | { read X; echo $X; }'
492
+compare_posix_output "read multiple vars" 'echo "a b c" | { read X Y Z; echo "$X:$Y:$Z"; }'
493
+compare_posix_output "read with IFS" 'echo "a:b:c" | { IFS=: read X Y Z; echo "$X $Y $Z"; }'
494
+compare_posix_output "read extra words" 'echo "a b c d" | { read X Y; echo "$X:$Y"; }'
495
+
496
+section "105. POSIX PRINTF FORMATTING"
497
+
498
+compare_posix_output "printf string" 'printf "%s\n" "test"'
499
+compare_posix_output "printf integer" 'printf "%d\n" 42'
500
+compare_posix_output "printf width" 'printf "%10s\n" "hi"'
501
+compare_posix_output "printf left align" 'printf "%-10s|\n" "hi"'
502
+compare_posix_output "printf zero pad" 'printf "%05d\n" 42'
503
+compare_posix_output "printf multiple" 'printf "%s %d\n" "val" 123'
504
+
505
+section "106. POSIX ARITHMETIC EDGE CASES"
506
+
507
+compare_posix_output "unary minus" 'echo $((-5))'
508
+compare_posix_output "unary plus" 'echo $((+5))'
509
+compare_posix_output "double negative" 'echo $((--5))'
510
+compare_posix_output "zero division check" 'echo $((5 / 1))'
511
+compare_posix_output "modulo zero check" 'echo $((5 % 1))'
512
+compare_posix_output "large number" 'echo $((1000000 * 1000))'
513
+compare_posix_output "negative modulo" 'echo $((-7 % 3))'
514
+compare_posix_output "chained ops" 'echo $((1 + 2 + 3 + 4 + 5))'
515
+
516
+section "107. POSIX VARIABLE EDGE CASES"
517
+
518
+compare_posix_output "underscore var" 'X_Y_Z=test; echo $X_Y_Z'
519
+compare_posix_output "numeric suffix var" 'VAR1=a; VAR2=b; echo $VAR1$VAR2'
520
+compare_posix_output "long var name" 'VERY_LONG_VARIABLE_NAME_HERE=val; echo $VERY_LONG_VARIABLE_NAME_HERE'
521
+compare_posix_output "var with digits" 'A1B2C3=mix; echo $A1B2C3'
522
+compare_posix_output "reassignment" 'X=1; X=2; X=3; echo $X'
523
+compare_posix_output "self reference" 'X=a; X=${X}b; echo $X'
524
+
525
+section "108. POSIX FUNCTION EDGE CASES"
526
+
527
+compare_posix_output "function redefine" 'f() { echo first; }; f() { echo second; }; f'
528
+compare_posix_output "function in pipeline" 'f() { echo test; }; f | cat'
529
+compare_posix_output "function with subshell" 'f() { (echo sub); }; f'
530
+compare_posix_output "function return in if" 'f() { if true; then return 0; fi; return 1; }; f; echo $?'
531
+compare_posix_output "function shift" 'f() { shift; echo $1; }; f a b c'
532
+compare_posix_output "function all args" 'f() { echo "$@"; }; f one two three'
533
+
534
+section "109. POSIX LOOP EDGE CASES"
535
+
536
+compare_posix_output "for single item" 'for i in single; do echo $i; done'
537
+compare_posix_output "for many items" 'for i in 1 2 3 4 5 6 7 8 9 10; do echo $i; done | wc -l'
538
+compare_posix_output "nested for" 'for i in a b; do for j in 1 2; do echo $i$j; done; done'
539
+compare_posix_output "while nested" 'i=0; while [ $i -lt 2 ]; do j=0; while [ $j -lt 2 ]; do echo $i$j; j=$((j+1)); done; i=$((i+1)); done'
540
+compare_posix_output "for with continue" 'for i in 1 2 3; do [ $i -eq 2 ] && continue; echo $i; done'
541
+
542
+section "110. POSIX CASE EDGE CASES"
543
+
544
+compare_posix_output "case empty pattern" 'x=; case "$x" in "") echo empty;; esac'
545
+compare_posix_output "case glob star" 'x=anything; case $x in *) echo matched;; esac'
546
+compare_posix_output "case multiple star" 'x=test; case $x in a*) echo a;; t*) echo t;; esac'
547
+compare_posix_output "case bracket range" 'x=5; case $x in [0-9]) echo digit;; esac'
548
+compare_posix_output "case no default" 'x=z; case $x in a) echo a;; b) echo b;; esac; echo done'
549
+
550
+section "111. POSIX REDIRECTION EDGE CASES"
551
+
552
+compare_posix_output "append create" 'rm -f /tmp/append_test_$$; echo first >> /tmp/append_test_$$; cat /tmp/append_test_$$; rm /tmp/append_test_$$'
553
+compare_posix_output "redirect in loop" 'for i in 1 2 3; do echo $i; done > /tmp/loop_redir_$$; wc -l < /tmp/loop_redir_$$; rm /tmp/loop_redir_$$'
554
+compare_posix_output "redirect in func" 'f() { echo test; }; f > /tmp/func_redir_$$; cat /tmp/func_redir_$$; rm /tmp/func_redir_$$'
555
+compare_posix_output "multiple output" 'echo a > /tmp/multi1_$$; echo b > /tmp/multi2_$$; cat /tmp/multi1_$$ /tmp/multi2_$$; rm /tmp/multi1_$$ /tmp/multi2_$$'
556
+
557
+section "112. POSIX PIPELINE EDGE CASES"
558
+
559
+compare_posix_output "five stage pipe" 'echo test | cat | cat | cat | cat | cat'
560
+compare_posix_output "pipe with grep" 'printf "a\nb\nc\n" | grep b'
561
+compare_posix_output "pipe with wc" 'printf "a\nb\nc\n" | wc -l'
562
+compare_posix_output "pipe with sort" 'printf "c\na\nb\n" | sort'
563
+compare_posix_output "pipe with uniq" 'printf "a\na\nb\n" | uniq'
564
+compare_posix_output "pipe with tr" 'echo abc | tr a-z A-Z'
565
+
566
+section "113. POSIX SUBSHELL EDGE CASES"
567
+
568
+compare_posix_output "subshell pwd" '(cd /tmp; pwd)'
569
+compare_posix_output "subshell var" 'X=outer; (X=inner); echo $X'
570
+compare_posix_output "subshell function" 'f() { echo hi; }; (f)'
571
+compare_posix_output "subshell pipeline" '(echo a; echo b) | wc -l'
572
+# Note: ((( expr ))) is arithmetic syntax in bash. Compare exit codes.
573
+compare_posix_exit_code "triple paren arithmetic" '(((echo deep)))'
574
+compare_posix_output "subshell arithmetic" '(echo $((2+2)))'
575
+
576
+section "114. POSIX BRACE GROUP EDGE CASES"
577
+
578
+compare_posix_output "brace simple" '{ echo test; }'
579
+compare_posix_output "brace multi" '{ echo a; echo b; echo c; }'
580
+compare_posix_output "brace var" 'X=1; { X=2; }; echo $X'
581
+compare_posix_output "brace redirect" '{ echo test; } > /tmp/brace_$$; cat /tmp/brace_$$; rm /tmp/brace_$$'
582
+compare_posix_output "brace in pipe" '{ echo a; echo b; } | wc -l'
583
+
584
+section "115. POSIX SPECIAL CHAR EDGE CASES"
585
+
586
+compare_posix_output "semicolon list" 'echo a; echo b; echo c'
587
+compare_posix_output "ampersand bg" 'sleep 0.01 & wait; echo done'
588
+compare_posix_output "double ampersand" 'true && echo yes'
589
+compare_posix_output "double pipe" 'false || echo fallback'
590
+compare_posix_output "mixed logic" 'true && true && echo all_true'
591
+compare_posix_output "negation" '! false && echo negated'
592
+
593
+# Summary
594
+printf "\n"
595
+printf "==========================================\n"
596
+printf "ADVANCED POSIX COMPLIANCE TEST RESULTS ${TEST_PREFIX}\n"
597
+printf "==========================================\n"
598
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
599
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
600
+printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
601
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
602
+printf "==========================================\n"
603
+
604
+if [ $((PASSED + FAILED)) -gt 0 ]; then
605
+    PASS_RATE=$((PASSED * 100 / (PASSED + FAILED)))
606
+    printf "Pass rate: %d%%\n" "$PASS_RATE"
607
+fi
608
+
609
+if [ "$FAILED" -gt 0 ]; then
610
+    printf "\n${RED}Failed tests:${NC}\n"
611
+    printf "%b" "$FAILED_TESTS_LIST"
612
+    printf "==========================================\n"
613
+fi
614
+
615
+if [ "$FAILED" -eq 0 ]; then
616
+    printf "${GREEN}ALL ADVANCED POSIX TESTS PASSED!${NC} ✓\n"
617
+    exit 0
618
+else
619
+    printf "${RED}SOME TESTS FAILED${NC} ✗\n"
620
+    exit 1
621
+fi
suites/posix/posix_compliance_builtins.shadded
@@ -0,0 +1,658 @@
1
+#!/usr/bin/env bash
2
+# ==============================================================================
3
+# POSIX Compliance - Builtin Commands & Shell Options Test Suite
4
+# ==============================================================================
5
+# Tests all gaps identified in POSIX compliance analysis (2025-10-17)
6
+# Covers: P0 (critical), P1 (important), P2 (nice-to-have) issues
7
+# ==============================================================================
8
+
9
+# Configuration
10
+FORTSH_BIN="${FORTSH_BIN:-./bin/fortsh}"
11
+VERBOSE="${VERBOSE:-0}"
12
+
13
+# Test identification
14
+TEST_PREFIX="[posix-builtins]"
15
+
16
+# Test counters
17
+TOTAL_TESTS=0
18
+PASSED_TESTS=0
19
+FAILED_TESTS=0
20
+FAILED_TESTS_LIST=""
21
+
22
+# Color codes (if terminal supports them)
23
+if [ -t 1 ]; then
24
+    RED='\033[0;31m'
25
+    GREEN='\033[0;32m'
26
+    YELLOW='\033[1;33m'
27
+    BLUE='\033[0;34m'
28
+    NC='\033[0m' # No Color
29
+else
30
+    RED=''
31
+    GREEN=''
32
+    YELLOW=''
33
+    BLUE=''
34
+    NC=''
35
+fi
36
+
37
+# Test result functions
38
+pass() {
39
+    PASSED_TESTS=$((PASSED_TESTS + 1))
40
+    TOTAL_TESTS=$((TOTAL_TESTS + 1))
41
+    echo -e "${GREEN}✓${NC} ${TEST_PREFIX} $1"
42
+}
43
+
44
+fail() {
45
+    FAILED_TESTS=$((FAILED_TESTS + 1))
46
+    TOTAL_TESTS=$((TOTAL_TESTS + 1))
47
+    echo -e "${RED}✗${NC} ${TEST_PREFIX} $1"
48
+    FAILED_TESTS_LIST="${FAILED_TESTS_LIST}  ${TEST_PREFIX} $1\n"
49
+    if [ "$VERBOSE" = "1" ]; then
50
+        echo -e "${RED}  Details: $2${NC}"
51
+    fi
52
+}
53
+
54
+section() {
55
+    echo ""
56
+    echo -e "${BLUE}========================================${NC}"
57
+    echo -e "${BLUE}$1${NC}"
58
+    echo -e "${BLUE}========================================${NC}"
59
+}
60
+
61
+# ==============================================================================
62
+# P0: CRITICAL TESTS (Must pass for basic POSIX compliance)
63
+# ==============================================================================
64
+
65
+test_p0_readonly_enforcement() {
66
+    section "P0-1: readonly Variable Enforcement"
67
+
68
+    # Test 1: Cannot modify readonly variable
69
+    output=$($FORTSH_BIN -c 'readonly VAR=test; VAR=other' 2>&1)
70
+    if echo "$output" | grep -q "readonly variable"; then
71
+        pass "P0-1.1: Readonly violation produces error message"
72
+    else
73
+        fail "P0-1.1: Readonly violation produces error message" "No error message found"
74
+    fi
75
+
76
+    # Test 2: Exit code is 1 for readonly violation (bash compatibility)
77
+    $FORTSH_BIN -c 'readonly VAR=test; VAR=other' >/dev/null 2>&1
78
+    if [ $? -eq 1 ]; then
79
+        pass "P0-1.2: Readonly violation returns exit code 1"
80
+    else
81
+        fail "P0-1.2: Readonly violation returns exit code 1" "Got exit code $?"
82
+    fi
83
+
84
+    # Test 3: Command after readonly violation should not execute
85
+    output=$($FORTSH_BIN -c 'readonly VAR=test; VAR=other; echo SHOULD_NOT_PRINT' 2>&1)
86
+    if ! echo "$output" | grep -q "SHOULD_NOT_PRINT"; then
87
+        pass "P0-1.3: Commands after readonly violation do not execute"
88
+    else
89
+        fail "P0-1.3: Commands after readonly violation do not execute" "Command was executed"
90
+    fi
91
+
92
+    # Test 4: Readonly variable preserves original value (test removed - see P0-1.3)
93
+    # Note: POSIX requires non-interactive shells to exit on readonly violations,
94
+    # so we cannot test value preservation after failed assignment in same script.
95
+    # Value preservation is implicitly tested by P0-1.1 (error occurs) and P0-1.3 (execution stops).
96
+
97
+    # Test 5: Multiple readonly violations
98
+    $FORTSH_BIN -c 'readonly A=1; readonly B=2; A=x; B=y' >/dev/null 2>&1
99
+    if [ $? -eq 1 ]; then
100
+        pass "P0-1.5: Multiple readonly violations handled correctly"
101
+    else
102
+        fail "P0-1.5: Multiple readonly violations handled correctly" "Got exit code $?"
103
+    fi
104
+}
105
+
106
+test_p0_set_u() {
107
+    section "P0-2: set -u (nounset option)"
108
+
109
+    # Test 1: Undefined variable in parameter expansion
110
+    output=$($FORTSH_BIN -c 'set -u; echo ${UNDEFINED_VAR}' 2>&1)
111
+    if echo "$output" | grep -q "unbound variable"; then
112
+        pass "P0-2.1: set -u detects undefined variable in \${VAR}"
113
+    else
114
+        fail "P0-2.1: set -u detects undefined variable in \${VAR}" "No error for undefined variable"
115
+    fi
116
+
117
+    # Test 2: Undefined variable in simple expansion
118
+    output=$($FORTSH_BIN -c 'set -u; echo $UNDEFINED_SIMPLE' 2>&1)
119
+    if echo "$output" | grep -q "unbound variable"; then
120
+        pass "P0-2.2: set -u detects undefined variable in \$VAR"
121
+    else
122
+        fail "P0-2.2: set -u detects undefined variable in \$VAR"
123
+    fi
124
+
125
+    # Test 3: Non-interactive shell exits on unbound variable
126
+    # Bash uses exit code 127 for expansion errors (matching bash behavior)
127
+    $FORTSH_BIN -c 'set -u; echo $UNDEF; echo SHOULD_NOT_PRINT' >/dev/null 2>&1
128
+    exit_code=$?
129
+    output=$($FORTSH_BIN -c 'set -u; echo $UNDEF; echo SHOULD_NOT_PRINT' 2>&1)
130
+    if [ $exit_code -eq 127 ] && ! echo "$output" | grep -q "SHOULD_NOT_PRINT"; then
131
+        pass "P0-2.3: Non-interactive shell exits on unbound variable"
132
+    else
133
+        fail "P0-2.3: Non-interactive shell exits on unbound variable" "Exit code: $exit_code"
134
+    fi
135
+
136
+    # Test 4: Defined variable works with set -u
137
+    output=$($FORTSH_BIN -c 'set -u; VAR=value; echo $VAR' 2>&1)
138
+    if echo "$output" | grep -q "value"; then
139
+        pass "P0-2.4: Defined variables work correctly with set -u"
140
+    else
141
+        fail "P0-2.4: Defined variables work correctly with set -u"
142
+    fi
143
+
144
+    # Test 5: set +u disables the option
145
+    output=$($FORTSH_BIN -c 'set -u; set +u; echo $UNDEFINED_AFTER_DISABLE' 2>&1)
146
+    if ! echo "$output" | grep -q "unbound variable"; then
147
+        pass "P0-2.5: set +u correctly disables nounset option"
148
+    else
149
+        fail "P0-2.5: set +u correctly disables nounset option"
150
+    fi
151
+
152
+    # Test 6: Special variables always defined ($?, $$, etc.)
153
+    output=$($FORTSH_BIN -c 'set -u; echo $? $$ $0' 2>&1)
154
+    if ! echo "$output" | grep -q "unbound variable"; then
155
+        pass "P0-2.6: Special variables don't trigger set -u"
156
+    else
157
+        fail "P0-2.6: Special variables don't trigger set -u"
158
+    fi
159
+}
160
+
161
+test_p0_trap_exit() {
162
+    section "P0-3: trap EXIT Execution"
163
+
164
+    # Test 1: EXIT trap executes on exit builtin
165
+    output=$($FORTSH_BIN -c 'trap "echo TRAPPED" EXIT; exit 0' 2>&1)
166
+    if echo "$output" | grep -q "TRAPPED"; then
167
+        pass "P0-3.1: EXIT trap executes on exit builtin"
168
+    else
169
+        fail "P0-3.1: EXIT trap executes on exit builtin" "Trap did not execute"
170
+    fi
171
+
172
+    # Test 2: EXIT trap preserves exit code
173
+    $FORTSH_BIN -c 'trap "echo cleanup" EXIT; exit 42' >/dev/null 2>&1
174
+    if [ $? -eq 42 ]; then
175
+        pass "P0-3.2: EXIT trap preserves original exit code"
176
+    else
177
+        fail "P0-3.2: EXIT trap preserves original exit code" "Got exit code $?"
178
+    fi
179
+
180
+    # Test 3: EXIT trap executes on natural shell exit
181
+    output=$(echo 'trap "echo CLEANUP" EXIT; echo done' | $FORTSH_BIN 2>&1)
182
+    if echo "$output" | grep -q "CLEANUP"; then
183
+        pass "P0-3.3: EXIT trap executes on natural shell termination"
184
+    else
185
+        fail "P0-3.3: EXIT trap executes on natural shell termination"
186
+    fi
187
+
188
+    # Test 4: EXIT trap executes only once
189
+    output=$($FORTSH_BIN -c 'trap "echo ONCE" EXIT; exit 0' 2>&1)
190
+    count=$(echo "$output" | grep -c "ONCE")
191
+    if [ "$count" -eq 1 ]; then
192
+        pass "P0-3.4: EXIT trap executes exactly once"
193
+    else
194
+        fail "P0-3.4: EXIT trap executes exactly once" "Executed $count times"
195
+    fi
196
+
197
+    # Test 5: EXIT trap can access variables
198
+    output=$($FORTSH_BIN -c 'VAR=value; trap "echo \$VAR" EXIT; exit 0' 2>&1)
199
+    if echo "$output" | grep -q "value"; then
200
+        pass "P0-3.5: EXIT trap can access shell variables"
201
+    else
202
+        fail "P0-3.5: EXIT trap can access shell variables"
203
+    fi
204
+
205
+    # Test 6: EXIT trap with non-zero exit from command
206
+    $FORTSH_BIN -c 'trap "echo cleanup" EXIT; false' >/dev/null 2>&1
207
+    if [ $? -eq 1 ]; then
208
+        pass "P0-3.6: EXIT trap preserves command failure exit code"
209
+    else
210
+        fail "P0-3.6: EXIT trap preserves command failure exit code"
211
+    fi
212
+}
213
+
214
+# ==============================================================================
215
+# P1: IMPORTANT TESTS (Correctness and error handling)
216
+# ==============================================================================
217
+
218
+test_p1_exit_code_126() {
219
+    section "P1-1: Exit Code 126 (Non-executable File)"
220
+
221
+    # Test 1: Non-executable file returns 126
222
+    touch /tmp/fortsh_test_nonexec
223
+    chmod 644 /tmp/fortsh_test_nonexec
224
+    $FORTSH_BIN -c '/tmp/fortsh_test_nonexec' >/dev/null 2>&1
225
+    exit_code=$?
226
+    rm -f /tmp/fortsh_test_nonexec
227
+
228
+    if [ $exit_code -eq 126 ]; then
229
+        pass "P1-1.1: Non-executable file returns exit code 126"
230
+    else
231
+        fail "P1-1.1: Non-executable file returns exit code 126" "Got exit code $exit_code"
232
+    fi
233
+
234
+    # Test 2: Non-existent file still returns 127
235
+    $FORTSH_BIN -c '/nonexistent/command/path' >/dev/null 2>&1
236
+    if [ $? -eq 127 ]; then
237
+        pass "P1-1.2: Non-existent command returns exit code 127"
238
+    else
239
+        fail "P1-1.2: Non-existent command returns exit code 127" "Got exit code $?"
240
+    fi
241
+
242
+    # Test 3: Executable file returns its own exit code
243
+    echo '#!/bin/sh' > /tmp/fortsh_test_exec
244
+    echo 'exit 5' >> /tmp/fortsh_test_exec
245
+    chmod 755 /tmp/fortsh_test_exec
246
+    $FORTSH_BIN -c '/tmp/fortsh_test_exec' >/dev/null 2>&1
247
+    exit_code=$?
248
+    rm -f /tmp/fortsh_test_exec
249
+
250
+    if [ $exit_code -eq 5 ]; then
251
+        pass "P1-1.3: Executable file returns its own exit code"
252
+    else
253
+        fail "P1-1.3: Executable file returns its own exit code" "Got exit code $exit_code"
254
+    fi
255
+}
256
+
257
+test_p1_set_c_noclobber() {
258
+    section "P1-2: set -C (noclobber option)"
259
+
260
+    # Test 1: set -C prevents overwriting existing files
261
+    echo "original" > /tmp/fortsh_test_noclobber
262
+    $FORTSH_BIN -c 'set -C; echo new > /tmp/fortsh_test_noclobber' >/dev/null 2>&1
263
+    exit_code=$?
264
+    content=$(cat /tmp/fortsh_test_noclobber 2>/dev/null)
265
+    rm -f /tmp/fortsh_test_noclobber
266
+
267
+    if [ $exit_code -ne 0 ] && [ "$content" = "original" ]; then
268
+        pass "P1-2.1: set -C prevents overwriting existing files"
269
+    else
270
+        fail "P1-2.1: set -C prevents overwriting existing files" "File was overwritten"
271
+    fi
272
+
273
+    # Test 2: set -C allows writing to new files
274
+    rm -f /tmp/fortsh_test_noclobber_new
275
+    $FORTSH_BIN -c 'set -C; echo content > /tmp/fortsh_test_noclobber_new' >/dev/null 2>&1
276
+    if [ -f /tmp/fortsh_test_noclobber_new ]; then
277
+        pass "P1-2.2: set -C allows writing to non-existent files"
278
+        rm -f /tmp/fortsh_test_noclobber_new
279
+    else
280
+        fail "P1-2.2: set -C allows writing to non-existent files"
281
+    fi
282
+
283
+    # Test 3: >| forces overwrite even with noclobber
284
+    echo "original" > /tmp/fortsh_test_force
285
+    $FORTSH_BIN -c 'set -C; echo forced >| /tmp/fortsh_test_force' >/dev/null 2>&1
286
+    content=$(cat /tmp/fortsh_test_force 2>/dev/null)
287
+    rm -f /tmp/fortsh_test_force
288
+
289
+    if [ "$content" = "forced" ]; then
290
+        pass "P1-2.3: >| forces overwrite with noclobber set"
291
+    else
292
+        fail "P1-2.3: >| forces overwrite with noclobber set" "Content: $content"
293
+    fi
294
+
295
+    # Test 4: set +C disables noclobber
296
+    echo "original" > /tmp/fortsh_test_disable
297
+    $FORTSH_BIN -c 'set -C; set +C; echo new > /tmp/fortsh_test_disable' >/dev/null 2>&1
298
+    content=$(cat /tmp/fortsh_test_disable 2>/dev/null)
299
+    rm -f /tmp/fortsh_test_disable
300
+
301
+    if [ "$content" = "new" ]; then
302
+        pass "P1-2.4: set +C disables noclobber option"
303
+    else
304
+        fail "P1-2.4: set +C disables noclobber option"
305
+    fi
306
+}
307
+
308
+test_p1_trap_removal() {
309
+    section "P1-3: trap - SIGNAL (Trap Removal)"
310
+
311
+    # Test 1: trap - INT removes INT trap
312
+    output=$($FORTSH_BIN -c 'trap "echo caught" INT; trap - INT; kill -INT $$' 2>&1)
313
+    if ! echo "$output" | grep -q "caught"; then
314
+        pass "P1-3.1: trap - INT removes INT trap"
315
+    else
316
+        fail "P1-3.1: trap - INT removes INT trap" "Trap still executed"
317
+    fi
318
+
319
+    # Test 2: trap - TERM removes TERM trap
320
+    $FORTSH_BIN -c 'trap "echo term" TERM; trap - TERM; exit 0' >/dev/null 2>&1
321
+    if [ $? -eq 0 ]; then
322
+        pass "P1-3.2: trap - TERM removes TERM trap"
323
+    else
324
+        fail "P1-3.2: trap - TERM removes TERM trap"
325
+    fi
326
+
327
+    # Test 3: trap - EXIT removes EXIT trap
328
+    output=$($FORTSH_BIN -c 'trap "echo exit" EXIT; trap - EXIT; exit 0' 2>&1)
329
+    if ! echo "$output" | grep -q "exit"; then
330
+        pass "P1-3.3: trap - EXIT removes EXIT trap"
331
+    else
332
+        fail "P1-3.3: trap - EXIT removes EXIT trap"
333
+    fi
334
+
335
+    # Test 4: Listing traps after removal
336
+    output=$($FORTSH_BIN -c 'trap "echo test" INT; trap - INT; trap' 2>&1)
337
+    if ! echo "$output" | grep -q "INT"; then
338
+        pass "P1-3.4: Removed traps don't appear in trap listing"
339
+    else
340
+        fail "P1-3.4: Removed traps don't appear in trap listing"
341
+    fi
342
+}
343
+
344
+test_p1_set_e_conditionals() {
345
+    section "P1-4: set -e in Conditional Contexts"
346
+
347
+    # Test 1: set -e doesn't exit on false in if condition
348
+    output=$($FORTSH_BIN -c 'set -e; if false; then echo no; else echo YES; fi' 2>&1)
349
+    if echo "$output" | grep -q "YES"; then
350
+        pass "P1-4.1: set -e doesn't exit on false in if condition"
351
+    else
352
+        fail "P1-4.1: set -e doesn't exit on false in if condition"
353
+    fi
354
+
355
+    # Test 2: set -e doesn't exit on false in while condition
356
+    output=$($FORTSH_BIN -c 'set -e; count=0; while false; do count=$((count+1)); done; echo AFTER' 2>&1)
357
+    if echo "$output" | grep -q "AFTER"; then
358
+        pass "P1-4.2: set -e doesn't exit on false in while condition"
359
+    else
360
+        fail "P1-4.2: set -e doesn't exit on false in while condition"
361
+    fi
362
+
363
+    # Test 3: set -e exits on command failure outside conditionals
364
+    $FORTSH_BIN -c 'set -e; false; echo SHOULD_NOT_PRINT' >/dev/null 2>&1
365
+    exit_code=$?
366
+    if [ $exit_code -ne 0 ]; then
367
+        pass "P1-4.3: set -e exits on command failure outside conditionals"
368
+    else
369
+        fail "P1-4.3: set -e exits on command failure outside conditionals"
370
+    fi
371
+
372
+    # Test 4: set -e with && and || operators
373
+    output=$($FORTSH_BIN -c 'set -e; false || echo AFTER_OR' 2>&1)
374
+    if echo "$output" | grep -q "AFTER_OR"; then
375
+        pass "P1-4.4: set -e doesn't exit on false before ||"
376
+    else
377
+        fail "P1-4.4: set -e doesn't exit on false before ||"
378
+    fi
379
+
380
+    # Test 5: set -e in until loop
381
+    output=$($FORTSH_BIN -c 'set -e; count=0; until [ $count -eq 1 ]; do count=1; done; echo DONE' 2>&1)
382
+    if echo "$output" | grep -q "DONE"; then
383
+        pass "P1-4.5: set -e doesn't exit on false in until condition"
384
+    else
385
+        fail "P1-4.5: set -e doesn't exit on false in until condition"
386
+    fi
387
+}
388
+
389
+# ==============================================================================
390
+# P2: NICE-TO-HAVE TESTS (Quality of life features)
391
+# ==============================================================================
392
+
393
+test_p2_background_pid() {
394
+    section "P2-1: \$! (Background Process PID)"
395
+
396
+    # Test 1: $! contains PID of last background job
397
+    output=$($FORTSH_BIN -c 'sleep 0.1 & echo $!' 2>&1)
398
+    if echo "$output" | grep -qE '^[0-9]+$'; then
399
+        pass "P2-1.1: \$! expands to numeric PID"
400
+    else
401
+        fail "P2-1.1: \$! expands to numeric PID" "Got: $output"
402
+    fi
403
+
404
+    # Test 2: $! updates after each background job
405
+    output=$($FORTSH_BIN -c 'sleep 0.1 & pid1=$!; sleep 0.1 & pid2=$!; if [ "$pid1" != "$pid2" ]; then echo DIFFERENT; fi' 2>&1)
406
+    if echo "$output" | grep -q "DIFFERENT"; then
407
+        pass "P2-1.2: \$! updates for each background job"
408
+    else
409
+        fail "P2-1.2: \$! updates for each background job" "Got: [$output]"
410
+    fi
411
+
412
+    # Test 3: $! is empty/zero before any background jobs
413
+    output=$($FORTSH_BIN -c 'echo "|$!|"' 2>&1)
414
+    if echo "$output" | grep -qE '\|[0-9]*\|'; then
415
+        pass "P2-1.3: \$! has valid value before background jobs"
416
+    else
417
+        fail "P2-1.3: \$! has valid value before background jobs"
418
+    fi
419
+}
420
+
421
+test_p2_dot_with_args() {
422
+    section "P2-2: dot (.) Source with Arguments"
423
+
424
+    # Test 1: Source script with positional parameters
425
+    echo 'echo "arg1=$1 arg2=$2"' > /tmp/fortsh_test_source
426
+    output=$($FORTSH_BIN -c '. /tmp/fortsh_test_source hello world' 2>&1)
427
+    rm -f /tmp/fortsh_test_source
428
+
429
+    if echo "$output" | grep -q "arg1=hello arg2=world"; then
430
+        pass "P2-2.1: dot command passes arguments to sourced script"
431
+    else
432
+        fail "P2-2.1: dot command passes arguments to sourced script" "Got: $output"
433
+    fi
434
+
435
+    # Test 2: Source script preserves caller's variables
436
+    echo 'SOURCED_VAR=from_script' > /tmp/fortsh_test_source2
437
+    output=$($FORTSH_BIN -c '. /tmp/fortsh_test_source2; echo $SOURCED_VAR' 2>&1)
438
+    rm -f /tmp/fortsh_test_source2
439
+
440
+    if echo "$output" | grep -q "from_script"; then
441
+        pass "P2-2.2: Sourced script sets variables in caller"
442
+    else
443
+        fail "P2-2.2: Sourced script sets variables in caller"
444
+    fi
445
+
446
+    # Test 3: Source with absolute path
447
+    echo 'echo sourced' > /tmp/fortsh_abs_source
448
+    output=$($FORTSH_BIN -c '. /tmp/fortsh_abs_source' 2>&1)
449
+    rm -f /tmp/fortsh_abs_source
450
+
451
+    if echo "$output" | grep -q "sourced"; then
452
+        pass "P2-2.3: dot command works with absolute paths"
453
+    else
454
+        fail "P2-2.3: dot command works with absolute paths"
455
+    fi
456
+}
457
+
458
+test_p2_readonly_unset() {
459
+    section "P2-3: Readonly Unset Prevention"
460
+
461
+    # Test 1: Cannot unset readonly variable
462
+    output=$($FORTSH_BIN -c 'readonly VAR=test; unset VAR' 2>&1)
463
+    if echo "$output" | grep -qE '(readonly|cannot unset)'; then
464
+        pass "P2-3.1: unset readonly variable produces error"
465
+    else
466
+        fail "P2-3.1: unset readonly variable produces error"
467
+    fi
468
+
469
+    # Test 2: Readonly variable still exists after unset attempt
470
+    output=$($FORTSH_BIN -c 'readonly VAR=value; unset VAR 2>/dev/null; echo $VAR' 2>&1)
471
+    if echo "$output" | grep -q "value"; then
472
+        pass "P2-3.2: Readonly variable survives unset attempt"
473
+    else
474
+        fail "P2-3.2: Readonly variable survives unset attempt"
475
+    fi
476
+
477
+    # Test 3: unset returns non-zero for readonly variables
478
+    $FORTSH_BIN -c 'readonly VAR=x; unset VAR' >/dev/null 2>&1
479
+    if [ $? -ne 0 ]; then
480
+        pass "P2-3.3: unset readonly variable returns non-zero"
481
+    else
482
+        fail "P2-3.3: unset readonly variable returns non-zero"
483
+    fi
484
+
485
+    # Test 4: Can unset non-readonly variables
486
+    output=$($FORTSH_BIN -c 'VAR=test; unset VAR; echo "|$VAR|"' 2>&1)
487
+    if echo "$output" | grep -q "||"; then
488
+        pass "P2-3.4: unset works for non-readonly variables"
489
+    else
490
+        fail "P2-3.4: unset works for non-readonly variables"
491
+    fi
492
+}
493
+
494
+# ==============================================================================
495
+# ADDITIONAL ROBUSTNESS TESTS
496
+# ==============================================================================
497
+
498
+test_additional_builtins() {
499
+    section "BONUS: Additional Builtin Tests"
500
+
501
+    # Test shift builtin
502
+    output=$($FORTSH_BIN -c 'set -- a b c; shift; echo $1' 2>&1)
503
+    if echo "$output" | grep -q "b"; then
504
+        pass "BONUS-1: shift builtin works correctly"
505
+    else
506
+        fail "BONUS-1: shift builtin works correctly"
507
+    fi
508
+
509
+    # Test times builtin
510
+    output=$($FORTSH_BIN -c 'times' 2>&1)
511
+    if echo "$output" | grep -qE '[0-9]'; then
512
+        pass "BONUS-2: times builtin produces output"
513
+    else
514
+        fail "BONUS-2: times builtin produces output"
515
+    fi
516
+
517
+    # Test hash builtin
518
+    output=$($FORTSH_BIN -c 'hash' 2>&1)
519
+    if [ $? -eq 0 ]; then
520
+        pass "BONUS-3: hash builtin executes without error"
521
+    else
522
+        fail "BONUS-3: hash builtin executes without error"
523
+    fi
524
+
525
+    # Test type builtin
526
+    output=$($FORTSH_BIN -c 'type echo' 2>&1)
527
+    if echo "$output" | grep -qiE '(builtin|command)'; then
528
+        pass "BONUS-4: type builtin identifies commands"
529
+    else
530
+        fail "BONUS-4: type builtin identifies commands"
531
+    fi
532
+
533
+    # Test umask builtin
534
+    output=$($FORTSH_BIN -c 'umask' 2>&1)
535
+    if echo "$output" | grep -qE '^[0-9]{4}$'; then
536
+        pass "BONUS-5: umask builtin displays mask"
537
+    else
538
+        fail "BONUS-5: umask builtin displays mask"
539
+    fi
540
+
541
+    # Test getopts builtin
542
+    output=$($FORTSH_BIN -c 'getopts "a:b" opt -a value; echo $opt' 2>&1)
543
+    if echo "$output" | grep -q "a"; then
544
+        pass "BONUS-6: getopts builtin parses options"
545
+    else
546
+        fail "BONUS-6: getopts builtin parses options"
547
+    fi
548
+}
549
+
550
+test_edge_cases() {
551
+    section "BONUS: Edge Cases"
552
+
553
+    # Test empty command
554
+    $FORTSH_BIN -c '' >/dev/null 2>&1
555
+    if [ $? -eq 0 ]; then
556
+        pass "EDGE-1: Empty command succeeds"
557
+    else
558
+        fail "EDGE-1: Empty command succeeds"
559
+    fi
560
+
561
+    # Test semicolon alone
562
+    # POSIX: Semicolon alone is a syntax error (exit code 2), not success
563
+    $FORTSH_BIN -c ';' >/dev/null 2>&1
564
+    if [ $? -eq 2 ]; then
565
+        pass "EDGE-2: Semicolon alone is syntax error"
566
+    else
567
+        fail "EDGE-2: Semicolon alone is syntax error" "Expected exit 2, got $?"
568
+    fi
569
+
570
+    # Test comment handling
571
+    output=$($FORTSH_BIN -c 'echo visible # this is comment' 2>&1)
572
+    if echo "$output" | grep -q "visible" && ! echo "$output" | grep -q "comment"; then
573
+        pass "EDGE-3: Comments are ignored correctly"
574
+    else
575
+        fail "EDGE-3: Comments are ignored correctly"
576
+    fi
577
+
578
+    # Test multiple semicolons
579
+    output=$($FORTSH_BIN -c 'echo a;; echo b' 2>&1)
580
+    if echo "$output" | grep -q "b"; then
581
+        pass "EDGE-4: Multiple semicolons handled"
582
+    else
583
+        fail "EDGE-4: Multiple semicolons handled"
584
+    fi
585
+}
586
+
587
+# ==============================================================================
588
+# MAIN TEST EXECUTION
589
+# ==============================================================================
590
+
591
+main() {
592
+    echo "=========================================="
593
+    echo "POSIX Compliance - Builtin Test Suite"
594
+    echo "=========================================="
595
+    echo "Testing: $FORTSH_BIN"
596
+    echo "Date: $(date)"
597
+    echo ""
598
+
599
+    # Verify fortsh exists
600
+    if [ ! -x "$FORTSH_BIN" ]; then
601
+        echo -e "${RED}ERROR: fortsh binary not found or not executable: $FORTSH_BIN${NC}"
602
+        echo "Please build fortsh or set FORTSH_BIN environment variable"
603
+        exit 1
604
+    fi
605
+
606
+    # Run all test suites
607
+    test_p0_readonly_enforcement
608
+    test_p0_set_u
609
+    test_p0_trap_exit
610
+
611
+    test_p1_exit_code_126
612
+    test_p1_set_c_noclobber
613
+    test_p1_trap_removal
614
+    test_p1_set_e_conditionals
615
+
616
+    test_p2_background_pid
617
+    test_p2_dot_with_args
618
+    test_p2_readonly_unset
619
+
620
+    test_additional_builtins
621
+    test_edge_cases
622
+
623
+    # Print summary
624
+    echo ""
625
+    echo "=========================================="
626
+    echo "TEST SUMMARY ${TEST_PREFIX}"
627
+    echo "=========================================="
628
+    echo -e "Total Tests:  $TOTAL_TESTS"
629
+    echo -e "${GREEN}Passed:       $PASSED_TESTS${NC}"
630
+    echo -e "${RED}Failed:       $FAILED_TESTS${NC}"
631
+
632
+    if [ $FAILED_TESTS -gt 0 ]; then
633
+        echo ""
634
+        echo -e "${RED}Failed tests:${NC}"
635
+        echo -e "$FAILED_TESTS_LIST"
636
+        echo "=========================================="
637
+    fi
638
+
639
+    if [ $FAILED_TESTS -eq 0 ]; then
640
+        echo ""
641
+        echo -e "${GREEN}=========================================="
642
+        echo -e "✓ ALL TESTS PASSED!"
643
+        echo -e "==========================================${NC}"
644
+        exit 0
645
+    else
646
+        echo ""
647
+        echo -e "${RED}=========================================="
648
+        echo -e "✗ SOME TESTS FAILED"
649
+        echo -e "==========================================${NC}"
650
+        echo ""
651
+        echo "Run with VERBOSE=1 for detailed failure information:"
652
+        echo "  VERBOSE=1 $0"
653
+        exit 1
654
+    fi
655
+}
656
+
657
+# Run main function
658
+main "$@"
suites/posix/posix_compliance_charclass.shadded
@@ -0,0 +1,407 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Character Class Test Suite for fortsh
4
+# =====================================
5
+# Tests POSIX bracket expression character classes per IEEE Std 1003.1-2017
6
+# Section 9.3.5 RE Bracket Expression
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-charclass]"
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
+
29
+# Check if fortsh exists
30
+if [ ! -x "$FORTSH_BIN" ]; then
31
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
32
+    printf "Please run 'make' first or set FORTSH_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
+# Create test directory with test files
73
+setup_test_files() {
74
+    TEST_DIR="/tmp/fortsh_charclass_$$"
75
+    mkdir -p "$TEST_DIR"
76
+    cd "$TEST_DIR" || exit 1
77
+
78
+    # Create files for character class testing
79
+    touch "file1.txt" "file2.txt" "file3.txt"
80
+    touch "FileA.txt" "FileB.txt" "FileC.txt"
81
+    touch "data_01.csv" "data_02.csv" "data_99.csv"
82
+    touch "test-file" "test_file" "test.file"
83
+    touch "UPPER.TXT" "lower.txt" "MiXeD.TxT"
84
+    touch "a1b2c3" "xyz789" "ABC123"
85
+    touch "file with space.txt"
86
+    touch ".hidden" ".dotfile"
87
+    touch "special!file" "special@file"
88
+}
89
+
90
+cleanup_test_files() {
91
+    cd /
92
+    rm -rf "$TEST_DIR"
93
+}
94
+
95
+# Trap to ensure cleanup
96
+trap cleanup_test_files EXIT
97
+
98
+setup_test_files
99
+
100
+# =====================================
101
+section "341. POSIX CHARACTER CLASS [:alpha:]"
102
+# =====================================
103
+
104
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [[:alpha:]]*' 2>&1)
105
+# Should match files starting with alphabetic characters
106
+if echo "$result" | grep -q "file1.txt\|FileA.txt"; then
107
+    pass "[:alpha:] matches alphabetic characters"
108
+else
109
+    fail "[:alpha:] matches alphabetic characters" "files starting with letters" "$result"
110
+fi
111
+
112
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && for f in [[:alpha:]]*.txt; do echo "$f"; done | head -1' 2>&1)
113
+if [ -n "$result" ] && [ "$result" != '[[:alpha:]]*.txt' ]; then
114
+    pass "[:alpha:] in loop context"
115
+else
116
+    fail "[:alpha:] in loop context" "matched files" "$result"
117
+fi
118
+
119
+# =====================================
120
+section "342. POSIX CHARACTER CLASS [:digit:]"
121
+# =====================================
122
+
123
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo data_[[:digit:]]*.csv' 2>&1)
124
+if echo "$result" | grep -q "data_0"; then
125
+    pass "[:digit:] matches numeric characters"
126
+else
127
+    fail "[:digit:] matches numeric characters" "data_0*.csv files" "$result"
128
+fi
129
+
130
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo *[[:digit:]][[:digit:]].csv' 2>&1)
131
+if echo "$result" | grep -q "data_"; then
132
+    pass "[:digit:][:digit:] matches two digits"
133
+else
134
+    fail "[:digit:][:digit:] matches two digits" "files ending in two digits" "$result"
135
+fi
136
+
137
+# =====================================
138
+section "343. POSIX CHARACTER CLASS [:alnum:]"
139
+# =====================================
140
+
141
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [[:alnum:]]*.txt' 2>&1)
142
+if echo "$result" | grep -q "file"; then
143
+    pass "[:alnum:] matches alphanumeric characters"
144
+else
145
+    fail "[:alnum:] matches alphanumeric characters" "alphanumeric files" "$result"
146
+fi
147
+
148
+# =====================================
149
+section "344. POSIX CHARACTER CLASS [:upper:]"
150
+# =====================================
151
+
152
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [[:upper:]]*.TXT' 2>&1)
153
+if echo "$result" | grep -q "UPPER.TXT\|File"; then
154
+    pass "[:upper:] matches uppercase characters"
155
+else
156
+    fail "[:upper:] matches uppercase characters" "uppercase files" "$result"
157
+fi
158
+
159
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [[:upper:]][[:upper:]][[:upper:]]*.TXT' 2>&1)
160
+if echo "$result" | grep -q "UPPER.TXT\|ABC"; then
161
+    pass "[:upper:] repeated matches multiple uppercase"
162
+else
163
+    fail "[:upper:] repeated matches multiple uppercase" "all-caps files" "$result"
164
+fi
165
+
166
+# =====================================
167
+section "345. POSIX CHARACTER CLASS [:lower:]"
168
+# =====================================
169
+
170
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [[:lower:]]*.txt' 2>&1)
171
+if echo "$result" | grep -q "file\|lower"; then
172
+    pass "[:lower:] matches lowercase characters"
173
+else
174
+    fail "[:lower:] matches lowercase characters" "lowercase files" "$result"
175
+fi
176
+
177
+# =====================================
178
+section "346. POSIX CHARACTER CLASS [:space:]"
179
+# =====================================
180
+
181
+# [:space:] in variable content
182
+result=$("$FORTSH_BIN" -c 'x="hello world"; case "$x" in *[[:space:]]*) echo "has space";; esac' 2>&1)
183
+if [ "$result" = "has space" ]; then
184
+    pass "[:space:] matches space in case pattern"
185
+else
186
+    fail "[:space:] matches space in case pattern" "has space" "$result"
187
+fi
188
+
189
+# =====================================
190
+section "347. POSIX CHARACTER CLASS [:blank:]"
191
+# =====================================
192
+
193
+# [:blank:] matches space and tab only
194
+result=$("$FORTSH_BIN" -c 'x="a	b"; case "$x" in *[[:blank:]]*) echo "has blank";; esac' 2>&1)
195
+if [ "$result" = "has blank" ]; then
196
+    pass "[:blank:] matches tab character"
197
+else
198
+    fail "[:blank:] matches tab character" "has blank" "$result"
199
+fi
200
+
201
+# =====================================
202
+section "348. POSIX CHARACTER CLASS [:xdigit:]"
203
+# =====================================
204
+
205
+result=$("$FORTSH_BIN" -c 'case "a1b2c3" in [[:xdigit:]]*) echo "starts with hex";; esac' 2>&1)
206
+if [ "$result" = "starts with hex" ]; then
207
+    pass "[:xdigit:] matches hexadecimal digits"
208
+else
209
+    fail "[:xdigit:] matches hexadecimal digits" "starts with hex" "$result"
210
+fi
211
+
212
+result=$("$FORTSH_BIN" -c 'case "DEADBEEF" in [[:xdigit:]]*) echo "valid hex";; esac' 2>&1)
213
+if [ "$result" = "valid hex" ]; then
214
+    pass "[:xdigit:] matches uppercase hex"
215
+else
216
+    fail "[:xdigit:] matches uppercase hex" "valid hex" "$result"
217
+fi
218
+
219
+# =====================================
220
+section "349. POSIX CHARACTER CLASS [:punct:]"
221
+# =====================================
222
+
223
+result=$("$FORTSH_BIN" -c 'case "hello!" in *[[:punct:]]) echo "ends with punct";; esac' 2>&1)
224
+if [ "$result" = "ends with punct" ]; then
225
+    pass "[:punct:] matches punctuation"
226
+else
227
+    fail "[:punct:] matches punctuation" "ends with punct" "$result"
228
+fi
229
+
230
+result=$("$FORTSH_BIN" -c 'case "@#$%" in [[:punct:]]*) echo "starts with punct";; esac' 2>&1)
231
+if [ "$result" = "starts with punct" ]; then
232
+    pass "[:punct:] matches special characters"
233
+else
234
+    fail "[:punct:] matches special characters" "starts with punct" "$result"
235
+fi
236
+
237
+# =====================================
238
+section "350. POSIX CHARACTER CLASS [:print:]"
239
+# =====================================
240
+
241
+result=$("$FORTSH_BIN" -c 'case "hello" in [[:print:]]*) echo "printable";; esac' 2>&1)
242
+if [ "$result" = "printable" ]; then
243
+    pass "[:print:] matches printable characters"
244
+else
245
+    fail "[:print:] matches printable characters" "printable" "$result"
246
+fi
247
+
248
+# =====================================
249
+section "351. POSIX CHARACTER CLASS [:graph:]"
250
+# =====================================
251
+
252
+result=$("$FORTSH_BIN" -c 'case "abc123" in [[:graph:]]*) echo "graphical";; esac' 2>&1)
253
+if [ "$result" = "graphical" ]; then
254
+    pass "[:graph:] matches graphical characters"
255
+else
256
+    fail "[:graph:] matches graphical characters" "graphical" "$result"
257
+fi
258
+
259
+# =====================================
260
+section "352. POSIX BRACKET NEGATION [^...] and [!...]"
261
+# =====================================
262
+
263
+# Note: POSIX uses [!...] for negation, [^...] is bash extension
264
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [!A-Z]*.txt' 2>&1)
265
+if echo "$result" | grep -q "file\|lower"; then
266
+    pass "[!...] negation matches non-uppercase (POSIX standard)"
267
+else
268
+    fail "[!...] negation matches non-uppercase (POSIX standard)" "lowercase files" "$result"
269
+fi
270
+
271
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [!0-9]*.txt' 2>&1)
272
+if echo "$result" | grep -q "file\|File"; then
273
+    pass "[!...] negation matches non-digits"
274
+else
275
+    fail "[!...] negation matches non-digits" "non-digit files" "$result"
276
+fi
277
+
278
+# =====================================
279
+section "353. POSIX RANGE EXPRESSIONS"
280
+# =====================================
281
+
282
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo file[1-3].txt' 2>&1)
283
+if echo "$result" | grep -q "file1.txt\|file2.txt\|file3.txt"; then
284
+    pass "[1-3] range matches digits 1-3"
285
+else
286
+    fail "[1-3] range matches digits 1-3" "file1-3.txt" "$result"
287
+fi
288
+
289
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo File[A-C].txt' 2>&1)
290
+if echo "$result" | grep -q "FileA.txt\|FileB.txt\|FileC.txt"; then
291
+    pass "[A-C] range matches uppercase A-C"
292
+else
293
+    fail "[A-C] range matches uppercase A-C" "FileA-C.txt" "$result"
294
+fi
295
+
296
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [a-z]*.txt' 2>&1)
297
+if echo "$result" | grep -q "file\|lower"; then
298
+    pass "[a-z] range matches lowercase"
299
+else
300
+    fail "[a-z] range matches lowercase" "lowercase files" "$result"
301
+fi
302
+
303
+# =====================================
304
+section "354. COMBINED CHARACTER CLASSES"
305
+# =====================================
306
+
307
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [[:alpha:][:digit:]]*.txt' 2>&1)
308
+if echo "$result" | grep -q "file\|File"; then
309
+    pass "[[:alpha:][:digit:]] combined classes"
310
+else
311
+    fail "[[:alpha:][:digit:]] combined classes" "alphanumeric start" "$result"
312
+fi
313
+
314
+result=$("$FORTSH_BIN" -c 'case "test123" in [[:alpha:]][[:alpha:]][[:alpha:]][[:alpha:]][[:digit:]][[:digit:]][[:digit:]]) echo "matched";; esac' 2>&1)
315
+if [ "$result" = "matched" ]; then
316
+    pass "Combined classes in exact pattern"
317
+else
318
+    fail "Combined classes in exact pattern" "matched" "$result"
319
+fi
320
+
321
+# =====================================
322
+section "355. CHARACTER CLASS IN CASE STATEMENTS"
323
+# =====================================
324
+
325
+result=$("$FORTSH_BIN" -c '
326
+for word in hello WORLD 123 test!; do
327
+    case "$word" in
328
+        [[:upper:]]*) echo "$word: upper" ;;
329
+        [[:lower:]]*) echo "$word: lower" ;;
330
+        [[:digit:]]*) echo "$word: digit" ;;
331
+        *) echo "$word: other" ;;
332
+    esac
333
+done
334
+' 2>&1)
335
+if echo "$result" | grep -q "hello: lower" && echo "$result" | grep -q "WORLD: upper" && echo "$result" | grep -q "123: digit"; then
336
+    pass "Character classes in case statement"
337
+else
338
+    fail "Character classes in case statement" "categorized output" "$result"
339
+fi
340
+
341
+# =====================================
342
+section "356. CHARACTER CLASS EDGE CASES"
343
+# =====================================
344
+
345
+# Literal hyphen at start
346
+result=$("$FORTSH_BIN" -c 'case "-test" in [-a]*) echo "matched";; esac' 2>&1)
347
+if [ "$result" = "matched" ]; then
348
+    pass "Literal hyphen at bracket start"
349
+else
350
+    fail "Literal hyphen at bracket start" "matched" "$result"
351
+fi
352
+
353
+# Literal bracket
354
+result=$("$FORTSH_BIN" -c 'case "[test]" in \[*) echo "matched";; esac' 2>&1)
355
+if [ "$result" = "matched" ]; then
356
+    pass "Escaped bracket in pattern"
357
+else
358
+    fail "Escaped bracket in pattern" "matched" "$result"
359
+fi
360
+
361
+# Empty class should not match
362
+result=$("$FORTSH_BIN" -c 'case "test" in []) echo "empty";; *) echo "star";; esac' 2>&1)
363
+if [ "$result" = "star" ]; then
364
+    pass "Empty bracket expression fallthrough"
365
+else
366
+    skip "Empty bracket expression fallthrough" "implementation varies"
367
+fi
368
+
369
+# =====================================
370
+section "357. CHARACTER CLASS WITH GLOB PATTERNS"
371
+# =====================================
372
+
373
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo *[[:digit:]].*' 2>&1)
374
+if echo "$result" | grep -q "data_0\|file"; then
375
+    pass "Character class with glob wildcards"
376
+else
377
+    fail "Character class with glob wildcards" "digit-containing files" "$result"
378
+fi
379
+
380
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && echo [[:alpha:]]?[[:alpha:]]?[[:alpha:]]*.txt' 2>&1)
381
+if echo "$result" | grep -q "file\|File\|lower"; then
382
+    pass "Character class with ? wildcards"
383
+else
384
+    fail "Character class with ? wildcards" "pattern matched files" "$result"
385
+fi
386
+
387
+# =====================================
388
+# Summary
389
+# =====================================
390
+printf "\n"
391
+printf "${BLUE}==========================================\n"
392
+printf "POSIX Character Class Test Summary\n"
393
+printf "==========================================${NC}\n"
394
+printf "Passed:  ${GREEN}%d${NC}\n" "$PASSED"
395
+printf "Failed:  ${RED}%d${NC}\n" "$FAILED"
396
+printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
397
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
398
+
399
+if [ -n "$FAILED_TESTS_LIST" ]; then
400
+    printf "\n${RED}Failed tests:${NC}\n"
401
+    printf "%b" "$FAILED_TESTS_LIST"
402
+fi
403
+
404
+if [ "$FAILED" -gt 0 ]; then
405
+    exit 1
406
+fi
407
+exit 0
suites/posix/posix_compliance_control.shadded
1326 lines changed — click to load
@@ -0,0 +1,1326 @@
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
suites/posix/posix_compliance_coverage.shadded
@@ -0,0 +1,480 @@
1
+#!/usr/bin/env bash
2
+# POSIX Compliance Coverage Tests - Filling Identified Gaps
3
+# These tests cover edge cases and behaviors not covered by other test suites
4
+
5
+FORTSH_BIN="${FORTSH_BIN:-./bin/fortsh}"
6
+BASH_REF="${BASH_REF:-bash}"
7
+
8
+# Colors
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-coverage]"
17
+CURRENT_SECTION=""
18
+TEST_NUM=0
19
+
20
+PASSED=0
21
+FAILED=0
22
+SKIPPED=0
23
+FAILED_TESTS_LIST=""
24
+
25
+pass() {
26
+    TEST_NUM=$((TEST_NUM + 1))
27
+    printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
28
+    PASSED=$((PASSED + 1))
29
+}
30
+
31
+fail() {
32
+    TEST_NUM=$((TEST_NUM + 1))
33
+    TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
34
+    printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
35
+    FAILED_TESTS_LIST="${FAILED_TESTS_LIST}  ${TEST_ID}: $1\n"
36
+    if [ -n "$2" ]; then
37
+        printf "  expected: %s\n" "$2"
38
+    fi
39
+    if [ -n "$3" ]; then
40
+        printf "  got:      %s\n" "$3"
41
+    fi
42
+    FAILED=$((FAILED + 1))
43
+}
44
+
45
+skip() {
46
+    TEST_NUM=$((TEST_NUM + 1))
47
+    printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
48
+    SKIPPED=$((SKIPPED + 1))
49
+}
50
+
51
+section() {
52
+    # Extract section number from header like "200. ARITHMETIC EDGE CASES"
53
+    CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
54
+    TEST_NUM=0
55
+    printf "\n${BLUE}==========================================\n"
56
+    printf "%s\n" "$1"
57
+    printf "==========================================${NC}\n"
58
+}
59
+
60
+# Normalize output by stripping shell name prefix
61
+normalize_output() {
62
+    sed -e 's|^[^ ]*bash: |sh: |' -e 's|^[^ ]*fortsh: |sh: |' -e 's/line [0-9]*: //'
63
+}
64
+
65
+# Compare output between sh and fortsh
66
+compare_posix_output() {
67
+    test_name="$1"
68
+    test_cmd="$2"
69
+
70
+    posix_output=$(FORTSH_RC_FILE=/dev/null "$BASH_REF" -c "$test_cmd" 2>&1 | normalize_output)
71
+    posix_exit=$?
72
+
73
+    fortsh_output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1 | normalize_output)
74
+    fortsh_exit=$?
75
+
76
+    if [ "$posix_output" = "$fortsh_output" ] && [ "$posix_exit" = "$fortsh_exit" ]; then
77
+        pass "$test_name"
78
+    else
79
+        fail "$test_name" "$posix_output" "$fortsh_output"
80
+    fi
81
+}
82
+
83
+# Normalize shell error messages by stripping shell name and "line N: " prefix
84
+# POSIX doesn't mandate error message format, so we normalize for comparison
85
+normalize_error() {
86
+    echo "$1" | sed -e 's|^[^ ]*bash: |sh: |g' -e 's|[^ ]*bash: |sh: |g' -e 's|^[^ ]*fortsh: |sh: |g' -e 's|[^ ]*fortsh: |sh: |g' -e 's/line [0-9]*: //g' -e 's/-c: //g'
87
+}
88
+
89
+# Compare error output, normalizing line number differences
90
+compare_posix_error() {
91
+    test_name="$1"
92
+    test_cmd="$2"
93
+
94
+    posix_output=$(FORTSH_RC_FILE=/dev/null "$BASH_REF" -c "$test_cmd" 2>&1)
95
+    posix_exit=$?
96
+
97
+    fortsh_output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1)
98
+    fortsh_exit=$?
99
+
100
+    posix_norm=$(normalize_error "$posix_output")
101
+    fortsh_norm=$(normalize_error "$fortsh_output")
102
+
103
+    if [ "$posix_norm" = "$fortsh_norm" ] && [ "$posix_exit" = "$fortsh_exit" ]; then
104
+        pass "$test_name"
105
+    else
106
+        fail "$test_name" "$posix_output" "$fortsh_output"
107
+    fi
108
+}
109
+
110
+# Test that fortsh accepts without error
111
+test_accepts() {
112
+    test_name="$1"
113
+    test_cmd="$2"
114
+
115
+    if FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" >/dev/null 2>&1; then
116
+        pass "$test_name"
117
+    else
118
+        fail "$test_name" "should succeed" "failed or not implemented"
119
+    fi
120
+}
121
+
122
+# Test that command fails
123
+test_fails() {
124
+    test_name="$1"
125
+    test_cmd="$2"
126
+
127
+    if ! FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" >/dev/null 2>&1; then
128
+        pass "$test_name"
129
+    else
130
+        fail "$test_name" "should fail" "succeeded unexpectedly"
131
+    fi
132
+}
133
+
134
+# =====================================
135
+# ARITHMETIC EDGE CASES
136
+# =====================================
137
+
138
+section "200. ARITHMETIC - OCTAL AND HEX LITERALS"
139
+
140
+compare_posix_output "octal literal" 'echo $((010))'
141
+compare_posix_output "hex literal lowercase" 'echo $((0xff))'
142
+compare_posix_output "hex literal uppercase" 'echo $((0xFF))'
143
+compare_posix_output "octal in expression" 'echo $((010 + 1))'
144
+compare_posix_output "hex in expression" 'echo $((0x10 + 1))'
145
+compare_posix_output "mixed bases" 'echo $((0x10 + 010 + 10))'
146
+
147
+section "201. ARITHMETIC - NEGATIVE NUMBERS"
148
+
149
+compare_posix_output "negative literal" 'echo $((-5))'
150
+compare_posix_output "negative in addition" 'echo $((10 + -3))'
151
+compare_posix_output "negative in subtraction" 'echo $((5 - -3))'
152
+compare_posix_output "double negative" 'echo $((--5))'
153
+compare_posix_output "negative multiplication" 'echo $((-3 * -4))'
154
+compare_posix_output "negative division" 'echo $((-10 / 3))'
155
+compare_posix_output "negative modulo" 'echo $((-10 % 3))'
156
+
157
+section "202. ARITHMETIC - NESTED EXPRESSIONS"
158
+
159
+compare_posix_output "nested arithmetic" 'echo $((1 + $((2 + 3))))'
160
+compare_posix_output "deeply nested" 'echo $((1 + $((2 + $((3 + 4))))))'
161
+compare_posix_output "nested with vars" 'a=5; echo $((a + $((a * 2))))'
162
+
163
+section "203. ARITHMETIC - COMMA OPERATOR"
164
+
165
+compare_posix_output "comma operator" 'echo $((1, 2, 3))'
166
+compare_posix_output "comma with assignment" 'echo $((a=1, b=2, a+b))'
167
+compare_posix_output "comma side effects" 'a=0; echo $((a=1, a=a+1, a))'
168
+
169
+# =====================================
170
+# WORD SPLITTING EDGE CASES
171
+# =====================================
172
+
173
+section "210. WORD SPLITTING - EMPTY IFS"
174
+
175
+compare_posix_output "empty IFS no split" 'IFS=""; var="a b c"; set -- $var; echo $#'
176
+compare_posix_output "empty IFS preserves spaces" 'IFS=""; var="a  b"; set -- $var; echo "$1"'
177
+
178
+section "211. WORD SPLITTING - IFS VARIATIONS"
179
+
180
+compare_posix_output "IFS whitespace only" 'IFS=" "; var="a  b"; set -- $var; echo $#'
181
+compare_posix_output "IFS non-whitespace" 'IFS=":"; var="a:b:c"; set -- $var; echo $#'
182
+compare_posix_output "IFS mixed" 'IFS=" :"; var="a : b"; set -- $var; echo $#'
183
+compare_posix_output "IFS tab" 'IFS="	"; var="a	b	c"; set -- $var; echo $#'
184
+
185
+section "212. WORD SPLITTING - $@ VS $*"
186
+
187
+compare_posix_output "$* unquoted" 'set -- "a b" "c d"; for x in $*; do echo "[$x]"; done'
188
+compare_posix_output "$@ unquoted" 'set -- "a b" "c d"; for x in $@; do echo "[$x]"; done'
189
+compare_posix_output "$* quoted" 'set -- "a b" "c d"; for x in "$*"; do echo "[$x]"; done'
190
+compare_posix_output "$@ quoted" 'set -- "a b" "c d"; for x in "$@"; do echo "[$x]"; done'
191
+compare_posix_output "$* with IFS" 'IFS=:; set -- a b c; echo "$*"'
192
+compare_posix_output "$@ with IFS" 'IFS=:; set -- a b c; echo "$@"'
193
+
194
+# =====================================
195
+# PARAMETER EXPANSION EDGE CASES
196
+# =====================================
197
+
198
+section "220. PARAMETER EXPANSION - EMPTY VS UNSET"
199
+
200
+compare_posix_output ":+ empty var" 'var=""; echo "${var:+set}"'
201
+compare_posix_output ":+ unset var" 'unset var; echo "${var:+set}"'
202
+compare_posix_output "+ empty var" 'var=""; echo "${var+set}"'
203
+compare_posix_output "+ unset var" 'unset var; echo "${var+set}"'
204
+compare_posix_output ":- empty var" 'var=""; echo "${var:-default}"'
205
+compare_posix_output "- empty var" 'var=""; echo "${var-default}"'
206
+
207
+section "221. PARAMETER EXPANSION - ASSIGNMENT FORMS"
208
+
209
+compare_posix_output ":= assigns when empty" 'unset var; echo "${var:=default}"; echo "$var"'
210
+compare_posix_output "= assigns when unset" 'unset var; echo "${var=default}"; echo "$var"'
211
+compare_posix_output ":= with empty" 'var=""; echo "${var:=default}"; echo "$var"'
212
+compare_posix_output "= with empty" 'var=""; echo "${var=default}"; echo "$var"'
213
+
214
+section "222. PARAMETER EXPANSION - PATTERN IN EXPANSION"
215
+
216
+compare_posix_output "nested pattern removal" 'suffix=.txt; file=test.txt; echo "${file%$suffix}"'
217
+compare_posix_output "var in pattern" 'pat=t; var=test; echo "${var#$pat}"'
218
+compare_posix_output "var in suffix pattern" 'pat=st; var=test; echo "${var%$pat}"'
219
+
220
+# =====================================
221
+# SIGNAL AND TRAP EDGE CASES
222
+# =====================================
223
+
224
+section "230. TRAP - IGNORE VS RESET"
225
+
226
+# Test trap behavior by checking patterns (bash versions may differ in exact output format)
227
+test_trap_behavior() {
228
+    test_name="$1"
229
+    test_cmd="$2"
230
+    expected_pattern="$3"
231
+
232
+    output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1)
233
+
234
+    if echo "$output" | grep -qE "$expected_pattern"; then
235
+        pass "$test_name"
236
+    else
237
+        fail "$test_name" "expected pattern: $expected_pattern" "got: $output"
238
+    fi
239
+}
240
+
241
+# trap "" INT sets ignore, trap shows it, trap - INT resets, trap shows nothing for INT
242
+test_trap_behavior "trap ignore" 'trap "" INT; trap; trap - INT; trap' "trap.*INT"
243
+test_trap_behavior "trap reset" 'trap "echo caught" INT; trap - INT; trap' "^$"
244
+compare_posix_output "trap empty string" 'trap "" EXIT; echo done'
245
+
246
+section "231. TRAP - IN SUBSHELLS"
247
+
248
+# Non-ignored traps should show in parent but subshell inherits parent's trap display
249
+# The EXIT trap runs when done, printing "trap"
250
+test_trap_behavior "trap not inherited" 'trap "echo trap" EXIT; (trap); echo done' "done"
251
+# Ignored traps ARE inherited by subshells
252
+test_trap_behavior "ignore trap inherited" 'trap "" INT; (trap)' "trap.*INT"
253
+
254
+section "232. TRAP - MULTIPLE SIGNALS"
255
+
256
+compare_posix_output "trap multiple" 'trap "echo exit" EXIT; trap "echo err" ERR 2>/dev/null || true; echo done'
257
+
258
+# =====================================
259
+# ERROR HANDLING EDGE CASES
260
+# =====================================
261
+
262
+section "240. SET -e EDGE CASES"
263
+
264
+compare_posix_output "set -e with &&" 'set -e; false && true; echo reached'
265
+compare_posix_output "set -e with ||" 'set -e; false || true; echo reached'
266
+compare_posix_output "set -e with !" 'set -e; ! false; echo reached'
267
+compare_posix_output "set -e in if condition" 'set -e; if false; then echo no; fi; echo reached'
268
+compare_posix_output "set -e in while condition" 'set -e; while false; do echo no; done; echo reached'
269
+
270
+section "241. SET -e IN SUBSHELLS"
271
+
272
+compare_posix_output "set -e inherited" 'set -e; (false; echo no) || echo caught'
273
+compare_posix_output "set -e in command sub" 'set -e; x=$(false; echo yes); echo "x=$x"'
274
+
275
+section "242. COMMAND NOT FOUND"
276
+
277
+# Test exit status for command not found (should be 127)
278
+test_cmd='nonexistent_command_12345 2>/dev/null; echo $?'
279
+compare_posix_output "command not found status" "$test_cmd"
280
+
281
+# =====================================
282
+# FUNCTION EDGE CASES
283
+# =====================================
284
+
285
+section "250. FUNCTION - BUILTIN SHADOWING"
286
+
287
+compare_posix_output "function shadows builtin" 'echo() { printf "custom: %s\n" "$1"; }; echo test; unset -f echo'
288
+compare_posix_output "function named cd" 'cd() { echo "custom cd"; }; cd /tmp; unset -f cd'
289
+
290
+section "251. FUNCTION - REDEFINITION"
291
+
292
+compare_posix_output "redefine function" 'f() { echo v1; }; f; f() { echo v2; }; f'
293
+
294
+section "252. FUNCTION - INDIRECT RECURSION"
295
+
296
+compare_posix_output "mutual recursion" 'a() { [ $1 -le 0 ] && echo done || b $(($1-1)); }; b() { a $1; }; a 3'
297
+
298
+section "253. FUNCTION - RETURN EDGE CASES"
299
+
300
+compare_posix_output "return outside function" '(return 5 2>/dev/null); echo $?'
301
+compare_posix_output "return in sourced file" 'echo "return 42" > /tmp/ret.sh; . /tmp/ret.sh; echo $?; rm /tmp/ret.sh'
302
+
303
+# =====================================
304
+# REDIRECTION EDGE CASES
305
+# =====================================
306
+
307
+section "260. REDIRECTION - CLOSED FD OPERATIONS"
308
+
309
+compare_posix_error "write to closed fd" 'exec 3>&-; echo test >&3 2>&1 | head -1'
310
+compare_posix_error "read from closed fd" 'exec 3<&-; cat <&3 2>&1 | head -1'
311
+
312
+section "261. REDIRECTION - MULTIPLE HEREDOCS"
313
+
314
+compare_posix_output "two heredocs" 'cat <<EOF1; cat <<EOF2
315
+first
316
+EOF1
317
+second
318
+EOF2'
319
+
320
+section "262. REDIRECTION - NOCLOBBER WITH APPEND"
321
+
322
+compare_posix_output "noclobber allows append" 'set -C; echo a > /tmp/nc_test; echo b >> /tmp/nc_test; cat /tmp/nc_test; rm /tmp/nc_test'
323
+
324
+# =====================================
325
+# PIPELINE EDGE CASES
326
+# =====================================
327
+
328
+section "270. PIPELINE - BUILTIN ONLY"
329
+
330
+compare_posix_output "pipeline all builtins" 'echo test | { read x; echo "got: $x"; }'
331
+compare_posix_output "pipeline with :" ': | echo piped'
332
+
333
+section "271. PIPELINE - LONG CHAINS"
334
+
335
+compare_posix_output "5-stage pipeline" 'echo test | cat | cat | cat | cat | cat'
336
+# Pipeline timing can produce non-deterministic pipe errors — filter them out
337
+TEST_NUM=$((TEST_NUM + 1))
338
+_pf_expected=$(FORTSH_RC_FILE=/dev/null "$BASH_REF" -c 'echo ok | false | cat; echo $?' 2>&1 | grep -v 'Broken pipe' | normalize_output)
339
+_pf_actual=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c 'echo ok | false | cat; echo $?' 2>&1 | grep -v 'Broken pipe' | normalize_output)
340
+if [ "$_pf_expected" = "$_pf_actual" ]; then pass "pipeline with failures"
341
+else fail "pipeline with failures" "$_pf_expected" "$_pf_actual"; fi
342
+
343
+# =====================================
344
+# ALIAS EDGE CASES
345
+# =====================================
346
+
347
+section "280. ALIAS - SPECIAL CASES"
348
+
349
+compare_posix_error "alias with quotes" "alias greet='echo hello'; greet; unalias greet"
350
+compare_posix_error "alias with semicolon" "alias both='echo a; echo b'; both; unalias both"
351
+compare_posix_output "alias -p format" 'alias foo=bar; alias -p | grep foo; unalias foo'
352
+
353
+section "281. ALIAS - EXPANSION CONTEXT"
354
+
355
+# Aliases don't expand in non-interactive mode - both bash and fortsh should fail
356
+# Test that we get "command not found" error (exact format varies by bash version)
357
+test_alias_behavior() {
358
+    test_name="$1"
359
+    test_cmd="$2"
360
+    expected_pattern="$3"
361
+
362
+    output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1)
363
+
364
+    if echo "$output" | grep -qiE "$expected_pattern"; then
365
+        pass "$test_name"
366
+    else
367
+        fail "$test_name" "expected pattern: $expected_pattern" "got: $output"
368
+    fi
369
+}
370
+
371
+test_alias_behavior "alias in function" "alias e=echo; f() { e test; }; f; unalias e" "command not found|not found"
372
+
373
+# =====================================
374
+# DOT (SOURCE) EDGE CASES
375
+# =====================================
376
+
377
+section "290. DOT - PATH HANDLING"
378
+
379
+compare_posix_output "dot with arguments" 'echo "echo args: \$1 \$2" > /tmp/src.sh; . /tmp/src.sh a b; rm /tmp/src.sh'
380
+compare_posix_output "dot return value" 'echo "false" > /tmp/src.sh; . /tmp/src.sh; echo $?; rm /tmp/src.sh'
381
+compare_posix_output "dot modifies vars" 'echo "X=modified" > /tmp/src.sh; X=original; . /tmp/src.sh; echo $X; rm /tmp/src.sh'
382
+
383
+# =====================================
384
+# SPECIAL BUILTIN EDGE CASES
385
+# =====================================
386
+
387
+section "300. SPECIAL BUILTINS - ERROR BEHAVIOR"
388
+
389
+compare_posix_output "break outside loop" '(break 2>/dev/null); echo $?'
390
+compare_posix_output "continue outside loop" '(continue 2>/dev/null); echo $?'
391
+compare_posix_output "colon with redirect" ': > /tmp/colon_test; test -f /tmp/colon_test && echo exists; rm /tmp/colon_test'
392
+
393
+# =====================================
394
+# ENVIRONMENT EDGE CASES
395
+# =====================================
396
+
397
+section "310. ENVIRONMENT - PATH HANDLING"
398
+
399
+compare_posix_output "empty PATH component" 'echo "echo found" > /tmp/pathtest; chmod +x /tmp/pathtest; PATH="/tmp:$PATH" pathtest; rm /tmp/pathtest'
400
+
401
+section "311. ENVIRONMENT - EXPORT BEHAVIOR"
402
+
403
+compare_posix_output "export in subshell" 'X=1; (export X=2); echo $X'
404
+compare_posix_output "export preserves" 'export X=1; X=2; sh -c "echo \$X"'
405
+
406
+# =====================================
407
+# GLOB EDGE CASES
408
+# =====================================
409
+
410
+section "320. GLOB - SPECIAL FILENAMES"
411
+
412
+compare_posix_output "glob with spaces" 'mkdir -p /tmp/gt; touch "/tmp/gt/a b"; echo /tmp/gt/*; rm -r /tmp/gt'
413
+compare_posix_output "glob no match" 'echo /nonexistent/path/*.xyz 2>/dev/null'
414
+
415
+section "321. GLOB - CHARACTER CLASSES"
416
+
417
+compare_posix_output "glob [:alpha:]" 'mkdir -p /tmp/gc; touch /tmp/gc/a1 /tmp/gc/1a; echo /tmp/gc/[[:alpha:]]*; rm -r /tmp/gc'
418
+compare_posix_output "glob [:digit:]" 'mkdir -p /tmp/gc; touch /tmp/gc/a1 /tmp/gc/1a; echo /tmp/gc/[[:digit:]]*; rm -r /tmp/gc'
419
+
420
+# =====================================
421
+# QUOTING EDGE CASES
422
+# =====================================
423
+
424
+section "330. QUOTING - COMPLEX NESTING"
425
+
426
+compare_posix_output "quotes in command sub" 'echo "$(echo "inner")"'
427
+compare_posix_output "escaped quotes" 'echo "say \"hello\""'
428
+compare_posix_output "single in double" "echo \"it's\""
429
+compare_posix_output "backslash in single" "echo 'back\\slash'"
430
+
431
+section "331. QUOTING - EMPTY STRINGS"
432
+
433
+compare_posix_output "empty preserves arg" 'set -- "" "a"; echo $#'
434
+compare_posix_output "unquoted empty gone" 'e=""; set -- $e a; echo $#'
435
+
436
+# =====================================
437
+# MISCELLANEOUS EDGE CASES
438
+# =====================================
439
+
440
+section "340. MISC - COMPOUND COMMANDS"
441
+
442
+compare_posix_output "if with compound cond" 'if true && true; then echo yes; fi'
443
+compare_posix_output "while with pipeline cond" 'n=0; while echo $n | grep -q 0 && [ $n -lt 1 ]; do n=$((n+1)); done; echo $n'
444
+
445
+section "341. MISC - COMPLEX COMBINATIONS"
446
+
447
+compare_posix_output "redirect in loop" 'for i in 1 2 3; do echo $i; done > /tmp/loop_out; cat /tmp/loop_out; rm /tmp/loop_out'
448
+compare_posix_output "case with patterns" 'x=abc; case $x in a*) echo match;; esac'
449
+
450
+# =====================================
451
+# SUMMARY
452
+# =====================================
453
+
454
+printf "\n==========================================\n"
455
+printf "COVERAGE GAP TEST RESULTS ${TEST_PREFIX}\n"
456
+printf "==========================================\n"
457
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
458
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
459
+printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
460
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
461
+printf "==========================================\n"
462
+
463
+if [ "$((PASSED + FAILED))" -gt 0 ]; then
464
+    pass_rate=$((PASSED * 100 / (PASSED + FAILED)))
465
+    printf "Pass rate: %d%%\n" "$pass_rate"
466
+fi
467
+
468
+if [ "$FAILED" -gt 0 ]; then
469
+    printf "\n${RED}Failed tests:${NC}\n"
470
+    printf "%b" "$FAILED_TESTS_LIST"
471
+    printf "==========================================\n"
472
+fi
473
+
474
+if [ "$FAILED" -eq 0 ]; then
475
+    printf "${GREEN}ALL COVERAGE GAP TESTS PASSED!${NC} ✓\n"
476
+    exit 0
477
+else
478
+    printf "${RED}SOME TESTS FAILED${NC} ✗\n"
479
+    exit 1
480
+fi
suites/posix/posix_compliance_extended.shadded
@@ -0,0 +1,682 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Extended Test Suite for fortsh
4
+# =====================================
5
+# Comprehensive POSIX compliance testing based on IEEE Std 1003.1-2017
6
+# This extends the basic test suite with additional coverage
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-extended]"
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 "  posix:  %s\n" "$2"
51
+    fi
52
+    if [ -n "$3" ]; then
53
+        printf "  fortsh: %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
+    # Extract section number from header like "26. EXTENDED TEST"
66
+    CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
67
+    TEST_NUM=0
68
+    printf "\n"
69
+    printf "${BLUE}==========================================\n"
70
+    printf "%s\n" "$1"
71
+    printf "==========================================${NC}\n"
72
+}
73
+
74
+# Helper function to run command in both shells and compare
75
+compare_posix_output() {
76
+    test_name="$1"
77
+    command="$2"
78
+    posix_file="/tmp/posix_ext_$$_posix"
79
+    fortsh_file="/tmp/posix_ext_$$_fortsh"
80
+
81
+    # Run in POSIX shell (sh)
82
+    "$BASH_REF" -c "$command" > "$posix_file" 2>&1 || true
83
+
84
+    # Run in fortsh
85
+    "$FORTSH_BIN" -c "$command" > "$fortsh_file" 2>&1 || true
86
+
87
+    # Compare outputs
88
+    if diff -q "$posix_file" "$fortsh_file" > /dev/null 2>&1; then
89
+        pass "$test_name"
90
+    else
91
+        fail "$test_name" "$(cat "$posix_file")" "$(cat "$fortsh_file")"
92
+    fi
93
+
94
+    rm -f "$posix_file" "$fortsh_file"
95
+}
96
+
97
+# Helper function to compare exit codes
98
+compare_posix_exit_code() {
99
+    test_name="$1"
100
+    command="$2"
101
+
102
+    "$BASH_REF" -c "$command" > /dev/null 2>&1
103
+    posix_exit=$?
104
+
105
+    "$FORTSH_BIN" -c "$command" > /dev/null 2>&1
106
+    fortsh_exit=$?
107
+
108
+    if [ "$posix_exit" -eq "$fortsh_exit" ]; then
109
+        pass "$test_name"
110
+    else
111
+        fail "$test_name" "exit=$posix_exit" "exit=$fortsh_exit"
112
+    fi
113
+}
114
+
115
+# Cleanup
116
+cleanup() {
117
+    rm -f /tmp/posix_ext_$$_* 2>/dev/null
118
+    rm -f /tmp/posix_test_* 2>/dev/null
119
+    rm -rf /tmp/posix_test_dir 2>/dev/null
120
+}
121
+trap cleanup EXIT INT TERM
122
+
123
+# Setup test environment
124
+mkdir -p /tmp/posix_test_dir
125
+cd /tmp/posix_test_dir || exit 1
126
+
127
+section "26. EXTENDED TEST COMMAND - FILE TYPE TESTS"
128
+
129
+# Create test files with different properties
130
+touch /tmp/posix_test_regular
131
+chmod 644 /tmp/posix_test_regular
132
+echo "data" > /tmp/posix_test_nonempty
133
+mkdir -p /tmp/posix_test_directory
134
+mkfifo /tmp/posix_test_fifo 2>/dev/null || skip "mkfifo" "not available"
135
+ln -sf /tmp/posix_test_regular /tmp/posix_test_symlink 2>/dev/null
136
+
137
+compare_posix_exit_code "test -e exists" "test -e /tmp/posix_test_regular"
138
+compare_posix_exit_code "test -e nonexistent" "! test -e /tmp/nonexistent_xyz_$$"
139
+compare_posix_exit_code "test -f regular file" "test -f /tmp/posix_test_regular"
140
+compare_posix_exit_code "test -d directory" "test -d /tmp/posix_test_directory"
141
+compare_posix_exit_code "test -s non-empty" "test -s /tmp/posix_test_nonempty"
142
+compare_posix_exit_code "test -s empty" "! test -s /tmp/posix_test_regular"
143
+
144
+# Symlink tests (if supported)
145
+if [ -L /tmp/posix_test_symlink ]; then
146
+    compare_posix_exit_code "test -L symlink" "test -L /tmp/posix_test_symlink"
147
+    compare_posix_exit_code "test -h symlink" "test -h /tmp/posix_test_symlink"
148
+fi
149
+
150
+# FIFO tests (if created successfully)
151
+if [ -p /tmp/posix_test_fifo ]; then
152
+    compare_posix_exit_code "test -p fifo" "test -p /tmp/posix_test_fifo"
153
+fi
154
+
155
+section "27. EXTENDED TEST COMMAND - FILE PERMISSION TESTS"
156
+
157
+# Create files with specific permissions
158
+touch /tmp/posix_test_readable
159
+chmod 644 /tmp/posix_test_readable
160
+touch /tmp/posix_test_writable
161
+chmod 644 /tmp/posix_test_writable
162
+touch /tmp/posix_test_executable
163
+chmod 755 /tmp/posix_test_executable
164
+
165
+compare_posix_exit_code "test -r readable" "test -r /tmp/posix_test_readable"
166
+compare_posix_exit_code "test -w writable" "test -w /tmp/posix_test_writable"
167
+compare_posix_exit_code "test -x executable" "test -x /tmp/posix_test_executable"
168
+compare_posix_exit_code "test ! -x nonexec" "! test -x /tmp/posix_test_readable"
169
+
170
+section "28. EXTENDED TEST COMMAND - FILE COMPARISON"
171
+
172
+# Create files for comparison
173
+echo "old" > /tmp/posix_test_old
174
+sleep 1
175
+echo "new" > /tmp/posix_test_new
176
+ln -f /tmp/posix_test_old /tmp/posix_test_hardlink 2>/dev/null
177
+
178
+compare_posix_exit_code "test -nt newer than" "test /tmp/posix_test_new -nt /tmp/posix_test_old"
179
+compare_posix_exit_code "test -ot older than" "test /tmp/posix_test_old -ot /tmp/posix_test_new"
180
+
181
+# Hard link test (if supported)
182
+if [ -f /tmp/posix_test_hardlink ]; then
183
+    compare_posix_exit_code "test -ef same file" "test /tmp/posix_test_old -ef /tmp/posix_test_hardlink"
184
+fi
185
+
186
+section "29. EXTENDED TEST COMMAND - STRING TESTS"
187
+
188
+compare_posix_exit_code "test -n nonempty string" "test -n 'hello'"
189
+compare_posix_exit_code "test -z empty string" "test -z ''"
190
+compare_posix_exit_code "test string = equal" "test 'abc' = 'abc'"
191
+compare_posix_exit_code "test string != not equal" "test 'abc' != 'xyz'"
192
+compare_posix_exit_code "test unary string" "test 'hello'"
193
+compare_posix_exit_code "test ! negation" "! test -z 'hello'"
194
+
195
+section "30. EXTENDED TEST COMMAND - INTEGER COMPARISON"
196
+
197
+compare_posix_exit_code "test -eq equal" "test 42 -eq 42"
198
+compare_posix_exit_code "test -ne not equal" "test 42 -ne 13"
199
+compare_posix_exit_code "test -gt greater" "test 10 -gt 5"
200
+compare_posix_exit_code "test -ge greater or equal" "test 10 -ge 10"
201
+compare_posix_exit_code "test -lt less" "test 5 -lt 10"
202
+compare_posix_exit_code "test -le less or equal" "test 5 -le 5"
203
+compare_posix_exit_code "test negative numbers" "test -5 -lt 0"
204
+
205
+section "31. EXTENDED TEST COMMAND - LOGICAL OPERATORS"
206
+
207
+compare_posix_exit_code "test -a and" "test 5 -gt 3 -a 10 -gt 8"
208
+compare_posix_exit_code "test -o or" "test 5 -gt 10 -o 10 -gt 8"
209
+compare_posix_exit_code "test ! negation" "! test 5 -gt 10"
210
+compare_posix_exit_code "test ( ) grouping" "test \( 5 -gt 3 \) -a \( 10 -gt 8 \)"
211
+
212
+section "32. EXTENDED PARAMETER EXPANSION - DEFAULT VALUES"
213
+
214
+compare_posix_output "use default unset" 'unset VAR; echo "${VAR-default}"'
215
+compare_posix_output "use default null" 'VAR=; echo "${VAR-default}"'
216
+compare_posix_output "use default null colon" 'VAR=; echo "${VAR:-default}"'
217
+compare_posix_output "use default set" 'VAR=value; echo "${VAR-default}"'
218
+compare_posix_output "assign default unset" 'unset VAR; echo "${VAR=assigned}"; echo $VAR'
219
+compare_posix_output "assign default null colon" 'VAR=; echo "${VAR:=assigned}"; echo $VAR'
220
+
221
+section "33. EXTENDED PARAMETER EXPANSION - ERROR IF UNSET"
222
+
223
+compare_posix_exit_code "error if unset" 'unset VAR; echo "${VAR?error}" 2>/dev/null'
224
+compare_posix_exit_code "error if null colon" 'VAR=; echo "${VAR:?error}" 2>/dev/null'
225
+compare_posix_output "no error if set" 'VAR=value; echo "${VAR?error}"'
226
+
227
+section "34. EXTENDED PARAMETER EXPANSION - ALTERNATIVE VALUE"
228
+
229
+compare_posix_output "alt unset" 'unset VAR; echo "${VAR+alternative}"'
230
+compare_posix_output "alt null" 'VAR=; echo "${VAR+alternative}"'
231
+compare_posix_output "alt null colon" 'VAR=; echo "${VAR:+alternative}"'
232
+compare_posix_output "alt set" 'VAR=value; echo "${VAR+alternative}"'
233
+compare_posix_output "alt set colon" 'VAR=value; echo "${VAR:+alternative}"'
234
+
235
+section "35. EXTENDED PARAMETER EXPANSION - STRING LENGTH"
236
+
237
+compare_posix_output "length empty" 'VAR=; echo "${#VAR}"'
238
+compare_posix_output "length short" 'VAR=hi; echo "${#VAR}"'
239
+compare_posix_output "length long" 'VAR="hello world"; echo "${#VAR}"'
240
+compare_posix_output "length special chars" 'VAR="a b c"; echo "${#VAR}"'
241
+
242
+section "36. EXTENDED PARAMETER EXPANSION - PATTERN REMOVAL"
243
+
244
+# Prefix removal
245
+compare_posix_output "prefix # simple" 'VAR=hello; echo "${VAR#hel}"'
246
+compare_posix_output "prefix # nomatch" 'VAR=hello; echo "${VAR#xyz}"'
247
+compare_posix_output "prefix # glob" 'VAR=foo.bar.baz; echo "${VAR#*.}"'
248
+compare_posix_output "prefix ## glob" 'VAR=foo.bar.baz; echo "${VAR##*.}"'
249
+compare_posix_output "prefix # star" 'VAR=/usr/local/bin; echo "${VAR#*/}"'
250
+compare_posix_output "prefix ## star" 'VAR=/usr/local/bin; echo "${VAR##*/}"'
251
+
252
+# Suffix removal
253
+compare_posix_output "suffix % simple" 'VAR=hello; echo "${VAR%lo}"'
254
+compare_posix_output "suffix % nomatch" 'VAR=hello; echo "${VAR%xyz}"'
255
+compare_posix_output "suffix % glob" 'VAR=foo.bar.baz; echo "${VAR%.*}"'
256
+compare_posix_output "suffix %% glob" 'VAR=foo.bar.baz; echo "${VAR%%.*}"'
257
+compare_posix_output "suffix % extension" 'VAR=file.tar.gz; echo "${VAR%.gz}"'
258
+compare_posix_output "suffix %% extension" 'VAR=file.tar.gz; echo "${VAR%%.*}"'
259
+
260
+section "37. ARITHMETIC EXPANSION - BASIC OPERATIONS"
261
+
262
+compare_posix_output "arith addition" 'echo $((5 + 3))'
263
+compare_posix_output "arith subtraction" 'echo $((10 - 4))'
264
+compare_posix_output "arith multiplication" 'echo $((6 * 7))'
265
+compare_posix_output "arith division" 'echo $((20 / 4))'
266
+compare_posix_output "arith modulo" 'echo $((17 % 5))'
267
+compare_posix_output "arith negative" 'echo $((-5 + 10))'
268
+compare_posix_output "arith zero" 'echo $((0 + 0))'
269
+
270
+section "38. ARITHMETIC EXPANSION - PRECEDENCE"
271
+
272
+compare_posix_output "arith precedence mult" 'echo $((2 + 3 * 4))'
273
+compare_posix_output "arith precedence paren" 'echo $(((2 + 3) * 4))'
274
+compare_posix_output "arith precedence div" 'echo $((10 - 8 / 2))'
275
+compare_posix_output "arith precedence complex" 'echo $((2 * 3 + 4 * 5))'
276
+
277
+section "39. ARITHMETIC EXPANSION - VARIABLES"
278
+
279
+compare_posix_output "arith var" 'X=5; echo $((X + 3))'
280
+compare_posix_output "arith var no dollar" 'X=10; Y=20; echo $((X + Y))'
281
+compare_posix_output "arith var assign" 'X=5; Y=$((X * 2)); echo $Y'
282
+compare_posix_output "arith var complex" 'A=3; B=4; echo $((A * A + B * B))'
283
+
284
+section "40. ARITHMETIC EXPANSION - COMPARISON"
285
+
286
+compare_posix_output "arith compare eq true" 'echo $((5 == 5))'
287
+compare_posix_output "arith compare eq false" 'echo $((5 == 3))'
288
+compare_posix_output "arith compare ne true" 'echo $((5 != 3))'
289
+compare_posix_output "arith compare ne false" 'echo $((5 != 5))'
290
+compare_posix_output "arith compare lt" 'echo $((3 < 5))'
291
+compare_posix_output "arith compare le" 'echo $((5 <= 5))'
292
+compare_posix_output "arith compare gt" 'echo $((7 > 5))'
293
+compare_posix_output "arith compare ge" 'echo $((5 >= 5))'
294
+
295
+section "41. ARITHMETIC EXPANSION - LOGICAL"
296
+
297
+compare_posix_output "arith logical and true" 'echo $((1 && 1))'
298
+compare_posix_output "arith logical and false" 'echo $((1 && 0))'
299
+compare_posix_output "arith logical or true" 'echo $((0 || 1))'
300
+compare_posix_output "arith logical or false" 'echo $((0 || 0))'
301
+compare_posix_output "arith logical not true" 'echo $((! 0))'
302
+compare_posix_output "arith logical not false" 'echo $((! 1))'
303
+
304
+section "42. SPECIAL PARAMETERS"
305
+
306
+compare_posix_output "\$\$ process id type" 'echo $$ | grep -c "^[0-9][0-9]*$"'
307
+compare_posix_output "\$- shell flags type" 'echo $- | grep -c "^[a-z]*$"'
308
+compare_posix_output "\$# arg count" 'set -- a b c; echo $#'
309
+compare_posix_output "\$1 first arg" 'set -- first second; echo $1'
310
+compare_posix_output "\$2 second arg" 'set -- first second; echo $2'
311
+compare_posix_output "\$9 ninth arg" 'set -- a b c d e f g h i j; echo $9'
312
+compare_posix_output "\$* all args" 'set -- a b c; echo $*'
313
+compare_posix_output "\$@ all args" 'set -- a b c; echo $@'
314
+
315
+section "43. ADDITIONAL POSIX BUILTINS - CD/PWD"
316
+
317
+compare_posix_output "cd to /tmp" 'cd /tmp && pwd'
318
+compare_posix_output "cd relative" 'cd /tmp && cd .. && pwd | grep -c "^/"'
319
+compare_posix_output "cd $HOME" 'cd && pwd | grep -c "^/"'
320
+
321
+section "44. ADDITIONAL POSIX BUILTINS - UNSET"
322
+
323
+compare_posix_output "unset variable" 'VAR=test; unset VAR; echo ${VAR:-unset}'
324
+compare_posix_output "unset nonexistent" 'unset NONEXISTENT_VAR_XYZ; echo ok'
325
+
326
+section "45. ADDITIONAL POSIX BUILTINS - EVAL"
327
+
328
+compare_posix_output "eval simple" 'CMD="echo hello"; eval $CMD'
329
+compare_posix_output "eval with var" 'X=5; CMD="echo \$X"; eval $CMD'
330
+compare_posix_output "eval complex" 'A=echo; B=test; eval $A $B'
331
+
332
+section "46. ADDITIONAL POSIX BUILTINS - COLON"
333
+
334
+compare_posix_output ": null command" ': ; echo ok'
335
+compare_posix_output ": with args" ': this is ignored; echo ok'
336
+compare_posix_exit_code ": exit status" ':'
337
+
338
+section "47. TILDE EXPANSION"
339
+
340
+compare_posix_output "tilde home" 'echo ~ | grep -c "^/"'
341
+compare_posix_output "tilde in path" 'echo ~/test | grep -c "^/"'
342
+
343
+section "48. FIELD SPLITTING - ADVANCED"
344
+
345
+compare_posix_output "IFS multiple fields" 'IFS=:; VAR="a:b:c:d"; set -- $VAR; echo $# $1 $4'
346
+compare_posix_output "IFS whitespace" 'IFS=" "; VAR="a b c"; set -- $VAR; echo $#'
347
+compare_posix_output "IFS comma" 'IFS=,; VAR="x,y,z"; set -- $VAR; echo $2'
348
+
349
+section "49. BACKGROUND JOBS"
350
+
351
+compare_posix_exit_code "background process" 'sleep 0.1 & wait'
352
+compare_posix_output "background exit" '(exit 0) & wait $!; echo $?'
353
+
354
+section "50. COMMAND GROUPING"
355
+
356
+compare_posix_output "subshell isolation" 'X=1; (X=2; echo $X); echo $X'
357
+compare_posix_output "brace grouping" 'X=1; { X=2; echo $X; }; echo $X'
358
+compare_posix_output "subshell exit" '(exit 5); echo $?'
359
+
360
+section "51. GLOB PATTERN MATCHING"
361
+
362
+# Create temp dir for glob tests
363
+GLOB_DIR="/tmp/posix_glob_test_$$"
364
+mkdir -p "$GLOB_DIR"
365
+touch "$GLOB_DIR/file1.txt" "$GLOB_DIR/file2.txt" "$GLOB_DIR/file3.log"
366
+touch "$GLOB_DIR/abc" "$GLOB_DIR/abd" "$GLOB_DIR/acd"
367
+
368
+compare_posix_output "star matches multiple" 'cd '"$GLOB_DIR"' && echo file*.txt | tr " " "\n" | wc -l'
369
+compare_posix_output "question mark single char" 'cd '"$GLOB_DIR"' && echo ab? | tr " " "\n" | wc -l'
370
+compare_posix_output "bracket range" 'cd '"$GLOB_DIR"' && echo a[bc]d | tr " " "\n" | wc -l'
371
+compare_posix_output "no match literal" 'cd '"$GLOB_DIR"' && echo zzz*.xyz 2>/dev/null || echo "zzz*.xyz"'
372
+
373
+rm -rf "$GLOB_DIR"
374
+
375
+section "52. DOTFILE HANDLING"
376
+
377
+DOT_DIR="/tmp/posix_dot_test_$$"
378
+mkdir -p "$DOT_DIR"
379
+touch "$DOT_DIR/.hidden" "$DOT_DIR/visible"
380
+
381
+compare_posix_output "star no dotfiles" 'cd '"$DOT_DIR"' && echo * | grep -c hidden || echo 0'
382
+compare_posix_output "explicit dot matches" 'cd '"$DOT_DIR"' && ls -d .* 2>/dev/null | grep -c hidden'
383
+
384
+rm -rf "$DOT_DIR"
385
+
386
+section "53. QUOTING IN GLOB"
387
+
388
+QUOTE_DIR="/tmp/posix_quote_glob_$$"
389
+mkdir -p "$QUOTE_DIR"
390
+touch "$QUOTE_DIR/star"
391
+
392
+compare_posix_output "quoted star literal" 'cd '"$QUOTE_DIR"' && echo "*" | grep -c "\\*"'
393
+compare_posix_output "single quote prevents glob" "cd '$QUOTE_DIR' && echo '*' | grep -c '\\*'"
394
+
395
+rm -rf "$QUOTE_DIR"
396
+
397
+section "54. EMPTY AND NULL EXPANSION"
398
+
399
+compare_posix_output "unset var empty" 'unset X; echo "[$X]"'
400
+compare_posix_output "null var empty" 'X=""; echo "[$X]"'
401
+compare_posix_output "unset in arith" 'unset X; echo $((X + 1))'
402
+
403
+section "55. SPECIAL VARIABLES READONLY"
404
+
405
+compare_posix_output "cannot assign to ?" '(eval "?=5" 2>/dev/null); echo ok'
406
+compare_posix_output "cannot assign to $" '(eval "\$=5" 2>/dev/null); echo ok'
407
+
408
+section "56. COMPLEX COMMAND LISTS"
409
+
410
+compare_posix_output "and-or chain" 'true && echo yes || echo no'
411
+compare_posix_output "or-and chain" 'false || echo fallback && echo then'
412
+compare_posix_output "semicolon list" 'echo a; echo b; echo c'
413
+compare_posix_output "mixed operators" 'true && true && echo all || echo none'
414
+
415
+section "57. NESTED SUBSHELLS"
416
+
417
+compare_posix_output "double nesting" '( ( echo deep ) )'
418
+compare_posix_output "triple nesting" '( ( ( echo deeper ) ) )'
419
+compare_posix_output "nested with vars" 'X=1; ( X=2; ( X=3; echo $X ) ); echo $X'
420
+
421
+section "58. BRACE GROUP EDGE CASES"
422
+
423
+compare_posix_output "brace needs space" '{ echo test; }'
424
+compare_posix_output "brace with semicolon" '{ echo a; echo b; }'
425
+compare_posix_output "brace preserves vars" 'X=1; { X=2; }; echo $X'
426
+
427
+section "59. ARITHMETIC BASE LITERALS"
428
+
429
+compare_posix_output "octal 010" 'echo $((010))'
430
+compare_posix_output "hex 0x10" 'echo $((0x10))'
431
+compare_posix_output "hex 0xFF" 'echo $((0xFF))'
432
+compare_posix_output "mixed bases" 'echo $((010 + 0x10 + 10))'
433
+
434
+section "60. STRING LENGTH EDGE CASES"
435
+
436
+compare_posix_output "length empty" 'X=""; echo ${#X}'
437
+compare_posix_output "length one" 'X="a"; echo ${#X}'
438
+compare_posix_output "length with spaces" 'X="a b c"; echo ${#X}'
439
+compare_posix_output "length special chars" 'X="$@#"; echo ${#X}'
440
+
441
+section "61. DEFAULT VALUE EDGE CASES"
442
+
443
+compare_posix_output "default unset" 'unset X; echo ${X:-default}'
444
+compare_posix_output "default null" 'X=""; echo ${X:-default}'
445
+compare_posix_output "default set" 'X="value"; echo ${X:-default}'
446
+compare_posix_output "no-colon unset" 'unset X; echo ${X-default}'
447
+compare_posix_output "no-colon null" 'X=""; echo ${X-default}'
448
+
449
+section "62. PATTERN REMOVAL EDGE CASES"
450
+
451
+compare_posix_output "prefix no match" 'X="hello"; echo ${X#xyz}'
452
+compare_posix_output "suffix no match" 'X="hello"; echo ${X%xyz}'
453
+compare_posix_output "prefix full match" 'X="aaa"; echo ${X##a*}'
454
+compare_posix_output "suffix full match" 'X="aaa"; echo ${X%%*a}'
455
+
456
+section "63. POSITIONAL PARAMS EDGE CASES"
457
+
458
+compare_posix_output "set clears all" 'set -- a b c; set --; echo $#'
459
+compare_posix_output "set replaces" 'set -- a b; set -- x y z; echo $#'
460
+compare_posix_output "shift all" 'set -- a b c; shift 3; echo $#'
461
+
462
+section "64. EXIT STATUS PROPAGATION"
463
+
464
+compare_posix_output "pipeline exit" 'true | true | false; echo $?'
465
+compare_posix_output "subshell exit" '(exit 42); echo $?'
466
+compare_posix_output "brace exit" '{ exit 7; }; echo $?'
467
+compare_posix_output "command sub exit" 'X=$(exit 5); echo $?'
468
+
469
+section "65. WORD SPLITTING IFS VARIANTS"
470
+
471
+compare_posix_output "IFS empty no split" 'IFS=""; X="a b c"; set -- $X; echo $#'
472
+compare_posix_output "IFS colon" 'IFS=":"; X="a:b:c"; set -- $X; echo $#'
473
+compare_posix_output "IFS multiple" 'IFS=":;"; X="a:b;c"; set -- $X; echo $#'
474
+compare_posix_output "IFS whitespace" 'IFS=" "; X="a  b  c"; set -- $X; echo $#'
475
+compare_posix_output "IFS default" 'X="a   b   c"; set -- $X; echo $#'
476
+
477
+section "66. PATHNAME EXPANSION DISABLING"
478
+
479
+compare_posix_output "noglob off" 'touch /tmp/glob_test_a.x /tmp/glob_test_b.x 2>/dev/null; ls /tmp/glob_test_*.x 2>/dev/null | wc -l'
480
+compare_posix_output "noglob on" 'set -f; echo /tmp/glob_test_*.x; set +f'
481
+
482
+section "67. TILDE EXPANSION CONTEXTS"
483
+
484
+compare_posix_output "tilde alone" 'echo ~ | grep -c "^/"'
485
+compare_posix_output "tilde in var" 'X=~; echo $X | grep -c "^/"'
486
+compare_posix_output "tilde quoted" 'echo "~" | grep -c "~"'
487
+compare_posix_output "tilde in path" 'echo ~/. | grep -c "^/"'
488
+
489
+section "68. ASSIGNMENT CONTEXTS"
490
+
491
+compare_posix_output "simple assign" 'X=hello; echo $X'
492
+compare_posix_output "assign with cmd sub" 'X=$(echo test); echo $X'
493
+compare_posix_output "assign with arith" 'X=$((5+3)); echo $X'
494
+compare_posix_output "multiple assign" 'X=1 Y=2 Z=3; echo $X $Y $Z'
495
+compare_posix_output "assign before cmd" 'X=val sh -c "echo \$X"'
496
+
497
+section "69. SPECIAL CHARACTER HANDLING"
498
+
499
+compare_posix_output "escaped newline" 'echo a\
500
+b'
501
+compare_posix_output "escaped dollar" 'echo \$VAR'
502
+compare_posix_output "escaped backslash" 'echo \\\\'
503
+compare_posix_output "escaped quote" 'echo \"quoted\"'
504
+
505
+section "70. WHILE LOOP VARIATIONS"
506
+
507
+compare_posix_output "while with pipe" 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done | wc -l'
508
+compare_posix_output "while false" 'while false; do echo never; done; echo done'
509
+compare_posix_output "while break" 'i=0; while true; do i=$((i+1)); [ $i -ge 3 ] && break; done; echo $i'
510
+compare_posix_output "while continue" 'i=0; while [ $i -lt 5 ]; do i=$((i+1)); [ $i -eq 3 ] && continue; echo $i; done'
511
+
512
+section "71. UNTIL LOOP VARIATIONS"
513
+
514
+compare_posix_output "until basic" 'i=0; until [ $i -ge 3 ]; do echo $i; i=$((i+1)); done'
515
+compare_posix_output "until true" 'until true; do echo never; done; echo done'
516
+compare_posix_output "until with break" 'i=0; until false; do i=$((i+1)); [ $i -ge 3 ] && break; done; echo $i'
517
+
518
+section "72. FOR LOOP WORD LIST"
519
+
520
+compare_posix_output "for with glob" 'touch /tmp/for_test_1.z /tmp/for_test_2.z 2>/dev/null; for f in /tmp/for_test_*.z; do echo found; done | wc -l'
521
+compare_posix_output "for empty" 'for x in; do echo never; done; echo done'
522
+compare_posix_output "for with expansion" 'X="a b c"; for i in $X; do echo $i; done | wc -l'
523
+compare_posix_output "for quoted" 'for i in "a b" "c d"; do echo "[$i]"; done'
524
+
525
+section "73. FUNCTION ARGUMENT HANDLING"
526
+
527
+compare_posix_output "func args count" 'f() { echo $#; }; f a b c'
528
+compare_posix_output "func args values" 'f() { echo $1 $2 $3; }; f x y z'
529
+compare_posix_output "func args shift" 'f() { shift; echo $1; }; f a b c'
530
+compare_posix_output "func args all" 'f() { echo $@; }; f 1 2 3'
531
+compare_posix_output "func nested call" 'a() { b; }; b() { echo hello; }; a'
532
+
533
+section "74. RETURN AND EXIT"
534
+
535
+compare_posix_output "return value" 'f() { return 42; }; f; echo $?'
536
+compare_posix_output "return default" 'f() { true; return; }; f; echo $?'
537
+compare_posix_output "exit in subshell" '(exit 5); echo $?'
538
+compare_posix_output "exit from func" 'f() { exit 7; }; f; echo never'
539
+
540
+section "75. EVAL COMPLEX"
541
+
542
+compare_posix_output "eval variable expansion" 'X=VAR; VAR=value; eval echo \$$X'
543
+compare_posix_output "eval multiple" 'eval "A=1; B=2"; echo $A $B'
544
+compare_posix_output "eval with special" 'eval "echo hello world"'
545
+compare_posix_output "eval nested" 'eval eval echo test'
546
+
547
+section "76. EXEC REDIRECTIONS"
548
+
549
+compare_posix_output "exec redirect stdout" 'exec >/tmp/exec_test_$$; echo test; exec >&-; cat /tmp/exec_test_$$; rm /tmp/exec_test_$$'
550
+
551
+section "77. TRAP IN SUBSHELL"
552
+
553
+compare_posix_output "trap inherited" '(trap "echo trapped" EXIT; exit 0) 2>/dev/null'
554
+compare_posix_output "trap reset in subshell" 'trap "echo outer" EXIT; (trap - EXIT; echo inner) 2>/dev/null; trap - EXIT'
555
+
556
+section "78. COLON COMMAND"
557
+
558
+compare_posix_output "colon returns 0" ':; echo $?'
559
+compare_posix_output "colon with args" ': arg1 arg2 arg3; echo $?'
560
+compare_posix_output "colon with expansion" 'X=test; : $X; echo $?'
561
+compare_posix_output "colon in if" 'if :; then echo yes; fi'
562
+
563
+section "79. DOT SOURCE"
564
+
565
+compare_posix_output "dot sources" 'echo "X=sourced" > /tmp/dot_test_$$; . /tmp/dot_test_$$; echo $X; rm /tmp/dot_test_$$'
566
+compare_posix_output "dot with args" 'echo "echo \$1" > /tmp/dot_arg_$$; . /tmp/dot_arg_$$ hello; rm /tmp/dot_arg_$$'
567
+
568
+section "80. UNSET BEHAVIOR"
569
+
570
+compare_posix_output "unset variable" 'X=test; unset X; echo ${X:-empty}'
571
+compare_posix_output "unset function" 'f() { echo hi; }; unset -f f; f 2>/dev/null || echo gone'
572
+compare_posix_output "unset nonexistent" 'unset NONEXISTENT_VAR_XYZ; echo $?'
573
+
574
+section "81. POSIX STRING OPERATIONS"
575
+
576
+compare_posix_output "string concat" 'A=hello; B=world; echo $A$B'
577
+compare_posix_output "string in quotes" 'A="hello world"; echo "$A"'
578
+compare_posix_output "string length indirect" 'A=test; echo ${#A}'
579
+compare_posix_output "empty string" 'A=""; echo "[$A]"'
580
+compare_posix_output "string with newline" 'A="line1
581
+line2"; echo "$A" | wc -l'
582
+
583
+section "82. POSIX NUMERIC OPERATIONS"
584
+
585
+compare_posix_output "add subtract" 'echo $((10 - 3 + 5))'
586
+compare_posix_output "multiply divide" 'echo $((20 / 4 * 2))'
587
+compare_posix_output "parentheses" 'echo $(((2 + 3) * (4 + 1)))'
588
+compare_posix_output "comparison chain" 'echo $((5 > 3 && 3 > 1))'
589
+compare_posix_output "ternary simulation" 'X=5; [ $X -gt 3 ] && echo big || echo small'
590
+
591
+section "83. POSIX ARRAY SIMULATION"
592
+
593
+compare_posix_output "positional as array" 'set -- a b c d e; echo $3'
594
+compare_posix_output "array length" 'set -- a b c d e; echo $#'
595
+compare_posix_output "array slice" 'set -- a b c d e; shift 2; echo $1'
596
+compare_posix_output "array all" 'set -- a b c; echo "$@"'
597
+compare_posix_output "array iterate" 'set -- a b c; for x in "$@"; do echo $x; done'
598
+
599
+section "84. POSIX PATTERN MATCHING"
600
+
601
+compare_posix_output "glob question" 'touch /tmp/pat_a /tmp/pat_b 2>/dev/null; ls /tmp/pat_? 2>/dev/null | wc -l; rm -f /tmp/pat_a /tmp/pat_b'
602
+compare_posix_output "glob star" 'touch /tmp/patstar_1 /tmp/patstar_2 2>/dev/null; ls /tmp/patstar_* 2>/dev/null | wc -l; rm -f /tmp/patstar_*'
603
+compare_posix_output "glob bracket" 'touch /tmp/patbr_a /tmp/patbr_b 2>/dev/null; ls /tmp/patbr_[ab] 2>/dev/null | wc -l; rm -f /tmp/patbr_*'
604
+compare_posix_output "case pattern star" 'x=hello; case $x in h*) echo yes;; esac'
605
+compare_posix_output "case pattern question" 'x=ab; case $x in ??) echo two;; esac'
606
+
607
+section "85. POSIX ENVIRONMENT"
608
+
609
+compare_posix_output "HOME exists" 'echo $HOME | grep -c "^/"'
610
+compare_posix_output "PATH exists" 'echo $PATH | grep -c ":"'
611
+compare_posix_output "PWD exists" 'echo $PWD | grep -c "^/"'
612
+compare_posix_output "export visible" 'export X=val; sh -c "echo \$X"'
613
+compare_posix_output "env assignment" 'X=test sh -c "echo \$X"'
614
+
615
+section "86. POSIX SPECIAL EXPANSIONS"
616
+
617
+compare_posix_output "dollar dollar" 'echo $$ | grep -E "^[0-9]+$" | wc -l'
618
+compare_posix_output "dollar question" 'true; echo $?'
619
+compare_posix_output "dollar bang" 'sleep 0.01 & echo $! | grep -E "^[0-9]+$" | wc -l; wait'
620
+compare_posix_output "dollar hash" 'set -- a b c; echo $#'
621
+compare_posix_output "dollar zero" 'echo $0 | wc -c | xargs test 0 -lt && echo yes'
622
+
623
+section "87. POSIX ERROR HANDLING"
624
+
625
+compare_posix_output "command not found" 'nonexistent_cmd_xyz 2>/dev/null; echo $?'
626
+compare_posix_output "file not found" 'cat /nonexistent_file_xyz 2>/dev/null; echo $?'
627
+compare_posix_output "permission denied sim" 'test -r /etc/shadow 2>/dev/null; echo done'
628
+compare_posix_output "syntax error handled" 'eval "if" 2>/dev/null; echo recovered'
629
+
630
+section "88. POSIX CONDITIONAL CHAINS"
631
+
632
+compare_posix_output "if and" 'if true && true; then echo yes; fi'
633
+compare_posix_output "if or" 'if false || true; then echo yes; fi'
634
+compare_posix_output "if not" 'if ! false; then echo yes; fi'
635
+compare_posix_output "complex condition" 'X=5; if [ $X -gt 3 ] && [ $X -lt 10 ]; then echo range; fi'
636
+compare_posix_output "elif chain long" 'X=4; if [ $X -eq 1 ]; then echo 1; elif [ $X -eq 2 ]; then echo 2; elif [ $X -eq 3 ]; then echo 3; elif [ $X -eq 4 ]; then echo 4; fi'
637
+
638
+section "89. POSIX INPUT PROCESSING"
639
+
640
+compare_posix_output "read line" 'echo "hello" | { read x; echo $x; }'
641
+compare_posix_output "read words" 'echo "a b c" | { read x y z; echo $y; }'
642
+compare_posix_output "read with IFS" 'echo "a:b:c" | { IFS=: read x y z; echo $y; }'
643
+compare_posix_output "cat file" 'echo "test" > /tmp/read_test_$$; cat /tmp/read_test_$$; rm /tmp/read_test_$$'
644
+
645
+section "90. POSIX OUTPUT FORMATTING"
646
+
647
+compare_posix_output "printf string" 'printf "%s\n" "hello"'
648
+compare_posix_output "printf number" 'printf "%d\n" 42'
649
+compare_posix_output "printf hex" 'printf "%x\n" 255'
650
+compare_posix_output "printf octal" 'printf "%o\n" 64'
651
+compare_posix_output "printf char" 'printf "%c\n" A'
652
+compare_posix_output "echo no newline" 'printf "no newline"'
653
+
654
+# Summary
655
+printf "\n"
656
+printf "==========================================\n"
657
+printf "EXTENDED POSIX COMPLIANCE TEST RESULTS ${TEST_PREFIX}\n"
658
+printf "==========================================\n"
659
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
660
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
661
+printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
662
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
663
+printf "==========================================\n"
664
+
665
+if [ $((PASSED + FAILED)) -gt 0 ]; then
666
+    PASS_RATE=$((PASSED * 100 / (PASSED + FAILED)))
667
+    printf "Pass rate: %d%%\n" "$PASS_RATE"
668
+fi
669
+
670
+if [ "$FAILED" -gt 0 ]; then
671
+    printf "\n${RED}Failed tests:${NC}\n"
672
+    printf "%b" "$FAILED_TESTS_LIST"
673
+    printf "==========================================\n"
674
+fi
675
+
676
+if [ "$FAILED" -eq 0 ]; then
677
+    printf "${GREEN}ALL EXTENDED POSIX TESTS PASSED!${NC} ✓\n"
678
+    exit 0
679
+else
680
+    printf "${RED}SOME TESTS FAILED${NC} ✗\n"
681
+    exit 1
682
+fi
suites/posix/posix_compliance_filetest.shadded
1108 lines changed — click to load
@@ -0,0 +1,1108 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance File Test Operators Suite for fortsh
4
+# =====================================
5
+# Tests test/[ builtin file and string operators per IEEE Std 1003.1-2017
6
+# Section: Shell Command Language - Conditional Expressions
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-filetest]"
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
+
29
+# Check if fortsh exists
30
+if [ ! -x "$FORTSH_BIN" ]; then
31
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
32
+    printf "Please run 'make' first or set FORTSH_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
+# Create test directory with test files
73
+setup_test_files() {
74
+    TEST_DIR="/tmp/fortsh_filetest_$$"
75
+    mkdir -p "$TEST_DIR"
76
+    cd "$TEST_DIR" || exit 1
77
+
78
+    # Create various test files
79
+    echo "content" > regular_file
80
+    mkdir test_dir
81
+    touch empty_file
82
+    chmod 755 executable_file 2>/dev/null || touch executable_file
83
+    chmod +x executable_file 2>/dev/null
84
+    chmod 000 unreadable_file 2>/dev/null || touch unreadable_file
85
+    ln -s regular_file symlink_file 2>/dev/null
86
+    ln -s nonexistent broken_link 2>/dev/null
87
+    mkfifo named_pipe 2>/dev/null || true
88
+}
89
+
90
+cleanup_test_files() {
91
+    cd /
92
+    chmod 644 "$TEST_DIR/unreadable_file" 2>/dev/null
93
+    rm -rf "$TEST_DIR"
94
+}
95
+
96
+# Trap to ensure cleanup
97
+trap cleanup_test_files EXIT
98
+
99
+setup_test_files
100
+
101
+# =====================================
102
+section "369. FILE EXISTENCE AND TYPE TESTS"
103
+# =====================================
104
+
105
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -e regular_file ] && echo yes || echo no' 2>&1)
106
+if [ "$result" = "yes" ]; then
107
+    pass "[ -e file ] exists test (regular file)"
108
+else
109
+    fail "[ -e file ] exists test (regular file)" "yes" "$result"
110
+fi
111
+
112
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -e test_dir ] && echo yes || echo no' 2>&1)
113
+if [ "$result" = "yes" ]; then
114
+    pass "[ -e dir ] exists test (directory)"
115
+else
116
+    fail "[ -e dir ] exists test (directory)" "yes" "$result"
117
+fi
118
+
119
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -e nonexistent ] && echo yes || echo no' 2>&1)
120
+if [ "$result" = "no" ]; then
121
+    pass "[ -e nonexistent ] returns false"
122
+else
123
+    fail "[ -e nonexistent ] returns false" "no" "$result"
124
+fi
125
+
126
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -f regular_file ] && echo yes || echo no' 2>&1)
127
+if [ "$result" = "yes" ]; then
128
+    pass "[ -f file ] regular file test"
129
+else
130
+    fail "[ -f file ] regular file test" "yes" "$result"
131
+fi
132
+
133
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -f test_dir ] && echo yes || echo no' 2>&1)
134
+if [ "$result" = "no" ]; then
135
+    pass "[ -f dir ] returns false for directory"
136
+else
137
+    fail "[ -f dir ] returns false for directory" "no" "$result"
138
+fi
139
+
140
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -d test_dir ] && echo yes || echo no' 2>&1)
141
+if [ "$result" = "yes" ]; then
142
+    pass "[ -d dir ] directory test"
143
+else
144
+    fail "[ -d dir ] directory test" "yes" "$result"
145
+fi
146
+
147
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -d regular_file ] && echo yes || echo no' 2>&1)
148
+if [ "$result" = "no" ]; then
149
+    pass "[ -d file ] returns false for file"
150
+else
151
+    fail "[ -d file ] returns false for file" "no" "$result"
152
+fi
153
+
154
+# =====================================
155
+section "370. SYMBOLIC LINK TESTS"
156
+# =====================================
157
+
158
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -L symlink_file ] && echo yes || echo no' 2>&1)
159
+if [ "$result" = "yes" ]; then
160
+    pass "[ -L symlink ] symbolic link test"
161
+else
162
+    fail "[ -L symlink ] symbolic link test" "yes" "$result"
163
+fi
164
+
165
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -h symlink_file ] && echo yes || echo no' 2>&1)
166
+if [ "$result" = "yes" ]; then
167
+    pass "[ -h symlink ] symbolic link test (alias)"
168
+else
169
+    fail "[ -h symlink ] symbolic link test (alias)" "yes" "$result"
170
+fi
171
+
172
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -L regular_file ] && echo yes || echo no' 2>&1)
173
+if [ "$result" = "no" ]; then
174
+    pass "[ -L regular ] returns false for non-symlink"
175
+else
176
+    fail "[ -L regular ] returns false for non-symlink" "no" "$result"
177
+fi
178
+
179
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -L broken_link ] && echo yes || echo no' 2>&1)
180
+if [ "$result" = "yes" ]; then
181
+    pass "[ -L broken_link ] true for broken symlink"
182
+else
183
+    fail "[ -L broken_link ] true for broken symlink" "yes" "$result"
184
+fi
185
+
186
+# =====================================
187
+section "371. FILE PERMISSION TESTS"
188
+# =====================================
189
+
190
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -r regular_file ] && echo yes || echo no' 2>&1)
191
+if [ "$result" = "yes" ]; then
192
+    pass "[ -r file ] readable test"
193
+else
194
+    fail "[ -r file ] readable test" "yes" "$result"
195
+fi
196
+
197
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -w regular_file ] && echo yes || echo no' 2>&1)
198
+if [ "$result" = "yes" ]; then
199
+    pass "[ -w file ] writable test"
200
+else
201
+    fail "[ -w file ] writable test" "yes" "$result"
202
+fi
203
+
204
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -x executable_file ] && echo yes || echo no' 2>&1)
205
+if [ "$result" = "yes" ]; then
206
+    pass "[ -x file ] executable test"
207
+else
208
+    fail "[ -x file ] executable test" "yes" "$result"
209
+fi
210
+
211
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -x regular_file ] && echo yes || echo no' 2>&1)
212
+if [ "$result" = "no" ]; then
213
+    pass "[ -x non-exec ] returns false"
214
+else
215
+    fail "[ -x non-exec ] returns false" "no" "$result"
216
+fi
217
+
218
+# =====================================
219
+section "372. FILE SIZE TESTS"
220
+# =====================================
221
+
222
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -s regular_file ] && echo yes || echo no' 2>&1)
223
+if [ "$result" = "yes" ]; then
224
+    pass "[ -s file ] size greater than zero"
225
+else
226
+    fail "[ -s file ] size greater than zero" "yes" "$result"
227
+fi
228
+
229
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -s empty_file ] && echo yes || echo no' 2>&1)
230
+if [ "$result" = "no" ]; then
231
+    pass "[ -s empty ] returns false for empty file"
232
+else
233
+    fail "[ -s empty ] returns false for empty file" "no" "$result"
234
+fi
235
+
236
+# =====================================
237
+section "373. STRING TESTS"
238
+# =====================================
239
+
240
+result=$("$FORTSH_BIN" -c '[ -z "" ] && echo yes || echo no' 2>&1)
241
+if [ "$result" = "yes" ]; then
242
+    pass "[ -z \"\" ] zero length string"
243
+else
244
+    fail "[ -z \"\" ] zero length string" "yes" "$result"
245
+fi
246
+
247
+result=$("$FORTSH_BIN" -c '[ -z "hello" ] && echo yes || echo no' 2>&1)
248
+if [ "$result" = "no" ]; then
249
+    pass "[ -z str ] returns false for non-empty"
250
+else
251
+    fail "[ -z str ] returns false for non-empty" "no" "$result"
252
+fi
253
+
254
+result=$("$FORTSH_BIN" -c '[ -n "hello" ] && echo yes || echo no' 2>&1)
255
+if [ "$result" = "yes" ]; then
256
+    pass "[ -n str ] non-zero length string"
257
+else
258
+    fail "[ -n str ] non-zero length string" "yes" "$result"
259
+fi
260
+
261
+result=$("$FORTSH_BIN" -c '[ -n "" ] && echo yes || echo no' 2>&1)
262
+if [ "$result" = "no" ]; then
263
+    pass "[ -n \"\" ] returns false for empty"
264
+else
265
+    fail "[ -n \"\" ] returns false for empty" "no" "$result"
266
+fi
267
+
268
+result=$("$FORTSH_BIN" -c '[ "hello" ] && echo yes || echo no' 2>&1)
269
+if [ "$result" = "yes" ]; then
270
+    pass "[ string ] implicit non-empty test"
271
+else
272
+    fail "[ string ] implicit non-empty test" "yes" "$result"
273
+fi
274
+
275
+result=$("$FORTSH_BIN" -c '[ "" ] && echo yes || echo no' 2>&1)
276
+if [ "$result" = "no" ]; then
277
+    pass "[ \"\" ] empty string is false"
278
+else
279
+    fail "[ \"\" ] empty string is false" "no" "$result"
280
+fi
281
+
282
+# =====================================
283
+section "374. STRING COMPARISON TESTS"
284
+# =====================================
285
+
286
+result=$("$FORTSH_BIN" -c '[ "abc" = "abc" ] && echo yes || echo no' 2>&1)
287
+if [ "$result" = "yes" ]; then
288
+    pass "[ str = str ] string equality"
289
+else
290
+    fail "[ str = str ] string equality" "yes" "$result"
291
+fi
292
+
293
+result=$("$FORTSH_BIN" -c '[ "abc" = "xyz" ] && echo yes || echo no' 2>&1)
294
+if [ "$result" = "no" ]; then
295
+    pass "[ str1 = str2 ] returns false for different"
296
+else
297
+    fail "[ str1 = str2 ] returns false for different" "no" "$result"
298
+fi
299
+
300
+result=$("$FORTSH_BIN" -c '[ "abc" != "xyz" ] && echo yes || echo no' 2>&1)
301
+if [ "$result" = "yes" ]; then
302
+    pass "[ str1 != str2 ] string inequality"
303
+else
304
+    fail "[ str1 != str2 ] string inequality" "yes" "$result"
305
+fi
306
+
307
+result=$("$FORTSH_BIN" -c '[ "abc" != "abc" ] && echo yes || echo no' 2>&1)
308
+if [ "$result" = "no" ]; then
309
+    pass "[ str != str ] returns false for same"
310
+else
311
+    fail "[ str != str ] returns false for same" "no" "$result"
312
+fi
313
+
314
+# =====================================
315
+section "375. NUMERIC COMPARISON TESTS"
316
+# =====================================
317
+
318
+result=$("$FORTSH_BIN" -c '[ 5 -eq 5 ] && echo yes || echo no' 2>&1)
319
+if [ "$result" = "yes" ]; then
320
+    pass "[ n -eq n ] numeric equality"
321
+else
322
+    fail "[ n -eq n ] numeric equality" "yes" "$result"
323
+fi
324
+
325
+result=$("$FORTSH_BIN" -c '[ 5 -eq 3 ] && echo yes || echo no' 2>&1)
326
+if [ "$result" = "no" ]; then
327
+    pass "[ n1 -eq n2 ] returns false for different"
328
+else
329
+    fail "[ n1 -eq n2 ] returns false for different" "no" "$result"
330
+fi
331
+
332
+result=$("$FORTSH_BIN" -c '[ 5 -ne 3 ] && echo yes || echo no' 2>&1)
333
+if [ "$result" = "yes" ]; then
334
+    pass "[ n1 -ne n2 ] numeric inequality"
335
+else
336
+    fail "[ n1 -ne n2 ] numeric inequality" "yes" "$result"
337
+fi
338
+
339
+result=$("$FORTSH_BIN" -c '[ 5 -gt 3 ] && echo yes || echo no' 2>&1)
340
+if [ "$result" = "yes" ]; then
341
+    pass "[ n1 -gt n2 ] greater than"
342
+else
343
+    fail "[ n1 -gt n2 ] greater than" "yes" "$result"
344
+fi
345
+
346
+result=$("$FORTSH_BIN" -c '[ 3 -gt 5 ] && echo yes || echo no' 2>&1)
347
+if [ "$result" = "no" ]; then
348
+    pass "[ n1 -gt n2 ] returns false when not greater"
349
+else
350
+    fail "[ n1 -gt n2 ] returns false when not greater" "no" "$result"
351
+fi
352
+
353
+result=$("$FORTSH_BIN" -c '[ 5 -ge 5 ] && echo yes || echo no' 2>&1)
354
+if [ "$result" = "yes" ]; then
355
+    pass "[ n -ge n ] greater or equal (equal)"
356
+else
357
+    fail "[ n -ge n ] greater or equal (equal)" "yes" "$result"
358
+fi
359
+
360
+result=$("$FORTSH_BIN" -c '[ 5 -ge 3 ] && echo yes || echo no' 2>&1)
361
+if [ "$result" = "yes" ]; then
362
+    pass "[ n1 -ge n2 ] greater or equal (greater)"
363
+else
364
+    fail "[ n1 -ge n2 ] greater or equal (greater)" "yes" "$result"
365
+fi
366
+
367
+result=$("$FORTSH_BIN" -c '[ 3 -lt 5 ] && echo yes || echo no' 2>&1)
368
+if [ "$result" = "yes" ]; then
369
+    pass "[ n1 -lt n2 ] less than"
370
+else
371
+    fail "[ n1 -lt n2 ] less than" "yes" "$result"
372
+fi
373
+
374
+result=$("$FORTSH_BIN" -c '[ 5 -lt 3 ] && echo yes || echo no' 2>&1)
375
+if [ "$result" = "no" ]; then
376
+    pass "[ n1 -lt n2 ] returns false when not less"
377
+else
378
+    fail "[ n1 -lt n2 ] returns false when not less" "no" "$result"
379
+fi
380
+
381
+result=$("$FORTSH_BIN" -c '[ 5 -le 5 ] && echo yes || echo no' 2>&1)
382
+if [ "$result" = "yes" ]; then
383
+    pass "[ n -le n ] less or equal (equal)"
384
+else
385
+    fail "[ n -le n ] less or equal (equal)" "yes" "$result"
386
+fi
387
+
388
+result=$("$FORTSH_BIN" -c '[ 3 -le 5 ] && echo yes || echo no' 2>&1)
389
+if [ "$result" = "yes" ]; then
390
+    pass "[ n1 -le n2 ] less or equal (less)"
391
+else
392
+    fail "[ n1 -le n2 ] less or equal (less)" "yes" "$result"
393
+fi
394
+
395
+# =====================================
396
+section "376. LOGICAL OPERATORS"
397
+# =====================================
398
+
399
+result=$("$FORTSH_BIN" -c '[ ! -e /nonexistent ] && echo yes || echo no' 2>&1)
400
+if [ "$result" = "yes" ]; then
401
+    pass "[ ! expr ] negation"
402
+else
403
+    fail "[ ! expr ] negation" "yes" "$result"
404
+fi
405
+
406
+result=$("$FORTSH_BIN" -c '[ ! -d /tmp ] && echo yes || echo no' 2>&1)
407
+if [ "$result" = "no" ]; then
408
+    pass "[ ! expr ] negation of true"
409
+else
410
+    fail "[ ! expr ] negation of true" "no" "$result"
411
+fi
412
+
413
+result=$("$FORTSH_BIN" -c '[ -d /tmp -a -e /tmp ] && echo yes || echo no' 2>&1)
414
+if [ "$result" = "yes" ]; then
415
+    pass "[ expr -a expr ] logical AND (both true)"
416
+else
417
+    fail "[ expr -a expr ] logical AND (both true)" "yes" "$result"
418
+fi
419
+
420
+result=$("$FORTSH_BIN" -c '[ -d /tmp -a -e /nonexistent ] && echo yes || echo no' 2>&1)
421
+if [ "$result" = "no" ]; then
422
+    pass "[ expr -a expr ] logical AND (one false)"
423
+else
424
+    fail "[ expr -a expr ] logical AND (one false)" "no" "$result"
425
+fi
426
+
427
+result=$("$FORTSH_BIN" -c '[ -e /nonexistent -o -d /tmp ] && echo yes || echo no' 2>&1)
428
+if [ "$result" = "yes" ]; then
429
+    pass "[ expr -o expr ] logical OR (one true)"
430
+else
431
+    fail "[ expr -o expr ] logical OR (one true)" "yes" "$result"
432
+fi
433
+
434
+result=$("$FORTSH_BIN" -c '[ -e /nonexistent -o -e /alsononexistent ] && echo yes || echo no' 2>&1)
435
+if [ "$result" = "no" ]; then
436
+    pass "[ expr -o expr ] logical OR (both false)"
437
+else
438
+    fail "[ expr -o expr ] logical OR (both false)" "no" "$result"
439
+fi
440
+
441
+# =====================================
442
+section "377. FILE COMPARISON TESTS"
443
+# =====================================
444
+
445
+# Create files with different timestamps
446
+touch "$TEST_DIR/older_file"
447
+sleep 1
448
+touch "$TEST_DIR/newer_file"
449
+
450
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ newer_file -nt older_file ] && echo yes || echo no' 2>&1)
451
+if [ "$result" = "yes" ]; then
452
+    pass "[ f1 -nt f2 ] newer than test"
453
+else
454
+    fail "[ f1 -nt f2 ] newer than test" "yes" "$result"
455
+fi
456
+
457
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ older_file -ot newer_file ] && echo yes || echo no' 2>&1)
458
+if [ "$result" = "yes" ]; then
459
+    pass "[ f1 -ot f2 ] older than test"
460
+else
461
+    fail "[ f1 -ot f2 ] older than test" "yes" "$result"
462
+fi
463
+
464
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ regular_file -ef regular_file ] && echo yes || echo no' 2>&1)
465
+if [ "$result" = "yes" ]; then
466
+    pass "[ f -ef f ] same file test"
467
+else
468
+    fail "[ f -ef f ] same file test" "yes" "$result"
469
+fi
470
+
471
+# =====================================
472
+section "378. TEST COMMAND FORM"
473
+# =====================================
474
+
475
+result=$("$FORTSH_BIN" -c 'test -d /tmp && echo yes || echo no' 2>&1)
476
+if [ "$result" = "yes" ]; then
477
+    pass "test -d dir (test command form)"
478
+else
479
+    fail "test -d dir (test command form)" "yes" "$result"
480
+fi
481
+
482
+result=$("$FORTSH_BIN" -c 'test "hello" = "hello" && echo yes || echo no' 2>&1)
483
+if [ "$result" = "yes" ]; then
484
+    pass "test str = str (test command form)"
485
+else
486
+    fail "test str = str (test command form)" "yes" "$result"
487
+fi
488
+
489
+result=$("$FORTSH_BIN" -c 'test 5 -gt 3 && echo yes || echo no' 2>&1)
490
+if [ "$result" = "yes" ]; then
491
+    pass "test n1 -gt n2 (test command form)"
492
+else
493
+    fail "test n1 -gt n2 (test command form)" "yes" "$result"
494
+fi
495
+
496
+# =====================================
497
+section "379. SPECIAL FILE TYPES"
498
+# =====================================
499
+
500
+# Named pipe test (if created successfully)
501
+if [ -p "$TEST_DIR/named_pipe" ]; then
502
+    result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -p named_pipe ] && echo yes || echo no' 2>&1)
503
+    if [ "$result" = "yes" ]; then
504
+        pass "[ -p fifo ] named pipe test"
505
+    else
506
+        fail "[ -p fifo ] named pipe test" "yes" "$result"
507
+    fi
508
+else
509
+    skip "[ -p fifo ] named pipe test" "mkfifo not available"
510
+fi
511
+
512
+result=$("$FORTSH_BIN" -c 'cd '"$TEST_DIR"' && [ -p regular_file ] && echo yes || echo no' 2>&1)
513
+if [ "$result" = "no" ]; then
514
+    pass "[ -p regular ] returns false for non-pipe"
515
+else
516
+    fail "[ -p regular ] returns false for non-pipe" "no" "$result"
517
+fi
518
+
519
+# Terminal test
520
+result=$("$FORTSH_BIN" -c '[ -t 0 ] && echo yes || echo no' 2>&1)
521
+# Since we're running non-interactively, stdin is not a terminal
522
+if [ "$result" = "no" ]; then
523
+    pass "[ -t 0 ] non-terminal stdin"
524
+else
525
+    fail "[ -t 0 ] non-terminal stdin" "no" "$result"
526
+fi
527
+
528
+# =====================================
529
+section "380. EDGE CASES"
530
+# =====================================
531
+
532
+result=$("$FORTSH_BIN" -c '[ ] && echo yes || echo no' 2>&1)
533
+if [ "$result" = "no" ]; then
534
+    pass "[ ] empty test returns false"
535
+else
536
+    fail "[ ] empty test returns false" "no" "$result"
537
+fi
538
+
539
+result=$("$FORTSH_BIN" -c 'x=""; [ -n "$x" ] && echo yes || echo no' 2>&1)
540
+if [ "$result" = "no" ]; then
541
+    pass "[ -n \"\$empty\" ] with empty variable"
542
+else
543
+    fail "[ -n \"\$empty\" ] with empty variable" "no" "$result"
544
+fi
545
+
546
+result=$("$FORTSH_BIN" -c 'x="hello"; [ -n "$x" ] && echo yes || echo no' 2>&1)
547
+if [ "$result" = "yes" ]; then
548
+    pass "[ -n \"\$var\" ] with non-empty variable"
549
+else
550
+    fail "[ -n \"\$var\" ] with non-empty variable" "yes" "$result"
551
+fi
552
+
553
+result=$("$FORTSH_BIN" -c '[ "=" = "=" ] && echo yes || echo no' 2>&1)
554
+if [ "$result" = "yes" ]; then
555
+    pass "[ \"=\" = \"=\" ] equals sign as string"
556
+else
557
+    fail "[ \"=\" = \"=\" ] equals sign as string" "yes" "$result"
558
+fi
559
+
560
+result=$("$FORTSH_BIN" -c '[ "-n" = "-n" ] && echo yes || echo no' 2>&1)
561
+if [ "$result" = "yes" ]; then
562
+    pass "[ \"-n\" = \"-n\" ] operator as string"
563
+else
564
+    fail "[ \"-n\" = \"-n\" ] operator as string" "yes" "$result"
565
+fi
566
+
567
+# =====================================
568
+section "381. COMPLEX TEST EXPRESSIONS"
569
+# =====================================
570
+
571
+result=$("$FORTSH_BIN" -c '[ 1 -eq 1 -a 2 -eq 2 ] && echo yes || echo no' 2>&1)
572
+if [ "$result" = "yes" ]; then
573
+    pass "[ a -a b ] both true"
574
+else
575
+    fail "[ a -a b ] both true" "yes" "$result"
576
+fi
577
+
578
+result=$("$FORTSH_BIN" -c '[ 1 -eq 1 -a 2 -eq 3 ] && echo yes || echo no' 2>&1)
579
+if [ "$result" = "no" ]; then
580
+    pass "[ a -a b ] second false"
581
+else
582
+    fail "[ a -a b ] second false" "no" "$result"
583
+fi
584
+
585
+result=$("$FORTSH_BIN" -c '[ 1 -eq 2 -o 2 -eq 2 ] && echo yes || echo no' 2>&1)
586
+if [ "$result" = "yes" ]; then
587
+    pass "[ a -o b ] second true"
588
+else
589
+    fail "[ a -o b ] second true" "yes" "$result"
590
+fi
591
+
592
+result=$("$FORTSH_BIN" -c '[ 1 -eq 2 -o 3 -eq 4 ] && echo yes || echo no' 2>&1)
593
+if [ "$result" = "no" ]; then
594
+    pass "[ a -o b ] both false"
595
+else
596
+    fail "[ a -o b ] both false" "no" "$result"
597
+fi
598
+
599
+result=$("$FORTSH_BIN" -c '[ ! 1 -eq 2 ] && echo yes || echo no' 2>&1)
600
+if [ "$result" = "yes" ]; then
601
+    pass "[ ! expr ] negates false"
602
+else
603
+    fail "[ ! expr ] negates false" "yes" "$result"
604
+fi
605
+
606
+result=$("$FORTSH_BIN" -c '[ ! 1 -eq 1 ] && echo yes || echo no' 2>&1)
607
+if [ "$result" = "no" ]; then
608
+    pass "[ ! expr ] negates true"
609
+else
610
+    fail "[ ! expr ] negates true" "no" "$result"
611
+fi
612
+
613
+# =====================================
614
+section "382. PARENTHESES IN TEST"
615
+# =====================================
616
+
617
+result=$("$FORTSH_BIN" -c '[ \( 1 -eq 1 \) ] && echo yes || echo no' 2>&1)
618
+if [ "$result" = "yes" ]; then
619
+    pass "[ \\( expr \\) ] parentheses"
620
+else
621
+    fail "[ \\( expr \\) ] parentheses" "yes" "$result"
622
+fi
623
+
624
+result=$("$FORTSH_BIN" -c '[ \( 1 -eq 1 -o 2 -eq 3 \) -a 3 -eq 3 ] && echo yes || echo no' 2>&1)
625
+if [ "$result" = "yes" ]; then
626
+    pass "[ \\( a -o b \\) -a c ] complex grouping"
627
+else
628
+    fail "[ \\( a -o b \\) -a c ] complex grouping" "yes" "$result"
629
+fi
630
+
631
+# =====================================
632
+section "383. STRING LENGTH COMPARISONS"
633
+# =====================================
634
+
635
+result=$("$FORTSH_BIN" -c '[ -z "" ] && echo yes || echo no' 2>&1)
636
+if [ "$result" = "yes" ]; then
637
+    pass "[ -z \"\" ] empty string is zero length"
638
+else
639
+    fail "[ -z \"\" ] empty string is zero length" "yes" "$result"
640
+fi
641
+
642
+result=$("$FORTSH_BIN" -c '[ -z "x" ] && echo yes || echo no' 2>&1)
643
+if [ "$result" = "no" ]; then
644
+    pass "[ -z \"x\" ] non-empty has length"
645
+else
646
+    fail "[ -z \"x\" ] non-empty has length" "no" "$result"
647
+fi
648
+
649
+result=$("$FORTSH_BIN" -c '[ -n "" ] && echo yes || echo no' 2>&1)
650
+if [ "$result" = "no" ]; then
651
+    pass "[ -n \"\" ] empty has no length"
652
+else
653
+    fail "[ -n \"\" ] empty has no length" "no" "$result"
654
+fi
655
+
656
+result=$("$FORTSH_BIN" -c '[ -n "abc" ] && echo yes || echo no' 2>&1)
657
+if [ "$result" = "yes" ]; then
658
+    pass "[ -n \"abc\" ] string has length"
659
+else
660
+    fail "[ -n \"abc\" ] string has length" "yes" "$result"
661
+fi
662
+
663
+# =====================================
664
+section "384. NUMERIC EDGE CASES"
665
+# =====================================
666
+
667
+result=$("$FORTSH_BIN" -c '[ 0 -eq 0 ] && echo yes || echo no' 2>&1)
668
+if [ "$result" = "yes" ]; then
669
+    pass "[ 0 -eq 0 ] zero equals zero"
670
+else
671
+    fail "[ 0 -eq 0 ] zero equals zero" "yes" "$result"
672
+fi
673
+
674
+result=$("$FORTSH_BIN" -c '[ -5 -lt 0 ] && echo yes || echo no' 2>&1)
675
+if [ "$result" = "yes" ]; then
676
+    pass "[ -5 -lt 0 ] negative less than zero"
677
+else
678
+    fail "[ -5 -lt 0 ] negative less than zero" "yes" "$result"
679
+fi
680
+
681
+result=$("$FORTSH_BIN" -c '[ -10 -gt -20 ] && echo yes || echo no' 2>&1)
682
+if [ "$result" = "yes" ]; then
683
+    pass "[ -10 -gt -20 ] negative comparisons"
684
+else
685
+    fail "[ -10 -gt -20 ] negative comparisons" "yes" "$result"
686
+fi
687
+
688
+result=$("$FORTSH_BIN" -c '[ 999999 -gt 1 ] && echo yes || echo no' 2>&1)
689
+if [ "$result" = "yes" ]; then
690
+    pass "[ large -gt 1 ] large numbers"
691
+else
692
+    fail "[ large -gt 1 ] large numbers" "yes" "$result"
693
+fi
694
+
695
+# =====================================
696
+section "385. STRING VS NUMERIC"
697
+# =====================================
698
+
699
+result=$("$FORTSH_BIN" -c '[ "10" = "10" ] && echo yes || echo no' 2>&1)
700
+if [ "$result" = "yes" ]; then
701
+    pass "[ \"10\" = \"10\" ] string comparison"
702
+else
703
+    fail "[ \"10\" = \"10\" ] string comparison" "yes" "$result"
704
+fi
705
+
706
+result=$("$FORTSH_BIN" -c '[ "10" = "010" ] && echo yes || echo no' 2>&1)
707
+if [ "$result" = "no" ]; then
708
+    pass "[ \"10\" = \"010\" ] string differs from numeric"
709
+else
710
+    fail "[ \"10\" = \"010\" ] string differs from numeric" "no" "$result"
711
+fi
712
+
713
+result=$("$FORTSH_BIN" -c '[ 10 -eq 010 ] && echo yes || echo no' 2>&1)
714
+# Numeric comparison - may or may not treat 010 as octal
715
+if [ "$result" = "yes" ] || [ "$result" = "no" ]; then
716
+    pass "[ 10 -eq 010 ] numeric comparison"
717
+else
718
+    fail "[ 10 -eq 010 ] numeric comparison" "yes or no" "$result"
719
+fi
720
+
721
+# =====================================
722
+section "386. FILE TEST WITH VARIABLES"
723
+# =====================================
724
+
725
+result=$("$FORTSH_BIN" -c 'f='"$TEST_DIR"'/regular_file; [ -f "$f" ] && echo yes || echo no' 2>&1)
726
+if [ "$result" = "yes" ]; then
727
+    pass "[ -f \"\$var\" ] variable as filename"
728
+else
729
+    fail "[ -f \"\$var\" ] variable as filename" "yes" "$result"
730
+fi
731
+
732
+result=$("$FORTSH_BIN" -c 'd='"$TEST_DIR"'; [ -d "$d" ] && echo yes || echo no' 2>&1)
733
+if [ "$result" = "yes" ]; then
734
+    pass "[ -d \"\$var\" ] variable as dirname"
735
+else
736
+    fail "[ -d \"\$var\" ] variable as dirname" "yes" "$result"
737
+fi
738
+
739
+# =====================================
740
+section "387. EMPTY OPERAND HANDLING"
741
+# =====================================
742
+
743
+result=$("$FORTSH_BIN" -c 'unset x; [ -n "$x" ] && echo yes || echo no' 2>&1)
744
+if [ "$result" = "no" ]; then
745
+    pass "[ -n \"\$unset\" ] unset variable"
746
+else
747
+    fail "[ -n \"\$unset\" ] unset variable" "no" "$result"
748
+fi
749
+
750
+result=$("$FORTSH_BIN" -c 'unset x; [ -z "$x" ] && echo yes || echo no' 2>&1)
751
+if [ "$result" = "yes" ]; then
752
+    pass "[ -z \"\$unset\" ] unset is zero length"
753
+else
754
+    fail "[ -z \"\$unset\" ] unset is zero length" "yes" "$result"
755
+fi
756
+
757
+result=$("$FORTSH_BIN" -c 'unset x; [ "$x" = "" ] && echo yes || echo no' 2>&1)
758
+if [ "$result" = "yes" ]; then
759
+    pass "[ \"\$unset\" = \"\" ] unset equals empty"
760
+else
761
+    fail "[ \"\$unset\" = \"\" ] unset equals empty" "yes" "$result"
762
+fi
763
+
764
+# =====================================
765
+section "388. NUMERIC COMPARISON OPERATORS"
766
+# =====================================
767
+
768
+result=$("$FORTSH_BIN" -c '[ 5 -eq 5 ] && echo yes || echo no' 2>&1)
769
+if [ "$result" = "yes" ]; then
770
+    pass "[ 5 -eq 5 ] equals"
771
+else
772
+    fail "[ 5 -eq 5 ] equals" "yes" "$result"
773
+fi
774
+
775
+result=$("$FORTSH_BIN" -c '[ 5 -ne 3 ] && echo yes || echo no' 2>&1)
776
+if [ "$result" = "yes" ]; then
777
+    pass "[ 5 -ne 3 ] not equals"
778
+else
779
+    fail "[ 5 -ne 3 ] not equals" "yes" "$result"
780
+fi
781
+
782
+result=$("$FORTSH_BIN" -c '[ 10 -gt 5 ] && echo yes || echo no' 2>&1)
783
+if [ "$result" = "yes" ]; then
784
+    pass "[ 10 -gt 5 ] greater than"
785
+else
786
+    fail "[ 10 -gt 5 ] greater than" "yes" "$result"
787
+fi
788
+
789
+result=$("$FORTSH_BIN" -c '[ 10 -ge 10 ] && echo yes || echo no' 2>&1)
790
+if [ "$result" = "yes" ]; then
791
+    pass "[ 10 -ge 10 ] greater or equal"
792
+else
793
+    fail "[ 10 -ge 10 ] greater or equal" "yes" "$result"
794
+fi
795
+
796
+result=$("$FORTSH_BIN" -c '[ 3 -lt 5 ] && echo yes || echo no' 2>&1)
797
+if [ "$result" = "yes" ]; then
798
+    pass "[ 3 -lt 5 ] less than"
799
+else
800
+    fail "[ 3 -lt 5 ] less than" "yes" "$result"
801
+fi
802
+
803
+result=$("$FORTSH_BIN" -c '[ 3 -le 3 ] && echo yes || echo no' 2>&1)
804
+if [ "$result" = "yes" ]; then
805
+    pass "[ 3 -le 3 ] less or equal"
806
+else
807
+    fail "[ 3 -le 3 ] less or equal" "yes" "$result"
808
+fi
809
+
810
+# =====================================
811
+section "389. NEGATIVE NUMBER TESTS"
812
+# =====================================
813
+
814
+result=$("$FORTSH_BIN" -c '[ -5 -lt 0 ] && echo yes || echo no' 2>&1)
815
+if [ "$result" = "yes" ]; then
816
+    pass "[ -5 -lt 0 ] negative less than zero"
817
+else
818
+    fail "[ -5 -lt 0 ] negative less than zero" "yes" "$result"
819
+fi
820
+
821
+result=$("$FORTSH_BIN" -c '[ -10 -lt -5 ] && echo yes || echo no' 2>&1)
822
+if [ "$result" = "yes" ]; then
823
+    pass "[ -10 -lt -5 ] negative comparison"
824
+else
825
+    fail "[ -10 -lt -5 ] negative comparison" "yes" "$result"
826
+fi
827
+
828
+result=$("$FORTSH_BIN" -c '[ -5 -eq -5 ] && echo yes || echo no' 2>&1)
829
+if [ "$result" = "yes" ]; then
830
+    pass "[ -5 -eq -5 ] negative equality"
831
+else
832
+    fail "[ -5 -eq -5 ] negative equality" "yes" "$result"
833
+fi
834
+
835
+# =====================================
836
+section "390. STRING TESTS"
837
+# =====================================
838
+
839
+result=$("$FORTSH_BIN" -c '[ "hello" = "hello" ] && echo yes || echo no' 2>&1)
840
+if [ "$result" = "yes" ]; then
841
+    pass "[ \"hello\" = \"hello\" ] string equality"
842
+else
843
+    fail "[ \"hello\" = \"hello\" ] string equality" "yes" "$result"
844
+fi
845
+
846
+result=$("$FORTSH_BIN" -c '[ "hello" != "world" ] && echo yes || echo no' 2>&1)
847
+if [ "$result" = "yes" ]; then
848
+    pass "[ \"hello\" != \"world\" ] string inequality"
849
+else
850
+    fail "[ \"hello\" != \"world\" ] string inequality" "yes" "$result"
851
+fi
852
+
853
+result=$("$FORTSH_BIN" -c '[ "abc" \< "def" ] && echo yes || echo no' 2>&1)
854
+if [ "$result" = "yes" ]; then
855
+    pass "[ \"abc\" < \"def\" ] string less than"
856
+else
857
+    fail "[ \"abc\" < \"def\" ] string less than" "yes" "$result"
858
+fi
859
+
860
+result=$("$FORTSH_BIN" -c '[ "xyz" \> "abc" ] && echo yes || echo no' 2>&1)
861
+if [ "$result" = "yes" ]; then
862
+    pass "[ \"xyz\" > \"abc\" ] string greater than"
863
+else
864
+    fail "[ \"xyz\" > \"abc\" ] string greater than" "yes" "$result"
865
+fi
866
+
867
+# =====================================
868
+section "391. FILE PERMISSION TESTS"
869
+# =====================================
870
+
871
+touch "$TEST_DIR/readable.txt"
872
+chmod 644 "$TEST_DIR/readable.txt"
873
+result=$("$FORTSH_BIN" -c '[ -r "'"$TEST_DIR"'/readable.txt" ] && echo yes || echo no' 2>&1)
874
+if [ "$result" = "yes" ]; then
875
+    pass "[ -r file ] readable file"
876
+else
877
+    fail "[ -r file ] readable file" "yes" "$result"
878
+fi
879
+
880
+result=$("$FORTSH_BIN" -c '[ -w "'"$TEST_DIR"'/readable.txt" ] && echo yes || echo no' 2>&1)
881
+if [ "$result" = "yes" ]; then
882
+    pass "[ -w file ] writable file"
883
+else
884
+    fail "[ -w file ] writable file" "yes" "$result"
885
+fi
886
+
887
+# =====================================
888
+section "392. TEST BUILTIN vs [ COMMAND"
889
+# =====================================
890
+
891
+result=$("$FORTSH_BIN" -c 'test -f /etc/passwd && echo yes || echo no' 2>&1)
892
+if [ "$result" = "yes" ]; then
893
+    pass "test -f /etc/passwd"
894
+else
895
+    fail "test -f /etc/passwd" "yes" "$result"
896
+fi
897
+
898
+result=$("$FORTSH_BIN" -c 'test 5 -eq 5 && echo yes || echo no' 2>&1)
899
+if [ "$result" = "yes" ]; then
900
+    pass "test 5 -eq 5"
901
+else
902
+    fail "test 5 -eq 5" "yes" "$result"
903
+fi
904
+
905
+result=$("$FORTSH_BIN" -c 'test "hello" = "hello" && echo yes || echo no' 2>&1)
906
+if [ "$result" = "yes" ]; then
907
+    pass "test string equality"
908
+else
909
+    fail "test string equality" "yes" "$result"
910
+fi
911
+
912
+# =====================================
913
+section "393. COMBINED LOGICAL TESTS"
914
+# =====================================
915
+
916
+result=$("$FORTSH_BIN" -c '[ 5 -gt 3 ] && [ 10 -gt 5 ] && echo yes || echo no' 2>&1)
917
+if [ "$result" = "yes" ]; then
918
+    pass "[ ] && [ ] both true"
919
+else
920
+    fail "[ ] && [ ] both true" "yes" "$result"
921
+fi
922
+
923
+result=$("$FORTSH_BIN" -c '[ 5 -lt 3 ] || [ 10 -gt 5 ] && echo yes || echo no' 2>&1)
924
+if [ "$result" = "yes" ]; then
925
+    pass "[ ] || [ ] one true"
926
+else
927
+    fail "[ ] || [ ] one true" "yes" "$result"
928
+fi
929
+
930
+result=$("$FORTSH_BIN" -c '! [ 5 -lt 3 ] && echo yes || echo no' 2>&1)
931
+if [ "$result" = "yes" ]; then
932
+    pass "! [ ] negation"
933
+else
934
+    fail "! [ ] negation" "yes" "$result"
935
+fi
936
+
937
+# =====================================
938
+section "394. FILE TYPE TESTS"
939
+# =====================================
940
+
941
+result=$("$FORTSH_BIN" -c '[ -f /etc/passwd ] && echo yes || echo no' 2>&1)
942
+if [ "$result" = "yes" ]; then
943
+    pass "[ -f /etc/passwd ] regular file"
944
+else
945
+    fail "[ -f /etc/passwd ] regular file" "yes" "$result"
946
+fi
947
+
948
+result=$("$FORTSH_BIN" -c '[ -d /tmp ] && echo yes || echo no' 2>&1)
949
+if [ "$result" = "yes" ]; then
950
+    pass "[ -d /tmp ] directory"
951
+else
952
+    fail "[ -d /tmp ] directory" "yes" "$result"
953
+fi
954
+
955
+result=$("$FORTSH_BIN" -c '[ -e /etc/passwd ] && echo yes || echo no' 2>&1)
956
+if [ "$result" = "yes" ]; then
957
+    pass "[ -e /etc/passwd ] exists"
958
+else
959
+    fail "[ -e /etc/passwd ] exists" "yes" "$result"
960
+fi
961
+
962
+result=$("$FORTSH_BIN" -c '[ -e /nonexistent/path ] && echo yes || echo no' 2>&1)
963
+if [ "$result" = "no" ]; then
964
+    pass "[ -e /nonexistent ] does not exist"
965
+else
966
+    fail "[ -e /nonexistent ] does not exist" "no" "$result"
967
+fi
968
+
969
+# =====================================
970
+section "395. FILE SIZE TESTS"
971
+# =====================================
972
+
973
+echo "content" > "$TEST_DIR/nonempty.txt"
974
+result=$("$FORTSH_BIN" -c '[ -s "'"$TEST_DIR"'/nonempty.txt" ] && echo yes || echo no' 2>&1)
975
+if [ "$result" = "yes" ]; then
976
+    pass "[ -s file ] non-empty file"
977
+else
978
+    fail "[ -s file ] non-empty file" "yes" "$result"
979
+fi
980
+
981
+: > "$TEST_DIR/empty.txt"
982
+result=$("$FORTSH_BIN" -c '[ -s "'"$TEST_DIR"'/empty.txt" ] && echo yes || echo no' 2>&1)
983
+if [ "$result" = "no" ]; then
984
+    pass "[ -s file ] empty file is false"
985
+else
986
+    fail "[ -s file ] empty file is false" "no" "$result"
987
+fi
988
+
989
+# =====================================
990
+section "396. SYMLINK TESTS"
991
+# =====================================
992
+
993
+ln -sf /etc/passwd "$TEST_DIR/link_to_passwd" 2>/dev/null || true
994
+result=$("$FORTSH_BIN" -c '[ -L "'"$TEST_DIR"'/link_to_passwd" ] && echo yes || echo no' 2>&1)
995
+if [ "$result" = "yes" ]; then
996
+    pass "[ -L symlink ] is symlink"
997
+else
998
+    fail "[ -L symlink ] is symlink" "yes" "$result"
999
+fi
1000
+
1001
+result=$("$FORTSH_BIN" -c '[ -h "'"$TEST_DIR"'/link_to_passwd" ] && echo yes || echo no' 2>&1)
1002
+if [ "$result" = "yes" ]; then
1003
+    pass "[ -h symlink ] is symlink (alternate)"
1004
+else
1005
+    fail "[ -h symlink ] is symlink (alternate)" "yes" "$result"
1006
+fi
1007
+
1008
+# =====================================
1009
+section "397. SPECIAL FILE TESTS"
1010
+# =====================================
1011
+
1012
+result=$("$FORTSH_BIN" -c '[ -c /dev/null ] && echo yes || echo no' 2>&1)
1013
+if [ "$result" = "yes" ]; then
1014
+    pass "[ -c /dev/null ] character device"
1015
+else
1016
+    fail "[ -c /dev/null ] character device" "yes" "$result"
1017
+fi
1018
+
1019
+result=$("$FORTSH_BIN" -c '[ -p /dev/null ] && echo yes || echo no' 2>&1)
1020
+if [ "$result" = "no" ]; then
1021
+    pass "[ -p /dev/null ] not a pipe"
1022
+else
1023
+    fail "[ -p /dev/null ] not a pipe" "no" "$result"
1024
+fi
1025
+
1026
+# =====================================
1027
+section "398. TERMINAL TESTS"
1028
+# =====================================
1029
+
1030
+result=$("$FORTSH_BIN" -c '[ -t 1 ] && echo tty || echo not_tty' 2>&1)
1031
+# When running in a script, stdout is typically not a tty
1032
+if echo "$result" | grep -qE "(tty|not_tty)"; then
1033
+    pass "[ -t 1 ] terminal test runs"
1034
+else
1035
+    fail "[ -t 1 ] terminal test runs"
1036
+fi
1037
+
1038
+# =====================================
1039
+section "399. COMPOUND EXPRESSION PRECEDENCE"
1040
+# =====================================
1041
+
1042
+result=$("$FORTSH_BIN" -c '[ 1 -eq 1 ] && [ 2 -eq 2 ] && [ 3 -eq 3 ] && echo yes || echo no' 2>&1)
1043
+if [ "$result" = "yes" ]; then
1044
+    pass "Multiple && chain"
1045
+else
1046
+    fail "Multiple && chain" "yes" "$result"
1047
+fi
1048
+
1049
+result=$("$FORTSH_BIN" -c '[ 1 -eq 2 ] || [ 2 -eq 2 ] && echo yes || echo no' 2>&1)
1050
+if [ "$result" = "yes" ]; then
1051
+    pass "|| then && precedence"
1052
+else
1053
+    fail "|| then && precedence" "yes" "$result"
1054
+fi
1055
+
1056
+# =====================================
1057
+section "400. EDGE CASE EXPRESSIONS"
1058
+# =====================================
1059
+
1060
+result=$("$FORTSH_BIN" -c '[ "" ] && echo yes || echo no' 2>&1)
1061
+if [ "$result" = "no" ]; then
1062
+    pass "[ \"\" ] empty string is false"
1063
+else
1064
+    fail "[ \"\" ] empty string is false" "no" "$result"
1065
+fi
1066
+
1067
+result=$("$FORTSH_BIN" -c '[ "x" ] && echo yes || echo no' 2>&1)
1068
+if [ "$result" = "yes" ]; then
1069
+    pass "[ \"x\" ] non-empty string is true"
1070
+else
1071
+    fail "[ \"x\" ] non-empty string is true" "yes" "$result"
1072
+fi
1073
+
1074
+result=$("$FORTSH_BIN" -c '[ 0 ] && echo yes || echo no' 2>&1)
1075
+if [ "$result" = "yes" ]; then
1076
+    pass "[ 0 ] zero string is true (not numeric)"
1077
+else
1078
+    fail "[ 0 ] zero string is true (not numeric)" "yes" "$result"
1079
+fi
1080
+
1081
+result=$("$FORTSH_BIN" -c '[ ! "" ] && echo yes || echo no' 2>&1)
1082
+if [ "$result" = "yes" ]; then
1083
+    pass "[ ! \"\" ] negated empty is true"
1084
+else
1085
+    fail "[ ! \"\" ] negated empty is true" "yes" "$result"
1086
+fi
1087
+
1088
+# =====================================
1089
+# Summary
1090
+# =====================================
1091
+printf "\n"
1092
+printf "${BLUE}==========================================\n"
1093
+printf "POSIX File Test Operators Summary\n"
1094
+printf "==========================================${NC}\n"
1095
+printf "Passed:  ${GREEN}%d${NC}\n" "$PASSED"
1096
+printf "Failed:  ${RED}%d${NC}\n" "$FAILED"
1097
+printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
1098
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
1099
+
1100
+if [ -n "$FAILED_TESTS_LIST" ]; then
1101
+    printf "\n${RED}Failed tests:${NC}\n"
1102
+    printf "%b" "$FAILED_TESTS_LIST"
1103
+fi
1104
+
1105
+if [ "$FAILED" -gt 0 ]; then
1106
+    exit 1
1107
+fi
1108
+exit 0
suites/posix/posix_compliance_gaps.shadded
2802 lines changed — click to load
@@ -0,0 +1,2802 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Gap Coverage Test Suite
4
+# =====================================
5
+# This test file specifically targets gaps in the existing 4 POSIX test files
6
+# by comparing against IEEE Std 1003.1-2017 (POSIX.1-2017)
7
+#
8
+# Focus areas:
9
+# - Here-document variations (<<- tab stripping)
10
+# - Additional redirection operators (<>)
11
+# - Complex IFS field splitting scenarios
12
+# - Nested and complex parameter expansions
13
+# - Command name resolution order
14
+# - Complex quoting and escaping
15
+# - Pipeline exit status rules
16
+# - Complex arithmetic edge cases
17
+# - Locale and environment variables
18
+# - Builtin command edge cases
19
+# - Pathname expansion edge cases
20
+# - Function scope and recursion edge cases
21
+# - Signal handling edge cases
22
+
23
+# Colors (POSIX-compliant way)
24
+RED='\033[0;31m'
25
+GREEN='\033[0;32m'
26
+YELLOW='\033[1;33m'
27
+BLUE='\033[0;34m'
28
+NC='\033[0m'
29
+
30
+# Test identification
31
+TEST_PREFIX="[posix-gaps]"
32
+CURRENT_SECTION=""
33
+TEST_NUM=0
34
+
35
+PASSED=0
36
+FAILED=0
37
+SKIPPED=0
38
+FAILED_TESTS_LIST=""
39
+DEBUG_INFO=""
40
+
41
+# Get script directory (POSIX way)
42
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
43
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
44
+BASH_REF="${BASH_REF:-bash}"
45
+
46
+# Check if fortsh exists
47
+if [ ! -x "$FORTSH_BIN" ]; then
48
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
49
+    printf "Please run 'make' first or set FORTSH_BIN environment variable\n"
50
+    exit 1
51
+fi
52
+
53
+# Test result trackers
54
+pass() {
55
+    TEST_NUM=$((TEST_NUM + 1))
56
+    printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
57
+    PASSED=$((PASSED + 1))
58
+}
59
+
60
+fail() {
61
+    TEST_NUM=$((TEST_NUM + 1))
62
+    TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
63
+    printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
64
+    FAILED_TESTS_LIST="${FAILED_TESTS_LIST}  ${TEST_ID}: $1\n"
65
+    if [ -n "$2" ]; then
66
+        printf "  posix:  %s\n" "$2"
67
+    fi
68
+    if [ -n "$3" ]; then
69
+        printf "  fortsh: %s\n" "$3"
70
+    fi
71
+    FAILED=$((FAILED + 1))
72
+}
73
+
74
+skip() {
75
+    TEST_NUM=$((TEST_NUM + 1))
76
+    printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
77
+    SKIPPED=$((SKIPPED + 1))
78
+}
79
+
80
+section() {
81
+    # Extract section number from header like "91. HERE DOCUMENT VARIATIONS"
82
+    CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
83
+    TEST_NUM=0
84
+    printf "\n"
85
+    printf "${BLUE}==========================================\n"
86
+    printf "%s\n" "$1"
87
+    printf "==========================================${NC}\n"
88
+}
89
+
90
+# Normalize shell error messages by stripping shell name and "line N: " prefix
91
+normalize_output() {
92
+    sed -e 's|^[^ ]*bash: |sh: |' -e 's|^[^ ]*fortsh: |sh: |' -e 's/line [0-9]*: //'
93
+}
94
+
95
+# Helper function to run command in both shells and compare
96
+compare_posix_output() {
97
+    test_name="$1"
98
+    command="$2"
99
+    posix_file="/tmp/posix_gaps_$$_posix"
100
+    fortsh_file="/tmp/posix_gaps_$$_fortsh"
101
+
102
+    # Run in POSIX shell (sh)
103
+    "$BASH_REF" -c "$command" 2>&1 | normalize_output > "$posix_file" || true
104
+
105
+    # Run in fortsh
106
+    "$FORTSH_BIN" -c "$command" 2>&1 | normalize_output > "$fortsh_file" || true
107
+
108
+    # Compare outputs
109
+    if diff -q "$posix_file" "$fortsh_file" > /dev/null 2>&1; then
110
+        pass "$test_name"
111
+    else
112
+        fail "$test_name" "$(cat "$posix_file")" "$(cat "$fortsh_file")"
113
+    fi
114
+
115
+    rm -f "$posix_file" "$fortsh_file"
116
+}
117
+
118
+# Normalize shell error messages by stripping shell name and "line N: " prefix
119
+# POSIX doesn't mandate error message format, so we normalize for comparison
120
+normalize_error() {
121
+    echo "$1" | sed -e 's|^[^ ]*bash: |sh: |' -e 's|^[^ ]*fortsh: |sh: |' -e 's/line [0-9]*: //'
122
+}
123
+
124
+# Compare error output, normalizing line number differences
125
+compare_posix_error() {
126
+    test_name="$1"
127
+    command="$2"
128
+    posix_file="/tmp/posix_gaps_$$_posix"
129
+    fortsh_file="/tmp/posix_gaps_$$_fortsh"
130
+
131
+    # Run in POSIX shell (sh)
132
+    "$BASH_REF" -c "$command" > "$posix_file" 2>&1 || true
133
+
134
+    # Run in fortsh
135
+    "$FORTSH_BIN" -c "$command" > "$fortsh_file" 2>&1 || true
136
+
137
+    # Normalize error messages before comparison
138
+    posix_norm=$(normalize_error "$(cat "$posix_file")")
139
+    fortsh_norm=$(normalize_error "$(cat "$fortsh_file")")
140
+
141
+    if [ "$posix_norm" = "$fortsh_norm" ]; then
142
+        pass "$test_name"
143
+    else
144
+        fail "$test_name" "$(cat "$posix_file")" "$(cat "$fortsh_file")"
145
+    fi
146
+
147
+    rm -f "$posix_file" "$fortsh_file"
148
+}
149
+
150
+# Helper function to compare exit codes
151
+compare_posix_exit_code() {
152
+    test_name="$1"
153
+    command="$2"
154
+
155
+    "$BASH_REF" -c "$command" > /dev/null 2>&1
156
+    posix_exit=$?
157
+
158
+    "$FORTSH_BIN" -c "$command" > /dev/null 2>&1
159
+    fortsh_exit=$?
160
+
161
+    if [ "$posix_exit" -eq "$fortsh_exit" ]; then
162
+        pass "$test_name"
163
+    else
164
+        fail "$test_name" "exit=$posix_exit" "exit=$fortsh_exit"
165
+        # Accumulate debug info for CI summary
166
+        DEBUG_INFO="${DEBUG_INFO}DEBUG [$test_name]: cmd='$command'\n"
167
+        DEBUG_INFO="${DEBUG_INFO}  bash exit: $posix_exit\n"
168
+        DEBUG_INFO="${DEBUG_INFO}  fortsh exit: $fortsh_exit\n"
169
+    fi
170
+}
171
+
172
+# Cleanup
173
+cleanup() {
174
+    rm -f /tmp/posix_gaps_$$_* 2>/dev/null
175
+    rm -rf /tmp/posix_gaps_test_* 2>/dev/null
176
+}
177
+trap cleanup EXIT INT TERM
178
+
179
+section "97. PIPELINE EXIT STATUS"
180
+
181
+compare_posix_exit_code "pipeline last command status" "true | true | false"
182
+compare_posix_exit_code "pipeline first fails" "false | true | true"
183
+compare_posix_exit_code "pipeline middle fails" "true | false | true"
184
+compare_posix_output "PIPESTATUS concept" "false | true | true; echo \$?"
185
+
186
+section "99. PATHNAME EXPANSION EDGE CASES"
187
+
188
+mkdir -p /tmp/posix_gaps_test_glob
189
+touch "/tmp/posix_gaps_test_glob/file1.txt"
190
+touch "/tmp/posix_gaps_test_glob/file2.txt"
191
+touch "/tmp/posix_gaps_test_glob/file[3].txt"
192
+touch "/tmp/posix_gaps_test_glob/-file.txt"
193
+mkdir -p "/tmp/posix_gaps_test_glob/.hidden"
194
+
195
+compare_posix_output "glob bracket literal" "ls /tmp/posix_gaps_test_glob/file[[]3].txt 2>/dev/null | wc -l"
196
+compare_posix_output "glob dash in bracket" "ls /tmp/posix_gaps_test_glob/[-a-z]file.txt 2>/dev/null | wc -l"
197
+compare_posix_output "glob no match returns literal" "echo /tmp/posix_gaps_test_glob/*.xyz | grep -c '\\*'"
198
+compare_posix_output "glob hidden dirs" "ls -d /tmp/posix_gaps_test_glob/.* 2>/dev/null | grep -c hidden"
199
+compare_posix_output "glob character class digit" "touch /tmp/posix_gaps_test_glob/f1.txt; ls /tmp/posix_gaps_test_glob/f[[:digit:]].txt 2>/dev/null | wc -l"
200
+
201
+rm -rf /tmp/posix_gaps_test_glob
202
+
203
+section "100. FUNCTION SCOPE AND RECURSION EDGE CASES"
204
+
205
+compare_posix_output "function local scope via subshell" "f() { (X=inner; echo \$X); }; X=outer; f; echo \$X"
206
+compare_posix_output "function positional params" "f() { echo \$1 \$2; }; set -- a b; f x y"
207
+compare_posix_output "function preserves positional" "f() { echo \$1; }; set -- a b; f x; echo \$1"
208
+compare_posix_output "nested function calls" "a() { b; }; b() { c; }; c() { echo deep; }; a"
209
+compare_posix_output "function return in subshell" "f() { (return 5); echo \$?; }; f"
210
+compare_posix_output "recursive factorial" "fact() { if [ \$1 -le 1 ]; then echo 1; else local r=\$(fact \$((\$1-1)) 2>/dev/null || fact \$((\$1-1))); echo \$((\$1 * r)); fi; }; fact 4"
211
+
212
+section "101. EXIT STATUS IN COMPOUND COMMANDS"
213
+
214
+compare_posix_exit_code "subshell exit status" "(exit 42); echo \$?"
215
+compare_posix_exit_code "brace group exit status" "{ exit 42; }; echo \$?"
216
+compare_posix_exit_code "if statement exit status" "if true; then true; fi; echo \$?"
217
+compare_posix_exit_code "for loop exit status" "for i in 1; do false; done; echo \$?"
218
+compare_posix_exit_code "while loop exit status" "while false; do :; done; echo \$?"
219
+compare_posix_exit_code "case exit status" "case x in x) false;; esac; echo \$?"
220
+
221
+section "114. ALIAS EDGE CASES"
222
+
223
+compare_posix_output "alias with args" "alias ll='ls -l'; alias ll | grep -c 'ls -l'"
224
+compare_posix_output "alias recursive prevention" "alias ls='ls -a'; command ls /tmp >/dev/null; echo \$?"
225
+compare_posix_output "unalias nonexistent" "unalias nonexistent_alias_$$ 2>/dev/null || echo ok"
226
+compare_posix_output "alias name same as builtin" "alias echo='printf'; unalias echo; command -v echo | grep -c echo"
227
+
228
+section "115. TEST COMMAND COMPLEX EXPRESSIONS"
229
+
230
+compare_posix_exit_code "test complex AND/OR" "test 5 -gt 3 -a \( 10 -lt 20 -o 1 -eq 2 \)"
231
+compare_posix_exit_code "test negation precedence" "test ! 5 -gt 10"
232
+compare_posix_exit_code "test string empty" "test -z ''"
233
+compare_posix_exit_code "test string nonempty" "test -n 'x'"
234
+compare_posix_exit_code "test string unary" "test 'nonempty'"
235
+
236
+section "118. COMMAND SUBSTITUTION EDGE CASES"
237
+
238
+compare_posix_output "command subst nested" "echo \$(echo \$(echo nested))"
239
+compare_posix_output "command subst with pipes" "echo \$(echo a | cat)"
240
+compare_posix_output "command subst multiline" "result=\$(echo a; echo b); echo \"\$result\" | wc -l"
241
+compare_posix_output "command subst empty" "x=\$(true); echo \"|\$x|\""
242
+compare_posix_output "backtick vs dollar-paren" "a=\`echo test\`; b=\$(echo test); test \"\$a\" = \"\$b\" && echo same"
243
+
244
+section "130. EXPORT EDGE CASES"
245
+
246
+compare_posix_output "export without value" "export VAR; sh -c 'echo \${VAR:-unset}'"
247
+compare_posix_output "export with value" "export VAR=value; sh -c 'echo \$VAR'"
248
+compare_posix_output "export multiple" "export A=1 B=2; sh -c 'echo \$A \$B'"
249
+compare_posix_output "export readonly" "readonly X=ro; export X; sh -c 'echo \$X'"
250
+
251
+section "131. VARIABLE SCOPE EDGE CASES"
252
+
253
+compare_posix_output "var in subshell lost" 'X=1; (X=2); echo $X'
254
+compare_posix_output "var in brace group kept" 'X=1; { X=2; }; echo $X'
255
+compare_posix_output "env var in subshell" 'export X=1; (echo $X)'
256
+compare_posix_output "command prefix assignment" 'X=val sh -c "echo \$X"'
257
+
258
+section "132. FUNCTION VARIABLE SCOPE"
259
+
260
+compare_posix_output "global in function" 'X=global; f() { echo $X; }; f'
261
+compare_posix_output "function modifies global" 'X=1; f() { X=2; }; f; echo $X'
262
+compare_posix_output "local shadows global" 'X=1; f() { local X=2; echo $X; }; f; echo $X'
263
+
264
+section "134. COMMAND SUBSTITUTION EDGE CASES"
265
+
266
+compare_posix_output "nested cmd sub" 'echo $(echo $(echo deep))'
267
+compare_posix_output "cmd sub with quotes" 'echo "$(echo "hello world")"'
268
+compare_posix_output "cmd sub trailing newline" 'x=$(printf "hi\n"); echo "[$x]"'
269
+compare_posix_output "backtick substitution" 'echo `echo hello`'
270
+
271
+section "137. WORD SPLITTING EDGE CASES"
272
+
273
+compare_posix_output "IFS colon split" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
274
+compare_posix_output "IFS multiple chars" 'IFS=":;"; x="a:b;c"; set -- $x; echo $#'
275
+compare_posix_output "empty IFS no split" 'IFS=""; x="a b c"; set -- $x; echo $#'
276
+compare_posix_output "default IFS" 'unset IFS; x="a   b"; set -- $x; echo $#'
277
+
278
+section "138. GLOB EDGE CASES"
279
+
280
+compare_posix_output "glob no match" 'echo /nonexistent_dir_xyz_$$/* 2>/dev/null | grep -c nonexistent || echo "0 or pattern"'
281
+compare_posix_output "set -f disables glob" 'set -f; echo *; set +f'
282
+compare_posix_output "quoted glob literal" 'echo "*"'
283
+
284
+section "139. SIGNAL EDGE CASES"
285
+
286
+compare_posix_output "trap list" 'trap "echo x" INT; trap | grep -c INT || echo 0'
287
+compare_posix_output "trap reset" 'trap "echo x" INT; trap - INT; trap | grep -c INT || echo 0'
288
+compare_posix_output "trap EXIT runs" 'sh -c "trap echo_exit EXIT; exit 0" 2>/dev/null; echo done'
289
+
290
+section "140. MISCELLANEOUS EDGE CASES"
291
+
292
+compare_posix_output "empty for list" 'for x in; do echo x; done; echo done'
293
+compare_posix_output "case no match" 'case x in y) echo y;; esac; echo done'
294
+compare_posix_output "if false branch" 'if false; then echo yes; else echo no; fi'
295
+compare_posix_output "elif chain" 'if false; then echo 1; elif false; then echo 2; else echo 3; fi'
296
+compare_posix_output "nested if" 'if true; then if true; then echo nested; fi; fi'
297
+
298
+section "141. ADDITIONAL EDGE CASES"
299
+
300
+compare_posix_output "while zero iterations" 'while false; do echo never; done; echo done'
301
+compare_posix_output "until zero iterations" 'until true; do echo never; done; echo done'
302
+compare_posix_output "function empty body" 'f() { :; }; f; echo $?'
303
+compare_posix_output "pipeline single" 'echo test | cat'
304
+compare_posix_output "assignment no space" 'x=value; echo $x'
305
+compare_posix_output "comment mid-line" 'echo visible # hidden'
306
+compare_posix_output "newline continuation" 'echo hel\
307
+lo'
308
+compare_posix_output "mixed operators" 'true && false || echo fallback'
309
+
310
+section "142. ADDITIONAL GAPS"
311
+
312
+compare_posix_output "expr string length" 'expr length "hello"'
313
+compare_posix_output "test with and" '[ 1 -eq 1 ] && [ 2 -eq 2 ] && echo both'
314
+compare_posix_output "test with or" '[ 1 -eq 2 ] || [ 2 -eq 2 ] && echo one'
315
+compare_posix_output "double negation" '! ! true && echo yes'
316
+compare_posix_output "triple pipe" 'echo test | cat | cat | cat'
317
+compare_posix_output "var in quotes" 'X="hello world"; echo "$X"'
318
+compare_posix_output "arithmetic compare" 'echo $((5 > 3))'
319
+compare_posix_output "arithmetic ternary sim" '[ 5 -gt 3 ] && echo big || echo small'
320
+compare_posix_output "for with seq" 'for i in 1 2 3 4 5; do echo $i; done | wc -l'
321
+compare_posix_output "while decrement" 'i=5; while [ $i -gt 0 ]; do i=$((i-1)); done; echo $i'
322
+compare_posix_output "case star pattern" 'x=anything; case $x in *) echo star;; esac'
323
+compare_posix_output "func recursion base" 'f() { [ $1 -le 0 ] && echo done || f $(($1-1)); }; f 3'
324
+compare_posix_output "heredoc simple" 'cat <<END
325
+test
326
+END'
327
+compare_posix_output "redirect both" 'echo out; echo err >&2'
328
+compare_posix_output "subshell cd" '(cd /tmp; pwd)'
329
+
330
+section "151. FUNCTION PATTERNS"
331
+
332
+compare_posix_output "func define call" 'f() { echo hello; }; f'
333
+compare_posix_output "func with args" 'f() { echo $1 $2; }; f a b'
334
+compare_posix_output "func return code" 'f() { return 0; }; f; echo $?'
335
+compare_posix_output "func return 1" 'f() { return 1; }; f; echo $?'
336
+compare_posix_output "func with local" 'x=outer; f() { x=inner; }; f; echo $x'
337
+compare_posix_output "func recursive" 'f() { [ $1 -eq 0 ] && echo done || f $(($1-1)); }; f 2'
338
+compare_posix_output "func in pipeline" 'f() { echo test; }; f | cat'
339
+compare_posix_output "func multi stmt" 'f() { echo a; echo b; }; f | wc -l'
340
+compare_posix_output "func empty body" 'f() { :; }; f; echo ok'
341
+compare_posix_output "func all args" 'f() { echo $@; }; f a b c'
342
+
343
+section "152. REDIRECTION PATTERNS"
344
+
345
+compare_posix_output "redir output" 'echo test > /tmp/test_$$; cat /tmp/test_$$; rm /tmp/test_$$'
346
+compare_posix_output "redir append" 'echo a > /tmp/test_$$; echo b >> /tmp/test_$$; wc -l < /tmp/test_$$; rm /tmp/test_$$'
347
+compare_posix_output "redir input" 'echo test > /tmp/test_$$; cat < /tmp/test_$$; rm /tmp/test_$$'
348
+compare_posix_output "redir stderr" 'echo err >&2 2>/dev/null; echo ok'
349
+compare_posix_output "redir both devnull" 'echo out; echo err >&2 2>/dev/null'
350
+compare_posix_output "redir fd dup" 'echo test 2>&1 | cat'
351
+compare_posix_output "redir here string alt" 'echo test | cat'
352
+compare_posix_output "redir noclobber safe" 'echo ok'
353
+compare_posix_output "multiple redir" 'echo a; echo b; echo c'
354
+compare_posix_output "redir in subshell" '(echo test > /tmp/test_$$); cat /tmp/test_$$ 2>/dev/null; rm /tmp/test_$$ 2>/dev/null; echo done'
355
+
356
+section "153. SPECIAL PARAMETERS"
357
+
358
+compare_posix_output "dollar question" 'true; echo $?'
359
+compare_posix_output "dollar question fail" 'false; echo $?'
360
+compare_posix_output "dollar hyphen" 'echo $- | grep -c "."'
361
+compare_posix_output "dollar dollar" 'echo $$ | grep -c "[0-9]"'
362
+compare_posix_output "dollar zero" 'echo ${0:-shell} | grep -c "."'
363
+compare_posix_output "positional one" 'set -- a b c; echo $1'
364
+compare_posix_output "positional two" 'set -- a b c; echo $2'
365
+compare_posix_output "positional three" 'set -- a b c; echo $3'
366
+compare_posix_output "dollar at" 'set -- a b c; echo "$@"'
367
+compare_posix_output "dollar star" 'set -- a b c; echo "$*"'
368
+
369
+section "154. TEST BUILTIN COMPREHENSIVE"
370
+
371
+compare_posix_output "test eq" '[ 1 -eq 1 ]; echo $?'
372
+compare_posix_output "test ne" '[ 1 -ne 2 ]; echo $?'
373
+compare_posix_output "test lt" '[ 1 -lt 2 ]; echo $?'
374
+compare_posix_output "test gt" '[ 2 -gt 1 ]; echo $?'
375
+compare_posix_output "test le" '[ 1 -le 1 ]; echo $?'
376
+compare_posix_output "test ge" '[ 1 -ge 1 ]; echo $?'
377
+compare_posix_output "test str eq" '[ "a" = "a" ]; echo $?'
378
+compare_posix_output "test str ne" '[ "a" != "b" ]; echo $?'
379
+compare_posix_output "test z" '[ -z "" ]; echo $?'
380
+compare_posix_output "test n" '[ -n "x" ]; echo $?'
381
+
382
+section "155. FILE TEST OPERATIONS"
383
+
384
+compare_posix_output "test f file" '[ -f /etc/passwd ] && echo yes || echo no'
385
+compare_posix_output "test d dir" '[ -d /tmp ] && echo yes || echo no'
386
+compare_posix_output "test e exists" '[ -e /tmp ] && echo yes || echo no'
387
+compare_posix_output "test r read" '[ -r /etc/passwd ] && echo yes || echo no'
388
+compare_posix_output "test w write" '[ -w /tmp ] && echo yes || echo no'
389
+compare_posix_output "test x exec" '[ -x /bin/sh ] && echo yes || echo no'
390
+compare_posix_output "test s size" '[ -s /etc/passwd ] && echo yes || echo no'
391
+compare_posix_output "test not exist" '[ -e /nonexistent_xyz ] && echo yes || echo no'
392
+compare_posix_output "test not dir" '[ -d /etc/passwd ] && echo yes || echo no'
393
+compare_posix_output "test not file" '[ -f /tmp ] && echo yes || echo no'
394
+
395
+section "156. LOGICAL COMBINATIONS"
396
+
397
+compare_posix_output "and true true" 'true && true; echo $?'
398
+compare_posix_output "and true false" 'true && false; echo $?'
399
+compare_posix_output "and false true" 'false && true; echo $?'
400
+compare_posix_output "and false false" 'false && false; echo $?'
401
+compare_posix_output "or true true" 'true || true; echo $?'
402
+compare_posix_output "or true false" 'true || false; echo $?'
403
+compare_posix_output "or false true" 'false || true; echo $?'
404
+compare_posix_output "or false false" 'false || false; echo $?'
405
+compare_posix_output "not true" '! true; echo $?'
406
+compare_posix_output "not false" '! false; echo $?'
407
+
408
+section "157. SUBSHELL AND GROUPING"
409
+
410
+compare_posix_output "subshell basic" '(echo sub)'
411
+compare_posix_output "subshell var scope" 'x=outer; (x=inner); echo $x'
412
+compare_posix_output "subshell exit" '(exit 5); echo $?'
413
+compare_posix_output "subshell cd scope" '(cd /tmp); pwd | grep -v "/tmp" | head -1'
414
+compare_posix_output "brace group" '{ echo brace; }'
415
+compare_posix_output "brace group var" 'x=outer; { x=inner; }; echo $x'
416
+compare_posix_output "brace group list" '{ echo a; echo b; } | wc -l'
417
+compare_posix_output "subshell nested" '(echo $(echo nested))'
418
+compare_posix_output "mixed grouping" '(echo sub); { echo brace; }'
419
+compare_posix_output "subshell pipeline" '(echo test) | cat'
420
+
421
+section "158. HEREDOC PATTERNS"
422
+
423
+compare_posix_output "heredoc basic" 'cat <<EOF
424
+test
425
+EOF'
426
+compare_posix_output "heredoc multi" 'cat <<EOF
427
+line1
428
+line2
429
+EOF'
430
+compare_posix_output "heredoc expand" 'X=val; cat <<EOF
431
+$X
432
+EOF'
433
+compare_posix_output "heredoc quoted" "cat <<'EOF'
434
+\$X
435
+EOF"
436
+compare_posix_output "heredoc tab" 'cat <<-EOF
437
+	indented
438
+EOF'
439
+compare_posix_output "heredoc empty" 'cat <<EOF
440
+EOF
441
+echo done'
442
+compare_posix_output "heredoc special" 'cat <<EOF
443
+* $ " '"'"'
444
+EOF'
445
+
446
+section "159. QUOTING EDGE CASES"
447
+
448
+compare_posix_output "double quote var" 'x=val; echo "$x"'
449
+compare_posix_output "single quote literal" "echo 'literal \$x'"
450
+compare_posix_output "escape in double" 'echo "a\\b"'
451
+compare_posix_output "dollar literal" 'echo "\$"'
452
+compare_posix_output "backtick literal" 'echo "\`"'
453
+compare_posix_output "newline in quote" 'echo "line1
454
+line2"'
455
+compare_posix_output "tab in quote" 'echo "a	b"'
456
+compare_posix_output "space preservation" 'echo "a   b"'
457
+compare_posix_output "empty string" 'echo ""'
458
+compare_posix_output "adjacent quotes" 'echo "a""b"'
459
+
460
+section "160. EXPANSION ORDER"
461
+
462
+compare_posix_output "tilde first" 'echo ~ | grep -c "/"'
463
+compare_posix_output "param before cmd" 'x=echo; $x hello'
464
+compare_posix_output "arith in var" 'x=$((1+1)); echo $x'
465
+compare_posix_output "cmd in arith" 'echo $(($(echo 5) + 1))'
466
+compare_posix_output "var in glob" 'x="*"; echo "$x"'
467
+compare_posix_output "split then glob" 'IFS=" "; x="a b"; for i in $x; do echo $i; done | wc -l'
468
+compare_posix_output "quote prevents split" 'x="a b c"; set -- "$x"; echo $#'
469
+compare_posix_output "unquote allows split" 'x="a b c"; set -- $x; echo $#'
470
+compare_posix_output "nested expansion" 'x=y; y=val; eval echo \$$x'
471
+compare_posix_output "complex chain" 'x=$(echo $((1+2))); echo $x'
472
+
473
+section "161. BUILTIN COMMAND VARIATIONS"
474
+
475
+compare_posix_output "echo no newline" 'echo -n test; echo done'
476
+compare_posix_output "echo escape e" 'echo -e "a\tb"'
477
+compare_posix_output "printf string" 'printf "%s\n" hello'
478
+compare_posix_output "printf number" 'printf "%d\n" 42'
479
+compare_posix_output "printf hex" 'printf "%x\n" 255'
480
+compare_posix_output "printf octal" 'printf "%o\n" 64'
481
+compare_posix_output "printf width" 'printf "%5d\n" 42'
482
+compare_posix_output "printf zero pad" 'printf "%05d\n" 42'
483
+compare_posix_output "printf left align" 'printf "%-5d|\n" 42'
484
+compare_posix_output "printf multiple" 'printf "%s %s\n" a b'
485
+
486
+section "162. PRINTF VARIATIONS"
487
+
488
+compare_posix_output "printf basic" 'printf "hello\n"'
489
+compare_posix_output "printf no newline" 'printf "test"; echo done'
490
+compare_posix_output "printf format s" 'printf "%s\n" hello'
491
+compare_posix_output "printf format d" 'printf "%d\n" 42'
492
+compare_posix_output "printf multi arg" 'printf "%s %s\n" a b'
493
+compare_posix_output "printf width" 'printf "%5s\n" ab'
494
+compare_posix_output "printf escape" 'printf "a\tb\n"'
495
+compare_posix_output "printf percent" 'printf "%%\n"'
496
+
497
+section "163. SET BUILTIN VARIATIONS"
498
+
499
+compare_posix_output "set positional" 'set -- a b c; echo $1 $2 $3'
500
+compare_posix_output "set clear" 'set -- a b; set --; echo ${1:-none}'
501
+compare_posix_output "set e exit" 'set -e; true; echo ok'
502
+compare_posix_output "set u unset" 'set +u; echo ${undefined_var:-default}'
503
+compare_posix_output "set f noglob" 'set -f; echo *; set +f'
504
+compare_posix_output "set minus" 'echo $-'
505
+compare_posix_output "set show" 'X=1; set | grep -c "^X=" || echo 0'
506
+compare_posix_output "shift once" 'set -- a b c; shift; echo $1'
507
+compare_posix_output "shift twice" 'set -- a b c; shift 2; echo $1'
508
+compare_posix_output "shift all" 'set -- a b; shift 2; echo ${1:-empty}'
509
+
510
+section "164. EXPORT AND ENV"
511
+
512
+compare_posix_output "export simple" 'export X=val; echo $X'
513
+compare_posix_output "export existing" 'X=val; export X; echo $X'
514
+compare_posix_output "export list" 'export X=1 Y=2; echo $X $Y'
515
+compare_posix_output "export subshell" 'export X=val; (echo $X)'
516
+compare_posix_output "env prefix" 'X=override sh -c "echo \$X"'
517
+compare_posix_output "env clear" 'X=val; unset X; echo ${X:-unset}'
518
+compare_posix_output "readonly var" 'readonly X=const; echo $X'
519
+compare_posix_output "unset normal" 'X=val; unset X; echo ${X:-gone}'
520
+
521
+section "165. TRAP VARIATIONS"
522
+
523
+# Note: Initial trap state may vary by environment - check both produce some output
524
+compare_posix_exit_code "trap list empty" 'trap >/dev/null 2>&1'
525
+compare_posix_output "trap set list" 'trap "echo x" INT; trap | grep -c INT || echo 0'
526
+compare_posix_output "trap reset" 'trap "echo x" INT; trap - INT; trap | grep -c INT || echo 0'
527
+compare_posix_output "trap exit" 'sh -c "trap \"echo exit\" EXIT" 2>/dev/null || echo done'
528
+compare_posix_output "trap ignore" 'trap "" INT; trap | grep -c INT || echo 0'
529
+compare_posix_output "trap in func" 'f() { trap "echo trap" EXIT; }; f 2>/dev/null; echo done'
530
+
531
+section "166. EVAL AND EXEC"
532
+
533
+compare_posix_output "eval simple" 'eval echo hello'
534
+compare_posix_output "eval var" 'X=world; eval echo hello $X'
535
+compare_posix_output "eval cmd" 'eval "echo test"'
536
+compare_posix_output "eval multi" 'eval "echo a; echo b"'
537
+compare_posix_output "eval indirect" 'X=Y; Y=val; eval echo \$$X'
538
+compare_posix_output "exec redirect" 'exec 3>&1; echo test >&3'
539
+compare_posix_output "exec close" 'exec 3>&1; exec 3>&-; echo ok'
540
+
541
+section "167. DOT AND SOURCE"
542
+
543
+compare_posix_output "dot inline" 'echo "X=sourced" > /tmp/src_$$; . /tmp/src_$$; echo $X; rm /tmp/src_$$'
544
+compare_posix_output "dot func" 'echo "f() { echo func; }" > /tmp/src_$$; . /tmp/src_$$; f; rm /tmp/src_$$'
545
+compare_posix_output "dot var persist" 'echo "Y=persist" > /tmp/src_$$; . /tmp/src_$$; echo $Y; rm /tmp/src_$$'
546
+
547
+section "168. COMMAND AND TYPE"
548
+
549
+compare_posix_output "command echo" 'command echo hello'
550
+compare_posix_output "command builtin" 'command true; echo $?'
551
+compare_posix_output "command v" 'command -v echo | grep -c echo'
552
+compare_posix_output "type builtin" 'type echo 2>/dev/null | grep -c echo || echo 1'
553
+compare_posix_output "type not found" 'type nonexistent_cmd_xyz 2>&1 | grep -c "not found" || echo 0'
554
+
555
+section "169. GETOPTS VARIATIONS"
556
+
557
+compare_posix_output "getopts simple" 'set -- -a; getopts a opt; echo $opt'
558
+compare_posix_output "getopts arg" 'set -- -a val; getopts a: opt; echo $opt $OPTARG'
559
+compare_posix_output "getopts multi" 'set -- -a -b; getopts ab opt && echo $opt'
560
+compare_posix_output "getopts unknown" 'set -- -x; getopts a opt 2>/dev/null; echo ${opt:-?}'
561
+compare_posix_output "getopts optind" 'set -- -a arg; getopts a opt; echo $OPTIND'
562
+
563
+section "170. COLON AND TRUE/FALSE"
564
+
565
+compare_posix_output "colon noop" ':; echo $?'
566
+compare_posix_output "colon with args" ': arg1 arg2; echo $?'
567
+compare_posix_output "true exit" 'true; echo $?'
568
+compare_posix_output "false exit" 'false; echo $?'
569
+compare_posix_output "colon in if" 'if :; then echo yes; fi'
570
+compare_posix_output "colon in while" 'n=0; while :; do n=$((n+1)); [ $n -ge 3 ] && break; done; echo $n'
571
+
572
+section "171. PWD AND CD"
573
+
574
+compare_posix_output "pwd basic" 'pwd | grep -c "/"'
575
+compare_posix_output "cd tmp" 'cd /tmp && pwd'
576
+compare_posix_output "cd home" 'cd ~ && pwd | grep -c "/"'
577
+compare_posix_output "cd dash" 'cd /tmp; cd /; cd -'
578
+compare_posix_output "cd dotdot" 'cd /tmp; cd ..; pwd'
579
+compare_posix_output "cd subshell" '(cd /tmp; pwd); pwd | grep -v "/tmp" | wc -l'
580
+compare_posix_output "OLDPWD" 'cd /tmp; cd /; echo $OLDPWD'
581
+
582
+section "172. UMASK VARIATIONS"
583
+
584
+compare_posix_output "umask get" 'umask | grep -c "[0-7]"'
585
+compare_posix_output "umask symbolic" 'umask -S | grep -c "u="'
586
+
587
+section "173. TIMES BUILTIN"
588
+
589
+compare_posix_output "times format" 'times 2>&1 | head -1 | grep -c "[0-9]" || echo 0'
590
+
591
+section "174. HASH BUILTIN"
592
+
593
+compare_posix_output "hash list" 'hash 2>/dev/null; echo $?'
594
+compare_posix_output "hash r clear" 'hash -r 2>/dev/null; echo $?'
595
+
596
+section "175. ALIAS VARIATIONS"
597
+
598
+compare_posix_output "alias set" 'alias x=echo 2>/dev/null; echo ok'
599
+compare_posix_output "alias list" 'alias 2>/dev/null; echo $?'
600
+compare_posix_output "unalias" 'alias x=echo 2>/dev/null; unalias x 2>/dev/null; echo $?'
601
+
602
+section "176. ADDITIONAL BUILTINS"
603
+
604
+compare_posix_output "test bracket" '[ 1 -eq 1 ]; echo $?'
605
+compare_posix_output "test not" '[ ! 1 -eq 2 ]; echo $?'
606
+compare_posix_output "test and" '[ 1 -eq 1 ] && [ 2 -eq 2 ]; echo $?'
607
+
608
+section "177. MORE CONTROL FLOW"
609
+
610
+compare_posix_output "if compound" 'if [ 1 -eq 1 ] && [ 2 -eq 2 ]; then echo yes; fi'
611
+compare_posix_output "while compound" 'n=2; while [ $n -gt 0 ] && true; do n=$((n-1)); done; echo $n'
612
+
613
+section "178. ULIMIT VARIATIONS"
614
+
615
+compare_posix_output "ulimit soft" 'ulimit -S -n 2>/dev/null | grep -c "[0-9]" || echo 1'
616
+compare_posix_output "ulimit hard" 'ulimit -H -n 2>/dev/null | grep -c "[0-9]" || echo 1'
617
+# Note: fortsh implements fewer limit types than bash - check both produce output
618
+compare_posix_exit_code "ulimit all" 'test $(ulimit -a 2>/dev/null | wc -l) -ge 5'
619
+
620
+section "179. SPECIAL EXPANSIONS"
621
+
622
+compare_posix_output "length special" 'set -- a b c; echo ${#@}'
623
+compare_posix_output "length star" 'set -- a b c; echo ${#*}'
624
+compare_posix_output "at in quotes" 'set -- a b c; for x in "$@"; do echo $x; done | wc -l'
625
+compare_posix_output "star in quotes" 'set -- a b c; echo "$*" | wc -w'
626
+compare_posix_output "hash positional" 'set -- a b c d e; echo $#'
627
+compare_posix_output "shift and hash" 'set -- a b c; shift; echo $#'
628
+compare_posix_output "at unquoted" 'set -- a b c; for x in $@; do echo $x; done | wc -l'
629
+compare_posix_output "star unquoted" 'set -- a b c; for x in $*; do echo $x; done | wc -l'
630
+
631
+section "180. COMPLEX PATTERNS"
632
+
633
+compare_posix_output "nested cmd sub" 'echo $(echo $(echo $(echo deep)))'
634
+compare_posix_output "nested arith" 'echo $(( $(( $(( 1+1 )) + 1 )) + 1 ))'
635
+compare_posix_output "mixed nesting" 'echo $(( $(echo 5) + $(echo 3) ))'
636
+compare_posix_output "pipeline chain" 'echo test | cat | cat | cat | cat'
637
+compare_posix_output "long and chain" 'true && true && true && true && echo yes'
638
+compare_posix_output "long or chain" 'false || false || false || true && echo yes'
639
+compare_posix_output "mixed logic" 'true && false || true && echo yes'
640
+# Note: (((( ))) is arithmetic syntax. Compare exit codes.
641
+compare_posix_exit_code "quad paren arithmetic" '((((echo deep))))'
642
+compare_posix_output "nested brace" '{ { { echo deep; }; }; }'
643
+compare_posix_output "func chain" 'f() { echo $1; }; g() { f hello; }; g'
644
+
645
+section "181. WORD BOUNDARY CASES"
646
+
647
+compare_posix_output "empty word" 'echo "" | cat'
648
+compare_posix_output "space word" 'echo " " | cat'
649
+compare_posix_output "tab word" 'echo "	" | cat'
650
+compare_posix_output "newline word" 'echo "
651
+" | wc -l'
652
+compare_posix_output "mixed whitespace" 'echo " 	 " | cat'
653
+compare_posix_output "leading space" 'echo " test" | cat'
654
+compare_posix_output "trailing space" 'echo "test " | cat'
655
+compare_posix_output "multiple spaces" 'echo "a    b" | cat'
656
+
657
+section "182. NUMERIC EDGE CASES"
658
+
659
+compare_posix_output "zero" 'echo $((0))'
660
+compare_posix_output "negative one" 'echo $((-1))'
661
+compare_posix_output "large number" 'echo $((999999))'
662
+compare_posix_output "add negative" 'echo $((5 + -3))'
663
+compare_posix_output "sub negative" 'echo $((5 - -3))'
664
+compare_posix_output "mult negative" 'echo $((5 * -3))'
665
+compare_posix_output "div negative" 'echo $((-15 / 3))'
666
+compare_posix_output "mod negative" 'echo $((-7 % 3))'
667
+compare_posix_output "compare negative" 'echo $((-1 < 0))'
668
+compare_posix_output "zero compare" 'echo $((0 == 0))'
669
+
670
+section "183. STRING BOUNDARY CASES"
671
+
672
+compare_posix_output "empty length" 'x=""; echo ${#x}'
673
+compare_posix_output "single char" 'x="a"; echo ${#x}'
674
+compare_posix_output "special chars" 'x="!@#"; echo ${#x}'
675
+compare_posix_output "spaces length" 'x="a b c"; echo ${#x}'
676
+compare_posix_output "suffix on empty" 'x=""; echo ${x%.txt}'
677
+compare_posix_output "prefix on empty" 'x=""; echo ${x#pre}'
678
+compare_posix_output "default on set" 'x="val"; echo ${x:-def}'
679
+compare_posix_output "alt on unset" 'unset x; echo ${x:+alt}'
680
+compare_posix_output "alt on set" 'x="val"; echo ${x:+alt}'
681
+compare_posix_output "assign on set" 'x="val"; echo ${x:=new}; echo $x'
682
+
683
+section "184. GLOB BOUNDARY CASES"
684
+
685
+compare_posix_output "star only" 'echo "*"'
686
+compare_posix_output "question only" 'echo "?"'
687
+compare_posix_output "bracket only" 'echo "[a]"'
688
+compare_posix_output "glob in quotes" 'echo "*.txt"'
689
+compare_posix_output "glob escaped" 'echo \*'
690
+compare_posix_output "noglob star" 'set -f; echo *; set +f'
691
+compare_posix_output "noglob question" 'set -f; echo ?; set +f'
692
+compare_posix_output "noglob bracket" 'set -f; echo [a]; set +f'
693
+
694
+section "185. REDIRECTION BOUNDARY CASES"
695
+
696
+compare_posix_output "redir empty file" 'echo -n > /tmp/empty_$$; wc -c < /tmp/empty_$$; rm /tmp/empty_$$'
697
+compare_posix_output "append to new" 'rm -f /tmp/new_$$; echo test >> /tmp/new_$$; cat /tmp/new_$$; rm /tmp/new_$$'
698
+compare_posix_output "input from empty" 'echo -n > /tmp/empty_$$; cat < /tmp/empty_$$; echo done; rm /tmp/empty_$$'
699
+compare_posix_output "stderr only" 'echo err >&2 2>&1 | cat'
700
+compare_posix_output "stdout to null" 'echo test > /dev/null; echo $?'
701
+compare_posix_output "stderr to null" 'echo err >&2 2>/dev/null; echo done'
702
+
703
+section "186. PIPELINE BOUNDARY CASES"
704
+
705
+compare_posix_output "empty input pipe" 'echo -n | cat'
706
+compare_posix_output "single char pipe" 'echo a | cat'
707
+compare_posix_output "large pipe" 'seq 1 100 | wc -l'
708
+compare_posix_output "pipe to head 1" 'seq 1 10 | head -1'
709
+compare_posix_output "pipe to tail 1" 'seq 1 10 | tail -1'
710
+compare_posix_output "multi filter" 'seq 1 10 | head -5 | tail -1'
711
+
712
+section "187. LOOP BOUNDARY CASES"
713
+
714
+compare_posix_output "for single item" 'for i in x; do echo $i; done'
715
+compare_posix_output "for no items" 'for i in; do echo $i; done; echo done'
716
+compare_posix_output "while never" 'while false; do echo no; done; echo done'
717
+compare_posix_output "until immediate" 'until true; do echo no; done; echo done'
718
+compare_posix_output "break immediate" 'for i in 1 2 3; do break; echo $i; done; echo done'
719
+compare_posix_output "continue all" 'for i in 1 2 3; do continue; echo $i; done; echo done'
720
+compare_posix_output "nested break" 'for i in 1 2; do for j in a b; do break; done; echo $i; done'
721
+compare_posix_output "break 2" 'for i in 1 2; do for j in a b; do break 2; done; done; echo done'
722
+
723
+section "188. FUNCTION BOUNDARY CASES"
724
+
725
+compare_posix_output "func no args" 'f() { echo ${1:-none}; }; f'
726
+compare_posix_output "func many args" 'f() { echo $#; }; f a b c d e f g h i j'
727
+compare_posix_output "func return max" 'f() { return 255; }; f; echo $?'
728
+compare_posix_output "func return zero" 'f() { return 0; }; f; echo $?'
729
+compare_posix_output "func empty" 'f() { :; }; f; echo $?'
730
+compare_posix_output "func redefine" 'f() { echo one; }; f() { echo two; }; f'
731
+compare_posix_output "func recursive depth" 'f() { [ $1 -eq 0 ] && echo done || f $(($1-1)); }; f 5'
732
+
733
+section "189. CASE BOUNDARY CASES"
734
+
735
+compare_posix_output "case empty string" 'case "" in "") echo empty;; esac'
736
+compare_posix_output "case single char" 'case "x" in x) echo x;; esac'
737
+compare_posix_output "case no patterns" 'case x in esac; echo done'
738
+compare_posix_output "case all patterns" 'case x in a|b|c|x|y) echo match;; esac'
739
+compare_posix_output "case star first" 'case x in *) echo star;; x) echo x;; esac'
740
+compare_posix_output "case question" 'case ab in ??) echo two;; esac'
741
+compare_posix_output "case bracket range" 'case m in [a-z]) echo lower;; esac'
742
+compare_posix_output "case bracket neg" 'case 5 in [!a-z]) echo notlower;; esac'
743
+
744
+section "190. SUBSHELL BOUNDARY CASES"
745
+
746
+compare_posix_output "subshell empty" '(); echo $?'
747
+compare_posix_output "subshell exit 0" '(exit 0); echo $?'
748
+compare_posix_output "subshell exit 1" '(exit 1); echo $?'
749
+compare_posix_output "subshell var" '(X=inner; echo $X)'
750
+compare_posix_output "subshell no leak" 'X=outer; (X=inner); echo $X'
751
+# Note: (( )) is arithmetic syntax. Compare exit codes.
752
+compare_posix_exit_code "subshell nested arithmetic" '((echo deep))'
753
+compare_posix_output "subshell pipe" '(echo test) | (cat)'
754
+compare_posix_output "subshell output" '(echo sub; echo shell) | wc -l'
755
+
756
+section "191. BRACE GROUP BOUNDARY CASES"
757
+
758
+compare_posix_output "brace simple" '{ echo test; }'
759
+compare_posix_output "brace multi" '{ echo a; echo b; }'
760
+compare_posix_output "brace var" '{ X=val; }; echo $X'
761
+compare_posix_output "brace exit" '{ exit 0; }; echo $?'
762
+compare_posix_output "brace pipe" '{ echo test; } | cat'
763
+compare_posix_output "brace nested" '{ { echo deep; }; }'
764
+compare_posix_output "brace and sub" '{ (echo sub); echo brace; }'
765
+compare_posix_output "brace redirect" '{ echo test; } > /tmp/br_$$; cat /tmp/br_$$; rm /tmp/br_$$'
766
+
767
+section "192. COMMENT VARIATIONS"
768
+
769
+compare_posix_output "comment end" 'echo test # comment'
770
+compare_posix_output "comment only" '# just a comment
771
+echo ok'
772
+compare_posix_output "comment in quotes" 'echo "# not comment"'
773
+compare_posix_output "hash in var" 'X="#value"; echo $X'
774
+compare_posix_output "comment after semi" 'echo a; # comment
775
+echo b'
776
+
777
+section "193. LINE CONTINUATION"
778
+
779
+compare_posix_output "backslash newline" 'echo hel\
780
+lo'
781
+compare_posix_output "continuation in cmd" 'ec\
782
+ho test'
783
+compare_posix_output "continuation in arg" 'echo "hel\
784
+lo"'
785
+compare_posix_output "multi continuation" 'echo a\
786
+b\
787
+c'
788
+
789
+section "194. SEMICOLON VARIATIONS"
790
+
791
+compare_posix_output "semi two cmds" 'echo a; echo b'
792
+compare_posix_output "semi three cmds" 'echo a; echo b; echo c'
793
+compare_posix_output "semi with space" 'echo a ; echo b'
794
+compare_posix_output "semi end of line" 'echo test;'
795
+compare_posix_output "semi in quotes" 'echo "a;b"'
796
+compare_posix_output "semi empty" '; echo ok'
797
+
798
+section "195. NEWLINE AS SEPARATOR"
799
+
800
+compare_posix_output "newline sep" 'echo a
801
+echo b'
802
+compare_posix_output "newline in if" 'if true
803
+then
804
+echo yes
805
+fi'
806
+compare_posix_output "newline in for" 'for i in 1 2
807
+do
808
+echo $i
809
+done | wc -l'
810
+compare_posix_output "newline in case" 'case x in
811
+x) echo yes;;
812
+esac'
813
+
814
+section "196. AMPERSAND VARIATIONS"
815
+
816
+compare_posix_output "and simple" 'true && echo yes'
817
+compare_posix_output "and fail" 'false && echo no; echo done'
818
+compare_posix_output "and chain" 'true && true && echo yes'
819
+compare_posix_output "and or mix" 'true && true || echo no'
820
+compare_posix_output "or and mix" 'false || true && echo yes'
821
+compare_posix_output "triple and" 'true && true && true && echo yes'
822
+
823
+section "197. PIPE VARIATIONS"
824
+
825
+compare_posix_output "or simple" 'false || echo yes'
826
+compare_posix_output "or pass" 'true || echo no; echo done'
827
+compare_posix_output "or chain" 'false || false || echo yes'
828
+compare_posix_output "pipe simple" 'echo test | cat'
829
+compare_posix_output "pipe chain" 'echo test | cat | cat'
830
+compare_posix_output "mixed or and" 'false || true && echo yes'
831
+
832
+section "198. PARENTHESES AND BRACES"
833
+
834
+compare_posix_output "paren group" '(echo a; echo b)'
835
+compare_posix_output "brace group" '{ echo a; echo b; }'
836
+# Note: (( )) is arithmetic syntax. Compare exit codes.
837
+compare_posix_exit_code "double paren arithmetic" '((echo deep))'
838
+compare_posix_output "brace in brace" '{ { echo deep; }; }'
839
+compare_posix_output "paren in brace" '{ (echo sub); }'
840
+compare_posix_output "brace in paren" '({ echo brace; })'
841
+
842
+section "199. RESERVED WORD POSITIONS"
843
+
844
+compare_posix_output "if as arg" 'echo if'
845
+compare_posix_output "then as arg" 'echo then'
846
+compare_posix_output "else as arg" 'echo else'
847
+compare_posix_output "fi as arg" 'echo fi'
848
+compare_posix_output "for as arg" 'echo for'
849
+compare_posix_output "while as arg" 'echo while'
850
+compare_posix_output "case as arg" 'echo case'
851
+compare_posix_output "do as arg" 'echo do'
852
+compare_posix_output "done as arg" 'echo done'
853
+
854
+section "200. ASSIGNMENT VARIATIONS"
855
+
856
+compare_posix_output "simple assign" 'X=val; echo $X'
857
+compare_posix_output "empty assign" 'X=; echo "[$X]"'
858
+compare_posix_output "quoted assign" 'X="val"; echo $X'
859
+compare_posix_output "single quote assign" "X='val'; echo \$X"
860
+compare_posix_output "cmd sub assign" 'X=$(echo val); echo $X'
861
+compare_posix_output "arith assign" 'X=$((1+1)); echo $X'
862
+compare_posix_output "multi assign" 'X=1 Y=2; echo $X $Y'
863
+compare_posix_output "prefix assign" 'X=val sh -c "echo \$X"'
864
+compare_posix_output "no space assign" 'X=nospace; echo $X'
865
+
866
+section "201. PARAMETER EXPANSION EDGE CASES"
867
+
868
+compare_posix_output "unset default" 'echo ${UNSET_VAR_XYZ:-default}'
869
+compare_posix_output "set default" 'X=val; echo ${X:-default}'
870
+compare_posix_output "empty default" 'X=; echo ${X:-default}'
871
+compare_posix_output "unset alt" 'echo ${UNSET_VAR_XYZ:+alt}'
872
+compare_posix_output "set alt" 'X=val; echo ${X:+alt}'
873
+compare_posix_output "empty alt" 'X=; echo ${X:+alt}'
874
+compare_posix_output "unset assign" 'echo ${UNSET_VAR_ABC:=assigned}; echo $UNSET_VAR_ABC'
875
+compare_posix_output "set no assign" 'X=val; echo ${X:=new}; echo $X'
876
+compare_posix_output "length zero" 'X=; echo ${#X}'
877
+compare_posix_output "length five" 'X=hello; echo ${#X}'
878
+
879
+section "202. PATTERN MATCHING IN EXPANSION"
880
+
881
+compare_posix_output "suffix percent" 'X=file.txt; echo ${X%.txt}'
882
+compare_posix_output "suffix double" 'X=a.b.c; echo ${X%%.*}'
883
+compare_posix_output "prefix hash" 'X=prefix_name; echo ${X#prefix_}'
884
+compare_posix_output "prefix double" 'X=a.b.c; echo ${X##*.}'
885
+compare_posix_output "no match suffix" 'X=file.txt; echo ${X%.jpg}'
886
+compare_posix_output "no match prefix" 'X=file.txt; echo ${X#img_}'
887
+compare_posix_output "star suffix" 'X=hello; echo ${X%l*}'
888
+compare_posix_output "star prefix" 'X=hello; echo ${X#*l}'
889
+compare_posix_output "question suffix" 'X=hello; echo ${X%?}'
890
+compare_posix_output "question prefix" 'X=hello; echo ${X#?}'
891
+
892
+section "203. ARITHMETIC OPERATORS"
893
+
894
+compare_posix_output "arith add" 'echo $((5 + 3))'
895
+compare_posix_output "arith sub" 'echo $((5 - 3))'
896
+compare_posix_output "arith mul" 'echo $((5 * 3))'
897
+compare_posix_output "arith div" 'echo $((15 / 3))'
898
+compare_posix_output "arith mod" 'echo $((17 % 5))'
899
+compare_posix_output "arith neg" 'echo $((-5))'
900
+compare_posix_output "arith paren" 'echo $(((2 + 3) * 4))'
901
+compare_posix_output "arith var" 'X=5; echo $((X + 1))'
902
+compare_posix_output "arith nested" 'echo $(($((1+1)) + $((2+2))))'
903
+compare_posix_output "arith zero div" 'echo $((0 / 1))'
904
+
905
+section "204. ARITHMETIC COMPARISONS"
906
+
907
+compare_posix_output "arith lt true" 'echo $((1 < 2))'
908
+compare_posix_output "arith lt false" 'echo $((2 < 1))'
909
+compare_posix_output "arith gt true" 'echo $((2 > 1))'
910
+compare_posix_output "arith gt false" 'echo $((1 > 2))'
911
+compare_posix_output "arith le true" 'echo $((1 <= 1))'
912
+compare_posix_output "arith ge true" 'echo $((1 >= 1))'
913
+compare_posix_output "arith eq true" 'echo $((5 == 5))'
914
+compare_posix_output "arith eq false" 'echo $((5 == 6))'
915
+compare_posix_output "arith ne true" 'echo $((5 != 6))'
916
+compare_posix_output "arith ne false" 'echo $((5 != 5))'
917
+
918
+section "205. ARITHMETIC LOGICAL"
919
+
920
+compare_posix_output "arith and true" 'echo $((1 && 1))'
921
+compare_posix_output "arith and false" 'echo $((1 && 0))'
922
+compare_posix_output "arith or true" 'echo $((0 || 1))'
923
+compare_posix_output "arith or false" 'echo $((0 || 0))'
924
+compare_posix_output "arith not true" 'echo $((!0))'
925
+compare_posix_output "arith not false" 'echo $((!1))'
926
+compare_posix_output "arith ternary t" 'echo $((1 ? 10 : 20))'
927
+compare_posix_output "arith ternary f" 'echo $((0 ? 10 : 20))'
928
+compare_posix_output "arith complex" 'echo $(((1 && 1) || 0))'
929
+compare_posix_output "arith nested log" 'echo $((!(0 || 0)))'
930
+
931
+section "206. TEST NUMERIC OPERATORS"
932
+
933
+compare_posix_output "test eq true" '[ 5 -eq 5 ]; echo $?'
934
+compare_posix_output "test eq false" '[ 5 -eq 6 ]; echo $?'
935
+compare_posix_output "test ne true" '[ 5 -ne 6 ]; echo $?'
936
+compare_posix_output "test ne false" '[ 5 -ne 5 ]; echo $?'
937
+compare_posix_output "test lt true" '[ 3 -lt 5 ]; echo $?'
938
+compare_posix_output "test lt false" '[ 5 -lt 3 ]; echo $?'
939
+compare_posix_output "test le true" '[ 5 -le 5 ]; echo $?'
940
+compare_posix_output "test gt true" '[ 5 -gt 3 ]; echo $?'
941
+compare_posix_output "test ge true" '[ 5 -ge 5 ]; echo $?'
942
+compare_posix_output "test zero" '[ 0 -eq 0 ]; echo $?'
943
+
944
+section "207. TEST STRING OPERATORS"
945
+
946
+compare_posix_output "test str eq true" '[ "abc" = "abc" ]; echo $?'
947
+compare_posix_output "test str eq false" '[ "abc" = "def" ]; echo $?'
948
+compare_posix_output "test str ne true" '[ "abc" != "def" ]; echo $?'
949
+compare_posix_output "test str ne false" '[ "abc" != "abc" ]; echo $?'
950
+compare_posix_output "test z true" '[ -z "" ]; echo $?'
951
+compare_posix_output "test z false" '[ -z "x" ]; echo $?'
952
+compare_posix_output "test n true" '[ -n "x" ]; echo $?'
953
+compare_posix_output "test n false" '[ -n "" ]; echo $?'
954
+compare_posix_output "test str space" '[ "a b" = "a b" ]; echo $?'
955
+compare_posix_output "test str empty" '[ "" = "" ]; echo $?'
956
+
957
+section "208. TEST FILE OPERATORS"
958
+
959
+compare_posix_output "test e exist" '[ -e /tmp ]; echo $?'
960
+compare_posix_output "test e noexist" '[ -e /nonexistent_xyz ]; echo $?'
961
+compare_posix_output "test f file" '[ -f /etc/passwd ]; echo $?'
962
+compare_posix_output "test f dir" '[ -f /tmp ]; echo $?'
963
+compare_posix_output "test d dir" '[ -d /tmp ]; echo $?'
964
+compare_posix_output "test d file" '[ -d /etc/passwd ]; echo $?'
965
+compare_posix_output "test r read" '[ -r /etc/passwd ]; echo $?'
966
+compare_posix_output "test w write" '[ -w /tmp ]; echo $?'
967
+compare_posix_output "test x exec" '[ -x /bin/sh ]; echo $?'
968
+compare_posix_output "test s size" '[ -s /etc/passwd ]; echo $?'
969
+
970
+section "209. TEST LOGICAL OPERATORS"
971
+
972
+compare_posix_output "test not true" '[ ! -f /nonexistent ]; echo $?'
973
+compare_posix_output "test not false" '[ ! -d /tmp ]; echo $?'
974
+compare_posix_output "test and true" '[ -d /tmp ] && [ -f /etc/passwd ]; echo $?'
975
+compare_posix_output "test and false" '[ -d /tmp ] && [ -f /nonexistent ]; echo $?'
976
+compare_posix_output "test or true" '[ -f /nonexistent ] || [ -d /tmp ]; echo $?'
977
+compare_posix_output "test or false" '[ -f /nonexistent ] || [ -d /nonexistent ]; echo $?'
978
+compare_posix_output "test complex" '[ -d /tmp ] && [ ! -f /nonexistent ]; echo $?'
979
+compare_posix_output "test chain" '[ 1 -eq 1 ] && [ 2 -eq 2 ] && [ 3 -eq 3 ]; echo $?'
980
+
981
+section "210. HEREDOC VARIATIONS"
982
+
983
+compare_posix_output "heredoc simple" 'cat <<EOF
984
+line
985
+EOF'
986
+compare_posix_output "heredoc multi" 'cat <<EOF
987
+one
988
+two
989
+three
990
+EOF'
991
+compare_posix_output "heredoc empty" 'cat <<EOF
992
+EOF
993
+echo done'
994
+compare_posix_output "heredoc expand" 'X=val; cat <<EOF
995
+$X
996
+EOF'
997
+compare_posix_output "heredoc no expand" "cat <<'EOF'
998
+\$X
999
+EOF"
1000
+compare_posix_output "heredoc indent" 'cat <<-EOF
1001
+	tab
1002
+EOF'
1003
+compare_posix_output "heredoc special" 'cat <<EOF
1004
+* $ @ !
1005
+EOF'
1006
+
1007
+section "211. IFS WORD SPLITTING"
1008
+
1009
+compare_posix_output "ifs split colon" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
1010
+compare_posix_output "ifs split space" 'IFS=" "; x="a b c"; set -- $x; echo $#'
1011
+compare_posix_output "ifs split multi" 'IFS=":;"; x="a:b;c"; set -- $x; echo $#'
1012
+compare_posix_output "ifs default split" 'x="a b c"; set -- $x; echo $#'
1013
+compare_posix_output "ifs empty no split" 'IFS=""; x="a b"; set -- $x; echo $#'
1014
+compare_posix_output "ifs unset default" 'unset IFS; x="a  b"; set -- $x; echo $#'
1015
+compare_posix_output "ifs in for" 'IFS=:; for i in a:b:c; do echo $i; done'
1016
+compare_posix_output "ifs restore" 'OLD="$IFS"; IFS=:; IFS="$OLD"; echo ok'
1017
+
1018
+section "212. WORD SPLITTING"
1019
+
1020
+compare_posix_output "split unquoted" 'x="a b c"; set -- $x; echo $#'
1021
+compare_posix_output "split quoted" 'x="a b c"; set -- "$x"; echo $#'
1022
+compare_posix_output "split ifs" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
1023
+compare_posix_output "split empty" 'x=""; set -- $x; echo $#'
1024
+compare_posix_output "split whitespace" 'x="  a  b  "; set -- $x; echo $#'
1025
+compare_posix_output "split preserve" 'x="a  b"; echo "$x"'
1026
+compare_posix_output "split multiple" 'x="a b" y="c d"; set -- $x $y; echo $#'
1027
+
1028
+section "213. PATHNAME EXPANSION"
1029
+
1030
+compare_posix_output "glob star" 'set -f; echo *; set +f'
1031
+compare_posix_output "glob question" 'set -f; echo ?; set +f'
1032
+compare_posix_output "glob bracket" 'set -f; echo [abc]; set +f'
1033
+compare_posix_output "glob quoted" 'echo "*"'
1034
+compare_posix_output "glob escaped" 'echo \*'
1035
+compare_posix_output "glob noglob" 'set -f; echo *.txt; set +f'
1036
+compare_posix_output "glob in var" 'x="*"; echo "$x"'
1037
+
1038
+section "214. TILDE EXPANSION"
1039
+
1040
+compare_posix_output "tilde alone" 'echo ~ | grep -c "/"'
1041
+compare_posix_output "tilde slash" 'echo ~/ | grep -c "/"'
1042
+compare_posix_output "tilde quoted" 'echo "~"'
1043
+compare_posix_output "tilde in var" 'x=~; echo $x | grep -c "/"'
1044
+compare_posix_output "tilde assign" 'HOME=/tmp; echo ~ | grep -c "/"'
1045
+
1046
+section "215. COMMAND SUBSTITUTION NESTING"
1047
+
1048
+compare_posix_output "cmd sub simple" 'echo $(echo hello)'
1049
+compare_posix_output "cmd sub nested" 'echo $(echo $(echo deep))'
1050
+compare_posix_output "cmd sub triple" 'echo $(echo $(echo $(echo triple)))'
1051
+compare_posix_output "cmd sub arith" 'echo $(echo $((1+1)))'
1052
+compare_posix_output "cmd sub var" 'X=val; echo $(echo $X)'
1053
+compare_posix_output "cmd sub quote" 'echo "$(echo "quoted")"'
1054
+compare_posix_output "backtick simple" 'echo `echo hello`'
1055
+compare_posix_output "backtick nested" 'echo `echo \`echo deep\``'
1056
+
1057
+section "216. PIPELINE ALTERNATIVES"
1058
+
1059
+compare_posix_output "pipe chain filter" 'echo test | grep test | cat'
1060
+compare_posix_output "subshell pipe" '(echo test) | cat'
1061
+compare_posix_output "brace pipe" '{ echo test; } | cat'
1062
+compare_posix_output "pipe to head" 'printf "a\nb\nc\n" | head -2 | wc -l'
1063
+
1064
+section "217. EXIT STATUS"
1065
+
1066
+compare_posix_output "exit 0" 'exit 0'
1067
+compare_posix_output "exit 1" '(exit 1); echo $?'
1068
+compare_posix_output "exit 255" '(exit 255); echo $?'
1069
+compare_posix_output "true status" 'true; echo $?'
1070
+compare_posix_output "false status" 'false; echo $?'
1071
+compare_posix_output "cmd not found" 'nonexistent_cmd_xyz 2>/dev/null; echo $?'
1072
+compare_posix_output "pipe status" 'false | true; echo $?'
1073
+compare_posix_output "and status" 'true && true; echo $?'
1074
+compare_posix_output "or status" 'false || true; echo $?'
1075
+compare_posix_output "not status" '! false; echo $?'
1076
+
1077
+section "218. SIGNAL HANDLING"
1078
+
1079
+# Note: trap output may vary by environment - test exit code
1080
+compare_posix_exit_code "trap list" 'trap >/dev/null 2>&1'
1081
+compare_posix_output "trap set" 'trap "echo caught" INT; trap | grep -c INT || echo 0'
1082
+compare_posix_output "trap reset" 'trap "echo x" INT; trap - INT; trap | grep -c INT || echo 0'
1083
+compare_posix_output "trap ignore" 'trap "" INT; trap | grep -c INT || echo 0'
1084
+compare_posix_output "kill signal" 'kill -l | head -1 | grep -c "[A-Z]" || echo 1'
1085
+
1086
+section "219. ADDITIONAL TESTS"
1087
+
1088
+compare_posix_output "pipeline exit" 'true | false; echo $?'
1089
+compare_posix_output "pipeline true" 'true | true; echo $?'
1090
+compare_posix_output "negation pipe" '! false | true; echo $?'
1091
+compare_posix_output "subshell exit" '(exit 42); echo $?'
1092
+compare_posix_output "brace exit" '{ true; }; echo $?'
1093
+
1094
+section "220. ENVIRONMENT VARIABLES"
1095
+
1096
+compare_posix_output "env home" 'echo ${HOME:-unset} | grep -c "/"'
1097
+compare_posix_output "env path" 'echo ${PATH:-unset} | grep -c ":"'
1098
+compare_posix_output "env pwd" 'echo ${PWD:-unset} | grep -c "/"'
1099
+compare_posix_output "env shell" 'echo ${SHELL:-unset} | grep -c "/" || echo 1'
1100
+compare_posix_output "export var" 'export X=val; sh -c "echo \$X"'
1101
+compare_posix_output "prefix assign" 'X=val sh -c "echo \$X"'
1102
+compare_posix_output "unset env" 'export X=val; unset X; echo ${X:-unset}'
1103
+
1104
+section "221. SPECIAL VARIABLES"
1105
+
1106
+compare_posix_output "dollar zero" 'echo ${0:-shell} | grep -c "."'
1107
+compare_posix_output "dollar hash" 'set -- a b c; echo $#'
1108
+compare_posix_output "dollar question" 'true; echo $?'
1109
+compare_posix_output "dollar dollar" 'echo $$ | grep -c "[0-9]"'
1110
+compare_posix_output "dollar underscore" 'echo test; echo ${_:-none}'
1111
+compare_posix_output "dollar at" 'set -- a b c; echo "$@"'
1112
+compare_posix_output "dollar star" 'set -- a b c; echo "$*"'
1113
+compare_posix_output "dollar minus" 'echo $- | grep -c "."'
1114
+
1115
+section "222. POSITIONAL PARAMETERS"
1116
+
1117
+compare_posix_output "pos one" 'set -- a b c; echo $1'
1118
+compare_posix_output "pos two" 'set -- a b c; echo $2'
1119
+compare_posix_output "pos three" 'set -- a b c; echo $3'
1120
+compare_posix_output "pos shift" 'set -- a b c; shift; echo $1'
1121
+compare_posix_output "pos shift n" 'set -- a b c d e; shift 3; echo $1'
1122
+compare_posix_output "pos set" 'set -- x y z; echo $1 $2 $3'
1123
+compare_posix_output "pos clear" 'set -- a b c; set --; echo ${1:-empty}'
1124
+compare_posix_output "pos count" 'set -- a b c d e; echo $#'
1125
+compare_posix_output "pos all at" 'set -- a b c; for x in "$@"; do echo $x; done | wc -l'
1126
+compare_posix_output "pos all star" 'set -- a b c; echo "$*" | wc -w'
1127
+
1128
+section "223. FUNCTION DEFINITION STYLES"
1129
+
1130
+compare_posix_output "func keyword" 'f() { echo hello; }; f'
1131
+compare_posix_output "func oneline" 'f() { echo one; }; f'
1132
+compare_posix_output "func multiline" 'f() {
1133
+echo multi
1134
+}; f'
1135
+compare_posix_output "func with args" 'f() { echo $1 $2; }; f a b'
1136
+compare_posix_output "func return" 'f() { return 5; }; f; echo $?'
1137
+compare_posix_output "func local var" 'X=outer; f() { X=inner; }; f; echo $X'
1138
+compare_posix_output "func recursive" 'f() { [ $1 -eq 0 ] && echo done || f $(($1-1)); }; f 3'
1139
+
1140
+section "224. FUNCTION SCOPE"
1141
+
1142
+compare_posix_output "func global var" 'X=global; f() { echo $X; }; f'
1143
+compare_posix_output "func modify var" 'X=old; f() { X=new; }; f; echo $X'
1144
+compare_posix_output "func args" 'f() { echo $#; }; f a b c'
1145
+compare_posix_output "func positional" 'f() { echo $1; }; set -- x; f a; echo $1'
1146
+compare_posix_output "func in subshell" 'f() { echo func; }; (f)'
1147
+compare_posix_output "func in pipeline" 'f() { echo test; }; f | cat'
1148
+compare_posix_output "func redefine" 'f() { echo one; }; f; f() { echo two; }; f'
1149
+
1150
+section "225. ALIAS BASICS"
1151
+
1152
+compare_posix_output "alias define" 'alias x=echo 2>/dev/null; echo ok'
1153
+compare_posix_output "alias list" 'alias 2>/dev/null; echo $?'
1154
+compare_posix_output "unalias" 'alias x=echo 2>/dev/null; unalias x 2>/dev/null; echo $?'
1155
+compare_posix_output "unalias all" 'unalias -a 2>/dev/null; echo $?'
1156
+
1157
+section "226. CONTROL STRUCTURES COMPREHENSIVE"
1158
+
1159
+compare_posix_output "if basic" 'if true; then echo yes; fi'
1160
+compare_posix_output "if else" 'if false; then echo no; else echo yes; fi'
1161
+compare_posix_output "if elif" 'if false; then echo 1; elif true; then echo 2; fi'
1162
+compare_posix_output "if nested" 'if true; then if true; then echo deep; fi; fi'
1163
+compare_posix_output "for basic" 'for i in 1 2 3; do echo $i; done | wc -l'
1164
+compare_posix_output "for empty" 'for i in; do echo $i; done; echo done'
1165
+compare_posix_output "while basic" 'n=3; while [ $n -gt 0 ]; do echo $n; n=$((n-1)); done | wc -l'
1166
+compare_posix_output "until basic" 'n=0; until [ $n -eq 3 ]; do n=$((n+1)); done; echo $n'
1167
+compare_posix_output "case basic" 'case x in x) echo yes;; esac'
1168
+compare_posix_output "case default" 'case x in y) echo no;; *) echo default;; esac'
1169
+
1170
+section "227. BREAK AND CONTINUE"
1171
+
1172
+compare_posix_output "break simple" 'for i in 1 2 3; do [ $i -eq 2 ] && break; echo $i; done'
1173
+compare_posix_output "continue simple" 'for i in 1 2 3; do [ $i -eq 2 ] && continue; echo $i; done'
1174
+compare_posix_output "break nested" 'for i in 1 2; do for j in a b; do break; done; echo $i; done'
1175
+compare_posix_output "break 2" 'for i in 1 2; do for j in a b; do break 2; done; done; echo done'
1176
+compare_posix_output "continue nested" 'for i in 1 2; do for j in a b; do continue 2; done; echo no; done; echo done'
1177
+
1178
+section "228. RETURN AND EXIT"
1179
+
1180
+compare_posix_output "return 0" 'f() { return 0; }; f; echo $?'
1181
+compare_posix_output "return 1" 'f() { return 1; }; f; echo $?'
1182
+compare_posix_output "return 255" 'f() { return 255; }; f; echo $?'
1183
+compare_posix_output "exit subshell" '(exit 5); echo $?'
1184
+compare_posix_output "exit brace" '{ exit 0; }; echo unreached'
1185
+compare_posix_output "return implicit" 'f() { true; }; f; echo $?'
1186
+
1187
+section "229. COMPOUND COMMANDS"
1188
+
1189
+compare_posix_output "paren list" '(echo a; echo b) | wc -l'
1190
+compare_posix_output "brace list" '{ echo a; echo b; } | wc -l'
1191
+compare_posix_output "if in paren" '(if true; then echo yes; fi)'
1192
+compare_posix_output "for in brace" '{ for i in 1 2; do echo $i; done; } | wc -l'
1193
+compare_posix_output "while in paren" '(n=2; while [ $n -gt 0 ]; do echo $n; n=$((n-1)); done) | wc -l'
1194
+compare_posix_output "case in brace" '{ case x in x) echo yes;; esac; }'
1195
+compare_posix_output "func in paren" '(f() { echo func; }; f)'
1196
+
1197
+section "230. COMPLEX COMMAND COMBINATIONS"
1198
+
1199
+compare_posix_output "for pipe filter" 'for i in 1 2 3 4 5; do echo $i; done | head -3 | wc -l'
1200
+compare_posix_output "case in for" 'for x in a b c; do case $x in a) echo first;; esac; done'
1201
+compare_posix_output "if in while" 'n=2; while [ $n -gt 0 ]; do if [ $n -eq 1 ]; then echo one; fi; n=$((n-1)); done'
1202
+compare_posix_output "nested for" 'for i in 1 2; do for j in a b; do echo $i$j; done; done | wc -l'
1203
+compare_posix_output "subshell in for" 'for i in 1 2; do (echo $i); done | wc -l'
1204
+compare_posix_output "func in for" 'f() { echo $1; }; for i in a b c; do f $i; done | wc -l'
1205
+compare_posix_output "pipe to sort" 'printf "c\na\nb\n" | sort | head -1'
1206
+# Note: (( )) is arithmetic syntax. Compare exit codes.
1207
+compare_posix_exit_code "nested subshell arithmetic" '((echo deep))'
1208
+
1209
+section "231. EXPR COMMAND"
1210
+
1211
+compare_posix_output "expr add" 'expr 5 + 3'
1212
+compare_posix_output "expr sub" 'expr 10 - 3'
1213
+compare_posix_output "expr mul" 'expr 4 \* 5'
1214
+compare_posix_output "expr div" 'expr 20 / 4'
1215
+compare_posix_output "expr mod" 'expr 17 % 5'
1216
+compare_posix_output "expr compare lt" 'expr 3 \< 5'
1217
+compare_posix_output "expr compare gt" 'expr 5 \> 3'
1218
+compare_posix_output "expr compare eq" 'expr 5 = 5'
1219
+compare_posix_output "expr string" 'expr length hello'
1220
+compare_posix_output "expr substr" 'expr substr hello 2 3'
1221
+
1222
+section "232. BASENAME AND DIRNAME"
1223
+
1224
+compare_posix_output "basename simple" 'basename /path/to/file'
1225
+compare_posix_output "basename suffix" 'basename /path/to/file.txt .txt'
1226
+compare_posix_output "basename no path" 'basename file.txt'
1227
+compare_posix_output "dirname simple" 'dirname /path/to/file'
1228
+compare_posix_output "dirname root" 'dirname /file'
1229
+compare_posix_output "dirname relative" 'dirname file'
1230
+compare_posix_output "dirname dot" 'dirname ./file'
1231
+compare_posix_output "dirname trailing" 'dirname /path/to/'
1232
+
1233
+section "233. CUT COMMAND"
1234
+
1235
+compare_posix_output "cut field" 'echo "a:b:c" | cut -d: -f2'
1236
+compare_posix_output "cut fields" 'echo "a:b:c" | cut -d: -f1,3'
1237
+compare_posix_output "cut chars" 'echo "hello" | cut -c1-3'
1238
+compare_posix_output "cut char single" 'echo "hello" | cut -c2'
1239
+compare_posix_output "cut range" 'echo "a:b:c:d" | cut -d: -f2-3'
1240
+
1241
+section "234. TR COMMAND"
1242
+
1243
+compare_posix_output "tr simple" 'echo abc | tr a-z A-Z'
1244
+compare_posix_output "tr delete" 'echo "a1b2c3" | tr -d 0-9'
1245
+compare_posix_output "tr squeeze" 'echo "aabbcc" | tr -s a-z'
1246
+compare_posix_output "tr single" 'echo abc | tr a x'
1247
+compare_posix_output "tr complement" 'echo "a1b2" | tr -cd a-z'
1248
+
1249
+section "235. SORT COMMAND"
1250
+
1251
+compare_posix_output "sort lines" 'printf "c\na\nb\n" | sort'
1252
+compare_posix_output "sort reverse" 'printf "a\nb\nc\n" | sort -r'
1253
+compare_posix_output "sort numeric" 'printf "10\n2\n1\n" | sort -n'
1254
+compare_posix_output "sort unique" 'printf "a\na\nb\n" | sort -u'
1255
+compare_posix_output "sort first" 'printf "c\na\nb\n" | sort | head -1'
1256
+
1257
+section "236. UNIQ COMMAND"
1258
+
1259
+compare_posix_output "uniq simple" 'printf "a\na\nb\n" | uniq'
1260
+compare_posix_output "uniq count" 'printf "a\na\nb\n" | uniq -c | wc -l'
1261
+compare_posix_output "uniq duplicate" 'printf "a\na\nb\n" | uniq -d'
1262
+compare_posix_output "uniq unique" 'printf "a\na\nb\n" | uniq -u'
1263
+
1264
+section "237. WC COMMAND"
1265
+
1266
+compare_posix_output "wc lines" 'printf "a\nb\nc\n" | wc -l'
1267
+compare_posix_output "wc words" 'echo "one two three" | wc -w'
1268
+compare_posix_output "wc chars" 'echo "hello" | wc -c'
1269
+compare_posix_output "wc empty" 'echo "" | wc -l'
1270
+
1271
+section "238. HEAD AND TAIL"
1272
+
1273
+compare_posix_output "head default" 'seq 1 20 | head | wc -l'
1274
+compare_posix_output "head n" 'seq 1 10 | head -3'
1275
+compare_posix_output "tail default" 'seq 1 20 | tail | wc -l'
1276
+compare_posix_output "tail n" 'seq 1 10 | tail -3'
1277
+compare_posix_output "head tail combo" 'seq 1 10 | head -5 | tail -1'
1278
+
1279
+section "239. GREP PATTERNS"
1280
+
1281
+compare_posix_output "grep simple" 'echo hello | grep hello'
1282
+compare_posix_output "grep no match" 'echo hello | grep xyz; echo $?'
1283
+compare_posix_output "grep count" 'printf "a\nb\na\n" | grep -c a'
1284
+compare_posix_output "grep ignore case" 'echo HELLO | grep -i hello'
1285
+compare_posix_output "grep invert" 'printf "a\nb\n" | grep -v a'
1286
+compare_posix_output "grep line" 'printf "abc\ndef\n" | grep -n abc'
1287
+
1288
+section "240. SED BASICS"
1289
+
1290
+compare_posix_output "sed substitute" 'echo hello | sed "s/hello/world/"'
1291
+compare_posix_output "sed global" 'echo "aaa" | sed "s/a/b/g"'
1292
+compare_posix_output "sed delete" 'printf "a\nb\nc\n" | sed "1d" | wc -l'
1293
+compare_posix_output "sed print" 'printf "a\nb\n" | sed -n "1p"'
1294
+compare_posix_output "sed range" 'printf "a\nb\nc\n" | sed "1,2d"'
1295
+
1296
+section "241. AWK BASICS"
1297
+
1298
+compare_posix_output "awk print" 'echo "a b c" | awk "{print \$2}"'
1299
+compare_posix_output "awk field" 'echo "a:b:c" | awk -F: "{print \$2}"'
1300
+compare_posix_output "awk NF" 'echo "a b c" | awk "{print NF}"'
1301
+compare_posix_output "awk NR" 'printf "a\nb\n" | awk "{print NR}"'
1302
+compare_posix_output "awk math" 'echo "5 3" | awk "{print \$1 + \$2}"'
1303
+
1304
+section "242. TEE COMMAND"
1305
+
1306
+compare_posix_output "tee output" 'echo test | tee /dev/null'
1307
+compare_posix_output "tee passthrough" 'echo hello | tee /dev/null | cat'
1308
+
1309
+section "243. XARGS BASICS"
1310
+
1311
+compare_posix_output "xargs echo" 'echo "a b c" | xargs echo'
1312
+compare_posix_output "xargs n1" 'printf "a\nb\nc\n" | xargs -n1 echo | wc -l'
1313
+
1314
+section "244. FIND BASICS"
1315
+
1316
+compare_posix_output "find type d" 'find /tmp -maxdepth 1 -type d 2>/dev/null | head -1 | grep -c "/"'
1317
+compare_posix_output "find name" 'find /etc -maxdepth 1 -name "passwd" 2>/dev/null | grep -c passwd || echo 0'
1318
+
1319
+section "245. TEST COMMAND FORMS"
1320
+
1321
+compare_posix_output "test bracket" '[ 1 -eq 1 ]; echo $?'
1322
+compare_posix_output "test keyword" 'test 1 -eq 1; echo $?'
1323
+compare_posix_output "test string" '[ "a" = "a" ]; echo $?'
1324
+compare_posix_output "test empty" '[ -z "" ]; echo $?'
1325
+compare_posix_output "test nonempty" '[ -n "x" ]; echo $?'
1326
+compare_posix_output "test not" '[ ! 1 -eq 2 ]; echo $?'
1327
+compare_posix_output "test and ext" '[ 1 -eq 1 -a 2 -eq 2 ]; echo $?'
1328
+compare_posix_output "test or ext" '[ 1 -eq 2 -o 2 -eq 2 ]; echo $?'
1329
+
1330
+section "246. ARITHMETIC BITWISE"
1331
+
1332
+compare_posix_output "arith and" 'echo $((5 & 3))'
1333
+compare_posix_output "arith or" 'echo $((5 | 3))'
1334
+compare_posix_output "arith xor" 'echo $((5 ^ 3))'
1335
+compare_posix_output "arith not" 'echo $((~0))'
1336
+compare_posix_output "arith lshift" 'echo $((1 << 4))'
1337
+compare_posix_output "arith rshift" 'echo $((16 >> 2))'
1338
+
1339
+section "247. COMPLEX CASE PATTERNS"
1340
+
1341
+compare_posix_output "case or pattern" 'case abc in a*|b*) echo match;; esac'
1342
+compare_posix_output "case bracket" 'case a in [abc]) echo match;; esac'
1343
+compare_posix_output "case negbracket" 'case d in [!abc]) echo match;; esac'
1344
+compare_posix_output "case question" 'case ab in ??) echo two;; esac'
1345
+compare_posix_output "case star" 'case anything in *) echo match;; esac'
1346
+compare_posix_output "case empty" 'case "" in "") echo empty;; esac'
1347
+compare_posix_output "case number" 'case 42 in [0-9]*) echo num;; esac'
1348
+
1349
+section "248. COMPLEX FOR LOOPS"
1350
+
1351
+compare_posix_output "for glob safe" 'set -f; for i in *; do echo "$i"; done; set +f'
1352
+compare_posix_output "for break early" 'for i in 1 2 3 4 5; do echo $i; [ $i -eq 3 ] && break; done | wc -l'
1353
+compare_posix_output "for continue skip" 'for i in 1 2 3; do [ $i -eq 2 ] && continue; echo $i; done | wc -l'
1354
+compare_posix_output "for nested count" 'for i in 1 2; do for j in a b c; do echo x; done; done | wc -l'
1355
+compare_posix_output "for empty list" 'for i in; do echo never; done; echo done'
1356
+compare_posix_output "for single" 'for i in only; do echo $i; done'
1357
+
1358
+section "249. COMPLEX WHILE LOOPS"
1359
+
1360
+compare_posix_output "while decrement" 'n=5; while [ $n -gt 0 ]; do n=$((n-1)); done; echo $n'
1361
+compare_posix_output "while false" 'while false; do echo never; done; echo done'
1362
+compare_posix_output "while break" 'n=0; while true; do n=$((n+1)); [ $n -ge 3 ] && break; done; echo $n'
1363
+compare_posix_output "while nested" 'i=2; while [ $i -gt 0 ]; do j=2; while [ $j -gt 0 ]; do echo x; j=$((j-1)); done; i=$((i-1)); done | wc -l'
1364
+compare_posix_output "until true" 'until true; do echo never; done; echo done'
1365
+compare_posix_output "until count" 'n=0; until [ $n -ge 3 ]; do n=$((n+1)); done; echo $n'
1366
+
1367
+section "250. COMPLEX FUNCTIONS"
1368
+
1369
+compare_posix_output "func args count" 'f() { echo $#; }; f a b c d e'
1370
+compare_posix_output "func all args" 'f() { echo "$@"; }; f a b c'
1371
+compare_posix_output "func star args" 'f() { echo "$*"; }; f a b c'
1372
+compare_posix_output "func return val" 'f() { return 42; }; f; echo $?'
1373
+compare_posix_output "func modify global" 'x=old; f() { x=new; }; f; echo $x'
1374
+compare_posix_output "func recursive" 'f() { [ $1 -le 1 ] && echo 1 || echo $(($(f $(($1-1))) + $(f $(($1-2))))); }; f 5'
1375
+compare_posix_output "func in subshell" 'f() { echo inner; }; (f)'
1376
+compare_posix_output "func pipe" 'f() { echo test; }; f | cat'
1377
+compare_posix_output "func empty" 'f() { :; }; f; echo $?'
1378
+compare_posix_output "func chain" 'f() { echo $1; }; g() { f hello; }; g'
1379
+
1380
+section "251. SHELL ARITHMETIC EDGE CASES"
1381
+
1382
+compare_posix_output "arith octal" 'echo $((010))'
1383
+compare_posix_output "arith hex" 'echo $((0x10))'
1384
+compare_posix_output "arith unary plus" 'echo $((+5))'
1385
+compare_posix_output "arith unary minus" 'echo $((-5))'
1386
+compare_posix_output "arith double neg" 'echo $((--5))'
1387
+compare_posix_output "arith complex" 'echo $(((1+2)*(3+4)))'
1388
+compare_posix_output "arith assign" 'x=5; echo $((x=x+1)); echo $x'
1389
+compare_posix_output "arith incr" 'x=5; echo $((x+=1))'
1390
+compare_posix_output "arith decr" 'x=5; echo $((x-=1))'
1391
+
1392
+section "252. VARIABLE EDGE CASES"
1393
+
1394
+compare_posix_output "var underscore" '_x=val; echo $_x'
1395
+compare_posix_output "var number suffix" 'x1=val; echo $x1'
1396
+compare_posix_output "var long name" 'very_long_variable_name_here=val; echo $very_long_variable_name_here'
1397
+compare_posix_output "var empty val" 'x=; echo "[$x]"'
1398
+compare_posix_output "var space val" 'x="a b"; echo "$x"'
1399
+compare_posix_output "var newline val" 'x="a
1400
+b"; echo "$x" | wc -l'
1401
+compare_posix_output "var special chars" 'x="!@#"; echo "$x"'
1402
+compare_posix_output "var equals in val" 'x="a=b"; echo "$x"'
1403
+
1404
+section "253. QUOTING EDGE CASES"
1405
+
1406
+compare_posix_output "quote empty" 'echo ""'
1407
+compare_posix_output "quote space" 'echo " "'
1408
+compare_posix_output "quote tab" 'echo "	"'
1409
+compare_posix_output "quote newline" 'echo "
1410
+"'
1411
+compare_posix_output "quote dollar" 'echo "\$"'
1412
+compare_posix_output "quote backslash" 'echo "\\"'
1413
+compare_posix_output "quote backtick" 'echo "\`"'
1414
+compare_posix_output "quote double" 'echo "\""'
1415
+compare_posix_output "single in double" 'echo "'"'"'"'
1416
+compare_posix_output "double in single" "echo '\"'"
1417
+
1418
+section "254. PARAMETER EXPANSION COMPREHENSIVE"
1419
+
1420
+compare_posix_output "param default unset" 'echo ${undef:-default}'
1421
+compare_posix_output "param default empty" 'x=; echo ${x:-default}'
1422
+compare_posix_output "param default set" 'x=val; echo ${x:-default}'
1423
+compare_posix_output "param alt unset" 'echo ${undef:+alt}'
1424
+compare_posix_output "param alt empty" 'x=; echo ${x:+alt}'
1425
+compare_posix_output "param alt set" 'x=val; echo ${x:+alt}'
1426
+compare_posix_output "param assign unset" 'unset y; echo ${y:=assigned}; echo $y'
1427
+compare_posix_output "param length" 'x=hello; echo ${#x}'
1428
+compare_posix_output "param suffix" 'x=file.txt; echo ${x%.txt}'
1429
+compare_posix_output "param prefix" 'x=prefix_name; echo ${x#prefix_}'
1430
+
1431
+section "255. REDIRECTION COMPREHENSIVE"
1432
+
1433
+compare_posix_output "redir stdout" 'echo test > /tmp/r$$; cat /tmp/r$$; rm /tmp/r$$'
1434
+compare_posix_output "redir append" 'echo a > /tmp/r$$; echo b >> /tmp/r$$; cat /tmp/r$$; rm /tmp/r$$'
1435
+compare_posix_output "redir stdin" 'echo test > /tmp/r$$; cat < /tmp/r$$; rm /tmp/r$$'
1436
+compare_posix_output "redir stderr" 'echo err >&2 2>/dev/null; echo ok'
1437
+compare_posix_output "redir fd dup" 'echo test 2>&1 | cat'
1438
+compare_posix_output "redir devnull" 'echo test > /dev/null; echo $?'
1439
+compare_posix_output "redir clobber" 'echo a > /tmp/r$$; echo b > /tmp/r$$; cat /tmp/r$$; rm /tmp/r$$'
1440
+
1441
+section "256. HEREDOC COMPREHENSIVE"
1442
+
1443
+compare_posix_output "heredoc basic" 'cat <<EOF
1444
+line
1445
+EOF'
1446
+compare_posix_output "heredoc multi" 'cat <<EOF
1447
+one
1448
+two
1449
+EOF'
1450
+compare_posix_output "heredoc var" 'x=val; cat <<EOF
1451
+$x
1452
+EOF'
1453
+compare_posix_output "heredoc quoted" "cat <<'EOF'
1454
+\$x
1455
+EOF"
1456
+compare_posix_output "heredoc tab strip" 'cat <<-EOF
1457
+	indented
1458
+EOF'
1459
+
1460
+section "257. PIPELINE COMPREHENSIVE"
1461
+
1462
+compare_posix_output "pipe two" 'echo test | cat'
1463
+compare_posix_output "pipe three" 'echo test | cat | cat'
1464
+compare_posix_output "pipe four" 'echo test | cat | cat | cat'
1465
+compare_posix_output "pipe filter" 'echo hello | grep h'
1466
+compare_posix_output "pipe transform" 'echo abc | tr a-z A-Z'
1467
+compare_posix_output "pipe count" 'printf "a\nb\nc\n" | wc -l'
1468
+compare_posix_output "pipe subshell" '(echo test) | cat'
1469
+compare_posix_output "pipe brace" '{ echo test; } | cat'
1470
+
1471
+section "258. LOGICAL OPERATORS COMPREHENSIVE"
1472
+
1473
+compare_posix_output "and tt" 'true && true; echo $?'
1474
+compare_posix_output "and tf" 'true && false; echo $?'
1475
+compare_posix_output "and ft" 'false && true; echo $?'
1476
+compare_posix_output "and ff" 'false && false; echo $?'
1477
+compare_posix_output "or tt" 'true || true; echo $?'
1478
+compare_posix_output "or tf" 'true || false; echo $?'
1479
+compare_posix_output "or ft" 'false || true; echo $?'
1480
+compare_posix_output "or ff" 'false || false; echo $?'
1481
+compare_posix_output "not t" '! true; echo $?'
1482
+compare_posix_output "not f" '! false; echo $?'
1483
+compare_posix_output "mixed 1" 'true && false || echo fallback'
1484
+compare_posix_output "mixed 2" 'false || true && echo success'
1485
+
1486
+section "259. SUBSHELL COMPREHENSIVE"
1487
+
1488
+compare_posix_output "sub echo" '(echo sub)'
1489
+compare_posix_output "sub var" '(x=inner; echo $x)'
1490
+compare_posix_output "sub no leak" 'x=outer; (x=inner); echo $x'
1491
+compare_posix_output "sub exit" '(exit 5); echo $?'
1492
+compare_posix_output "sub cd" '(cd /tmp; pwd)'
1493
+compare_posix_output "sub pipe" '(echo a; echo b) | wc -l'
1494
+# Note: (( )) is arithmetic syntax. Compare exit codes.
1495
+compare_posix_exit_code "sub nested arithmetic" '((echo deep))'
1496
+compare_posix_output "sub multi" '(echo a); (echo b)'
1497
+
1498
+section "260. BRACE GROUP COMPREHENSIVE"
1499
+
1500
+compare_posix_output "brace echo" '{ echo brace; }'
1501
+compare_posix_output "brace multi" '{ echo a; echo b; }'
1502
+compare_posix_output "brace var" '{ x=val; }; echo $x'
1503
+compare_posix_output "brace pipe" '{ echo test; } | cat'
1504
+compare_posix_output "brace redir" '{ echo test; } > /tmp/b$$; cat /tmp/b$$; rm /tmp/b$$'
1505
+compare_posix_output "brace nested" '{ { echo deep; }; }'
1506
+compare_posix_output "brace and sub" '{ (echo sub); }'
1507
+
1508
+section "261. SPECIAL CHARACTERS"
1509
+
1510
+compare_posix_output "char star" 'echo "*"'
1511
+compare_posix_output "char question" 'echo "?"'
1512
+compare_posix_output "char bracket" 'echo "[]"'
1513
+compare_posix_output "char brace" 'echo "{}"'
1514
+compare_posix_output "char paren" 'echo "()"'
1515
+compare_posix_output "char pipe" 'echo "|"'
1516
+compare_posix_output "char amp" 'echo "&"'
1517
+compare_posix_output "char semi" 'echo ";"'
1518
+compare_posix_output "char dollar" 'echo "\$"'
1519
+compare_posix_output "char hash" 'echo "#"'
1520
+
1521
+section "262. COMMAND LINE PARSING"
1522
+
1523
+compare_posix_output "parse simple" 'echo hello'
1524
+compare_posix_output "parse multi arg" 'echo a b c'
1525
+compare_posix_output "parse quoted arg" 'echo "a b c"'
1526
+compare_posix_output "parse mixed" 'echo a "b c" d'
1527
+compare_posix_output "parse empty arg" 'echo "" x'
1528
+compare_posix_output "parse escape" 'echo a\ b'
1529
+compare_posix_output "parse continued" 'echo hel\
1530
+lo'
1531
+
1532
+section "263. EXECUTION CONTEXT"
1533
+
1534
+compare_posix_output "exec subshell" '(echo sub)'
1535
+compare_posix_output "exec pipeline" 'echo test | cat'
1536
+compare_posix_output "exec background" 'true & echo fg'
1537
+compare_posix_output "exec compound" '{ echo a; echo b; }'
1538
+compare_posix_output "exec function" 'f() { echo func; }; f'
1539
+compare_posix_output "exec builtin" 'echo hello'
1540
+compare_posix_output "exec external" '/bin/echo hello'
1541
+
1542
+section "264. STRING COMPARISON"
1543
+
1544
+compare_posix_output "str eq" '[ "a" = "a" ]; echo $?'
1545
+compare_posix_output "str ne" '[ "a" != "b" ]; echo $?'
1546
+compare_posix_output "str lt" '[ "a" \< "b" ]; echo $?'
1547
+compare_posix_output "str gt" '[ "b" \> "a" ]; echo $?'
1548
+compare_posix_output "str empty" '[ -z "" ]; echo $?'
1549
+compare_posix_output "str nonempty" '[ -n "x" ]; echo $?'
1550
+compare_posix_output "str space" '[ "a b" = "a b" ]; echo $?'
1551
+
1552
+section "265. NUMERIC COMPARISON"
1553
+
1554
+compare_posix_output "num eq" '[ 5 -eq 5 ]; echo $?'
1555
+compare_posix_output "num ne" '[ 5 -ne 6 ]; echo $?'
1556
+compare_posix_output "num lt" '[ 3 -lt 5 ]; echo $?'
1557
+compare_posix_output "num gt" '[ 5 -gt 3 ]; echo $?'
1558
+compare_posix_output "num le" '[ 5 -le 5 ]; echo $?'
1559
+compare_posix_output "num ge" '[ 5 -ge 5 ]; echo $?'
1560
+compare_posix_output "num zero" '[ 0 -eq 0 ]; echo $?'
1561
+compare_posix_output "num neg" '[ -1 -lt 0 ]; echo $?'
1562
+
1563
+section "266. FILE TESTS COMPREHENSIVE"
1564
+
1565
+compare_posix_output "file e" '[ -e /tmp ]; echo $?'
1566
+compare_posix_output "file f" '[ -f /etc/passwd ]; echo $?'
1567
+compare_posix_output "file d" '[ -d /tmp ]; echo $?'
1568
+compare_posix_output "file r" '[ -r /etc/passwd ]; echo $?'
1569
+compare_posix_output "file w" '[ -w /tmp ]; echo $?'
1570
+compare_posix_output "file x" '[ -x /bin/sh ]; echo $?'
1571
+compare_posix_output "file s" '[ -s /etc/passwd ]; echo $?'
1572
+compare_posix_output "file L" '[ -L /dev/stdin ] 2>/dev/null; echo $?'
1573
+
1574
+section "267. PRINTF FORMAT SPECIFIERS"
1575
+
1576
+compare_posix_output "printf s" 'printf "%s\n" hello'
1577
+compare_posix_output "printf d" 'printf "%d\n" 42'
1578
+compare_posix_output "printf i" 'printf "%i\n" 42'
1579
+compare_posix_output "printf o" 'printf "%o\n" 8'
1580
+compare_posix_output "printf x" 'printf "%x\n" 255'
1581
+compare_posix_output "printf X" 'printf "%X\n" 255'
1582
+compare_posix_output "printf c" 'printf "%c\n" A'
1583
+compare_posix_output "printf percent" 'printf "%%\n"'
1584
+
1585
+section "268. PRINTF WIDTH AND PRECISION"
1586
+
1587
+compare_posix_output "printf width" 'printf "%5d\n" 42'
1588
+compare_posix_output "printf zero" 'printf "%05d\n" 42'
1589
+compare_posix_output "printf left" 'printf "%-5d|\n" 42'
1590
+compare_posix_output "printf prec" 'printf "%.3s\n" hello'
1591
+compare_posix_output "printf both" 'printf "%8.3s\n" hello'
1592
+
1593
+section "269. ECHO OPTIONS"
1594
+
1595
+compare_posix_output "echo simple" 'echo hello'
1596
+compare_posix_output "echo multi" 'echo a b c'
1597
+compare_posix_output "echo n" 'echo -n test; echo done'
1598
+compare_posix_output "echo e tab" 'echo -e "a\tb"'
1599
+compare_posix_output "echo e nl" 'echo -e "a\nb" | wc -l'
1600
+
1601
+section "270. ENVIRONMENT MANIPULATION"
1602
+
1603
+compare_posix_output "env set" 'X=val; echo $X'
1604
+compare_posix_output "env export" 'export X=val; echo $X'
1605
+compare_posix_output "env unset" 'X=val; unset X; echo ${X:-unset}'
1606
+compare_posix_output "env prefix" 'X=val sh -c "echo \$X"'
1607
+compare_posix_output "env readonly" 'readonly X=const; echo $X'
1608
+compare_posix_output "env home" 'echo ${HOME:-none} | grep -c "/"'
1609
+compare_posix_output "env path" 'echo ${PATH:-none} | grep -c ":"'
1610
+compare_posix_output "env pwd" 'echo ${PWD:-none} | grep -c "/"'
1611
+
1612
+section "271. GETOPTS COMPREHENSIVE"
1613
+
1614
+compare_posix_output "getopts single" 'set -- -a; getopts a opt; echo $opt'
1615
+compare_posix_output "getopts value" 'set -- -a val; getopts a: opt; echo $opt $OPTARG'
1616
+compare_posix_output "getopts multi" 'set -- -ab; getopts ab opt; echo $opt'
1617
+compare_posix_output "getopts optind" 'set -- -a -b; OPTIND=1; getopts ab opt; echo $OPTIND'
1618
+compare_posix_output "getopts unknown" 'set -- -x; getopts a opt 2>/dev/null; echo $?'
1619
+compare_posix_output "getopts missing" 'set -- -a; getopts a: opt 2>/dev/null; echo $?'
1620
+
1621
+section "272. TRAP COMPREHENSIVE"
1622
+
1623
+# Note: trap output may vary by environment - test exit code
1624
+compare_posix_exit_code "trap list" 'trap >/dev/null 2>&1'
1625
+compare_posix_output "trap set" 'trap "echo trapped" INT; trap | grep -c INT || echo 0'
1626
+compare_posix_output "trap reset" 'trap "echo x" INT; trap - INT; echo done'
1627
+compare_posix_output "trap ignore" 'trap "" INT; echo done'
1628
+compare_posix_output "trap exit" 'sh -c "trap \"echo bye\" EXIT; exit 0" 2>/dev/null || echo done'
1629
+
1630
+section "273. EVAL COMPREHENSIVE"
1631
+
1632
+compare_posix_output "eval simple" 'eval echo hello'
1633
+compare_posix_output "eval var" 'x=world; eval echo $x'
1634
+compare_posix_output "eval quoted" 'eval "echo test"'
1635
+compare_posix_output "eval multi" 'eval "echo a; echo b" | wc -l'
1636
+compare_posix_output "eval indirect" 'x=y; y=val; eval echo \$$x'
1637
+compare_posix_output "eval assign" 'eval "x=test"; echo $x'
1638
+compare_posix_output "eval complex" 'x="echo hello"; eval $x'
1639
+
1640
+section "274. EXEC COMPREHENSIVE"
1641
+
1642
+compare_posix_output "exec fd open" 'exec 3>&1; echo test >&3; exec 3>&-'
1643
+compare_posix_output "exec fd close" 'exec 3>&1; exec 3>&-; echo ok'
1644
+compare_posix_output "exec redir" 'exec 3>/tmp/e$$; echo test >&3; exec 3>&-; cat /tmp/e$$; rm /tmp/e$$'
1645
+
1646
+section "275. DOT SOURCE COMPREHENSIVE"
1647
+
1648
+compare_posix_output "dot var" 'echo "X=sourced" > /tmp/s$$; . /tmp/s$$; echo $X; rm /tmp/s$$'
1649
+compare_posix_output "dot func" 'echo "f() { echo func; }" > /tmp/s$$; . /tmp/s$$; f; rm /tmp/s$$'
1650
+compare_posix_output "dot persist" 'echo "Y=persist" > /tmp/s$$; . /tmp/s$$; echo $Y; rm /tmp/s$$'
1651
+
1652
+section "276. COMMAND BUILTIN"
1653
+
1654
+compare_posix_output "command echo" 'command echo hello'
1655
+compare_posix_output "command v" 'command -v echo | grep -c echo'
1656
+compare_posix_output "command V" 'command -V echo 2>/dev/null | grep -c echo || echo 1'
1657
+compare_posix_output "command p" 'command -p echo hello'
1658
+compare_posix_output "command bypass" 'echo() { printf "func\n"; }; command echo builtin; unset -f echo'
1659
+
1660
+section "277. TYPE BUILTIN"
1661
+
1662
+compare_posix_output "type builtin" 'type echo 2>/dev/null | head -1 | grep -c echo || echo 1'
1663
+compare_posix_output "type external" 'type cat 2>/dev/null | head -1 | grep -c cat || echo 1'
1664
+compare_posix_output "type notfound" 'type nonexistent_xyz 2>&1 | grep -ci "not found" || echo 0'
1665
+
1666
+section "278. HASH BUILTIN"
1667
+
1668
+compare_posix_output "hash show" 'hash 2>/dev/null; echo $?'
1669
+compare_posix_output "hash clear" 'hash -r 2>/dev/null; echo $?'
1670
+
1671
+section "279. ULIMIT BUILTIN"
1672
+
1673
+compare_posix_output "ulimit show" 'ulimit 2>/dev/null | grep -c "[0-9]" || echo 1'
1674
+compare_posix_output "ulimit n" 'ulimit -n 2>/dev/null | grep -c "[0-9]" || echo 1'
1675
+# Note: fortsh implements fewer limit types than bash - check both produce output
1676
+compare_posix_exit_code "ulimit a" 'test $(ulimit -a 2>/dev/null | wc -l) -ge 5'
1677
+
1678
+section "280. UMASK BUILTIN"
1679
+
1680
+compare_posix_output "umask show" 'umask | grep -c "[0-7]"'
1681
+compare_posix_output "umask symbolic" 'umask -S | grep -c "u="'
1682
+compare_posix_output "umask set" 'old=$(umask); umask 022; umask $old; echo done'
1683
+
1684
+section "281. TIMES BUILTIN"
1685
+
1686
+compare_posix_output "times output" 'times 2>&1 | head -1 | grep -c "[0-9]" || echo 0'
1687
+
1688
+section "282. KILL BUILTIN"
1689
+
1690
+compare_posix_output "kill list" 'kill -l | head -1 | grep -c "[A-Z]" || echo 1'
1691
+compare_posix_output "kill l num" 'kill -l 1 2>/dev/null | grep -ci "hup\|term" || echo 0'
1692
+
1693
+section "283. CD COMPREHENSIVE"
1694
+
1695
+compare_posix_output "cd tmp" 'cd /tmp && pwd'
1696
+compare_posix_output "cd home" 'cd ~ && pwd | grep -c "/"'
1697
+compare_posix_output "cd dash" 'cd /tmp; cd /; cd - 2>/dev/null | grep -c "/" || pwd'
1698
+compare_posix_output "cd dotdot" 'cd /tmp; cd ..; pwd | grep -v "^/tmp$" | grep -c "/"'
1699
+compare_posix_output "cd absolute" 'cd /usr && pwd'
1700
+compare_posix_output "cd relative" 'cd /; cd tmp && pwd'
1701
+compare_posix_output "cd oldpwd" 'cd /tmp; cd /; echo $OLDPWD | grep -c tmp'
1702
+
1703
+section "284. PWD COMPREHENSIVE"
1704
+
1705
+compare_posix_output "pwd basic" 'pwd | grep -c "/"'
1706
+compare_posix_output "pwd L" 'pwd -L 2>/dev/null | grep -c "/" || pwd | grep -c "/"'
1707
+compare_posix_output "pwd P" 'pwd -P 2>/dev/null | grep -c "/" || pwd | grep -c "/"'
1708
+compare_posix_output "pwd var" 'echo $PWD | grep -c "/"'
1709
+
1710
+section "285. COLON BUILTIN"
1711
+
1712
+compare_posix_output "colon simple" ':; echo $?'
1713
+compare_posix_output "colon args" ': arg1 arg2; echo $?'
1714
+compare_posix_output "colon in if" 'if :; then echo yes; fi'
1715
+compare_posix_output "colon in while" 'n=0; while :; do n=$((n+1)); [ $n -ge 2 ] && break; done; echo $n'
1716
+compare_posix_output "colon expansion" ': ${x:=default}; echo $x'
1717
+
1718
+section "286. TRUE FALSE BUILTINS"
1719
+
1720
+compare_posix_output "true exit" 'true; echo $?'
1721
+compare_posix_output "false exit" 'false; echo $?'
1722
+compare_posix_output "true in if" 'if true; then echo yes; fi'
1723
+compare_posix_output "false in if" 'if false; then echo no; else echo yes; fi'
1724
+compare_posix_output "true and" 'true && echo yes'
1725
+compare_posix_output "false or" 'false || echo yes'
1726
+
1727
+section "287. RETURN BUILTIN"
1728
+
1729
+compare_posix_output "return 0" 'f() { return 0; }; f; echo $?'
1730
+compare_posix_output "return 1" 'f() { return 1; }; f; echo $?'
1731
+compare_posix_output "return 42" 'f() { return 42; }; f; echo $?'
1732
+compare_posix_output "return 255" 'f() { return 255; }; f; echo $?'
1733
+compare_posix_output "return implicit" 'f() { true; }; f; echo $?'
1734
+compare_posix_output "return last" 'f() { false; }; f; echo $?'
1735
+
1736
+section "288. EXIT BUILTIN"
1737
+
1738
+compare_posix_output "exit 0" '(exit 0); echo $?'
1739
+compare_posix_output "exit 1" '(exit 1); echo $?'
1740
+compare_posix_output "exit 42" '(exit 42); echo $?'
1741
+compare_posix_output "exit 255" '(exit 255); echo $?'
1742
+compare_posix_output "exit implicit" '(true); echo $?'
1743
+
1744
+section "289. BREAK BUILTIN"
1745
+
1746
+compare_posix_output "break for" 'for i in 1 2 3; do [ $i -eq 2 ] && break; echo $i; done'
1747
+compare_posix_output "break while" 'n=0; while true; do n=$((n+1)); [ $n -ge 2 ] && break; done; echo $n'
1748
+compare_posix_output "break nested" 'for i in 1 2; do for j in a b; do break; done; echo $i; done'
1749
+compare_posix_output "break 2" 'for i in 1 2; do for j in a b; do break 2; done; done; echo done'
1750
+
1751
+section "290. CONTINUE BUILTIN"
1752
+
1753
+compare_posix_output "continue for" 'for i in 1 2 3; do [ $i -eq 2 ] && continue; echo $i; done'
1754
+compare_posix_output "continue while" 'n=0; while [ $n -lt 3 ]; do n=$((n+1)); [ $n -eq 2 ] && continue; echo $n; done'
1755
+compare_posix_output "continue nested" 'for i in 1 2; do for j in a b; do continue; done; echo $i; done'
1756
+compare_posix_output "continue 2" 'for i in 1 2; do for j in a b; do continue 2; done; echo never; done; echo done'
1757
+
1758
+section "291. SHIFT BUILTIN"
1759
+
1760
+compare_posix_output "shift once" 'set -- a b c; shift; echo $1'
1761
+compare_posix_output "shift twice" 'set -- a b c; shift; shift; echo $1'
1762
+compare_posix_output "shift n" 'set -- a b c d e; shift 3; echo $1'
1763
+compare_posix_output "shift all" 'set -- a b; shift 2; echo ${1:-empty}'
1764
+compare_posix_output "shift count" 'set -- a b c; shift; echo $#'
1765
+
1766
+section "292. SET BUILTIN COMPREHENSIVE"
1767
+
1768
+compare_posix_output "set args" 'set -- a b c; echo $1 $2 $3'
1769
+compare_posix_output "set clear" 'set -- a b; set --; echo ${1:-none}'
1770
+compare_posix_output "set e" 'set -e; true; echo ok'
1771
+compare_posix_output "set f" 'set -f; echo *; set +f'
1772
+compare_posix_output "set x" 'set +x; echo ok'
1773
+compare_posix_output "set minus" 'echo $- | grep -c "."'
1774
+compare_posix_output "set show" 'X=val; set | grep -c "^X=" || echo 0'
1775
+
1776
+section "293. UNSET BUILTIN"
1777
+
1778
+compare_posix_output "unset var" 'X=val; unset X; echo ${X:-gone}'
1779
+compare_posix_output "unset func" 'f() { echo func; }; unset -f f 2>/dev/null; type f 2>&1 | grep -c "not found" || echo 0'
1780
+compare_posix_output "unset v" 'X=val; unset -v X; echo ${X:-gone}'
1781
+compare_posix_output "unset multi" 'X=1 Y=2; unset X Y; echo ${X:-a} ${Y:-b}'
1782
+
1783
+section "294. EXPORT BUILTIN"
1784
+
1785
+compare_posix_output "export simple" 'export X=val; echo $X'
1786
+compare_posix_output "export existing" 'X=val; export X; echo $X'
1787
+compare_posix_output "export multi" 'export X=1 Y=2; echo $X $Y'
1788
+compare_posix_output "export child" 'export X=val; sh -c "echo \$X"'
1789
+compare_posix_output "export list" 'export 2>/dev/null | head -1 | grep -c "=" || echo 0'
1790
+
1791
+section "295. READONLY BUILTIN"
1792
+
1793
+compare_posix_output "readonly simple" 'readonly X=val; echo $X'
1794
+compare_posix_output "readonly existing" 'X=val; readonly X; echo $X'
1795
+compare_posix_output "readonly list" 'readonly 2>/dev/null | wc -l'
1796
+
1797
+section "296. LOCAL SCOPE SIMULATION"
1798
+
1799
+compare_posix_output "scope global" 'X=global; f() { echo $X; }; f'
1800
+compare_posix_output "scope modify" 'X=old; f() { X=new; }; f; echo $X'
1801
+compare_posix_output "scope subshell" 'X=outer; (X=inner; echo $X); echo $X'
1802
+compare_posix_output "scope func arg" 'f() { echo $1; }; f arg'
1803
+compare_posix_output "scope nested" 'f() { g() { echo inner; }; g; }; f'
1804
+
1805
+section "297. GLOB PATTERNS COMPREHENSIVE"
1806
+
1807
+compare_posix_output "glob star" 'set -f; echo *; set +f'
1808
+compare_posix_output "glob question" 'set -f; echo ?; set +f'
1809
+compare_posix_output "glob bracket" 'set -f; echo [abc]; set +f'
1810
+compare_posix_output "glob range" 'set -f; echo [a-z]; set +f'
1811
+compare_posix_output "glob neg" 'set -f; echo [!abc]; set +f'
1812
+compare_posix_output "glob quoted" 'echo "*"'
1813
+compare_posix_output "glob escaped" 'echo \*'
1814
+compare_posix_output "glob in var" 'x="*"; echo "$x"'
1815
+
1816
+section "298. TILDE EXPANSION COMPREHENSIVE"
1817
+
1818
+compare_posix_output "tilde home" 'echo ~ | grep -c "/"'
1819
+compare_posix_output "tilde slash" 'echo ~/ | grep -c "/"'
1820
+compare_posix_output "tilde quoted" 'echo "~"'
1821
+compare_posix_output "tilde var" 'x=~; echo $x | grep -c "/"'
1822
+compare_posix_output "tilde plus" 'cd /tmp; echo ~+ | grep -c "/"'
1823
+compare_posix_output "tilde minus" 'cd /tmp; cd /; echo ~- | grep -c tmp'
1824
+
1825
+section "299. BRACE EXPANSION TESTS"
1826
+
1827
+compare_posix_output "brace literal" 'echo {a,b,c}'
1828
+compare_posix_output "brace seq" 'echo {1..3} 2>/dev/null || echo "1 2 3"'
1829
+compare_posix_output "brace prefix" 'echo pre{a,b} 2>/dev/null || echo "prea preb"'
1830
+compare_posix_output "brace suffix" 'echo {a,b}suf 2>/dev/null || echo "asuf bsuf"'
1831
+
1832
+section "300. WORD SPLITTING COMPREHENSIVE"
1833
+
1834
+compare_posix_output "split default" 'x="a b c"; set -- $x; echo $#'
1835
+compare_posix_output "split quoted" 'x="a b c"; set -- "$x"; echo $#'
1836
+compare_posix_output "split ifs" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
1837
+compare_posix_output "split empty ifs" 'IFS=""; x="a b"; set -- $x; echo $#'
1838
+compare_posix_output "split unset ifs" 'unset IFS; x="a  b"; set -- $x; echo $#'
1839
+compare_posix_output "split whitespace" 'x="  a  b  "; set -- $x; echo $#'
1840
+compare_posix_output "split preserve" 'x="a  b"; echo "$x" | grep -c "  "'
1841
+compare_posix_output "split tab" 'x="a	b"; set -- $x; echo $#'
1842
+
1843
+section "301. FIELD SPLITTING EDGE CASES"
1844
+
1845
+compare_posix_output "field leading" 'IFS=:; x=":a:b"; set -- $x; echo $# $1'
1846
+compare_posix_output "field trailing" 'IFS=:; x="a:b:"; set -- $x; echo $#'
1847
+compare_posix_output "field empty" 'IFS=:; x="a::b"; set -- $x; echo $#'
1848
+compare_posix_output "field multi ifs" 'IFS=":;"; x="a:b;c"; set -- $x; echo $#'
1849
+
1850
+section "302. COMMAND SUBSTITUTION COMPREHENSIVE"
1851
+
1852
+compare_posix_output "cmdsub simple" 'echo $(echo hello)'
1853
+compare_posix_output "cmdsub nested" 'echo $(echo $(echo deep))'
1854
+compare_posix_output "cmdsub backtick" 'echo `echo hello`'
1855
+compare_posix_output "cmdsub var" 'x=$(echo val); echo $x'
1856
+compare_posix_output "cmdsub arith" 'echo $(echo $((1+1)))'
1857
+compare_posix_output "cmdsub pipe" 'echo $(echo test | cat)'
1858
+compare_posix_output "cmdsub multiline" 'x=$(printf "a\nb"); echo "$x" | wc -l'
1859
+# Note: Exit status propagation from command substitution may vary
1860
+compare_posix_exit_code "cmdsub exit" '$(exit 5); test $? -ne 0'
1861
+compare_posix_output "cmdsub empty" 'x=$(true); echo "[$x]"'
1862
+
1863
+section "303. ARITHMETIC EXPANSION COMPREHENSIVE"
1864
+
1865
+compare_posix_output "arith simple" 'echo $((1+1))'
1866
+compare_posix_output "arith all ops" 'echo $((2+3-1*2/1%3))'
1867
+compare_posix_output "arith paren" 'echo $(((1+2)*3))'
1868
+compare_posix_output "arith var" 'x=5; echo $((x+1))'
1869
+compare_posix_output "arith compare" 'echo $((5 > 3))'
1870
+compare_posix_output "arith logical" 'echo $((1 && 1))'
1871
+compare_posix_output "arith ternary" 'echo $((1 ? 10 : 20))'
1872
+compare_posix_output "arith nested" 'echo $(($((1+1)) + 1))'
1873
+compare_posix_output "arith negative" 'echo $((-5))'
1874
+compare_posix_output "arith zero" 'echo $((0))'
1875
+
1876
+section "304. PATTERN MATCHING COMPREHENSIVE"
1877
+
1878
+compare_posix_output "pat suffix" 'x=file.txt; echo ${x%.txt}'
1879
+compare_posix_output "pat suffix long" 'x=a.b.c; echo ${x%%.*}'
1880
+compare_posix_output "pat prefix" 'x=prefix_name; echo ${x#prefix_}'
1881
+compare_posix_output "pat prefix long" 'x=a.b.c; echo ${x##*.}'
1882
+compare_posix_output "pat star" 'x=hello; echo ${x%l*}'
1883
+compare_posix_output "pat question" 'x=hello; echo ${x%?}'
1884
+compare_posix_output "pat no match" 'x=hello; echo ${x%.txt}'
1885
+compare_posix_output "pat empty" 'x=; echo ${x%.txt}'
1886
+
1887
+section "305. SPECIAL PARAMETER COMPREHENSIVE"
1888
+
1889
+compare_posix_output "param zero" 'echo ${0:-shell} | grep -c "."'
1890
+compare_posix_output "param hash" 'set -- a b c; echo $#'
1891
+compare_posix_output "param question" 'true; echo $?'
1892
+compare_posix_output "param dollar" 'echo $$ | grep -c "[0-9]"'
1893
+compare_posix_output "param at" 'set -- a b c; echo "$@"'
1894
+compare_posix_output "param star" 'set -- a b c; echo "$*"'
1895
+compare_posix_output "param minus" 'echo $- | grep -c "."'
1896
+compare_posix_output "param at loop" 'set -- a b c; for x in "$@"; do echo $x; done | wc -l'
1897
+compare_posix_output "param star loop" 'set -- a b c; for x in "$*"; do echo $x; done | wc -l'
1898
+
1899
+section "306. CONTROL FLOW EDGE CASES"
1900
+
1901
+compare_posix_output "if true simple" 'if true; then echo yes; fi'
1902
+compare_posix_output "if false simple" 'if false; then echo no; fi; echo done'
1903
+compare_posix_output "if else" 'if false; then echo no; else echo yes; fi'
1904
+compare_posix_output "if elif" 'if false; then echo 1; elif true; then echo 2; fi'
1905
+compare_posix_output "if nested" 'if true; then if true; then echo deep; fi; fi'
1906
+compare_posix_output "if compound" 'if true && true; then echo yes; fi'
1907
+compare_posix_output "if pipeline" 'if echo test | grep -q test; then echo yes; fi'
1908
+compare_posix_output "if negation" 'if ! false; then echo yes; fi'
1909
+
1910
+section "307. FOR LOOP EDGE CASES"
1911
+
1912
+compare_posix_output "for basic" 'for i in a b c; do echo $i; done | wc -l'
1913
+compare_posix_output "for single" 'for i in only; do echo $i; done'
1914
+compare_posix_output "for empty" 'for i in; do echo $i; done; echo done'
1915
+compare_posix_output "for numbers" 'for i in 1 2 3 4 5; do echo $i; done | wc -l'
1916
+compare_posix_output "for var" 'list="a b c"; for i in $list; do echo $i; done | wc -l'
1917
+compare_posix_output "for quoted" 'for i in "a b" "c d"; do echo "$i"; done | wc -l'
1918
+compare_posix_output "for break" 'for i in 1 2 3; do [ $i -eq 2 ] && break; echo $i; done'
1919
+compare_posix_output "for continue" 'for i in 1 2 3; do [ $i -eq 2 ] && continue; echo $i; done'
1920
+
1921
+section "308. WHILE LOOP EDGE CASES"
1922
+
1923
+compare_posix_output "while count" 'n=3; while [ $n -gt 0 ]; do echo $n; n=$((n-1)); done | wc -l'
1924
+compare_posix_output "while false" 'while false; do echo never; done; echo done'
1925
+compare_posix_output "while true break" 'n=0; while true; do n=$((n+1)); [ $n -ge 3 ] && break; done; echo $n'
1926
+compare_posix_output "while compound" 'n=2; while [ $n -gt 0 ] && true; do n=$((n-1)); done; echo $n'
1927
+compare_posix_output "while pipeline" 'echo ok | while true; do echo yes; break; done'
1928
+
1929
+section "309. UNTIL LOOP EDGE CASES"
1930
+
1931
+compare_posix_output "until count" 'n=0; until [ $n -ge 3 ]; do n=$((n+1)); done; echo $n'
1932
+compare_posix_output "until true" 'until true; do echo never; done; echo done'
1933
+compare_posix_output "until false" 'n=0; until false; do n=$((n+1)); [ $n -ge 2 ] && break; done; echo $n'
1934
+
1935
+section "310. CASE EDGE CASES"
1936
+
1937
+compare_posix_output "case simple" 'case x in x) echo yes;; esac'
1938
+compare_posix_output "case default" 'case x in y) echo no;; *) echo default;; esac'
1939
+compare_posix_output "case multi" 'case a in a|b|c) echo match;; esac'
1940
+compare_posix_output "case glob" 'case hello in h*) echo match;; esac'
1941
+compare_posix_output "case question" 'case ab in ??) echo two;; esac'
1942
+compare_posix_output "case bracket" 'case a in [abc]) echo match;; esac'
1943
+compare_posix_output "case empty" 'case "" in "") echo empty;; esac'
1944
+compare_posix_output "case var" 'x=test; case $x in test) echo yes;; esac'
1945
+compare_posix_output "case quoted" 'case "a b" in "a b") echo space;; esac'
1946
+compare_posix_output "case no match" 'case x in y) echo no;; esac; echo done'
1947
+
1948
+section "311. FUNCTION EDGE CASES"
1949
+
1950
+compare_posix_output "func simple" 'f() { echo hello; }; f'
1951
+compare_posix_output "func args" 'f() { echo $1 $2; }; f a b'
1952
+compare_posix_output "func return" 'f() { return 42; }; f; echo $?'
1953
+compare_posix_output "func local" 'x=outer; f() { x=inner; }; f; echo $x'
1954
+compare_posix_output "func recursive" 'f() { [ $1 -le 0 ] && echo done || f $(($1-1)); }; f 3'
1955
+compare_posix_output "func in func" 'f() { g() { echo inner; }; g; }; f'
1956
+compare_posix_output "func pipe" 'f() { echo test; }; f | cat'
1957
+compare_posix_output "func subshell" 'f() { echo func; }; (f)'
1958
+compare_posix_output "func all args" 'f() { echo $@; }; f a b c'
1959
+compare_posix_output "func count" 'f() { echo $#; }; f a b c d e'
1960
+
1961
+section "312. REDIRECTION EDGE CASES"
1962
+
1963
+compare_posix_output "redir out" 'echo test > /tmp/r$$; cat /tmp/r$$; rm /tmp/r$$'
1964
+compare_posix_output "redir append" 'echo a > /tmp/r$$; echo b >> /tmp/r$$; wc -l < /tmp/r$$; rm /tmp/r$$'
1965
+compare_posix_output "redir in" 'echo test > /tmp/r$$; cat < /tmp/r$$; rm /tmp/r$$'
1966
+compare_posix_output "redir err" 'echo err >&2 2>/dev/null; echo ok'
1967
+compare_posix_output "redir fd" 'echo test 2>&1 | cat'
1968
+compare_posix_output "redir null" 'echo test > /dev/null; echo $?'
1969
+compare_posix_output "redir here" 'cat <<EOF
1970
+test
1971
+EOF'
1972
+compare_posix_output "redir here var" 'x=val; cat <<EOF
1973
+$x
1974
+EOF'
1975
+
1976
+section "313. PIPELINE EDGE CASES"
1977
+
1978
+compare_posix_output "pipe simple" 'echo test | cat'
1979
+compare_posix_output "pipe chain" 'echo test | cat | cat | cat'
1980
+compare_posix_output "pipe grep" 'echo hello | grep h'
1981
+compare_posix_output "pipe wc" 'printf "a\nb\nc\n" | wc -l'
1982
+compare_posix_output "pipe head" 'seq 1 10 | head -3'
1983
+compare_posix_output "pipe tail" 'seq 1 10 | tail -3'
1984
+compare_posix_output "pipe sort" 'printf "c\na\nb\n" | sort'
1985
+compare_posix_output "pipe subshell" '(echo test) | cat'
1986
+compare_posix_output "pipe brace" '{ echo test; } | cat'
1987
+compare_posix_output "pipe status" 'true | false; echo $?'
1988
+
1989
+section "314. SUBSHELL EDGE CASES"
1990
+
1991
+compare_posix_output "sub simple" '(echo sub)'
1992
+compare_posix_output "sub var" '(x=inner; echo $x)'
1993
+compare_posix_output "sub no leak" 'x=outer; (x=inner); echo $x'
1994
+compare_posix_output "sub exit" '(exit 42); echo $?'
1995
+compare_posix_output "sub cd" '(cd /tmp; pwd)'
1996
+# Note: (( )) is arithmetic syntax. Compare exit codes.
1997
+compare_posix_exit_code "sub nested arithmetic" '((echo deep))'
1998
+compare_posix_output "sub pipe" '(echo a; echo b) | wc -l'
1999
+compare_posix_output "sub multi" '(echo a); (echo b)'
2000
+compare_posix_output "sub compound" '(echo a; echo b; echo c) | wc -l'
2001
+
2002
+section "315. BRACE GROUP EDGE CASES"
2003
+
2004
+compare_posix_output "brace simple" '{ echo test; }'
2005
+compare_posix_output "brace multi" '{ echo a; echo b; }'
2006
+compare_posix_output "brace var" '{ x=val; }; echo $x'
2007
+compare_posix_output "brace pipe" '{ echo test; } | cat'
2008
+compare_posix_output "brace nested" '{ { echo deep; }; }'
2009
+compare_posix_output "brace and sub" '{ (echo sub); }'
2010
+compare_posix_output "brace redir" '{ echo test; } > /tmp/b$$; cat /tmp/b$$; rm /tmp/b$$'
2011
+
2012
+section "316. LOGICAL OPERATOR EDGE CASES"
2013
+
2014
+compare_posix_output "and both true" 'true && true; echo $?'
2015
+compare_posix_output "and first false" 'false && echo never; echo $?'
2016
+compare_posix_output "and second false" 'true && false; echo $?'
2017
+compare_posix_output "or both false" 'false || false; echo $?'
2018
+compare_posix_output "or first true" 'true || echo never; echo $?'
2019
+compare_posix_output "or second true" 'false || true; echo $?'
2020
+compare_posix_output "not true" '! true; echo $?'
2021
+compare_posix_output "not false" '! false; echo $?'
2022
+compare_posix_output "chain and" 'true && true && true; echo $?'
2023
+compare_posix_output "chain or" 'false || false || true; echo $?'
2024
+compare_posix_output "mixed" 'true && false || echo fallback'
2025
+
2026
+section "317. QUOTING COMPREHENSIVE"
2027
+
2028
+compare_posix_output "quote single" "echo 'hello'"
2029
+compare_posix_output "quote double" 'echo "hello"'
2030
+compare_posix_output "quote escape" 'echo hello\ world'
2031
+compare_posix_output "quote mixed" "echo 'a'\"b\"c"
2032
+compare_posix_output "quote empty single" "echo ''"
2033
+compare_posix_output "quote empty double" 'echo ""'
2034
+compare_posix_output "quote dollar" 'echo "\$x"'
2035
+compare_posix_output "quote backtick" 'echo "\`"'
2036
+compare_posix_output "quote backslash" 'echo "\\"'
2037
+compare_posix_output "quote newline" 'echo "a
2038
+b" | wc -l'
2039
+
2040
+section "318. VARIABLE COMPREHENSIVE"
2041
+
2042
+compare_posix_output "var simple" 'x=val; echo $x'
2043
+compare_posix_output "var empty" 'x=; echo "[$x]"'
2044
+compare_posix_output "var quoted" 'x="a b"; echo "$x"'
2045
+compare_posix_output "var concat" 'x=hel; y=lo; echo $x$y'
2046
+compare_posix_output "var braces" 'x=val; echo ${x}'
2047
+compare_posix_output "var default" 'echo ${undef:-default}'
2048
+compare_posix_output "var alt" 'x=val; echo ${x:+alt}'
2049
+compare_posix_output "var length" 'x=hello; echo ${#x}'
2050
+compare_posix_output "var suffix" 'x=file.txt; echo ${x%.txt}'
2051
+compare_posix_output "var prefix" 'x=pre_name; echo ${x#pre_}'
2052
+
2053
+section "319. ARITHMETIC COMPREHENSIVE"
2054
+
2055
+compare_posix_output "arith add" 'echo $((5+3))'
2056
+compare_posix_output "arith sub" 'echo $((5-3))'
2057
+compare_posix_output "arith mul" 'echo $((5*3))'
2058
+compare_posix_output "arith div" 'echo $((15/3))'
2059
+compare_posix_output "arith mod" 'echo $((17%5))'
2060
+compare_posix_output "arith paren" 'echo $(((1+2)*3))'
2061
+compare_posix_output "arith var" 'x=5; echo $((x+1))'
2062
+compare_posix_output "arith neg" 'echo $((-5))'
2063
+compare_posix_output "arith cmp" 'echo $((5>3))'
2064
+compare_posix_output "arith log" 'echo $((1&&1))'
2065
+
2066
+section "320. TEST COMPREHENSIVE"
2067
+
2068
+compare_posix_output "test eq" '[ 5 -eq 5 ]; echo $?'
2069
+compare_posix_output "test ne" '[ 5 -ne 6 ]; echo $?'
2070
+compare_posix_output "test lt" '[ 3 -lt 5 ]; echo $?'
2071
+compare_posix_output "test gt" '[ 5 -gt 3 ]; echo $?'
2072
+compare_posix_output "test le" '[ 5 -le 5 ]; echo $?'
2073
+compare_posix_output "test ge" '[ 5 -ge 5 ]; echo $?'
2074
+compare_posix_output "test str eq" '[ "a" = "a" ]; echo $?'
2075
+compare_posix_output "test str ne" '[ "a" != "b" ]; echo $?'
2076
+compare_posix_output "test z" '[ -z "" ]; echo $?'
2077
+compare_posix_output "test n" '[ -n "x" ]; echo $?'
2078
+compare_posix_output "test f" '[ -f /etc/passwd ]; echo $?'
2079
+compare_posix_output "test d" '[ -d /tmp ]; echo $?'
2080
+compare_posix_output "test not" '[ ! 1 -eq 2 ]; echo $?'
2081
+
2082
+# ============================================================================
2083
+# SECTION 321-340: PARAMETER EXPANSION WITHOUT COLON (POSIX 2.6.2)
2084
+# These test unset-only behavior vs null-or-unset behavior
2085
+# ============================================================================
2086
+
2087
+section "321. USE DEFAULT WITHOUT COLON"
2088
+
2089
+# ${parameter-word} - substitute word only if parameter is UNSET (not for null)
2090
+compare_posix_output "default unset only" 'unset x; echo ${x-default}'
2091
+compare_posix_output "default set empty" 'x=""; echo "[${x-default}]"'
2092
+compare_posix_output "default set value" 'x=val; echo ${x-default}'
2093
+compare_posix_output "colon default unset" 'unset x; echo ${x:-default}'
2094
+compare_posix_output "colon default empty" 'x=""; echo "[${x:-default}]"'
2095
+compare_posix_output "colon default value" 'x=val; echo ${x:-default}'
2096
+
2097
+section "322. ASSIGN DEFAULT WITHOUT COLON"
2098
+
2099
+# ${parameter=word} - assign only if UNSET
2100
+compare_posix_output "assign unset only" 'unset x; echo ${x=assigned}; echo $x'
2101
+compare_posix_output "assign set empty" 'x=""; echo "[${x=assigned}]"; echo "[$x]"'
2102
+compare_posix_output "colon assign unset" 'unset x; echo ${x:=assigned}; echo $x'
2103
+compare_posix_output "colon assign empty" 'x=""; echo "[${x:=assigned}]"; echo "[$x]"'
2104
+
2105
+section "323. ERROR IF UNSET WITHOUT COLON"
2106
+
2107
+# ${parameter?word} - error only if UNSET
2108
+compare_posix_output "error unset only" '(unset x; echo ${x?errmsg}) 2>/dev/null; echo $?'
2109
+compare_posix_output "error set empty" 'x=""; echo "[${x?errmsg}]"'
2110
+compare_posix_output "colon error unset" '(unset x; echo ${x:?errmsg}) 2>/dev/null; echo $?'
2111
+compare_posix_output "colon error empty" '(x=""; echo ${x:?errmsg}) 2>/dev/null; echo $?'
2112
+
2113
+section "324. ALT VALUE WITHOUT COLON"
2114
+
2115
+# ${parameter+word} - substitute word if parameter is SET (even if null)
2116
+compare_posix_output "alt unset" 'unset x; echo "[${x+alt}]"'
2117
+compare_posix_output "alt empty" 'x=""; echo "[${x+alt}]"'
2118
+compare_posix_output "alt value" 'x=val; echo "[${x+alt}]"'
2119
+compare_posix_output "colon alt unset" 'unset x; echo "[${x:+alt}]"'
2120
+compare_posix_output "colon alt empty" 'x=""; echo "[${x:+alt}]"'
2121
+compare_posix_output "colon alt value" 'x=val; echo "[${x:+alt}]"'
2122
+
2123
+# ============================================================================
2124
+# SECTION 325-335: SPECIAL PARAMETERS (POSIX 2.5.2)
2125
+# ============================================================================
2126
+
2127
+section "325. DOLLAR AT VS DOLLAR STAR"
2128
+
2129
+compare_posix_output "at basic" 'set -- a b c; echo "$@"'
2130
+compare_posix_output "star basic" 'set -- a b c; echo "$*"'
2131
+compare_posix_output "at count" 'set -- a b c; for x in "$@"; do echo $x; done | wc -l'
2132
+compare_posix_output "star count" 'set -- a b c; for x in "$*"; do echo $x; done | wc -l'
2133
+compare_posix_output "at with spaces" 'set -- "a b" "c d"; for x in "$@"; do echo "[$x]"; done'
2134
+compare_posix_output "star with spaces" 'set -- "a b" "c d"; for x in "$*"; do echo "[$x]"; done'
2135
+
2136
+section "326. IFS AFFECTS DOLLAR STAR"
2137
+
2138
+compare_posix_output "star ifs colon" 'IFS=:; set -- a b c; echo "$*"'
2139
+compare_posix_output "star ifs comma" 'IFS=,; set -- x y z; echo "$*"'
2140
+compare_posix_output "star ifs empty" 'IFS=""; set -- a b c; echo "$*"'
2141
+compare_posix_output "at ifs colon" 'IFS=:; set -- a b c; echo "$@"'
2142
+
2143
+section "327. DOLLAR HASH"
2144
+
2145
+compare_posix_output "hash zero" 'set --; echo $#'
2146
+compare_posix_output "hash one" 'set -- a; echo $#'
2147
+compare_posix_output "hash five" 'set -- a b c d e; echo $#'
2148
+compare_posix_output "hash after shift" 'set -- a b c; shift; echo $#'
2149
+
2150
+section "328. DOLLAR QUESTION"
2151
+
2152
+compare_posix_output "question success" 'true; echo $?'
2153
+compare_posix_output "question fail" 'false; echo $?'
2154
+compare_posix_output "question exit" '(exit 42); echo $?'
2155
+compare_posix_output "question pipe" 'true | false; echo $?'
2156
+
2157
+section "329. DOLLAR HYPHEN"
2158
+
2159
+compare_posix_output "hyphen basic" 'echo $- | grep -c .'
2160
+compare_posix_output "hyphen after set" 'set -f; case "$-" in *f*) echo yes;; esac; set +f'
2161
+compare_posix_output "hyphen in subsh" '(echo $-) | grep -c .'
2162
+
2163
+section "330. DOLLAR DOLLAR"
2164
+
2165
+compare_posix_output "pid numeric" 'echo $$ | grep -cE "^[0-9]+$"'
2166
+compare_posix_output "pid same subsh" 'x=$$; (echo $(( $$ == x ? 1 : 0 )))'
2167
+compare_posix_output "pid consistent" 'x=$$; echo $(( $$ == x ))'
2168
+
2169
+section "331. DOLLAR ZERO"
2170
+
2171
+compare_posix_output "zero set" 'echo $0 | grep -c .'
2172
+compare_posix_output "zero in func" 'f() { echo $0 | grep -c .; }; f'
2173
+
2174
+section "332. LINENO VARIABLE"
2175
+
2176
+compare_posix_output "lineno set" 'echo $LINENO | grep -cE "^[0-9]+$"'
2177
+compare_posix_output "lineno in script" 'echo "echo \$LINENO" > /tmp/ln$$; sh /tmp/ln$$; rm /tmp/ln$$'
2178
+
2179
+section "333. PPID VARIABLE"
2180
+
2181
+compare_posix_output "ppid set" 'echo $PPID | grep -cE "^[0-9]+$"'
2182
+compare_posix_output "ppid same subsh" '(echo $PPID) | grep -cE "^[0-9]+$"'
2183
+compare_posix_output "ppid not self" 'test $PPID -ne $$; echo $?'
2184
+
2185
+section "334. PWD AND OLDPWD"
2186
+
2187
+compare_posix_output "pwd set" 'echo $PWD | grep -c /'
2188
+compare_posix_output "pwd equals pwd" 'test "$PWD" = "$(pwd)"; echo $?'
2189
+compare_posix_output "oldpwd after cd" 'cd /tmp; cd /; echo $OLDPWD | grep -c tmp'
2190
+
2191
+section "335. PS VARIABLES"
2192
+
2193
+compare_posix_output "ps1 set" 'echo ${PS1:-unset} | grep -c .'
2194
+compare_posix_output "ps2 set" 'echo ${PS2:-unset} | grep -c .'
2195
+compare_posix_output "ps4 default" 'echo ${PS4:-unset} | grep -c .'
2196
+
2197
+# ============================================================================
2198
+# SECTION 336-345: ADDITIONAL FILE TEST OPERATORS (POSIX test utility)
2199
+# ============================================================================
2200
+
2201
+section "336. TEST TERMINAL FD"
2202
+
2203
+compare_posix_output "test t stdin" '[ -t 0 ] </dev/null; echo $?'
2204
+compare_posix_output "test t stdout" '[ -t 1 ] >/dev/null; echo $?'
2205
+compare_posix_output "test t invalid" '[ -t 999 ]; echo $?'
2206
+
2207
+section "337. TEST SETUID SETGID"
2208
+
2209
+compare_posix_output "test u nofile" '[ -u /etc/passwd ]; echo $?'
2210
+compare_posix_output "test g nofile" '[ -g /etc/passwd ]; echo $?'
2211
+
2212
+section "338. TEST SPECIAL FILES"
2213
+
2214
+compare_posix_output "test b regular" '[ -b /etc/passwd ]; echo $?'
2215
+compare_posix_output "test c regular" '[ -c /etc/passwd ]; echo $?'
2216
+compare_posix_output "test c tty" '[ -c /dev/tty ] 2>/dev/null; echo $?'
2217
+compare_posix_output "test p regular" '[ -p /etc/passwd ]; echo $?'
2218
+compare_posix_output "test S regular" '[ -S /etc/passwd ]; echo $?'
2219
+
2220
+section "339. TEST FILE PERMS"
2221
+
2222
+compare_posix_output "test r readable" '[ -r /etc/passwd ]; echo $?'
2223
+compare_posix_output "test w writable" 'touch /tmp/tw$$; [ -w /tmp/tw$$ ]; echo $?; rm /tmp/tw$$'
2224
+compare_posix_output "test x dir" '[ -x /tmp ]; echo $?'
2225
+compare_posix_output "test r nonexist" '[ -r /nonexistent$$ ]; echo $?'
2226
+
2227
+section "340. TEST STRING PRIMARIES"
2228
+
2229
+compare_posix_output "test str alone" '[ "nonempty" ]; echo $?'
2230
+compare_posix_output "test str empty" '[ "" ]; echo $?'
2231
+compare_posix_output "test str n" '[ -n "abc" ]; echo $?'
2232
+compare_posix_output "test str n empty" '[ -n "" ]; echo $?'
2233
+compare_posix_output "test str z" '[ -z "" ]; echo $?'
2234
+compare_posix_output "test str z non" '[ -z "abc" ]; echo $?'
2235
+
2236
+section "341. TEST STRING COMPARE"
2237
+
2238
+compare_posix_output "test str eq" '[ "abc" = "abc" ]; echo $?'
2239
+compare_posix_output "test str ne" '[ "abc" = "def" ]; echo $?'
2240
+compare_posix_output "test str neq" '[ "abc" != "def" ]; echo $?'
2241
+compare_posix_output "test str neq same" '[ "abc" != "abc" ]; echo $?'
2242
+
2243
+section "342. TEST NUMERIC COMPARE"
2244
+
2245
+compare_posix_output "test num eq" '[ 5 -eq 5 ]; echo $?'
2246
+compare_posix_output "test num ne" '[ 5 -ne 3 ]; echo $?'
2247
+compare_posix_output "test num lt" '[ 3 -lt 5 ]; echo $?'
2248
+compare_posix_output "test num le" '[ 5 -le 5 ]; echo $?'
2249
+compare_posix_output "test num gt" '[ 5 -gt 3 ]; echo $?'
2250
+compare_posix_output "test num ge" '[ 5 -ge 5 ]; echo $?'
2251
+compare_posix_output "test num neg" '[ -5 -lt 0 ]; echo $?'
2252
+
2253
+section "343. TEST COMPOUND"
2254
+
2255
+compare_posix_output "test not" '[ ! -d /tmp ]; echo $?'
2256
+compare_posix_output "test not false" '[ ! -d /nonexistent ]; echo $?'
2257
+compare_posix_output "test and" '[ -d /tmp -a -f /etc/passwd ]; echo $?'
2258
+compare_posix_output "test or" '[ -d /nonexistent -o -f /etc/passwd ]; echo $?'
2259
+compare_posix_output "test parens" '[ \( -d /tmp \) ]; echo $?'
2260
+
2261
+section "344. TEST EDGE CASES"
2262
+
2263
+compare_posix_output "test no args" '[ ]; echo $?'
2264
+compare_posix_output "test one arg" '[ x ]; echo $?'
2265
+compare_posix_output "test empty arg" '[ "" ]; echo $?'
2266
+compare_posix_output "test dash arg" '[ "-n" = "-n" ]; echo $?'
2267
+
2268
+section "345. TEST BUILTIN BRACKET"
2269
+
2270
+compare_posix_output "bracket basic" '[ 1 -eq 1 ]; echo $?'
2271
+compare_posix_output "bracket string" '[ "a" = "a" ]; echo $?'
2272
+compare_posix_output "bracket file" '[ -f /etc/passwd ]; echo $?'
2273
+compare_posix_output "bracket missing bracket" '[ 1 -eq 1 2>/dev/null; echo $?'
2274
+
2275
+# ============================================================================
2276
+# SECTION 346-355: REDIRECTION (POSIX 2.7)
2277
+# ============================================================================
2278
+
2279
+section "346. INPUT REDIRECTION"
2280
+
2281
+compare_posix_output "redir input" 'echo test > /tmp/ri$$; cat < /tmp/ri$$; rm /tmp/ri$$'
2282
+compare_posix_output "redir input fd" 'echo test > /tmp/ri$$; cat 0< /tmp/ri$$; rm /tmp/ri$$'
2283
+
2284
+section "347. OUTPUT REDIRECTION"
2285
+
2286
+compare_posix_output "redir output" 'echo test > /tmp/ro$$; cat /tmp/ro$$; rm /tmp/ro$$'
2287
+compare_posix_output "redir output fd" 'echo test 1> /tmp/ro$$; cat /tmp/ro$$; rm /tmp/ro$$'
2288
+compare_posix_output "redir clobber" 'echo a > /tmp/ro$$; echo b > /tmp/ro$$; cat /tmp/ro$$; rm /tmp/ro$$'
2289
+
2290
+section "348. APPEND REDIRECTION"
2291
+
2292
+compare_posix_output "redir append" 'echo a > /tmp/ra$$; echo b >> /tmp/ra$$; cat /tmp/ra$$; rm /tmp/ra$$'
2293
+compare_posix_output "redir append new" 'rm -f /tmp/ra$$; echo x >> /tmp/ra$$; cat /tmp/ra$$; rm /tmp/ra$$'
2294
+
2295
+section "349. STDERR REDIRECTION"
2296
+
2297
+compare_posix_output "redir stderr" 'ls /nonexistent$$ 2>/dev/null; echo done'
2298
+compare_posix_output "redir stderr to file" 'ls /nonexistent$$ 2>/tmp/re$$; cat /tmp/re$$ | grep -c .; rm /tmp/re$$'
2299
+compare_posix_output "redir both" '{ echo out; ls /nonexistent$$; } >/tmp/re$$ 2>&1; grep -c . /tmp/re$$; rm /tmp/re$$'
2300
+
2301
+section "350. FD DUPLICATION"
2302
+
2303
+compare_posix_output "dup stdout to stderr" 'echo test >&2 2>/tmp/rd$$; cat /tmp/rd$$; rm /tmp/rd$$'
2304
+compare_posix_output "dup stderr to stdout" '{ ls /nonexistent$$; } 2>&1 | grep -c .'
2305
+compare_posix_output "dup close" 'exec 3>/tmp/rd$$; echo test >&3; exec 3>&-; cat /tmp/rd$$; rm /tmp/rd$$'
2306
+
2307
+section "351. READ WRITE REDIRECTION"
2308
+
2309
+compare_posix_output "redir rw" 'echo test > /tmp/rw$$; exec 3<>/tmp/rw$$; cat <&3; exec 3>&-; rm /tmp/rw$$'
2310
+
2311
+section "352. HEREDOC BASIC"
2312
+
2313
+compare_posix_output "heredoc simple" 'cat <<EOF
2314
+hello
2315
+EOF'
2316
+compare_posix_output "heredoc multi" 'cat <<EOF
2317
+line1
2318
+line2
2319
+line3
2320
+EOF'
2321
+
2322
+section "353. HEREDOC EXPANSION"
2323
+
2324
+compare_posix_output "heredoc var" 'x=value; cat <<EOF
2325
+$x
2326
+EOF'
2327
+compare_posix_output "heredoc cmd" 'cat <<EOF
2328
+$(echo hello)
2329
+EOF'
2330
+compare_posix_output "heredoc arith" 'cat <<EOF
2331
+$((1+2))
2332
+EOF'
2333
+
2334
+section "354. HEREDOC QUOTED DELIM"
2335
+
2336
+compare_posix_output "heredoc no expand" "cat <<'EOF'
2337
+\$x
2338
+EOF"
2339
+compare_posix_output "heredoc quote double" 'cat <<"EOF"
2340
+$x
2341
+EOF'
2342
+
2343
+section "355. HEREDOC TAB STRIP"
2344
+
2345
+compare_posix_output "heredoc dash" 'cat <<-EOF
2346
+	hello
2347
+	EOF'
2348
+
2349
+# ============================================================================
2350
+# SECTION 356-365: SPECIAL BUILTINS ERROR HANDLING (POSIX 2.14)
2351
+# ============================================================================
2352
+
2353
+section "356. BREAK CONTINUE"
2354
+
2355
+compare_posix_output "break basic" 'for i in 1 2 3; do echo $i; break; done'
2356
+compare_posix_output "break nested" 'for i in 1 2; do for j in a b; do echo $i$j; break; done; done'
2357
+compare_posix_output "break 2" 'for i in 1 2; do for j in a b; do echo $i$j; break 2; done; done'
2358
+compare_posix_output "continue basic" 'for i in 1 2 3; do if [ $i = 2 ]; then continue; fi; echo $i; done'
2359
+compare_posix_output "continue 2" 'for i in 1 2 3; do for j in a b; do if [ $j = a ]; then continue 2; fi; echo $i$j; done; done'
2360
+
2361
+section "357. COLON COMMAND"
2362
+
2363
+compare_posix_output "colon return" ': ; echo $?'
2364
+compare_posix_output "colon with args" ': arg1 arg2 arg3; echo $?'
2365
+compare_posix_output "colon expansion" 'unset x; : ${x:=default}; echo $x'
2366
+
2367
+section "358. DOT COMMAND"
2368
+
2369
+compare_posix_output "dot source" 'echo "x=sourced" > /tmp/ds$$; . /tmp/ds$$; echo $x; rm /tmp/ds$$'
2370
+compare_posix_output "dot with args" 'echo "echo \$1 \$2" > /tmp/ds$$; . /tmp/ds$$ arg1 arg2; rm /tmp/ds$$'
2371
+compare_posix_output "dot function" 'echo "f() { echo func; }" > /tmp/ds$$; . /tmp/ds$$; f; rm /tmp/ds$$'
2372
+
2373
+section "359. EVAL COMMAND"
2374
+
2375
+compare_posix_output "eval basic" 'eval echo hello'
2376
+compare_posix_output "eval var" 'x=echo; eval $x world'
2377
+compare_posix_output "eval multi" 'eval "echo one; echo two"'
2378
+compare_posix_output "eval indirect" 'x=y; y=value; eval echo \$$x'
2379
+compare_posix_output "eval quote" 'eval "echo \"quoted\""'
2380
+
2381
+section "360. EXEC COMMAND"
2382
+
2383
+compare_posix_output "exec redirect" 'exec 3>/tmp/ex$$; echo test >&3; exec 3>&-; cat /tmp/ex$$; rm /tmp/ex$$'
2384
+compare_posix_output "exec input" 'echo test > /tmp/ex$$; exec 4</tmp/ex$$; cat <&4; exec 4<&-; rm /tmp/ex$$'
2385
+
2386
+section "361. EXIT COMMAND"
2387
+
2388
+compare_posix_output "exit basic" '(exit); echo $?'
2389
+compare_posix_output "exit code" '(exit 5); echo $?'
2390
+compare_posix_output "exit 0" '(exit 0); echo $?'
2391
+compare_posix_output "exit 255" '(exit 255); echo $?'
2392
+
2393
+section "362. EXPORT COMMAND"
2394
+
2395
+compare_posix_output "export basic" 'export X=5; sh -c "echo \$X"'
2396
+compare_posix_output "export separate" 'Y=6; export Y; sh -c "echo \$Y"'
2397
+compare_posix_output "export list" 'export | grep -c ='
2398
+compare_posix_output "export unset" 'export Z=7; unset Z; sh -c "echo \${Z:-unset}"'
2399
+
2400
+section "363. READONLY COMMAND"
2401
+
2402
+compare_posix_output "readonly basic" 'readonly X=5; echo $X'
2403
+compare_posix_output "readonly list" 'readonly | grep -c .'
2404
+compare_posix_output "readonly modify" '(readonly Y=1; Y=2) 2>/dev/null; echo $?'
2405
+
2406
+section "364. RETURN COMMAND"
2407
+
2408
+compare_posix_output "return basic" 'f() { return; }; f; echo $?'
2409
+compare_posix_output "return code" 'f() { return 5; }; f; echo $?'
2410
+compare_posix_output "return from func" 'f() { echo before; return 3; echo after; }; f; echo $?'
2411
+
2412
+section "365. SET COMMAND"
2413
+
2414
+compare_posix_output "set args" 'set -- a b c; echo $1 $2 $3'
2415
+compare_posix_output "set count" 'set -- a b c d e; echo $#'
2416
+compare_posix_output "set clear" 'set -- a b; set --; echo $#'
2417
+compare_posix_output "set option f" 'set -f; echo $- | grep -c f; set +f'
2418
+compare_posix_output "set minus" 'set -x; set +x; echo ok'
2419
+
2420
+# ============================================================================
2421
+# SECTION 366-375: MORE SPECIAL BUILTINS
2422
+# ============================================================================
2423
+
2424
+section "366. SHIFT COMMAND"
2425
+
2426
+compare_posix_output "shift basic" 'set -- a b c; shift; echo $1'
2427
+compare_posix_output "shift count" 'set -- a b c; shift; echo $#'
2428
+compare_posix_output "shift 2" 'set -- a b c d; shift 2; echo $1'
2429
+compare_posix_output "shift all" 'set -- a b; shift 2; echo $#'
2430
+
2431
+section "367. TIMES COMMAND"
2432
+
2433
+compare_posix_output "times output" 'times 2>/dev/null | wc -l | xargs test 0 -lt && echo ok || echo ok'
2434
+
2435
+section "368. TRAP COMMAND"
2436
+
2437
+# Note: trap output may vary by environment - test exit code
2438
+compare_posix_exit_code "trap list" 'trap >/dev/null 2>&1'
2439
+compare_posix_output "trap exit" 'trap "echo trapped" EXIT; exit 0'
2440
+compare_posix_output "trap reset" 'trap "" INT; trap - INT; echo ok'
2441
+
2442
+section "369. UNSET COMMAND"
2443
+
2444
+compare_posix_output "unset var" 'x=5; unset x; echo ${x:-unset}'
2445
+compare_posix_output "unset func" 'f() { echo func; }; unset -f f; f 2>/dev/null || echo unset'
2446
+compare_posix_output "unset v flag" 'x=5; unset -v x; echo ${x:-unset}'
2447
+
2448
+section "370. ALIAS COMMAND"
2449
+
2450
+compare_posix_output "alias set" 'alias ll="ls -l" 2>/dev/null; alias ll 2>/dev/null | grep -c ll || echo 0'
2451
+compare_posix_output "alias list" 'alias 2>/dev/null; echo $?'
2452
+compare_posix_output "unalias" 'alias xx="echo xx" 2>/dev/null; unalias xx 2>/dev/null; echo $?'
2453
+
2454
+section "371. COMMAND BUILTIN"
2455
+
2456
+compare_posix_output "command v" 'command -v echo | grep -c echo'
2457
+compare_posix_output "command V" 'command -V echo 2>/dev/null | grep -c echo'
2458
+compare_posix_output "command p" 'command -p echo test'
2459
+compare_posix_output "command bypass" 'echo() { printf "func\n"; }; command echo real; unset -f echo'
2460
+
2461
+section "372. GETOPTS COMMAND"
2462
+
2463
+compare_posix_output "getopts basic" 'set -- -a; getopts a opt; echo $opt'
2464
+compare_posix_output "getopts optarg" 'set -- -b val; getopts b: opt; echo $opt $OPTARG'
2465
+compare_posix_output "getopts optind" 'set -- -a arg; getopts a opt; echo $OPTIND'
2466
+
2467
+section "373. READ COMMAND OPTIONS"
2468
+
2469
+compare_posix_output "read r flag" 'echo "a\\b" > /tmp/rr$$; read -r x < /tmp/rr$$; echo $x; rm /tmp/rr$$'
2470
+compare_posix_output "read multiple" 'echo "a b c" > /tmp/rm$$; read x y z < /tmp/rm$$; echo "$x:$y:$z"; rm /tmp/rm$$'
2471
+compare_posix_output "read extra" 'echo "a b c d" > /tmp/re$$; read x y < /tmp/re$$; echo "$x:$y"; rm /tmp/re$$'
2472
+
2473
+section "374. PRINTF COMMAND"
2474
+
2475
+compare_posix_output "printf basic" 'printf "hello\n"'
2476
+compare_posix_output "printf format s" 'printf "%s\n" world'
2477
+compare_posix_output "printf format d" 'printf "%d\n" 42'
2478
+compare_posix_output "printf format x" 'printf "%x\n" 255'
2479
+compare_posix_output "printf format o" 'printf "%o\n" 8'
2480
+compare_posix_output "printf width" 'printf "%5d\n" 42'
2481
+compare_posix_output "printf left" 'printf "%-5d|\n" 42'
2482
+compare_posix_output "printf zero" 'printf "%05d\n" 42'
2483
+compare_posix_output "printf multi" 'printf "%s %s\n" hello world'
2484
+
2485
+section "375. ECHO COMMAND"
2486
+
2487
+compare_posix_output "echo basic" 'echo hello'
2488
+compare_posix_output "echo multi" 'echo hello world'
2489
+compare_posix_output "echo empty" 'echo ""'
2490
+compare_posix_output "echo n flag" 'echo -n hello; echo world'
2491
+compare_posix_output "echo escapes" 'echo "a\tb"'
2492
+
2493
+# ============================================================================
2494
+# SECTION 376-385: WORD EXPANSION EDGE CASES
2495
+# ============================================================================
2496
+
2497
+section "376. TILDE EXPANSION"
2498
+
2499
+compare_posix_output "tilde home" 'echo ~ | grep -c /'
2500
+compare_posix_output "tilde slash" 'echo ~/ | grep -c /'
2501
+compare_posix_output "tilde plus" 'echo ~+ | grep -c /'
2502
+compare_posix_output "tilde minus" 'OLDPWD=/tmp; echo ~-'
2503
+compare_posix_output "tilde quoted" 'echo "~"'
2504
+compare_posix_output "tilde mid word" 'echo a~b'
2505
+compare_posix_output "tilde in assign" 'x=~/test; echo $x | grep -c /'
2506
+
2507
+section "377. PATHNAME EXPANSION"
2508
+
2509
+compare_posix_output "glob star" 'mkdir -p /tmp/pg$$; touch /tmp/pg$$/a /tmp/pg$$/b; echo /tmp/pg$$/* | grep -c pg; rm -rf /tmp/pg$$'
2510
+compare_posix_output "glob question" 'mkdir -p /tmp/pg$$; touch /tmp/pg$$/ab; echo /tmp/pg$$/a? | grep -c ab; rm -rf /tmp/pg$$'
2511
+compare_posix_output "glob bracket" 'mkdir -p /tmp/pg$$; touch /tmp/pg$$/a1 /tmp/pg$$/a2; ls /tmp/pg$$/a[12] | wc -l; rm -rf /tmp/pg$$'
2512
+compare_posix_output "glob negate" 'mkdir -p /tmp/pg$$; touch /tmp/pg$$/a1 /tmp/pg$$/b1; ls /tmp/pg$$/[!a]1 | wc -l; rm -rf /tmp/pg$$'
2513
+compare_posix_output "glob range" 'mkdir -p /tmp/pg$$; touch /tmp/pg$$/a1 /tmp/pg$$/b1 /tmp/pg$$/c1; ls /tmp/pg$$/[a-c]1 | wc -l; rm -rf /tmp/pg$$'
2514
+# Note: Use fixed path since $$ differs between shells
2515
+compare_posix_output "glob no match" 'echo /definitely_nonexistent_xyz/*'
2516
+
2517
+section "378. FIELD SPLITTING"
2518
+
2519
+compare_posix_output "split default" 'x="a b c"; set -- $x; echo $#'
2520
+compare_posix_output "split tab" 'x="a	b	c"; set -- $x; echo $#'
2521
+compare_posix_output "split newline" 'x="a
2522
+b
2523
+c"; set -- $x; echo $#'
2524
+compare_posix_output "split ifs colon" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
2525
+compare_posix_output "split ifs multi" 'IFS=":;"; x="a:b;c"; set -- $x; echo $#'
2526
+compare_posix_output "split no split" 'x="a b c"; set -- "$x"; echo $#'
2527
+
2528
+section "379. QUOTE REMOVAL"
2529
+
2530
+compare_posix_output "quote remove single" "echo 'hello'"
2531
+compare_posix_output "quote remove double" 'echo "hello"'
2532
+compare_posix_output "quote remove escape" 'echo hello\ world'
2533
+compare_posix_output "quote remove mixed" "echo 'a'b'c'"
2534
+compare_posix_output "quote remove empty" "echo ''"
2535
+compare_posix_output "quote preserve space" 'echo "a   b"'
2536
+
2537
+section "380. COMMAND SUBSTITUTION"
2538
+
2539
+compare_posix_output "cmd sub basic" 'echo $(echo hello)'
2540
+compare_posix_output "cmd sub backtick" 'echo `echo hello`'
2541
+compare_posix_output "cmd sub nested" 'echo $(echo $(echo deep))'
2542
+compare_posix_output "cmd sub quote" 'echo "$(echo "hello world")"'
2543
+compare_posix_output "cmd sub multi" 'echo $(echo a; echo b)'
2544
+compare_posix_output "cmd sub exit" 'echo $(exit 5); echo $?'
2545
+
2546
+section "381. ARITHMETIC EXPANSION"
2547
+
2548
+compare_posix_output "arith basic" 'echo $((1+2))'
2549
+compare_posix_output "arith var" 'x=5; echo $((x*2))'
2550
+compare_posix_output "arith nested" 'echo $(( $((1+2)) + 3 ))'
2551
+compare_posix_output "arith in string" 'echo "result: $((3*4))"'
2552
+compare_posix_output "arith negative" 'echo $((-5))'
2553
+
2554
+section "382. PARAMETER LENGTH"
2555
+
2556
+compare_posix_output "length basic" 'x=hello; echo ${#x}'
2557
+compare_posix_output "length empty" 'x=""; echo ${#x}'
2558
+compare_posix_output "length special" 'set -- a b c; echo ${#@}'
2559
+compare_posix_output "length star" 'set -- a b c; echo ${#*}'
2560
+
2561
+section "383. PATTERN SUFFIX REMOVAL"
2562
+
2563
+compare_posix_output "suffix short" 'x=file.txt; echo ${x%.txt}'
2564
+compare_posix_output "suffix long" 'x=file.tar.gz; echo ${x%%.*}'
2565
+compare_posix_output "suffix star" 'x=abcabc; echo ${x%abc}'
2566
+compare_posix_output "suffix star long" 'x=abcabc; echo ${x%%a*}'
2567
+compare_posix_output "suffix no match" 'x=hello; echo ${x%.xyz}'
2568
+
2569
+section "384. PATTERN PREFIX REMOVAL"
2570
+
2571
+compare_posix_output "prefix short" 'x=prefix_name; echo ${x#prefix_}'
2572
+compare_posix_output "prefix long" 'x=/usr/local/bin; echo ${x##*/}'
2573
+compare_posix_output "prefix star" 'x=abcabc; echo ${x#abc}'
2574
+compare_posix_output "prefix star long" 'x=abcabc; echo ${x##*a}'
2575
+compare_posix_output "prefix no match" 'x=hello; echo ${x#xyz}'
2576
+
2577
+section "385. EXPANSION ORDER"
2578
+
2579
+compare_posix_output "order brace tilde" 'echo ~/{a,b} | grep -c /'
2580
+compare_posix_output "order param cmd" 'x=$(echo val); echo $x'
2581
+compare_posix_output "order arith param" 'x=5; echo $((x+1))'
2582
+compare_posix_output "order split glob" 'mkdir -p /tmp/eo$$; touch /tmp/eo$$/f1; x="/tmp/eo$$/f*"; echo $x | grep -c f; rm -rf /tmp/eo$$'
2583
+
2584
+# ============================================================================
2585
+# SECTION 386-400: POSIX CHARACTER CLASSES (basedefs/V1_chap09)
2586
+# ============================================================================
2587
+
2588
+section "401. IFS EDGE CASES"
2589
+
2590
+compare_posix_output "ifs default split" 'x="a b c"; set -- $x; echo $#'
2591
+compare_posix_output "ifs null no split" 'IFS=""; x="abc"; set -- $x; echo $#'
2592
+compare_posix_output "ifs custom colon" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
2593
+compare_posix_output "ifs whitespace" 'IFS=" "; x="a  b"; set -- $x; echo $#'
2594
+
2595
+section "402. HOME VARIABLE"
2596
+
2597
+compare_posix_output "home set" 'echo ${HOME:-unset} | grep -c /'
2598
+compare_posix_output "home in tilde" 'test ~ = "$HOME"; echo $?'
2599
+
2600
+section "403. PATH VARIABLE"
2601
+
2602
+compare_posix_output "path set" 'echo ${PATH:-unset} | grep -c :'
2603
+compare_posix_output "path search" 'PATH=/bin:/usr/bin; command -v ls | grep -c /'
2604
+
2605
+section "404. SHELL OPTION FLAGS"
2606
+
2607
+compare_posix_output "set f noglob" 'set -f; echo *; set +f'
2608
+compare_posix_output "set u nounset" '(set -u; echo ${x:-default})'
2609
+compare_posix_output "set e errexit" '(set -e; true; echo ok)'
2610
+# Note: xtrace redirection through pipes differs - check both produce output
2611
+compare_posix_exit_code "set x xtrace" 'test "$(set -x; echo xtrace_test 2>&1 | grep -c xtrace)" -ge 1'
2612
+
2613
+section "405. EXIT STATUS PROPAGATION"
2614
+
2615
+compare_posix_output "exit from pipe" 'true | false; echo $?'
2616
+compare_posix_output "exit from and" 'true && true; echo $?'
2617
+compare_posix_output "exit from or" 'false || true; echo $?'
2618
+compare_posix_output "exit from not" '! false; echo $?'
2619
+
2620
+section "406. SUBSHELL ISOLATION"
2621
+
2622
+compare_posix_output "subshell var" 'x=1; (x=2); echo $x'
2623
+compare_posix_output "subshell cd" 'cd /tmp; (cd /); pwd | grep -c tmp'
2624
+compare_posix_output "subshell exit" '(exit 5); echo $?'
2625
+
2626
+section "407. BRACE GROUP SEMANTICS"
2627
+
2628
+compare_posix_output "brace var" 'x=1; { x=2; }; echo $x'
2629
+compare_posix_output "brace redir" '{ echo a; echo b; } > /tmp/bg$$; wc -l < /tmp/bg$$; rm /tmp/bg$$'
2630
+
2631
+section "408. FUNCTION SEMANTICS"
2632
+
2633
+compare_posix_output "func scope" 'x=1; f() { x=2; }; f; echo $x'
2634
+compare_posix_output "func params" 'f() { echo $1 $2 $#; }; f a b c'
2635
+compare_posix_output "func return" 'f() { return 42; }; f; echo $?'
2636
+
2637
+section "409. ALIAS EXPANSION"
2638
+
2639
+compare_posix_output "alias define" 'alias x="echo test" 2>/dev/null; echo $?'
2640
+compare_posix_output "unalias" 'alias x="echo test" 2>/dev/null; unalias x 2>/dev/null; echo $?'
2641
+
2642
+section "410. COMPOUND ASSIGNMENT"
2643
+
2644
+compare_posix_output "assign simple" 'x=5; echo $x'
2645
+compare_posix_output "assign expand" 'x=$(echo val); echo $x'
2646
+compare_posix_output "assign arith" 'x=$((2+3)); echo $x'
2647
+compare_posix_output "assign concat" 'x=hel; x=${x}lo; echo $x'
2648
+
2649
+# ============================================================================
2650
+# SECTION 411-420: COMPLEX PATTERNS
2651
+# ============================================================================
2652
+
2653
+section "411. CASE PATTERN MATCHING"
2654
+
2655
+compare_posix_output "case star" 'case abc in a*) echo yes;; esac'
2656
+compare_posix_output "case question" 'case ab in a?) echo yes;; esac'
2657
+compare_posix_output "case bracket" 'case a in [abc]) echo yes;; esac'
2658
+compare_posix_output "case or" 'case b in a|b|c) echo yes;; esac'
2659
+compare_posix_output "case default" 'case x in a) echo a;; *) echo default;; esac'
2660
+
2661
+section "412. GLOB PATTERNS IN EXPANSION"
2662
+
2663
+compare_posix_output "glob files" 'mkdir -p /tmp/gt$$; touch /tmp/gt$$/a /tmp/gt$$/b; ls /tmp/gt$$/* | wc -l; rm -rf /tmp/gt$$'
2664
+compare_posix_output "glob question" 'mkdir -p /tmp/gt$$; touch /tmp/gt$$/ab; echo /tmp/gt$$/a? | grep -c ab; rm -rf /tmp/gt$$'
2665
+# Note: Use fixed path since $$ differs between shells
2666
+compare_posix_output "glob no match" 'echo /definitely_nonexistent_abc/*'
2667
+
2668
+section "413. SUFFIX REMOVAL PATTERNS"
2669
+
2670
+compare_posix_output "suffix short" 'x=file.txt; echo ${x%.txt}'
2671
+compare_posix_output "suffix long" 'x=file.tar.gz; echo ${x%%.*}'
2672
+compare_posix_output "suffix star" 'x=path/to/file; echo ${x%/*}'
2673
+
2674
+section "414. PREFIX REMOVAL PATTERNS"
2675
+
2676
+compare_posix_output "prefix short" 'x=file.txt; echo ${x#*.}'
2677
+compare_posix_output "prefix long" 'x=file.tar.gz; echo ${x##*.}'
2678
+compare_posix_output "prefix path" 'x=/path/to/file; echo ${x##*/}'
2679
+
2680
+section "415. LENGTH OPERATOR"
2681
+
2682
+compare_posix_output "length string" 'x=hello; echo ${#x}'
2683
+compare_posix_output "length empty" 'x=""; echo ${#x}'
2684
+compare_posix_output "length positional" 'set -- a b c; echo $#'
2685
+
2686
+section "416. CONDITIONAL DEFAULTS"
2687
+
2688
+compare_posix_output "default unset" 'unset x; echo ${x:-default}'
2689
+compare_posix_output "default empty" 'x=""; echo ${x:-default}'
2690
+compare_posix_output "default set" 'x=val; echo ${x:-default}'
2691
+
2692
+section "417. CONDITIONAL ASSIGN"
2693
+
2694
+compare_posix_output "assign unset" 'unset x; echo ${x:=assigned}; echo $x'
2695
+compare_posix_output "assign empty" 'x=""; echo ${x:=assigned}; echo $x'
2696
+
2697
+section "418. CONDITIONAL ERROR"
2698
+
2699
+compare_posix_output "error unset" '(unset x; echo ${x:?msg}) 2>/dev/null; echo $?'
2700
+compare_posix_output "error set" 'x=val; echo ${x:?msg}'
2701
+
2702
+section "419. CONDITIONAL ALT"
2703
+
2704
+compare_posix_output "alt unset" 'unset x; echo "[${x:+alt}]"'
2705
+compare_posix_output "alt set" 'x=val; echo "[${x:+alt}]"'
2706
+
2707
+section "420. NESTED PARAMETER"
2708
+
2709
+compare_posix_output "nested default" 'y=inner; echo ${x:-${y:-outer}}'
2710
+compare_posix_output "nested length" 'x=hello; echo $((${#x} + 1))'
2711
+
2712
+# ============================================================================
2713
+# SECTION 421-430: HEREDOC VARIATIONS
2714
+# ============================================================================
2715
+
2716
+section "441. SIMPLE COMMANDS"
2717
+
2718
+compare_posix_output "simple echo" 'echo hello'
2719
+compare_posix_output "simple true" 'true; echo $?'
2720
+compare_posix_output "simple false" 'false; echo $?'
2721
+compare_posix_output "simple colon" ': ; echo $?'
2722
+
2723
+section "442. PIPELINES"
2724
+
2725
+compare_posix_output "pipe simple" 'echo hello | cat'
2726
+compare_posix_output "pipe multi" 'echo hello | cat | cat'
2727
+compare_posix_output "pipe exit" 'true | false; echo $?'
2728
+compare_posix_output "pipe negation" '! echo x | grep y 2>/dev/null; echo $?'
2729
+
2730
+section "443. LISTS"
2731
+
2732
+compare_posix_output "list semi" 'echo a; echo b'
2733
+compare_posix_output "list newline" 'echo a
2734
+echo b'
2735
+compare_posix_output "list and" 'true && echo yes'
2736
+compare_posix_output "list or" 'false || echo yes'
2737
+
2738
+section "444. COMPOUND COMMANDS"
2739
+
2740
+compare_posix_output "subshell" '(echo hello)'
2741
+compare_posix_output "brace" '{ echo hello; }'
2742
+compare_posix_output "if" 'if true; then echo yes; fi'
2743
+compare_posix_output "for" 'for i in a b; do echo $i; done'
2744
+compare_posix_output "case" 'case x in x) echo yes;; esac'
2745
+
2746
+section "448. WORD SPLITTING"
2747
+
2748
+compare_posix_output "split default" 'x="a b c"; set -- $x; echo $#'
2749
+compare_posix_output "split quoted" 'x="a b c"; set -- "$x"; echo $#'
2750
+compare_posix_output "split ifs" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
2751
+
2752
+section "449. GLOB EXPANSION"
2753
+
2754
+compare_posix_output "glob star" 'mkdir -p /tmp/g$$; touch /tmp/g$$/a; echo /tmp/g$$/* | grep -c g; rm -rf /tmp/g$$'
2755
+compare_posix_output "glob question" 'mkdir -p /tmp/g$$; touch /tmp/g$$/ab; echo /tmp/g$$/a? | grep -c ab; rm -rf /tmp/g$$'
2756
+compare_posix_output "glob bracket" 'mkdir -p /tmp/g$$; touch /tmp/g$$/a1; echo /tmp/g$$/a[12] | grep -c a1; rm -rf /tmp/g$$'
2757
+# Note: Use fixed path since $$ differs between shells
2758
+compare_posix_output "glob nomatch" 'echo /definitely_nonexistent_def/*'
2759
+
2760
+section "450. TILDE EXPANSION"
2761
+
2762
+compare_posix_output "tilde home" 'echo ~ | grep -c /'
2763
+compare_posix_output "tilde plus" 'echo ~+ | grep -c /'
2764
+compare_posix_output "tilde in assign" 'x=~/test; echo $x | grep -c /'
2765
+compare_posix_output "tilde quoted" 'echo "~"'
2766
+
2767
+# ============================================================================
2768
+# SECTION 451-460: BUILTINS EDGE CASES
2769
+# ============================================================================
2770
+
2771
+# Summary
2772
+printf "\n"
2773
+printf "==========================================\n"
2774
+printf "GAP COVERAGE POSIX COMPLIANCE TEST RESULTS ${TEST_PREFIX}\n"
2775
+printf "==========================================\n"
2776
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
2777
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
2778
+printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
2779
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
2780
+printf "==========================================\n"
2781
+
2782
+if [ $((PASSED + FAILED)) -gt 0 ]; then
2783
+    PASS_RATE=$((PASSED * 100 / (PASSED + FAILED)))
2784
+    printf "Pass rate: %d%%\n" "$PASS_RATE"
2785
+fi
2786
+
2787
+if [ "$FAILED" -gt 0 ]; then
2788
+    printf "\n${RED}Failed tests:${NC}\n"
2789
+    printf "%b" "$FAILED_TESTS_LIST"
2790
+    if [ -n "$DEBUG_INFO" ]; then
2791
+        printf "\n${YELLOW}Debug info:${NC}\n%b" "$DEBUG_INFO"
2792
+    fi
2793
+    printf "==========================================\n"
2794
+fi
2795
+
2796
+if [ "$FAILED" -eq 0 ]; then
2797
+    printf "${GREEN}ALL GAP COVERAGE TESTS PASSED!${NC} ✓\n"
2798
+    exit 0
2799
+else
2800
+    printf "${RED}SOME TESTS FAILED${NC} ✗\n"
2801
+    exit 1
2802
+fi
suites/posix/posix_compliance_heredoc.shadded
@@ -0,0 +1,758 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Here-Document and Expansion Test Suite for fortsh
4
+# =====================================
5
+# Tests here-documents and parameter expansion per IEEE Std 1003.1-2017
6
+# Section: Shell Command Language - Here-Documents, Parameter Expansion
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-heredoc]"
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
+
29
+# Check if fortsh exists
30
+if [ ! -x "$FORTSH_BIN" ]; then
31
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
32
+    printf "Please run 'make' first or set FORTSH_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 "396. BASIC HERE-DOCUMENT"
74
+# =====================================
75
+
76
+result=$("$FORTSH_BIN" -c 'cat <<EOF
77
+hello world
78
+EOF' 2>&1)
79
+if [ "$result" = "hello world" ]; then
80
+    pass "Basic here-document"
81
+else
82
+    fail "Basic here-document" "hello world" "$result"
83
+fi
84
+
85
+result=$("$FORTSH_BIN" -c 'cat <<END
86
+line one
87
+line two
88
+END' 2>&1)
89
+expected=$(printf "line one\nline two")
90
+if [ "$result" = "$expected" ]; then
91
+    pass "Here-document multiple lines"
92
+else
93
+    fail "Here-document multiple lines" "$expected" "$result"
94
+fi
95
+
96
+result=$("$FORTSH_BIN" -c 'x=world; cat <<EOF
97
+hello $x
98
+EOF' 2>&1)
99
+if [ "$result" = "hello world" ]; then
100
+    pass "Here-document with variable expansion"
101
+else
102
+    fail "Here-document with variable expansion" "hello world" "$result"
103
+fi
104
+
105
+result=$("$FORTSH_BIN" -c 'cat <<EOF
106
+result: $(echo test)
107
+EOF' 2>&1)
108
+if [ "$result" = "result: test" ]; then
109
+    pass "Here-document with command substitution"
110
+else
111
+    fail "Here-document with command substitution" "result: test" "$result"
112
+fi
113
+
114
+# =====================================
115
+section "397. QUOTED HERE-DOCUMENT DELIMITER"
116
+# =====================================
117
+
118
+result=$("$FORTSH_BIN" -c 'x=world; cat <<"EOF"
119
+hello $x
120
+EOF' 2>&1)
121
+if [ "$result" = 'hello $x' ]; then
122
+    pass "Quoted delimiter prevents expansion"
123
+else
124
+    fail "Quoted delimiter prevents expansion" 'hello $x' "$result"
125
+fi
126
+
127
+result=$("$FORTSH_BIN" -c "cat <<'END'
128
+test \$(echo foo)
129
+END" 2>&1)
130
+if [ "$result" = 'test $(echo foo)' ]; then
131
+    pass "Single-quoted delimiter prevents command sub"
132
+else
133
+    fail "Single-quoted delimiter prevents command sub" 'test $(echo foo)' "$result"
134
+fi
135
+
136
+# =====================================
137
+section "398. HERE-DOCUMENT WITH TAB STRIPPING (<<-)"
138
+# =====================================
139
+
140
+result=$("$FORTSH_BIN" -c '	cat <<-EOF
141
+	hello
142
+	world
143
+	EOF' 2>&1)
144
+expected=$(printf "hello\nworld")
145
+if [ "$result" = "$expected" ]; then
146
+    pass "<<- strips leading tabs"
147
+else
148
+    fail "<<- strips leading tabs" "$expected" "$result"
149
+fi
150
+
151
+result=$("$FORTSH_BIN" -c 'cat <<-END
152
+		indented
153
+	END' 2>&1)
154
+if [ "$result" = "indented" ]; then
155
+    pass "<<- strips multiple tabs"
156
+else
157
+    fail "<<- strips multiple tabs" "indented" "$result"
158
+fi
159
+
160
+# =====================================
161
+section "399. HERE-DOCUMENT TO DIFFERENT COMMANDS"
162
+# =====================================
163
+
164
+result=$("$FORTSH_BIN" -c 'wc -l <<EOF
165
+one
166
+two
167
+three
168
+EOF' 2>&1)
169
+if echo "$result" | grep -q "3"; then
170
+    pass "Here-document to wc -l"
171
+else
172
+    fail "Here-document to wc -l" "3" "$result"
173
+fi
174
+
175
+# Note: read with heredoc can hang if not implemented - use timeout
176
+# Portable: macOS lacks GNU timeout
177
+if command -v timeout >/dev/null 2>&1; then
178
+    _timeout() { timeout "$@"; }
179
+elif command -v gtimeout >/dev/null 2>&1; then
180
+    _timeout() { gtimeout "$@"; }
181
+else
182
+    _timeout() {
183
+        _to_secs="$1"; shift
184
+        _to_tmp=$(mktemp /tmp/fortsh_timeout.XXXXXX)
185
+        ( "$@" ) > "$_to_tmp" 2>&1 &
186
+        _to_pid=$!
187
+        ( sleep "$_to_secs"; kill "$_to_pid" 2>/dev/null ) &
188
+        _to_wd=$!
189
+        wait "$_to_pid" 2>/dev/null
190
+        _to_rc=$?
191
+        kill "$_to_wd" 2>/dev/null
192
+        wait "$_to_wd" 2>/dev/null
193
+        cat "$_to_tmp"
194
+        rm -f "$_to_tmp"
195
+        return $_to_rc
196
+    }
197
+fi
198
+result=$(_timeout 2 "$FORTSH_BIN" -c 'read x <<EOF
199
+test input
200
+EOF
201
+echo "$x"' 2>&1)
202
+exit_code=$?
203
+if [ "$result" = "test input" ]; then
204
+    pass "Here-document to read"
205
+elif [ $exit_code -eq 124 ]; then
206
+    fail "Here-document to read" "test input" "(timeout - hangs)"
207
+else
208
+    fail "Here-document to read" "test input" "$result"
209
+fi
210
+
211
+# =====================================
212
+section "400. PARAMETER EXPANSION ${var:-default}"
213
+# =====================================
214
+
215
+result=$("$FORTSH_BIN" -c 'unset x; echo ${x:-default}' 2>&1)
216
+if [ "$result" = "default" ]; then
217
+    pass "\${var:-default} for unset variable"
218
+else
219
+    fail "\${var:-default} for unset variable" "default" "$result"
220
+fi
221
+
222
+result=$("$FORTSH_BIN" -c 'x=""; echo ${x:-default}' 2>&1)
223
+if [ "$result" = "default" ]; then
224
+    pass "\${var:-default} for empty variable"
225
+else
226
+    fail "\${var:-default} for empty variable" "default" "$result"
227
+fi
228
+
229
+result=$("$FORTSH_BIN" -c 'x=value; echo ${x:-default}' 2>&1)
230
+if [ "$result" = "value" ]; then
231
+    pass "\${var:-default} for set variable"
232
+else
233
+    fail "\${var:-default} for set variable" "value" "$result"
234
+fi
235
+
236
+result=$("$FORTSH_BIN" -c 'unset x; echo ${x-default}' 2>&1)
237
+if [ "$result" = "default" ]; then
238
+    pass "\${var-default} for unset (no colon)"
239
+else
240
+    fail "\${var-default} for unset (no colon)" "default" "$result"
241
+fi
242
+
243
+result=$("$FORTSH_BIN" -c 'x=""; echo ${x-default}' 2>&1)
244
+if [ "$result" = "" ]; then
245
+    pass "\${var-default} for empty (no colon)"
246
+else
247
+    fail "\${var-default} for empty (no colon)" "(empty)" "$result"
248
+fi
249
+
250
+# =====================================
251
+section "401. PARAMETER EXPANSION ${var:+alternate}"
252
+# =====================================
253
+
254
+result=$("$FORTSH_BIN" -c 'x=value; echo ${x:+alternate}' 2>&1)
255
+if [ "$result" = "alternate" ]; then
256
+    pass "\${var:+alternate} for set variable"
257
+else
258
+    fail "\${var:+alternate} for set variable" "alternate" "$result"
259
+fi
260
+
261
+result=$("$FORTSH_BIN" -c 'unset x; echo ${x:+alternate}' 2>&1)
262
+if [ "$result" = "" ]; then
263
+    pass "\${var:+alternate} for unset variable"
264
+else
265
+    fail "\${var:+alternate} for unset variable" "(empty)" "$result"
266
+fi
267
+
268
+result=$("$FORTSH_BIN" -c 'x=""; echo ${x:+alternate}' 2>&1)
269
+if [ "$result" = "" ]; then
270
+    pass "\${var:+alternate} for empty variable"
271
+else
272
+    fail "\${var:+alternate} for empty variable" "(empty)" "$result"
273
+fi
274
+
275
+# =====================================
276
+section "402. PARAMETER EXPANSION ${var:=assign}"
277
+# =====================================
278
+
279
+result=$("$FORTSH_BIN" -c 'unset x; echo ${x:=assigned}; echo $x' 2>&1)
280
+expected=$(printf "assigned\nassigned")
281
+if [ "$result" = "$expected" ]; then
282
+    pass "\${var:=value} assigns when unset"
283
+else
284
+    fail "\${var:=value} assigns when unset" "$expected" "$result"
285
+fi
286
+
287
+result=$("$FORTSH_BIN" -c 'x=""; echo ${x:=assigned}; echo $x' 2>&1)
288
+expected=$(printf "assigned\nassigned")
289
+if [ "$result" = "$expected" ]; then
290
+    pass "\${var:=value} assigns when empty"
291
+else
292
+    fail "\${var:=value} assigns when empty" "$expected" "$result"
293
+fi
294
+
295
+result=$("$FORTSH_BIN" -c 'x=original; echo ${x:=assigned}; echo $x' 2>&1)
296
+expected=$(printf "original\noriginal")
297
+if [ "$result" = "$expected" ]; then
298
+    pass "\${var:=value} keeps original when set"
299
+else
300
+    fail "\${var:=value} keeps original when set" "$expected" "$result"
301
+fi
302
+
303
+# =====================================
304
+section "403. PARAMETER EXPANSION ${var:?error}"
305
+# =====================================
306
+
307
+result=$("$FORTSH_BIN" -c 'x=value; echo ${x:?error message}' 2>&1)
308
+if [ "$result" = "value" ]; then
309
+    pass "\${var:?error} returns value when set"
310
+else
311
+    fail "\${var:?error} returns value when set" "value" "$result"
312
+fi
313
+
314
+result=$("$FORTSH_BIN" -c 'unset x; echo ${x:?custom error}' 2>&1)
315
+if echo "$result" | grep -qi "error\|custom"; then
316
+    pass "\${var:?error} shows error when unset"
317
+else
318
+    fail "\${var:?error} shows error when unset" "error message" "$result"
319
+fi
320
+
321
+# =====================================
322
+section "404. PARAMETER EXPANSION ${#var}"
323
+# =====================================
324
+
325
+result=$("$FORTSH_BIN" -c 'x=hello; echo ${#x}' 2>&1)
326
+if [ "$result" = "5" ]; then
327
+    pass "\${#var} string length"
328
+else
329
+    fail "\${#var} string length" "5" "$result"
330
+fi
331
+
332
+result=$("$FORTSH_BIN" -c 'x=""; echo ${#x}' 2>&1)
333
+if [ "$result" = "0" ]; then
334
+    pass "\${#var} empty string length"
335
+else
336
+    fail "\${#var} empty string length" "0" "$result"
337
+fi
338
+
339
+result=$("$FORTSH_BIN" -c 'x="hello world"; echo ${#x}' 2>&1)
340
+if [ "$result" = "11" ]; then
341
+    pass "\${#var} string with space"
342
+else
343
+    fail "\${#var} string with space" "11" "$result"
344
+fi
345
+
346
+# =====================================
347
+section "405. PARAMETER EXPANSION ${var%pattern}"
348
+# =====================================
349
+
350
+result=$("$FORTSH_BIN" -c 'x="file.txt"; echo ${x%.txt}' 2>&1)
351
+if [ "$result" = "file" ]; then
352
+    pass "\${var%pattern} removes shortest suffix"
353
+else
354
+    fail "\${var%pattern} removes shortest suffix" "file" "$result"
355
+fi
356
+
357
+result=$("$FORTSH_BIN" -c 'x="file.tar.gz"; echo ${x%.*}' 2>&1)
358
+if [ "$result" = "file.tar" ]; then
359
+    pass "\${var%.*} removes extension"
360
+else
361
+    fail "\${var%.*} removes extension" "file.tar" "$result"
362
+fi
363
+
364
+result=$("$FORTSH_BIN" -c 'x="file.tar.gz"; echo ${x%%.*}' 2>&1)
365
+if [ "$result" = "file" ]; then
366
+    pass "\${var%%.*} removes all extensions"
367
+else
368
+    fail "\${var%%.*} removes all extensions" "file" "$result"
369
+fi
370
+
371
+# =====================================
372
+section "406. PARAMETER EXPANSION ${var#pattern}"
373
+# =====================================
374
+
375
+result=$("$FORTSH_BIN" -c 'x="/path/to/file"; echo ${x#*/}' 2>&1)
376
+if [ "$result" = "path/to/file" ]; then
377
+    pass "\${var#*/} removes shortest prefix"
378
+else
379
+    fail "\${var#*/} removes shortest prefix" "path/to/file" "$result"
380
+fi
381
+
382
+result=$("$FORTSH_BIN" -c 'x="/path/to/file"; echo ${x##*/}' 2>&1)
383
+if [ "$result" = "file" ]; then
384
+    pass "\${var##*/} basename extraction"
385
+else
386
+    fail "\${var##*/} basename extraction" "file" "$result"
387
+fi
388
+
389
+result=$("$FORTSH_BIN" -c 'x="prefix_name"; echo ${x#prefix_}' 2>&1)
390
+if [ "$result" = "name" ]; then
391
+    pass "\${var#prefix} removes literal prefix"
392
+else
393
+    fail "\${var#prefix} removes literal prefix" "name" "$result"
394
+fi
395
+
396
+# =====================================
397
+section "407. BACKTICK COMMAND SUBSTITUTION"
398
+# =====================================
399
+
400
+result=$("$FORTSH_BIN" -c 'echo `echo hello`' 2>&1)
401
+if [ "$result" = "hello" ]; then
402
+    pass "Basic backtick substitution"
403
+else
404
+    fail "Basic backtick substitution" "hello" "$result"
405
+fi
406
+
407
+result=$("$FORTSH_BIN" -c 'x=`expr 2 + 3`; echo $x' 2>&1)
408
+if [ "$result" = "5" ]; then
409
+    pass "Backtick with expr"
410
+else
411
+    fail "Backtick with expr" "5" "$result"
412
+fi
413
+
414
+result=$("$FORTSH_BIN" -c 'echo `echo \`echo nested\``' 2>&1)
415
+if [ "$result" = "nested" ]; then
416
+    pass "Nested backtick substitution"
417
+else
418
+    fail "Nested backtick substitution" "nested" "$result"
419
+fi
420
+
421
+# =====================================
422
+section "408. COMMAND SUBSTITUTION EDGE CASES"
423
+# =====================================
424
+
425
+result=$("$FORTSH_BIN" -c 'echo "$(echo "inner quotes")"' 2>&1)
426
+if [ "$result" = "inner quotes" ]; then
427
+    pass "\$(...) with inner quotes"
428
+else
429
+    fail "\$(...) with inner quotes" "inner quotes" "$result"
430
+fi
431
+
432
+result=$("$FORTSH_BIN" -c 'echo $(echo $(echo nested))' 2>&1)
433
+if [ "$result" = "nested" ]; then
434
+    pass "Nested \$(...) substitution"
435
+else
436
+    fail "Nested \$(...) substitution" "nested" "$result"
437
+fi
438
+
439
+result=$("$FORTSH_BIN" -c 'x=$(cat <<EOF
440
+multi
441
+line
442
+EOF
443
+); echo "$x"' 2>&1)
444
+expected=$(printf "multi\nline")
445
+if [ "$result" = "$expected" ]; then
446
+    pass "\$(...) with here-document inside"
447
+else
448
+    fail "\$(...) with here-document inside" "$expected" "$result"
449
+fi
450
+
451
+# =====================================
452
+section "409. ARITHMETIC EXPANSION"
453
+# =====================================
454
+
455
+result=$("$FORTSH_BIN" -c 'echo $((2 + 3))' 2>&1)
456
+if [ "$result" = "5" ]; then
457
+    pass "\$((...)) addition"
458
+else
459
+    fail "\$((...)) addition" "5" "$result"
460
+fi
461
+
462
+result=$("$FORTSH_BIN" -c 'echo $((10 - 3))' 2>&1)
463
+if [ "$result" = "7" ]; then
464
+    pass "\$((...)) subtraction"
465
+else
466
+    fail "\$((...)) subtraction" "7" "$result"
467
+fi
468
+
469
+result=$("$FORTSH_BIN" -c 'echo $((4 * 5))' 2>&1)
470
+if [ "$result" = "20" ]; then
471
+    pass "\$((...)) multiplication"
472
+else
473
+    fail "\$((...)) multiplication" "20" "$result"
474
+fi
475
+
476
+result=$("$FORTSH_BIN" -c 'echo $((20 / 4))' 2>&1)
477
+if [ "$result" = "5" ]; then
478
+    pass "\$((...)) division"
479
+else
480
+    fail "\$((...)) division" "5" "$result"
481
+fi
482
+
483
+result=$("$FORTSH_BIN" -c 'echo $((17 % 5))' 2>&1)
484
+if [ "$result" = "2" ]; then
485
+    pass "\$((...)) modulo"
486
+else
487
+    fail "\$((...)) modulo" "2" "$result"
488
+fi
489
+
490
+result=$("$FORTSH_BIN" -c 'x=5; echo $((x * 2))' 2>&1)
491
+if [ "$result" = "10" ]; then
492
+    pass "\$((...)) with variable (no \$)"
493
+else
494
+    fail "\$((...)) with variable (no \$)" "10" "$result"
495
+fi
496
+
497
+result=$("$FORTSH_BIN" -c 'x=5; echo $(($x * 2))' 2>&1)
498
+if [ "$result" = "10" ]; then
499
+    pass "\$((...)) with \$variable"
500
+else
501
+    fail "\$((...)) with \$variable" "10" "$result"
502
+fi
503
+
504
+result=$("$FORTSH_BIN" -c 'echo $(( (2 + 3) * 4 ))' 2>&1)
505
+if [ "$result" = "20" ]; then
506
+    pass "\$((...)) with parentheses"
507
+else
508
+    fail "\$((...)) with parentheses" "20" "$result"
509
+fi
510
+
511
+result=$("$FORTSH_BIN" -c 'echo $((-5 + 3))' 2>&1)
512
+if [ "$result" = "-2" ]; then
513
+    pass "\$((...)) negative numbers"
514
+else
515
+    fail "\$((...)) negative numbers" "-2" "$result"
516
+fi
517
+
518
+result=$("$FORTSH_BIN" -c 'echo $((5 > 3))' 2>&1)
519
+if [ "$result" = "1" ]; then
520
+    pass "\$((...)) greater than comparison"
521
+else
522
+    fail "\$((...)) greater than comparison" "1" "$result"
523
+fi
524
+
525
+result=$("$FORTSH_BIN" -c 'echo $((3 > 5))' 2>&1)
526
+if [ "$result" = "0" ]; then
527
+    pass "\$((...)) greater than false"
528
+else
529
+    fail "\$((...)) greater than false" "0" "$result"
530
+fi
531
+
532
+result=$("$FORTSH_BIN" -c 'echo $((5 < 10))' 2>&1)
533
+if [ "$result" = "1" ]; then
534
+    pass "\$((...)) less than comparison"
535
+else
536
+    fail "\$((...)) less than comparison" "1" "$result"
537
+fi
538
+
539
+result=$("$FORTSH_BIN" -c 'echo $((5 == 5))' 2>&1)
540
+if [ "$result" = "1" ]; then
541
+    pass "\$((...)) equality"
542
+else
543
+    fail "\$((...)) equality" "1" "$result"
544
+fi
545
+
546
+result=$("$FORTSH_BIN" -c 'echo $((5 != 3))' 2>&1)
547
+if [ "$result" = "1" ]; then
548
+    pass "\$((...)) inequality"
549
+else
550
+    fail "\$((...)) inequality" "1" "$result"
551
+fi
552
+
553
+result=$("$FORTSH_BIN" -c 'echo $((5 >= 5))' 2>&1)
554
+if [ "$result" = "1" ]; then
555
+    pass "\$((...)) greater or equal"
556
+else
557
+    fail "\$((...)) greater or equal" "1" "$result"
558
+fi
559
+
560
+result=$("$FORTSH_BIN" -c 'echo $((3 <= 5))' 2>&1)
561
+if [ "$result" = "1" ]; then
562
+    pass "\$((...)) less or equal"
563
+else
564
+    fail "\$((...)) less or equal" "1" "$result"
565
+fi
566
+
567
+result=$("$FORTSH_BIN" -c 'echo $((1 && 1))' 2>&1)
568
+if [ "$result" = "1" ]; then
569
+    pass "\$((...)) logical AND"
570
+else
571
+    fail "\$((...)) logical AND" "1" "$result"
572
+fi
573
+
574
+result=$("$FORTSH_BIN" -c 'echo $((1 && 0))' 2>&1)
575
+if [ "$result" = "0" ]; then
576
+    pass "\$((...)) logical AND false"
577
+else
578
+    fail "\$((...)) logical AND false" "0" "$result"
579
+fi
580
+
581
+result=$("$FORTSH_BIN" -c 'echo $((0 || 1))' 2>&1)
582
+if [ "$result" = "1" ]; then
583
+    pass "\$((...)) logical OR"
584
+else
585
+    fail "\$((...)) logical OR" "1" "$result"
586
+fi
587
+
588
+result=$("$FORTSH_BIN" -c 'echo $((!0))' 2>&1)
589
+if [ "$result" = "1" ]; then
590
+    pass "\$((...)) logical NOT"
591
+else
592
+    fail "\$((...)) logical NOT" "1" "$result"
593
+fi
594
+
595
+result=$("$FORTSH_BIN" -c 'echo $((5 & 3))' 2>&1)
596
+if [ "$result" = "1" ]; then
597
+    pass "\$((...)) bitwise AND"
598
+else
599
+    fail "\$((...)) bitwise AND" "1" "$result"
600
+fi
601
+
602
+result=$("$FORTSH_BIN" -c 'echo $((5 | 3))' 2>&1)
603
+if [ "$result" = "7" ]; then
604
+    pass "\$((...)) bitwise OR"
605
+else
606
+    fail "\$((...)) bitwise OR" "7" "$result"
607
+fi
608
+
609
+result=$("$FORTSH_BIN" -c 'echo $((5 ^ 3))' 2>&1)
610
+if [ "$result" = "6" ]; then
611
+    pass "\$((...)) bitwise XOR"
612
+else
613
+    fail "\$((...)) bitwise XOR" "6" "$result"
614
+fi
615
+
616
+result=$("$FORTSH_BIN" -c 'echo $((1 << 4))' 2>&1)
617
+if [ "$result" = "16" ]; then
618
+    pass "\$((...)) left shift"
619
+else
620
+    fail "\$((...)) left shift" "16" "$result"
621
+fi
622
+
623
+result=$("$FORTSH_BIN" -c 'echo $((16 >> 2))' 2>&1)
624
+if [ "$result" = "4" ]; then
625
+    pass "\$((...)) right shift"
626
+else
627
+    fail "\$((...)) right shift" "4" "$result"
628
+fi
629
+
630
+result=$("$FORTSH_BIN" -c 'echo $((5 > 3 ? 10 : 20))' 2>&1)
631
+if [ "$result" = "10" ]; then
632
+    pass "\$((...)) ternary true"
633
+else
634
+    fail "\$((...)) ternary true" "10" "$result"
635
+fi
636
+
637
+result=$("$FORTSH_BIN" -c 'echo $((5 < 3 ? 10 : 20))' 2>&1)
638
+if [ "$result" = "20" ]; then
639
+    pass "\$((...)) ternary false"
640
+else
641
+    fail "\$((...)) ternary false" "20" "$result"
642
+fi
643
+
644
+result=$("$FORTSH_BIN" -c 'x=5; echo $((x += 3)); echo $x' 2>&1)
645
+expected=$(printf "8\n8")
646
+if [ "$result" = "$expected" ]; then
647
+    pass "\$((...)) += assignment"
648
+else
649
+    fail "\$((...)) += assignment" "$expected" "$result"
650
+fi
651
+
652
+result=$("$FORTSH_BIN" -c 'x=10; echo $((x -= 3)); echo $x' 2>&1)
653
+expected=$(printf "7\n7")
654
+if [ "$result" = "$expected" ]; then
655
+    pass "\$((...)) -= assignment"
656
+else
657
+    fail "\$((...)) -= assignment" "$expected" "$result"
658
+fi
659
+
660
+result=$("$FORTSH_BIN" -c 'x=5; echo $((x *= 3))' 2>&1)
661
+if [ "$result" = "15" ]; then
662
+    pass "\$((...)) *= assignment"
663
+else
664
+    fail "\$((...)) *= assignment" "15" "$result"
665
+fi
666
+
667
+result=$("$FORTSH_BIN" -c 'x=5; echo $((++x)); echo $x' 2>&1)
668
+expected=$(printf "6\n6")
669
+if [ "$result" = "$expected" ]; then
670
+    pass "\$((...)) pre-increment"
671
+else
672
+    fail "\$((...)) pre-increment" "$expected" "$result"
673
+fi
674
+
675
+result=$("$FORTSH_BIN" -c 'x=5; echo $((x++)); echo $x' 2>&1)
676
+expected=$(printf "5\n6")
677
+if [ "$result" = "$expected" ]; then
678
+    pass "\$((...)) post-increment"
679
+else
680
+    fail "\$((...)) post-increment" "$expected" "$result"
681
+fi
682
+
683
+result=$("$FORTSH_BIN" -c 'x=5; echo $((--x)); echo $x' 2>&1)
684
+expected=$(printf "4\n4")
685
+if [ "$result" = "$expected" ]; then
686
+    pass "\$((...)) pre-decrement"
687
+else
688
+    fail "\$((...)) pre-decrement" "$expected" "$result"
689
+fi
690
+
691
+result=$("$FORTSH_BIN" -c 'x=5; echo $((x--)); echo $x' 2>&1)
692
+expected=$(printf "5\n4")
693
+if [ "$result" = "$expected" ]; then
694
+    pass "\$((...)) post-decrement"
695
+else
696
+    fail "\$((...)) post-decrement" "$expected" "$result"
697
+fi
698
+
699
+result=$("$FORTSH_BIN" -c 'echo $(( 2 + 3 * 4 ))' 2>&1)
700
+if [ "$result" = "14" ]; then
701
+    pass "\$((...)) operator precedence"
702
+else
703
+    fail "\$((...)) operator precedence" "14" "$result"
704
+fi
705
+
706
+result=$("$FORTSH_BIN" -c 'a=2; b=3; echo $(( a * b + a ))' 2>&1)
707
+if [ "$result" = "8" ]; then
708
+    pass "\$((...)) multiple variables"
709
+else
710
+    fail "\$((...)) multiple variables" "8" "$result"
711
+fi
712
+
713
+# =====================================
714
+section "410. TILDE EXPANSION"
715
+# =====================================
716
+
717
+result=$("$FORTSH_BIN" -c 'echo ~' 2>&1)
718
+if [ "$result" = "$HOME" ]; then
719
+    pass "~ expands to \$HOME"
720
+else
721
+    fail "~ expands to \$HOME" "$HOME" "$result"
722
+fi
723
+
724
+result=$("$FORTSH_BIN" -c 'echo ~/test' 2>&1)
725
+if [ "$result" = "$HOME/test" ]; then
726
+    pass "~/path expands correctly"
727
+else
728
+    fail "~/path expands correctly" "$HOME/test" "$result"
729
+fi
730
+
731
+result=$("$FORTSH_BIN" -c 'x=~; echo $x' 2>&1)
732
+if [ "$result" = "$HOME" ]; then
733
+    pass "~ in assignment expands"
734
+else
735
+    fail "~ in assignment expands" "$HOME" "$result"
736
+fi
737
+
738
+# =====================================
739
+# Summary
740
+# =====================================
741
+printf "\n"
742
+printf "${BLUE}==========================================\n"
743
+printf "POSIX Here-Document and Expansion Summary\n"
744
+printf "==========================================${NC}\n"
745
+printf "Passed:  ${GREEN}%d${NC}\n" "$PASSED"
746
+printf "Failed:  ${RED}%d${NC}\n" "$FAILED"
747
+printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
748
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
749
+
750
+if [ -n "$FAILED_TESTS_LIST" ]; then
751
+    printf "\n${RED}Failed tests:${NC}\n"
752
+    printf "%b" "$FAILED_TESTS_LIST"
753
+fi
754
+
755
+if [ "$FAILED" -gt 0 ]; then
756
+    exit 1
757
+fi
758
+exit 0
suites/posix/posix_compliance_jobcontrol.shadded
@@ -0,0 +1,258 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance - Job Control Tests
4
+# =====================================
5
+# Tests for POSIX job control features (jobs, fg, bg, job specs)
6
+
7
+# Colors (POSIX-compliant way)
8
+RED='\033[0;31m'
9
+GREEN='\033[0;32m'
10
+YELLOW='\033[1;33m'
11
+BLUE='\033[0;34m'
12
+NC='\033[0m'
13
+
14
+# Test identification
15
+TEST_PREFIX="[posix-jobcontrol]"
16
+CURRENT_SECTION=""
17
+TEST_NUM=0
18
+
19
+PASSED=0
20
+FAILED=0
21
+SKIPPED=0
22
+FAILED_TESTS_LIST=""
23
+
24
+# Get script directory (POSIX way)
25
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
26
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
27
+
28
+# Check if fortsh exists
29
+if [ ! -x "$FORTSH_BIN" ]; then
30
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
31
+    printf "Please run 'make' first or set FORTSH_BIN environment variable\n"
32
+    exit 1
33
+fi
34
+
35
+# Test result trackers
36
+pass() {
37
+    TEST_NUM=$((TEST_NUM + 1))
38
+    printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
39
+    PASSED=$((PASSED + 1))
40
+}
41
+
42
+fail() {
43
+    TEST_NUM=$((TEST_NUM + 1))
44
+    TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
45
+    printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
46
+    FAILED_TESTS_LIST="${FAILED_TESTS_LIST}  ${TEST_ID}: $1\n"
47
+    if [ -n "$2" ]; then
48
+        printf "  expected: %s\n" "$2"
49
+    fi
50
+    if [ -n "$3" ]; then
51
+        printf "  got:      %s\n" "$3"
52
+    fi
53
+    FAILED=$((FAILED + 1))
54
+}
55
+
56
+skip() {
57
+    TEST_NUM=$((TEST_NUM + 1))
58
+    printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
59
+    SKIPPED=$((SKIPPED + 1))
60
+}
61
+
62
+section() {
63
+    # Extract section number from header like "146. BASIC JOB CONTROL"
64
+    CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
65
+    TEST_NUM=0
66
+    printf "\n${BLUE}==========================================\n"
67
+    printf "%s\n" "$1"
68
+    printf "==========================================${NC}\n"
69
+}
70
+
71
+# Test that command succeeds
72
+test_succeeds() {
73
+    test_name="$1"
74
+    test_cmd="$2"
75
+
76
+    if FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" >/dev/null 2>&1; then
77
+        pass "$test_name"
78
+    else
79
+        fail "$test_name" "should succeed" "failed"
80
+    fi
81
+}
82
+
83
+# Test that command produces expected output
84
+test_output() {
85
+    test_name="$1"
86
+    test_cmd="$2"
87
+    expected="$3"
88
+
89
+    output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1)
90
+    if [ "$output" = "$expected" ]; then
91
+        pass "$test_name"
92
+    else
93
+        fail "$test_name" "$expected" "$output"
94
+    fi
95
+}
96
+
97
+# Test that output contains pattern
98
+test_contains() {
99
+    test_name="$1"
100
+    test_cmd="$2"
101
+    pattern="$3"
102
+
103
+    output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1)
104
+    if echo "$output" | grep -q "$pattern"; then
105
+        pass "$test_name"
106
+    else
107
+        fail "$test_name" "output containing '$pattern'" "$output"
108
+    fi
109
+}
110
+
111
+# =====================================
112
+# TESTS START HERE
113
+# =====================================
114
+
115
+section "146. BASIC JOB CONTROL"
116
+
117
+# Note: Many job control features require interactive mode
118
+# We test what we can in non-interactive mode
119
+
120
+test_succeeds "jobs builtin exists" 'jobs >/dev/null 2>&1 || true'
121
+test_succeeds "bg builtin exists" 'bg 2>/dev/null || true'
122
+test_succeeds "fg builtin exists" 'fg 2>/dev/null || true'
123
+
124
+section "147. BACKGROUND JOBS"
125
+
126
+test_succeeds "simple background job" 'sleep 0.1 &'
127
+test_succeeds "background with wait" 'sleep 0.1 & wait'
128
+test_output "background job count" 'sleep 0.2 & sleep 0.2 & jobs | wc -l | tr -d " "' '2'
129
+test_succeeds "wait for specific job" 'sleep 0.1 & PID=$!; wait $PID'
130
+
131
+section "148. JOB EXIT STATUS"
132
+
133
+test_output "background true exit" 'true & wait $!; echo $?' '0'
134
+test_output "background false exit" 'false & wait $!; echo $?' '1'
135
+test_output "wait preserves status" 'sh -c "exit 42" & wait $!; echo $?' '42'
136
+
137
+section "149. JOBS BUILTIN OUTPUT"
138
+
139
+test_succeeds "jobs with no jobs" 'jobs'
140
+test_succeeds "jobs after background" 'sleep 0.5 & jobs; wait'
141
+# Test that jobs shows running processes
142
+test_contains "jobs shows running" 'sleep 0.5 & jobs' 'sleep'
143
+
144
+section "150. BACKGROUND PIPELINES"
145
+
146
+test_succeeds "pipeline in background" 'echo test | cat &'
147
+test_succeeds "multi-stage background pipeline" 'echo test | cat | cat & wait'
148
+# Pipeline output appears before exit status since it runs in background
149
+test_output "background pipeline exit" 'true | cat & wait $!; echo $?' '0'
150
+
151
+section "151. $! LAST BACKGROUND PID"
152
+
153
+test_succeeds "$! is set after background" 'sleep 0.1 & test -n "$!"'
154
+# Use test -gt to check numeric - pattern [!0-9] has portability issues
155
+test_succeeds "$! is numeric" 'sleep 0.1 & test "$!" -gt 0'
156
+test_succeeds "wait for $!" 'sleep 0.1 & wait $!'
157
+
158
+section "152. JOB SPECIFICATIONS (if supported)"
159
+
160
+# These may not work in non-interactive mode, so we're lenient
161
+skip "job spec %1 (interactive feature)"
162
+skip "job spec %% (interactive feature)"
163
+skip "job spec %+ (interactive feature)"
164
+skip "job spec %- (interactive feature)"
165
+
166
+section "153. FG/BG WITH SUSPENDED JOBS"
167
+
168
+# Suspending jobs requires interactive terminal (Ctrl-Z)
169
+skip "fg with suspended job (requires interactive tty)"
170
+skip "bg with suspended job (requires interactive tty)"
171
+
172
+section "154. WAIT EDGE CASES"
173
+
174
+test_output "wait with no args" 'sleep 0.1 & sleep 0.1 & wait; echo done' 'done'
175
+test_output "wait nonexistent PID" 'wait 999999 2>&1 | grep -q "not found\|No such" && echo error || echo ok' 'error'
176
+test_succeeds "multiple waits" 'sleep 0.1 & P1=$!; sleep 0.1 & P2=$!; wait $P1; wait $P2'
177
+
178
+section "155. SET -m MONITOR MODE"
179
+
180
+test_succeeds "set -m enables" 'set -m'
181
+test_succeeds "set +m disables" 'set -m; set +m'
182
+test_output "monitor doesn't affect output" 'set -m; echo test; set +m' 'test'
183
+
184
+section "156. JOB CONTROL WITH FUNCTIONS"
185
+
186
+test_succeeds "background function" 'f() { echo test; }; f &'
187
+test_output "wait for function" 'f() { echo ok; }; f & wait; echo done' 'ok
188
+done'
189
+
190
+section "157. DISOWN (if implemented)"
191
+
192
+# disown is not strictly POSIX but common
193
+skip "disown builtin (not required by POSIX)"
194
+
195
+section "158. AMPERSAND SEMANTICS"
196
+
197
+# Background jobs still output to stdout - just test it succeeds
198
+test_succeeds "& at end" 'echo test &'
199
+# Background output order is nondeterministic; check all values are present
200
+test_output_unordered() {
201
+    test_name="$1"
202
+    test_cmd="$2"
203
+    shift 2
204
+
205
+    output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1)
206
+    for word in "$@"; do
207
+        if ! echo "$output" | grep -qF "$word"; then
208
+            fail "$test_name" "output containing '$word'" "$output"
209
+            return
210
+        fi
211
+    done
212
+    pass "$test_name"
213
+}
214
+test_output_unordered "multiple & commands" 'echo a & echo b & wait; echo done' a b done
215
+
216
+section "159. BACKGROUND SUBSHELLS"
217
+
218
+test_succeeds "subshell in background" '(sleep 0.1) &'
219
+test_output "background subshell isolation" 'VAR=outer; (VAR=inner) & wait; echo $VAR' 'outer'
220
+
221
+section "160. JOB CONTROL ERROR CASES"
222
+
223
+# Test error messages for invalid job control operations
224
+test_contains "fg with no jobs" 'fg 2>&1' 'no.*job\|No current job'
225
+test_contains "bg with no jobs" 'bg 2>&1' 'no.*job\|No current job'
226
+
227
+# =====================================
228
+# SUMMARY
229
+# =====================================
230
+
231
+printf "\n==========================================\n"
232
+printf "JOB CONTROL TEST RESULTS ${TEST_PREFIX}\n"
233
+printf "==========================================\n"
234
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
235
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
236
+printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
237
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
238
+printf "==========================================\n"
239
+
240
+# Calculate pass rate (excluding skipped)
241
+if [ "$((PASSED + FAILED))" -gt 0 ]; then
242
+    pass_rate=$((PASSED * 100 / (PASSED + FAILED)))
243
+    printf "Pass rate: %d%% (excluding skipped)\n" "$pass_rate"
244
+fi
245
+
246
+if [ "$FAILED" -gt 0 ]; then
247
+    printf "\n${RED}Failed tests:${NC}\n"
248
+    printf "%b" "$FAILED_TESTS_LIST"
249
+    printf "==========================================\n"
250
+fi
251
+
252
+if [ "$FAILED" -eq 0 ]; then
253
+    printf "${GREEN}ALL NON-SKIPPED JOB CONTROL TESTS PASSED!${NC} ✓\n"
254
+    exit 0
255
+else
256
+    printf "${RED}SOME TESTS FAILED${NC} ✗\n"
257
+    exit 1
258
+fi
suites/posix/posix_compliance_options.shadded
1248 lines changed — click to load
@@ -0,0 +1,1248 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Shell Options and Read Builtin Test Suite for fortsh
4
+# =====================================
5
+# Tests set options and read builtin per IEEE Std 1003.1-2017
6
+# Section: Shell Command Language - set, read
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-options]"
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 "381. SET -e (ERREXIT)"
75
+# =====================================
76
+
77
+result=$("$FORTSH_BIN" -c 'set -e; true; echo reached' 2>&1)
78
+if [ "$result" = "reached" ]; then
79
+    pass "set -e allows true command"
80
+else
81
+    fail "set -e allows true command" "reached" "$result"
82
+fi
83
+
84
+result=$("$FORTSH_BIN" -c 'set -e; false; echo "should not reach"' 2>&1)
85
+if [ -z "$result" ] || ! echo "$result" | grep -q "should not reach"; then
86
+    pass "set -e exits on false"
87
+else
88
+    fail "set -e exits on false" "(no output)" "$result"
89
+fi
90
+
91
+result=$("$FORTSH_BIN" -c 'set -e; if false; then echo no; fi; echo reached' 2>&1)
92
+if [ "$result" = "reached" ]; then
93
+    pass "set -e ignores false in if condition"
94
+else
95
+    fail "set -e ignores false in if condition" "reached" "$result"
96
+fi
97
+
98
+result=$("$FORTSH_BIN" -c 'set -e; false || echo fallback' 2>&1)
99
+if [ "$result" = "fallback" ]; then
100
+    pass "set -e ignores false with || continuation"
101
+else
102
+    fail "set -e ignores false with || continuation" "fallback" "$result"
103
+fi
104
+
105
+result=$("$FORTSH_BIN" -c 'set -e; ! false; echo reached' 2>&1)
106
+if [ "$result" = "reached" ]; then
107
+    pass "set -e ignores negated false"
108
+else
109
+    fail "set -e ignores negated false" "reached" "$result"
110
+fi
111
+
112
+# =====================================
113
+section "382. SET -u (NOUNSET)"
114
+# =====================================
115
+
116
+result=$("$FORTSH_BIN" -c 'set -u; x=hello; echo $x' 2>&1)
117
+if [ "$result" = "hello" ]; then
118
+    pass "set -u allows set variable"
119
+else
120
+    fail "set -u allows set variable" "hello" "$result"
121
+fi
122
+
123
+result=$("$FORTSH_BIN" -c 'set -u; echo $UNSET_VAR_12345' 2>&1)
124
+exit_code=$?
125
+if echo "$result" | grep -qi "unbound\|unset\|error" || [ $exit_code -ne 0 ]; then
126
+    pass "set -u errors on unset variable"
127
+else
128
+    fail "set -u errors on unset variable" "error message" "$result"
129
+fi
130
+
131
+result=$("$FORTSH_BIN" -c 'set -u; echo ${UNSET_VAR_12345:-default}' 2>&1)
132
+if [ "$result" = "default" ]; then
133
+    pass "set -u allows default expansion"
134
+else
135
+    fail "set -u allows default expansion" "default" "$result"
136
+fi
137
+
138
+# =====================================
139
+section "383. SET -f (NOGLOB)"
140
+# =====================================
141
+
142
+result=$("$FORTSH_BIN" -c 'set -f; echo *' 2>&1)
143
+if [ "$result" = "*" ]; then
144
+    pass "set -f disables globbing"
145
+else
146
+    fail "set -f disables globbing" "*" "$result"
147
+fi
148
+
149
+result=$("$FORTSH_BIN" -c 'set -f; echo [a-z]*' 2>&1)
150
+if [ "$result" = "[a-z]*" ]; then
151
+    pass "set -f disables bracket patterns"
152
+else
153
+    fail "set -f disables bracket patterns" "[a-z]*" "$result"
154
+fi
155
+
156
+result=$("$FORTSH_BIN" -c 'set -f; set +f; cd /tmp && echo [a-z]* | head -c 20' 2>&1)
157
+if [ "$result" != "[a-z]*" ] && [ -n "$result" ]; then
158
+    pass "set +f re-enables globbing"
159
+else
160
+    fail "set +f re-enables globbing" "(expanded)" "$result"
161
+fi
162
+
163
+# =====================================
164
+section "384. SET -n (NOEXEC)"
165
+# =====================================
166
+
167
+result=$("$FORTSH_BIN" -c 'set -n; echo hello' 2>&1)
168
+if [ -z "$result" ]; then
169
+    pass "set -n prevents execution"
170
+else
171
+    fail "set -n prevents execution" "(empty)" "$result"
172
+fi
173
+
174
+# =====================================
175
+section "385. SET -x (XTRACE)"
176
+# =====================================
177
+
178
+result=$("$FORTSH_BIN" -c 'set -x; echo hello' 2>&1)
179
+if echo "$result" | grep -q "echo hello\|+ echo"; then
180
+    pass "set -x shows trace output"
181
+else
182
+    fail "set -x shows trace output" "trace line" "$result"
183
+fi
184
+
185
+result=$("$FORTSH_BIN" -c 'set -x; x=5; echo $x' 2>&1)
186
+if echo "$result" | grep -qE "x=5|echo 5|\+ echo"; then
187
+    pass "set -x traces variable assignment"
188
+else
189
+    fail "set -x traces variable assignment" "trace lines" "$result"
190
+fi
191
+
192
+# =====================================
193
+section "386. SET -v (VERBOSE)"
194
+# =====================================
195
+
196
+# Note: set -v only echoes lines as they're READ, not from -c argument
197
+# Test that -v option is accepted and runs without error
198
+"$FORTSH_BIN" -c 'set -v; echo hello' >/dev/null 2>&1
199
+fortsh_exit=$?
200
+"$BASH_REF" -c 'set -v; echo hello' >/dev/null 2>&1
201
+bash_exit=$?
202
+if [ "$fortsh_exit" -eq "$bash_exit" ]; then
203
+    pass "set -v runs without error"
204
+else
205
+    fail "set -v runs without error" "exit=$bash_exit" "exit=$fortsh_exit"
206
+fi
207
+
208
+# =====================================
209
+section "387. SET -C (NOCLOBBER)"
210
+# =====================================
211
+
212
+TEST_DIR="/tmp/fortsh_options_$$"
213
+mkdir -p "$TEST_DIR"
214
+
215
+result=$("$FORTSH_BIN" -c 'set -C; echo test > '"$TEST_DIR"'/clobber1; echo test2 > '"$TEST_DIR"'/clobber1' 2>&1)
216
+if echo "$result" | grep -qi "exist\|cannot\|error\|clobber"; then
217
+    pass "set -C prevents clobbering existing file"
218
+else
219
+    # Check if file still has original content
220
+    if [ -f "$TEST_DIR/clobber1" ]; then
221
+        content=$(cat "$TEST_DIR/clobber1")
222
+        if [ "$content" = "test" ]; then
223
+            pass "set -C prevents clobbering existing file"
224
+        else
225
+            fail "set -C prevents clobbering existing file" "error or original content" "$result"
226
+        fi
227
+    else
228
+        fail "set -C prevents clobbering existing file" "error message" "$result"
229
+    fi
230
+fi
231
+
232
+rm -rf "$TEST_DIR"
233
+
234
+# =====================================
235
+section "388. SET -o OPTIONS"
236
+# =====================================
237
+
238
+result=$("$FORTSH_BIN" -c 'set -o errexit; true; echo ok' 2>&1)
239
+if [ "$result" = "ok" ]; then
240
+    pass "set -o errexit (same as -e)"
241
+else
242
+    fail "set -o errexit (same as -e)" "ok" "$result"
243
+fi
244
+
245
+result=$("$FORTSH_BIN" -c 'set -o noglob; echo *' 2>&1)
246
+if [ "$result" = "*" ]; then
247
+    pass "set -o noglob (same as -f)"
248
+else
249
+    fail "set -o noglob (same as -f)" "*" "$result"
250
+fi
251
+
252
+result=$("$FORTSH_BIN" -c 'set -o nounset; x=val; echo $x' 2>&1)
253
+if [ "$result" = "val" ]; then
254
+    pass "set -o nounset (same as -u)"
255
+else
256
+    fail "set -o nounset (same as -u)" "val" "$result"
257
+fi
258
+
259
+# =====================================
260
+section "389. SET -- POSITIONAL PARAMETERS"
261
+# =====================================
262
+
263
+result=$("$FORTSH_BIN" -c 'set -- a b c; echo $1 $2 $3' 2>&1)
264
+if [ "$result" = "a b c" ]; then
265
+    pass "set -- sets positional parameters"
266
+else
267
+    fail "set -- sets positional parameters" "a b c" "$result"
268
+fi
269
+
270
+result=$("$FORTSH_BIN" -c 'set -- a b c d e; echo $#' 2>&1)
271
+if [ "$result" = "5" ]; then
272
+    pass "set -- updates \$#"
273
+else
274
+    fail "set -- updates \$#" "5" "$result"
275
+fi
276
+
277
+result=$("$FORTSH_BIN" -c 'set -- a b c; set --; echo $#' 2>&1)
278
+if [ "$result" = "0" ]; then
279
+    pass "set -- clears positional parameters"
280
+else
281
+    fail "set -- clears positional parameters" "0" "$result"
282
+fi
283
+
284
+result=$("$FORTSH_BIN" -c 'set -- "a b" c; echo $1' 2>&1)
285
+if [ "$result" = "a b" ]; then
286
+    pass "set -- preserves quoted args"
287
+else
288
+    fail "set -- preserves quoted args" "a b" "$result"
289
+fi
290
+
291
+# =====================================
292
+section "390. READ BUILTIN BASIC"
293
+# =====================================
294
+
295
+result=$("$FORTSH_BIN" -c 'echo "hello" | { read x; echo $x; }' 2>&1)
296
+if [ "$result" = "hello" ]; then
297
+    pass "read basic input"
298
+else
299
+    fail "read basic input" "hello" "$result"
300
+fi
301
+
302
+result=$("$FORTSH_BIN" -c 'echo "one two three" | { read a b c; echo "$a|$b|$c"; }' 2>&1)
303
+if [ "$result" = "one|two|three" ]; then
304
+    pass "read multiple variables"
305
+else
306
+    fail "read multiple variables" "one|two|three" "$result"
307
+fi
308
+
309
+result=$("$FORTSH_BIN" -c 'echo "one two three four" | { read a b; echo "$a|$b"; }' 2>&1)
310
+if [ "$result" = "one|two three four" ]; then
311
+    pass "read remaining into last variable"
312
+else
313
+    fail "read remaining into last variable" "one|two three four" "$result"
314
+fi
315
+
316
+# =====================================
317
+section "391. READ WITH IFS"
318
+# =====================================
319
+
320
+result=$("$FORTSH_BIN" -c 'echo "a:b:c" | { IFS=: read x y z; echo "$x|$y|$z"; }' 2>&1)
321
+if [ "$result" = "a|b|c" ]; then
322
+    pass "read with custom IFS"
323
+else
324
+    fail "read with custom IFS" "a|b|c" "$result"
325
+fi
326
+
327
+result=$("$FORTSH_BIN" -c 'echo "a::c" | { IFS=: read x y z; echo "$x|$y|$z"; }' 2>&1)
328
+if [ "$result" = "a||c" ]; then
329
+    pass "read with empty field"
330
+else
331
+    fail "read with empty field" "a||c" "$result"
332
+fi
333
+
334
+result=$("$FORTSH_BIN" -c 'echo "  a  b  " | { read x y; echo ">$x|$y<"; }' 2>&1)
335
+if [ "$result" = ">a|b<" ]; then
336
+    pass "read strips leading/trailing whitespace"
337
+else
338
+    fail "read strips leading/trailing whitespace" ">a|b<" "$result"
339
+fi
340
+
341
+# =====================================
342
+section "392. READ -r (RAW MODE)"
343
+# =====================================
344
+
345
+result=$("$FORTSH_BIN" -c 'echo "hello\\nworld" | { read -r x; echo "$x"; }' 2>&1)
346
+if [ "$result" = 'hello\nworld' ]; then
347
+    pass "read -r preserves backslashes"
348
+else
349
+    fail "read -r preserves backslashes" 'hello\nworld' "$result"
350
+fi
351
+
352
+# POSIX: read without -r joins lines ending with backslash (line continuation)
353
+# printf with single quotes produces: line<backslash><newline>continued<newline>
354
+# read joins: line + continued = linecontinued
355
+result=$("$FORTSH_BIN" -c 'printf '"'"'line\\\ncontinued\n'"'"' | { read x; echo "$x"; }' 2>&1)
356
+if [ "$result" = "linecontinued" ]; then
357
+    pass "read without -r joins continued lines"
358
+else
359
+    fail "read without -r joins continued lines" "linecontinued" "$result"
360
+fi
361
+
362
+# =====================================
363
+section "393. READ EXIT STATUS"
364
+# =====================================
365
+
366
+result=$("$FORTSH_BIN" -c 'echo "test" | { read x; echo $?; }' 2>&1)
367
+if [ "$result" = "0" ]; then
368
+    pass "read returns 0 on success"
369
+else
370
+    fail "read returns 0 on success" "0" "$result"
371
+fi
372
+
373
+result=$("$FORTSH_BIN" -c 'echo -n "" | { read x; echo $?; }' 2>&1)
374
+if [ "$result" = "1" ]; then
375
+    pass "read returns non-zero on EOF"
376
+else
377
+    fail "read returns non-zero on EOF" "1" "$result"
378
+fi
379
+
380
+# =====================================
381
+section "394. SHIFT BUILTIN"
382
+# =====================================
383
+
384
+result=$("$FORTSH_BIN" -c 'set -- a b c d; shift; echo $1 $2 $3' 2>&1)
385
+if [ "$result" = "b c d" ]; then
386
+    pass "shift removes first parameter"
387
+else
388
+    fail "shift removes first parameter" "b c d" "$result"
389
+fi
390
+
391
+result=$("$FORTSH_BIN" -c 'set -- a b c d e; shift 2; echo $1 $2 $3' 2>&1)
392
+if [ "$result" = "c d e" ]; then
393
+    pass "shift n removes n parameters"
394
+else
395
+    fail "shift n removes n parameters" "c d e" "$result"
396
+fi
397
+
398
+result=$("$FORTSH_BIN" -c 'set -- a b c; shift; echo $#' 2>&1)
399
+if [ "$result" = "2" ]; then
400
+    pass "shift updates \$#"
401
+else
402
+    fail "shift updates \$#" "2" "$result"
403
+fi
404
+
405
+result=$("$FORTSH_BIN" -c 'set -- a; shift 5 2>/dev/null; echo $?' 2>&1)
406
+if [ "$result" = "1" ]; then
407
+    pass "shift beyond count returns error"
408
+else
409
+    fail "shift beyond count returns error" "1" "$result"
410
+fi
411
+
412
+# =====================================
413
+section "395. SPECIAL PARAMETERS"
414
+# =====================================
415
+
416
+result=$("$FORTSH_BIN" -c 'set -- a b c; echo "$@"' 2>&1)
417
+if [ "$result" = "a b c" ]; then
418
+    pass "\$@ expands all positional parameters"
419
+else
420
+    fail "\$@ expands all positional parameters" "a b c" "$result"
421
+fi
422
+
423
+result=$("$FORTSH_BIN" -c 'set -- a b c; echo "$*"' 2>&1)
424
+if [ "$result" = "a b c" ]; then
425
+    pass "\$* expands all positional parameters"
426
+else
427
+    fail "\$* expands all positional parameters" "a b c" "$result"
428
+fi
429
+
430
+result=$("$FORTSH_BIN" -c 'set -- a b c; for x in "$@"; do echo "[$x]"; done' 2>&1)
431
+expected=$(printf "[a]\n[b]\n[c]")
432
+if [ "$result" = "$expected" ]; then
433
+    pass "\"\$@\" preserves separate arguments"
434
+else
435
+    fail "\"\$@\" preserves separate arguments" "$expected" "$result"
436
+fi
437
+
438
+result=$("$FORTSH_BIN" -c 'set -- "a b" c; for x in "$@"; do echo "[$x]"; done' 2>&1)
439
+expected=$(printf "[a b]\n[c]")
440
+if [ "$result" = "$expected" ]; then
441
+    pass "\"\$@\" preserves quoted arguments"
442
+else
443
+    fail "\"\$@\" preserves quoted arguments" "$expected" "$result"
444
+fi
445
+
446
+result=$("$FORTSH_BIN" -c 'echo $$' 2>&1)
447
+if echo "$result" | grep -qE '^[0-9]+$' && [ "$result" -gt 0 ]; then
448
+    pass "\$\$ returns PID"
449
+else
450
+    fail "\$\$ returns PID" "numeric PID" "$result"
451
+fi
452
+
453
+result=$("$FORTSH_BIN" -c 'echo $?' 2>&1)
454
+if [ "$result" = "0" ]; then
455
+    pass "\$? returns exit status"
456
+else
457
+    fail "\$? returns exit status" "0" "$result"
458
+fi
459
+
460
+# =====================================
461
+section "396. IFS WORD SPLITTING"
462
+# =====================================
463
+
464
+result=$("$FORTSH_BIN" -c 'x="a:b:c"; IFS=:; for w in $x; do echo "[$w]"; done' 2>&1)
465
+expected=$(printf "[a]\n[b]\n[c]")
466
+if [ "$result" = "$expected" ]; then
467
+    pass "IFS changes word splitting"
468
+else
469
+    fail "IFS changes word splitting" "$expected" "$result"
470
+fi
471
+
472
+result=$("$FORTSH_BIN" -c 'x="a::c"; IFS=:; set -- $x; echo $#' 2>&1)
473
+if [ "$result" = "3" ]; then
474
+    pass "IFS empty fields create arguments"
475
+else
476
+    fail "IFS empty fields create arguments" "3" "$result"
477
+fi
478
+
479
+result=$("$FORTSH_BIN" -c 'IFS=:; x="a:b:c"; echo $x' 2>&1)
480
+if [ "$result" = "a b c" ]; then
481
+    pass "IFS affects echo output"
482
+else
483
+    fail "IFS affects echo output" "a b c" "$result"
484
+fi
485
+
486
+result=$("$FORTSH_BIN" -c 'IFS=""; x="a b c"; for w in $x; do echo "[$w]"; done' 2>&1)
487
+if [ "$result" = "[a b c]" ]; then
488
+    pass "Empty IFS prevents splitting"
489
+else
490
+    fail "Empty IFS prevents splitting" "[a b c]" "$result"
491
+fi
492
+
493
+result=$("$FORTSH_BIN" -c 'unset IFS; x="a  b"; echo $x' 2>&1)
494
+if [ "$result" = "a b" ]; then
495
+    pass "unset IFS uses default"
496
+else
497
+    fail "unset IFS uses default" "a b" "$result"
498
+fi
499
+
500
+result=$("$FORTSH_BIN" -c 'IFS=",;"; x="a,b;c"; set -- $x; echo $1 $2 $3' 2>&1)
501
+if [ "$result" = "a b c" ]; then
502
+    pass "IFS with multiple characters"
503
+else
504
+    fail "IFS with multiple characters" "a b c" "$result"
505
+fi
506
+
507
+# =====================================
508
+section "397. ALIAS BUILTIN"
509
+# =====================================
510
+
511
+result=$("$FORTSH_BIN" -c 'alias ll="ls -la"; alias ll' 2>&1)
512
+if echo "$result" | grep -q "ls -la\|ll="; then
513
+    pass "alias defines and shows alias"
514
+else
515
+    fail "alias defines and shows alias" "ls -la" "$result"
516
+fi
517
+
518
+# Note: Aliases defined in -c mode aren't expanded in the same command line
519
+# This matches bash behavior - aliases are expanded at parse time
520
+result=$("$FORTSH_BIN" -c 'alias greeting="echo hello"; alias greeting' 2>&1)
521
+if echo "$result" | grep -q "echo hello"; then
522
+    pass "alias defines correctly (matches bash)"
523
+else
524
+    fail "alias defines correctly (matches bash)" "echo hello" "$result"
525
+fi
526
+
527
+result=$("$FORTSH_BIN" -c 'alias x="echo a"; alias y="echo b"; alias | wc -l' 2>&1)
528
+if [ "$result" -ge 2 ] 2>/dev/null; then
529
+    pass "alias lists all aliases"
530
+else
531
+    fail "alias lists all aliases" "at least 2" "$result"
532
+fi
533
+
534
+result=$("$FORTSH_BIN" -c 'alias greet="echo hi"; unalias greet; greet 2>/dev/null; echo $?' 2>&1)
535
+if echo "$result" | grep -qE "127|1"; then
536
+    pass "unalias removes alias"
537
+else
538
+    fail "unalias removes alias" "non-zero exit" "$result"
539
+fi
540
+
541
+# Note: Test redefinition by checking alias output (execution won't work in same line)
542
+result=$("$FORTSH_BIN" -c 'alias foo="echo one"; alias foo="echo two"; alias foo' 2>&1)
543
+if echo "$result" | grep -q "echo two"; then
544
+    pass "alias can be redefined"
545
+else
546
+    fail "alias can be redefined" "echo two" "$result"
547
+fi
548
+
549
+# =====================================
550
+section "398. EXPORT BUILTIN"
551
+# =====================================
552
+
553
+result=$("$FORTSH_BIN" -c 'export TESTVAR=hello; echo $TESTVAR' 2>&1)
554
+if [ "$result" = "hello" ]; then
555
+    pass "export sets and exports variable"
556
+else
557
+    fail "export sets and exports variable" "hello" "$result"
558
+fi
559
+
560
+result=$("$FORTSH_BIN" -c 'MYVAR=world; export MYVAR; echo $MYVAR' 2>&1)
561
+if [ "$result" = "world" ]; then
562
+    pass "export existing variable"
563
+else
564
+    fail "export existing variable" "world" "$result"
565
+fi
566
+
567
+result=$("$FORTSH_BIN" -c 'export X=1 Y=2 Z=3; echo $X $Y $Z' 2>&1)
568
+if [ "$result" = "1 2 3" ]; then
569
+    pass "export multiple variables"
570
+else
571
+    fail "export multiple variables" "1 2 3" "$result"
572
+fi
573
+
574
+result=$("$FORTSH_BIN" -c 'export SUBTEST=value; sh -c "echo \$SUBTEST"' 2>&1)
575
+if [ "$result" = "value" ]; then
576
+    pass "exported var visible in subshell"
577
+else
578
+    fail "exported var visible in subshell" "value" "$result"
579
+fi
580
+
581
+# =====================================
582
+section "399. READONLY BUILTIN"
583
+# =====================================
584
+
585
+result=$("$FORTSH_BIN" -c 'readonly CONST=42; echo $CONST' 2>&1)
586
+if [ "$result" = "42" ]; then
587
+    pass "readonly sets variable"
588
+else
589
+    fail "readonly sets variable" "42" "$result"
590
+fi
591
+
592
+result=$("$FORTSH_BIN" -c 'readonly RO=1; RO=2 2>&1; echo $RO' 2>&1)
593
+if echo "$result" | grep -q "1"; then
594
+    pass "readonly prevents modification"
595
+else
596
+    fail "readonly prevents modification" "1" "$result"
597
+fi
598
+
599
+result=$("$FORTSH_BIN" -c 'readonly A=1 B=2; echo $A $B' 2>&1)
600
+if [ "$result" = "1 2" ]; then
601
+    pass "readonly multiple variables"
602
+else
603
+    fail "readonly multiple variables" "1 2" "$result"
604
+fi
605
+
606
+# =====================================
607
+section "400. UNSET BUILTIN"
608
+# =====================================
609
+
610
+result=$("$FORTSH_BIN" -c 'x=hello; unset x; echo "${x:-unset}"' 2>&1)
611
+if [ "$result" = "unset" ]; then
612
+    pass "unset removes variable"
613
+else
614
+    fail "unset removes variable" "unset" "$result"
615
+fi
616
+
617
+result=$("$FORTSH_BIN" -c 'x=1; y=2; unset x y; echo "${x:-a} ${y:-b}"' 2>&1)
618
+if [ "$result" = "a b" ]; then
619
+    pass "unset multiple variables"
620
+else
621
+    fail "unset multiple variables" "a b" "$result"
622
+fi
623
+
624
+result=$("$FORTSH_BIN" -c 'f() { echo hi; }; unset -f f; type f 2>/dev/null; echo $?' 2>&1)
625
+if echo "$result" | grep -qE "1|127"; then
626
+    pass "unset -f removes function"
627
+else
628
+    fail "unset -f removes function" "non-zero exit" "$result"
629
+fi
630
+
631
+# =====================================
632
+section "401. GETOPTS BUILTIN"
633
+# =====================================
634
+
635
+# Basic option parsing
636
+result=$("$FORTSH_BIN" -c 'set -- -a; getopts "ab" opt; echo $opt' 2>&1)
637
+if [ "$result" = "a" ]; then
638
+    pass "getopts parses simple option"
639
+else
640
+    fail "getopts parses simple option" "a" "$result"
641
+fi
642
+
643
+# Option with argument
644
+result=$("$FORTSH_BIN" -c 'set -- -a value; getopts "a:" opt; echo "$OPTARG"' 2>&1)
645
+if [ "$result" = "value" ]; then
646
+    pass "getopts sets OPTARG"
647
+else
648
+    fail "getopts sets OPTARG" "value" "$result"
649
+fi
650
+
651
+# OPTIND increment
652
+result=$("$FORTSH_BIN" -c 'set -- -a -b; getopts "ab" opt; getopts "ab" opt; echo $OPTIND' 2>&1)
653
+if [ "$result" = "3" ]; then
654
+    pass "getopts increments OPTIND"
655
+else
656
+    fail "getopts increments OPTIND" "3" "$result"
657
+fi
658
+
659
+# Multiple options in loop
660
+result=$("$FORTSH_BIN" -c 'set -- -a -b -c; while getopts "abc" opt; do echo -n $opt; done' 2>&1)
661
+if [ "$result" = "abc" ]; then
662
+    pass "getopts in while loop"
663
+else
664
+    fail "getopts in while loop" "abc" "$result"
665
+fi
666
+
667
+# Invalid option handling
668
+result=$("$FORTSH_BIN" -c 'set -- -z; getopts "ab" opt 2>/dev/null; echo $opt' 2>&1)
669
+if [ "$result" = "?" ]; then
670
+    pass "getopts returns ? for invalid option"
671
+else
672
+    fail "getopts returns ? for invalid option" "?" "$result"
673
+fi
674
+
675
+# Silent mode with leading colon
676
+result=$("$FORTSH_BIN" -c 'set -- -a; getopts ":a:b" opt; echo $opt' 2>&1)
677
+if [ "$result" = ":" ] || [ "$result" = "a" ]; then
678
+    pass "getopts silent mode with colon"
679
+else
680
+    fail "getopts silent mode with colon" ": or a" "$result"
681
+fi
682
+
683
+# Reset OPTIND
684
+result=$("$FORTSH_BIN" -c 'set -- -a; getopts "a" opt; OPTIND=1; getopts "a" opt; echo $opt' 2>&1)
685
+if [ "$result" = "a" ]; then
686
+    pass "OPTIND reset allows reparse"
687
+else
688
+    fail "OPTIND reset allows reparse" "a" "$result"
689
+fi
690
+
691
+# =====================================
692
+section "402. TYPE BUILTIN"
693
+# =====================================
694
+
695
+# type finds commands
696
+result=$("$FORTSH_BIN" -c 'type echo' 2>&1)
697
+if echo "$result" | grep -qE "echo|builtin|/"; then
698
+    pass "type finds echo"
699
+else
700
+    fail "type finds echo" "echo info" "$result"
701
+fi
702
+
703
+# type returns error for unknown
704
+result=$("$FORTSH_BIN" -c 'type nonexistent_xyz_cmd 2>/dev/null; echo $?' 2>&1)
705
+if [ "$result" != "0" ]; then
706
+    pass "type returns error for unknown command"
707
+else
708
+    fail "type returns error for unknown command" "non-zero" "$result"
709
+fi
710
+
711
+# type finds functions
712
+result=$("$FORTSH_BIN" -c 'myfunc() { :; }; type myfunc' 2>&1)
713
+if echo "$result" | grep -qE "myfunc|function"; then
714
+    pass "type finds functions"
715
+else
716
+    fail "type finds functions" "function info" "$result"
717
+fi
718
+
719
+# type finds aliases
720
+result=$("$FORTSH_BIN" -c 'alias myalias=echo; type myalias' 2>&1)
721
+if echo "$result" | grep -qE "myalias|alias"; then
722
+    pass "type finds aliases"
723
+else
724
+    fail "type finds aliases" "alias info" "$result"
725
+fi
726
+
727
+# type finds builtins
728
+result=$("$FORTSH_BIN" -c 'type cd' 2>&1)
729
+if echo "$result" | grep -qE "cd|builtin"; then
730
+    pass "type identifies builtins"
731
+else
732
+    fail "type identifies builtins" "builtin info" "$result"
733
+fi
734
+
735
+# =====================================
736
+section "403. COMMAND BUILTIN"
737
+# =====================================
738
+
739
+# command -v finds commands
740
+result=$("$FORTSH_BIN" -c 'command -v echo' 2>&1)
741
+if [ -n "$result" ]; then
742
+    pass "command -v finds echo"
743
+else
744
+    fail "command -v finds echo" "non-empty" "$result"
745
+fi
746
+
747
+# command -v returns empty for unknown
748
+result=$("$FORTSH_BIN" -c 'command -v nonexistent_xyz_cmd; echo $?' 2>&1)
749
+if [ "$result" != "0" ]; then
750
+    pass "command -v returns error for unknown"
751
+else
752
+    fail "command -v returns error for unknown" "non-zero exit" "$result"
753
+fi
754
+
755
+# command bypasses functions
756
+result=$("$FORTSH_BIN" -c 'echo() { printf "FUNC"; }; command echo hello' 2>&1)
757
+if [ "$result" = "hello" ]; then
758
+    pass "command bypasses function"
759
+else
760
+    fail "command bypasses function" "hello" "$result"
761
+fi
762
+
763
+# command bypasses aliases
764
+result=$("$FORTSH_BIN" -c 'alias echo="printf ALIAS"; command echo hello' 2>&1)
765
+if [ "$result" = "hello" ]; then
766
+    pass "command bypasses alias"
767
+else
768
+    fail "command bypasses alias" "hello" "$result"
769
+fi
770
+
771
+# =====================================
772
+section "404. TRAP BUILTIN EDGE CASES"
773
+# =====================================
774
+
775
+# trap with empty action
776
+result=$("$FORTSH_BIN" -c 'trap "" INT; trap' 2>&1)
777
+if echo "$result" | grep -qE "INT|''"; then
778
+    pass "trap with empty action (ignore signal)"
779
+else
780
+    fail "trap with empty action (ignore signal)" "INT trap" "$result"
781
+fi
782
+
783
+# trap - removes trap
784
+result=$("$FORTSH_BIN" -c 'trap "echo x" EXIT; trap - EXIT; trap' 2>&1)
785
+if ! echo "$result" | grep -q "EXIT"; then
786
+    pass "trap - removes trap"
787
+else
788
+    fail "trap - removes trap" "no EXIT" "$result"
789
+fi
790
+
791
+# Multiple traps
792
+result=$("$FORTSH_BIN" -c 'trap "echo INT" INT; trap "echo TERM" TERM; trap | wc -l' 2>&1)
793
+if [ "$result" -ge 2 ] 2>/dev/null; then
794
+    pass "Multiple traps can be set"
795
+else
796
+    fail "Multiple traps can be set" ">=2 lines" "$result"
797
+fi
798
+
799
+# trap with no args lists traps
800
+result=$("$FORTSH_BIN" -c 'trap "echo x" INT; trap' 2>&1)
801
+if [ -n "$result" ]; then
802
+    pass "trap with no args lists traps"
803
+else
804
+    fail "trap with no args lists traps" "non-empty" "$result"
805
+fi
806
+
807
+# EXIT trap runs on exit
808
+result=$("$FORTSH_BIN" -c 'trap "echo EXITING" EXIT; exit 0' 2>&1)
809
+if [ "$result" = "EXITING" ]; then
810
+    pass "EXIT trap executes on exit"
811
+else
812
+    fail "EXIT trap executes on exit" "EXITING" "$result"
813
+fi
814
+
815
+# =====================================
816
+section "405. HASH BUILTIN"
817
+# =====================================
818
+
819
+# hash without args
820
+result=$("$FORTSH_BIN" -c 'hash 2>&1; echo $?' 2>&1)
821
+# hash returns 0 even if empty
822
+pass "hash builtin exists"
823
+
824
+# hash -r clears cache
825
+result=$("$FORTSH_BIN" -c 'ls >/dev/null 2>&1; hash -r; echo $?' 2>&1)
826
+if [ "$result" = "0" ]; then
827
+    pass "hash -r clears cache"
828
+else
829
+    fail "hash -r clears cache" "0" "$result"
830
+fi
831
+
832
+# =====================================
833
+section "406. WAIT BUILTIN EDGE CASES"
834
+# =====================================
835
+
836
+# wait with no args
837
+result=$("$FORTSH_BIN" -c 'sleep 0.1 & sleep 0.1 & wait; echo done' 2>&1)
838
+if [ "$result" = "done" ]; then
839
+    pass "wait with no args waits for all"
840
+else
841
+    fail "wait with no args waits for all" "done" "$result"
842
+fi
843
+
844
+# wait with specific PID
845
+result=$("$FORTSH_BIN" -c 'sleep 0.1 & pid=$!; wait $pid; echo $?' 2>&1)
846
+if [ "$result" = "0" ]; then
847
+    pass "wait with PID"
848
+else
849
+    fail "wait with PID" "0" "$result"
850
+fi
851
+
852
+# wait preserves exit status
853
+result=$("$FORTSH_BIN" -c '(exit 42) & pid=$!; wait $pid; echo $?' 2>&1)
854
+if [ "$result" = "42" ]; then
855
+    pass "wait preserves exit status"
856
+else
857
+    fail "wait preserves exit status" "42" "$result"
858
+fi
859
+
860
+# =====================================
861
+section "407. ULIMIT BUILTIN"
862
+# =====================================
863
+
864
+# ulimit -a shows limits
865
+result=$("$FORTSH_BIN" -c 'ulimit -a 2>&1' | head -5)
866
+if [ -n "$result" ]; then
867
+    pass "ulimit -a shows limits"
868
+else
869
+    fail "ulimit -a shows limits" "non-empty" "$result"
870
+fi
871
+
872
+# ulimit shows soft limit
873
+result=$("$FORTSH_BIN" -c 'ulimit 2>&1' | head -1)
874
+if [ -n "$result" ]; then
875
+    pass "ulimit shows default limit"
876
+else
877
+    fail "ulimit shows default limit" "non-empty" "$result"
878
+fi
879
+
880
+# =====================================
881
+section "408. READ BUILTIN"
882
+# =====================================
883
+
884
+# read single variable
885
+result=$("$FORTSH_BIN" -c 'echo "hello" | { read x; echo $x; }' 2>&1)
886
+if [ "$result" = "hello" ]; then
887
+    pass "read single variable"
888
+else
889
+    fail "read single variable" "hello" "$result"
890
+fi
891
+
892
+# read multiple variables
893
+result=$("$FORTSH_BIN" -c 'echo "a b c" | { read x y z; echo "$x:$y:$z"; }' 2>&1)
894
+if [ "$result" = "a:b:c" ]; then
895
+    pass "read multiple variables"
896
+else
897
+    fail "read multiple variables" "a:b:c" "$result"
898
+fi
899
+
900
+# read with extra words
901
+result=$("$FORTSH_BIN" -c 'echo "a b c d e" | { read x y; echo "$x|$y"; }' 2>&1)
902
+if [ "$result" = "a|b c d e" ]; then
903
+    pass "read extra words to last var"
904
+else
905
+    fail "read extra words to last var" "a|b c d e" "$result"
906
+fi
907
+
908
+# =====================================
909
+section "409. PRINTF BUILTIN"
910
+# =====================================
911
+
912
+# printf basic
913
+result=$("$FORTSH_BIN" -c 'printf "hello\n"' 2>&1)
914
+if [ "$result" = "hello" ]; then
915
+    pass "printf basic"
916
+else
917
+    fail "printf basic" "hello" "$result"
918
+fi
919
+
920
+# printf with format
921
+result=$("$FORTSH_BIN" -c 'printf "%s world\n" "hello"' 2>&1)
922
+if [ "$result" = "hello world" ]; then
923
+    pass "printf with %s"
924
+else
925
+    fail "printf with %s" "hello world" "$result"
926
+fi
927
+
928
+# printf with number
929
+result=$("$FORTSH_BIN" -c 'printf "%d\n" 42' 2>&1)
930
+if [ "$result" = "42" ]; then
931
+    pass "printf with %d"
932
+else
933
+    fail "printf with %d" "42" "$result"
934
+fi
935
+
936
+# printf width
937
+result=$("$FORTSH_BIN" -c 'printf "%5d\n" 42' 2>&1)
938
+if [ "$result" = "   42" ]; then
939
+    pass "printf with width"
940
+else
941
+    fail "printf with width" "   42" "$result"
942
+fi
943
+
944
+# =====================================
945
+section "410. ECHO BUILTIN"
946
+# =====================================
947
+
948
+# echo basic
949
+result=$("$FORTSH_BIN" -c 'echo hello' 2>&1)
950
+if [ "$result" = "hello" ]; then
951
+    pass "echo basic"
952
+else
953
+    fail "echo basic" "hello" "$result"
954
+fi
955
+
956
+# echo multiple args
957
+result=$("$FORTSH_BIN" -c 'echo a b c' 2>&1)
958
+if [ "$result" = "a b c" ]; then
959
+    pass "echo multiple args"
960
+else
961
+    fail "echo multiple args" "a b c" "$result"
962
+fi
963
+
964
+# echo with quotes
965
+result=$("$FORTSH_BIN" -c 'echo "hello world"' 2>&1)
966
+if [ "$result" = "hello world" ]; then
967
+    pass "echo with quotes"
968
+else
969
+    fail "echo with quotes" "hello world" "$result"
970
+fi
971
+
972
+# =====================================
973
+section "411. KILL BUILTIN"
974
+# =====================================
975
+
976
+# kill -l lists signals
977
+result=$("$FORTSH_BIN" -c 'kill -l 2>&1 | head -1')
978
+if [ -n "$result" ]; then
979
+    pass "kill -l lists signals"
980
+else
981
+    fail "kill -l lists signals" "non-empty" "$result"
982
+fi
983
+
984
+# =====================================
985
+section "412. SET OPTIONS"
986
+# =====================================
987
+
988
+# set -e (errexit)
989
+result=$("$FORTSH_BIN" -c 'set -e; true; echo reached' 2>&1)
990
+if [ "$result" = "reached" ]; then
991
+    pass "set -e continues on success"
992
+else
993
+    fail "set -e continues on success" "reached" "$result"
994
+fi
995
+
996
+# set -x (xtrace)
997
+result=$("$FORTSH_BIN" -c 'set -x; echo test 2>&1' | grep -c test)
998
+if [ "$result" -ge 1 ]; then
999
+    pass "set -x traces commands"
1000
+else
1001
+    fail "set -x traces commands"
1002
+fi
1003
+
1004
+# set -f (noglob)
1005
+result=$("$FORTSH_BIN" -c 'set -f; echo *' 2>&1)
1006
+if [ "$result" = "*" ]; then
1007
+    pass "set -f disables glob"
1008
+else
1009
+    fail "set -f disables glob" "*" "$result"
1010
+fi
1011
+
1012
+# set +f (enable glob)
1013
+result=$("$FORTSH_BIN" -c 'set -f; set +f; ls /*.txt 2>/dev/null | wc -l || echo 0' 2>&1)
1014
+if echo "$result" | grep -qE '^[[:space:]]*[0-9]+[[:space:]]*$'; then
1015
+    pass "set +f enables glob"
1016
+else
1017
+    fail "set +f enables glob"
1018
+fi
1019
+
1020
+# =====================================
1021
+section "413. UNSET BUILTIN"
1022
+# =====================================
1023
+
1024
+# unset variable
1025
+result=$("$FORTSH_BIN" -c 'x=value; unset x; echo ${x:-empty}' 2>&1)
1026
+if [ "$result" = "empty" ]; then
1027
+    pass "unset removes variable"
1028
+else
1029
+    fail "unset removes variable" "empty" "$result"
1030
+fi
1031
+
1032
+# unset function
1033
+result=$("$FORTSH_BIN" -c 'f() { echo hi; }; unset -f f; f 2>/dev/null || echo gone' 2>&1)
1034
+if [ "$result" = "gone" ]; then
1035
+    pass "unset -f removes function"
1036
+else
1037
+    fail "unset -f removes function" "gone" "$result"
1038
+fi
1039
+
1040
+# unset readonly fails
1041
+result=$("$FORTSH_BIN" -c 'readonly x=1; unset x 2>/dev/null; echo $?' 2>&1)
1042
+if [ "$result" != "0" ]; then
1043
+    pass "unset readonly fails"
1044
+else
1045
+    fail "unset readonly fails" "non-zero" "$result"
1046
+fi
1047
+
1048
+# =====================================
1049
+section "414. SHIFT BUILTIN"
1050
+# =====================================
1051
+
1052
+# basic shift
1053
+result=$("$FORTSH_BIN" -c 'set -- a b c; shift; echo $1' 2>&1)
1054
+if [ "$result" = "b" ]; then
1055
+    pass "shift removes first arg"
1056
+else
1057
+    fail "shift removes first arg" "b" "$result"
1058
+fi
1059
+
1060
+# shift with count
1061
+result=$("$FORTSH_BIN" -c 'set -- a b c d e; shift 3; echo $1' 2>&1)
1062
+if [ "$result" = "d" ]; then
1063
+    pass "shift with count"
1064
+else
1065
+    fail "shift with count" "d" "$result"
1066
+fi
1067
+
1068
+# shift updates $#
1069
+result=$("$FORTSH_BIN" -c 'set -- a b c; shift; echo $#' 2>&1)
1070
+if [ "$result" = "2" ]; then
1071
+    pass "shift updates arg count"
1072
+else
1073
+    fail "shift updates arg count" "2" "$result"
1074
+fi
1075
+
1076
+# =====================================
1077
+section "415. TRUE AND FALSE BUILTINS"
1078
+# =====================================
1079
+
1080
+# true returns 0
1081
+result=$("$FORTSH_BIN" -c 'true; echo $?' 2>&1)
1082
+if [ "$result" = "0" ]; then
1083
+    pass "true returns 0"
1084
+else
1085
+    fail "true returns 0" "0" "$result"
1086
+fi
1087
+
1088
+# false returns 1
1089
+result=$("$FORTSH_BIN" -c 'false; echo $?' 2>&1)
1090
+if [ "$result" = "1" ]; then
1091
+    pass "false returns 1"
1092
+else
1093
+    fail "false returns 1" "1" "$result"
1094
+fi
1095
+
1096
+# true in conditional
1097
+result=$("$FORTSH_BIN" -c 'if true; then echo yes; fi' 2>&1)
1098
+if [ "$result" = "yes" ]; then
1099
+    pass "true in if condition"
1100
+else
1101
+    fail "true in if condition" "yes" "$result"
1102
+fi
1103
+
1104
+# false in conditional
1105
+result=$("$FORTSH_BIN" -c 'if false; then echo no; else echo yes; fi' 2>&1)
1106
+if [ "$result" = "yes" ]; then
1107
+    pass "false in if condition"
1108
+else
1109
+    fail "false in if condition" "yes" "$result"
1110
+fi
1111
+
1112
+# =====================================
1113
+section "416. COLON BUILTIN"
1114
+# =====================================
1115
+
1116
+# colon returns 0
1117
+result=$("$FORTSH_BIN" -c ':; echo $?' 2>&1)
1118
+if [ "$result" = "0" ]; then
1119
+    pass "colon returns 0"
1120
+else
1121
+    fail "colon returns 0" "0" "$result"
1122
+fi
1123
+
1124
+# colon with args (ignored)
1125
+result=$("$FORTSH_BIN" -c ': arg1 arg2 arg3; echo $?' 2>&1)
1126
+if [ "$result" = "0" ]; then
1127
+    pass "colon ignores args"
1128
+else
1129
+    fail "colon ignores args" "0" "$result"
1130
+fi
1131
+
1132
+# colon in loop condition
1133
+result=$("$FORTSH_BIN" -c 'i=0; while :; do i=$((i+1)); [ $i -ge 3 ] && break; done; echo $i' 2>&1)
1134
+if [ "$result" = "3" ]; then
1135
+    pass "colon as infinite loop condition"
1136
+else
1137
+    fail "colon as infinite loop condition" "3" "$result"
1138
+fi
1139
+
1140
+# =====================================
1141
+section "417. PWD BUILTIN"
1142
+# =====================================
1143
+
1144
+# pwd returns current dir
1145
+result=$("$FORTSH_BIN" -c 'pwd | grep -c "^/"' 2>&1)
1146
+if [ "$result" = "1" ]; then
1147
+    pass "pwd returns absolute path"
1148
+else
1149
+    fail "pwd returns absolute path" "1" "$result"
1150
+fi
1151
+
1152
+# pwd after cd
1153
+result=$("$FORTSH_BIN" -c 'cd /tmp && pwd' 2>&1)
1154
+if [ "$result" = "/tmp" ]; then
1155
+    pass "pwd after cd"
1156
+else
1157
+    fail "pwd after cd" "/tmp" "$result"
1158
+fi
1159
+
1160
+# =====================================
1161
+section "418. CD BUILTIN"
1162
+# =====================================
1163
+
1164
+# cd to absolute path
1165
+result=$("$FORTSH_BIN" -c 'cd /tmp && pwd' 2>&1)
1166
+if [ "$result" = "/tmp" ]; then
1167
+    pass "cd to absolute path"
1168
+else
1169
+    fail "cd to absolute path" "/tmp" "$result"
1170
+fi
1171
+
1172
+# cd - returns to OLDPWD
1173
+result=$("$FORTSH_BIN" -c 'cd /tmp; cd /; cd - 2>&1' 2>&1)
1174
+if echo "$result" | grep -q "tmp"; then
1175
+    pass "cd - returns to OLDPWD"
1176
+else
1177
+    fail "cd - returns to OLDPWD"
1178
+fi
1179
+
1180
+# cd nonexistent fails
1181
+result=$("$FORTSH_BIN" -c 'cd /nonexistent_dir_xyz 2>/dev/null; echo $?' 2>&1)
1182
+if [ "$result" != "0" ]; then
1183
+    pass "cd nonexistent fails"
1184
+else
1185
+    fail "cd nonexistent fails" "non-zero" "$result"
1186
+fi
1187
+
1188
+# =====================================
1189
+section "419. TIMES BUILTIN"
1190
+# =====================================
1191
+
1192
+# times outputs something
1193
+result=$("$FORTSH_BIN" -c 'times 2>&1 | head -1 || echo skipped')
1194
+if [ -n "$result" ]; then
1195
+    pass "times outputs timing info"
1196
+else
1197
+    fail "times outputs timing info" "non-empty" "$result"
1198
+fi
1199
+
1200
+# =====================================
1201
+section "420. EXIT BUILTIN"
1202
+# =====================================
1203
+
1204
+# exit with code
1205
+result=$("$FORTSH_BIN" -c 'exit 42' 2>&1; echo $?)
1206
+if [ "$result" = "42" ]; then
1207
+    pass "exit with code"
1208
+else
1209
+    fail "exit with code" "42" "$result"
1210
+fi
1211
+
1212
+# exit default 0
1213
+result=$("$FORTSH_BIN" -c 'exit' 2>&1; echo $?)
1214
+if [ "$result" = "0" ]; then
1215
+    pass "exit default 0"
1216
+else
1217
+    fail "exit default 0" "0" "$result"
1218
+fi
1219
+
1220
+# exit from subshell
1221
+result=$("$FORTSH_BIN" -c '(exit 7); echo $?' 2>&1)
1222
+if [ "$result" = "7" ]; then
1223
+    pass "exit from subshell"
1224
+else
1225
+    fail "exit from subshell" "7" "$result"
1226
+fi
1227
+
1228
+# =====================================
1229
+# Summary
1230
+# =====================================
1231
+printf "\n"
1232
+printf "${BLUE}==========================================\n"
1233
+printf "POSIX Shell Options and Read Summary\n"
1234
+printf "==========================================${NC}\n"
1235
+printf "Passed:  ${GREEN}%d${NC}\n" "$PASSED"
1236
+printf "Failed:  ${RED}%d${NC}\n" "$FAILED"
1237
+printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
1238
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
1239
+
1240
+if [ -n "$FAILED_TESTS_LIST" ]; then
1241
+    printf "\n${RED}Failed tests:${NC}\n"
1242
+    printf "%b" "$FAILED_TESTS_LIST"
1243
+fi
1244
+
1245
+if [ "$FAILED" -gt 0 ]; then
1246
+    exit 1
1247
+fi
1248
+exit 0
suites/posix/posix_compliance_printf.shadded
@@ -0,0 +1,428 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance printf Builtin Test Suite for fortsh
4
+# =====================================
5
+# Tests the printf builtin command per IEEE Std 1003.1-2017
6
+# Section: Shell & Utilities - printf
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-printf]"
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
+
29
+# Check if fortsh exists
30
+if [ ! -x "$FORTSH_BIN" ]; then
31
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
32
+    printf "Please run 'make' first or set FORTSH_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. PRINTF BASIC STRING FORMAT %s"
74
+# =====================================
75
+
76
+result=$("$FORTSH_BIN" -c 'printf "%s\n" "hello"' 2>&1)
77
+if [ "$result" = "hello" ]; then
78
+    pass "printf %s basic string"
79
+else
80
+    fail "printf %s basic string" "hello" "$result"
81
+fi
82
+
83
+result=$("$FORTSH_BIN" -c 'printf "%s %s\n" "hello" "world"' 2>&1)
84
+if [ "$result" = "hello world" ]; then
85
+    pass "printf %s multiple arguments"
86
+else
87
+    fail "printf %s multiple arguments" "hello world" "$result"
88
+fi
89
+
90
+result=$("$FORTSH_BIN" -c 'printf "%s" ""' 2>&1)
91
+if [ "$result" = "" ]; then
92
+    pass "printf %s empty string"
93
+else
94
+    fail "printf %s empty string" "(empty)" "$result"
95
+fi
96
+
97
+result=$("$FORTSH_BIN" -c 'printf "%.3s\n" "hello"' 2>&1)
98
+if [ "$result" = "hel" ]; then
99
+    pass "printf %.3s precision truncates"
100
+else
101
+    fail "printf %.3s precision truncates" "hel" "$result"
102
+fi
103
+
104
+result=$("$FORTSH_BIN" -c 'printf "%10s\n" "hi"' 2>&1)
105
+if [ "$result" = "        hi" ]; then
106
+    pass "printf %10s width right-align"
107
+else
108
+    fail "printf %10s width right-align" "        hi" "$result"
109
+fi
110
+
111
+result=$("$FORTSH_BIN" -c 'printf "%-10s|\n" "hi"' 2>&1)
112
+if [ "$result" = "hi        |" ]; then
113
+    pass "printf %-10s width left-align"
114
+else
115
+    fail "printf %-10s width left-align" "hi        |" "$result"
116
+fi
117
+
118
+# =====================================
119
+section "359. PRINTF INTEGER FORMATS %d %i"
120
+# =====================================
121
+
122
+result=$("$FORTSH_BIN" -c 'printf "%d\n" 42' 2>&1)
123
+if [ "$result" = "42" ]; then
124
+    pass "printf %d basic decimal"
125
+else
126
+    fail "printf %d basic decimal" "42" "$result"
127
+fi
128
+
129
+result=$("$FORTSH_BIN" -c 'printf "%i\n" 42' 2>&1)
130
+if [ "$result" = "42" ]; then
131
+    pass "printf %i basic integer"
132
+else
133
+    fail "printf %i basic integer" "42" "$result"
134
+fi
135
+
136
+result=$("$FORTSH_BIN" -c 'printf "%d\n" -42' 2>&1)
137
+if [ "$result" = "-42" ]; then
138
+    pass "printf %d negative number"
139
+else
140
+    fail "printf %d negative number" "-42" "$result"
141
+fi
142
+
143
+result=$("$FORTSH_BIN" -c 'printf "%5d\n" 42' 2>&1)
144
+if [ "$result" = "   42" ]; then
145
+    pass "printf %5d width padding"
146
+else
147
+    fail "printf %5d width padding" "   42" "$result"
148
+fi
149
+
150
+result=$("$FORTSH_BIN" -c 'printf "%05d\n" 42' 2>&1)
151
+if [ "$result" = "00042" ]; then
152
+    pass "printf %05d zero padding"
153
+else
154
+    fail "printf %05d zero padding" "00042" "$result"
155
+fi
156
+
157
+result=$("$FORTSH_BIN" -c 'printf "%-5d|\n" 42' 2>&1)
158
+if [ "$result" = "42   |" ]; then
159
+    pass "printf %-5d left-align"
160
+else
161
+    fail "printf %-5d left-align" "42   |" "$result"
162
+fi
163
+
164
+result=$("$FORTSH_BIN" -c 'printf "%+d\n" 42' 2>&1)
165
+if [ "$result" = "+42" ]; then
166
+    pass "printf %+d explicit plus sign"
167
+else
168
+    fail "printf %+d explicit plus sign" "+42" "$result"
169
+fi
170
+
171
+# =====================================
172
+section "360. PRINTF OCTAL AND HEX FORMATS %o %x %X"
173
+# =====================================
174
+
175
+result=$("$FORTSH_BIN" -c 'printf "%o\n" 8' 2>&1)
176
+if [ "$result" = "10" ]; then
177
+    pass "printf %o octal format"
178
+else
179
+    fail "printf %o octal format" "10" "$result"
180
+fi
181
+
182
+result=$("$FORTSH_BIN" -c 'printf "%x\n" 255' 2>&1)
183
+if [ "$result" = "ff" ]; then
184
+    pass "printf %x lowercase hex"
185
+else
186
+    fail "printf %x lowercase hex" "ff" "$result"
187
+fi
188
+
189
+result=$("$FORTSH_BIN" -c 'printf "%X\n" 255' 2>&1)
190
+if [ "$result" = "FF" ]; then
191
+    pass "printf %X uppercase hex"
192
+else
193
+    fail "printf %X uppercase hex" "FF" "$result"
194
+fi
195
+
196
+result=$("$FORTSH_BIN" -c 'printf "%#x\n" 255' 2>&1)
197
+if [ "$result" = "0xff" ]; then
198
+    pass "printf %#x alternate form"
199
+else
200
+    fail "printf %#x alternate form" "0xff" "$result"
201
+fi
202
+
203
+result=$("$FORTSH_BIN" -c 'printf "%#o\n" 8' 2>&1)
204
+if [ "$result" = "010" ]; then
205
+    pass "printf %#o alternate octal form"
206
+else
207
+    fail "printf %#o alternate octal form" "010" "$result"
208
+fi
209
+
210
+# =====================================
211
+section "361. PRINTF CHARACTER FORMAT %c"
212
+# =====================================
213
+
214
+result=$("$FORTSH_BIN" -c 'printf "%c\n" A' 2>&1)
215
+if [ "$result" = "A" ]; then
216
+    pass "printf %c single character"
217
+else
218
+    fail "printf %c single character" "A" "$result"
219
+fi
220
+
221
+result=$("$FORTSH_BIN" -c 'printf "%c\n" "hello"' 2>&1)
222
+if [ "$result" = "h" ]; then
223
+    pass "printf %c first char of string"
224
+else
225
+    fail "printf %c first char of string" "h" "$result"
226
+fi
227
+
228
+result=$("$FORTSH_BIN" -c 'printf "%c%c%c\n" a b c' 2>&1)
229
+if [ "$result" = "abc" ]; then
230
+    pass "printf %c multiple chars"
231
+else
232
+    fail "printf %c multiple chars" "abc" "$result"
233
+fi
234
+
235
+# =====================================
236
+section "362. PRINTF ESCAPE SEQUENCES"
237
+# =====================================
238
+
239
+result=$("$FORTSH_BIN" -c 'printf "hello\nworld\n"' 2>&1)
240
+expected=$(printf "hello\nworld")
241
+if [ "$result" = "$expected" ]; then
242
+    pass "printf \\n newline"
243
+else
244
+    fail "printf \\n newline" "$expected" "$result"
245
+fi
246
+
247
+result=$("$FORTSH_BIN" -c 'printf "a\tb\n"' 2>&1)
248
+expected=$(printf "a\tb")
249
+if [ "$result" = "$expected" ]; then
250
+    pass "printf \\t tab"
251
+else
252
+    fail "printf \\t tab" "$expected" "$result"
253
+fi
254
+
255
+result=$("$FORTSH_BIN" -c 'printf "back\\\\slash\n"' 2>&1)
256
+if [ "$result" = 'back\slash' ]; then
257
+    pass "printf \\\\ literal backslash"
258
+else
259
+    fail "printf \\\\ literal backslash" 'back\slash' "$result"
260
+fi
261
+
262
+result=$("$FORTSH_BIN" -c 'printf "100%%\n"' 2>&1)
263
+if [ "$result" = "100%" ]; then
264
+    pass "printf %% literal percent"
265
+else
266
+    fail "printf %% literal percent" "100%" "$result"
267
+fi
268
+
269
+result=$("$FORTSH_BIN" -c 'printf "\101\n"' 2>&1)
270
+if [ "$result" = "A" ]; then
271
+    pass "printf \\NNN octal escape"
272
+else
273
+    fail "printf \\NNN octal escape" "A" "$result"
274
+fi
275
+
276
+# =====================================
277
+section "363. PRINTF %b ESCAPE INTERPRETATION"
278
+# =====================================
279
+
280
+result=$("$FORTSH_BIN" -c 'printf "%b\n" "hello\nworld"' 2>&1)
281
+expected=$(printf "hello\nworld")
282
+if [ "$result" = "$expected" ]; then
283
+    pass "printf %b interprets backslash escapes"
284
+else
285
+    fail "printf %b interprets backslash escapes" "(newline)" "$result"
286
+fi
287
+
288
+result=$("$FORTSH_BIN" -c 'printf "%b\n" "tab\there"' 2>&1)
289
+expected=$(printf "tab\there")
290
+if [ "$result" = "$expected" ]; then
291
+    pass "printf %b interprets \\t"
292
+else
293
+    fail "printf %b interprets \\t" "(tab)" "$result"
294
+fi
295
+
296
+# =====================================
297
+section "364. PRINTF FORMAT REUSE"
298
+# =====================================
299
+
300
+result=$("$FORTSH_BIN" -c 'printf "%s\n" one two three' 2>&1)
301
+expected=$(printf "one\ntwo\nthree")
302
+if [ "$result" = "$expected" ]; then
303
+    pass "printf format reused for extra args"
304
+else
305
+    fail "printf format reused for extra args" "$expected" "$result"
306
+fi
307
+
308
+result=$("$FORTSH_BIN" -c 'printf "%d " 1 2 3 4 5; printf "\n"' 2>&1)
309
+if [ "$result" = "1 2 3 4 5 " ]; then
310
+    pass "printf %d reused for multiple integers"
311
+else
312
+    fail "printf %d reused for multiple integers" "1 2 3 4 5 " "$result"
313
+fi
314
+
315
+# =====================================
316
+section "365. PRINTF WITH VARIABLES"
317
+# =====================================
318
+
319
+result=$("$FORTSH_BIN" -c 'x="hello"; printf "%s\n" "$x"' 2>&1)
320
+if [ "$result" = "hello" ]; then
321
+    pass "printf with variable argument"
322
+else
323
+    fail "printf with variable argument" "hello" "$result"
324
+fi
325
+
326
+result=$("$FORTSH_BIN" -c 'n=42; printf "Value: %d\n" "$n"' 2>&1)
327
+if [ "$result" = "Value: 42" ]; then
328
+    pass "printf integer from variable"
329
+else
330
+    fail "printf integer from variable" "Value: 42" "$result"
331
+fi
332
+
333
+result=$("$FORTSH_BIN" -c 'fmt="%s: %d\n"; printf "$fmt" name 42' 2>&1)
334
+if [ "$result" = "name: 42" ]; then
335
+    pass "printf format from variable"
336
+else
337
+    fail "printf format from variable" "name: 42" "$result"
338
+fi
339
+
340
+# =====================================
341
+section "366. PRINTF DYNAMIC WIDTH AND PRECISION"
342
+# =====================================
343
+
344
+result=$("$FORTSH_BIN" -c 'printf "%*s\n" 10 hi' 2>&1)
345
+if [ "$result" = "        hi" ]; then
346
+    pass "printf %*s dynamic width"
347
+else
348
+    fail "printf %*s dynamic width" "        hi" "$result"
349
+fi
350
+
351
+result=$("$FORTSH_BIN" -c 'printf "%.*s\n" 3 hello' 2>&1)
352
+if [ "$result" = "hel" ]; then
353
+    pass "printf %.*s dynamic precision"
354
+else
355
+    fail "printf %.*s dynamic precision" "hel" "$result"
356
+fi
357
+
358
+result=$("$FORTSH_BIN" -c 'printf "%*.*s\n" 10 3 hello' 2>&1)
359
+if [ "$result" = "       hel" ]; then
360
+    pass "printf %*.*s dynamic width and precision"
361
+else
362
+    fail "printf %*.*s dynamic width and precision" "       hel" "$result"
363
+fi
364
+
365
+# =====================================
366
+section "367. PRINTF RETURN VALUE"
367
+# =====================================
368
+
369
+result=$("$FORTSH_BIN" -c 'printf "%s" "test"; echo $?' 2>&1)
370
+if echo "$result" | grep -q "0"; then
371
+    pass "printf returns 0 on success"
372
+else
373
+    fail "printf returns 0 on success" "0" "$result"
374
+fi
375
+
376
+# =====================================
377
+section "368. PRINTF EDGE CASES"
378
+# =====================================
379
+
380
+result=$("$FORTSH_BIN" -c 'printf "%d\n" 0' 2>&1)
381
+if [ "$result" = "0" ]; then
382
+    pass "printf %d zero"
383
+else
384
+    fail "printf %d zero" "0" "$result"
385
+fi
386
+
387
+result=$("$FORTSH_BIN" -c 'printf "%s\n" ""' 2>&1)
388
+if [ "$result" = "" ]; then
389
+    pass "printf empty argument"
390
+else
391
+    fail "printf empty argument" "(empty)" "$result"
392
+fi
393
+
394
+result=$("$FORTSH_BIN" -c 'printf "no format"' 2>&1)
395
+if [ "$result" = "no format" ]; then
396
+    pass "printf literal string no format"
397
+else
398
+    fail "printf literal string no format" "no format" "$result"
399
+fi
400
+
401
+result=$("$FORTSH_BIN" -c 'printf ""' 2>&1)
402
+if [ "$result" = "" ]; then
403
+    pass "printf empty format string"
404
+else
405
+    fail "printf empty format string" "(empty)" "$result"
406
+fi
407
+
408
+# =====================================
409
+# Summary
410
+# =====================================
411
+printf "\n"
412
+printf "${BLUE}==========================================\n"
413
+printf "POSIX printf Builtin Test Summary\n"
414
+printf "==========================================${NC}\n"
415
+printf "Passed:  ${GREEN}%d${NC}\n" "$PASSED"
416
+printf "Failed:  ${RED}%d${NC}\n" "$FAILED"
417
+printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
418
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
419
+
420
+if [ -n "$FAILED_TESTS_LIST" ]; then
421
+    printf "\n${RED}Failed tests:${NC}\n"
422
+    printf "%b" "$FAILED_TESTS_LIST"
423
+fi
424
+
425
+if [ "$FAILED" -gt 0 ]; then
426
+    exit 1
427
+fi
428
+exit 0
suites/posix/posix_compliance_quoting.shadded
@@ -0,0 +1,959 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Quoting Test Suite for fortsh
4
+# =====================================
5
+# Tests quoting and escaping per IEEE Std 1003.1-2017
6
+# Section: Shell Command Language - Quoting
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-quoting]"
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
+
29
+# Check if fortsh exists
30
+if [ ! -x "$FORTSH_BIN" ]; then
31
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
32
+    printf "Please run 'make' first or set FORTSH_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 "446. SINGLE QUOTES"
74
+# =====================================
75
+
76
+result=$("$FORTSH_BIN" -c "echo 'hello world'" 2>&1)
77
+if [ "$result" = "hello world" ]; then
78
+    pass "Single quotes preserve spaces"
79
+else
80
+    fail "Single quotes preserve spaces" "hello world" "$result"
81
+fi
82
+
83
+result=$("$FORTSH_BIN" -c "x=test; echo '\$x'" 2>&1)
84
+if [ "$result" = '$x' ]; then
85
+    pass "Single quotes prevent variable expansion"
86
+else
87
+    fail "Single quotes prevent variable expansion" '$x' "$result"
88
+fi
89
+
90
+result=$("$FORTSH_BIN" -c "echo 'back\\slash'" 2>&1)
91
+if [ "$result" = 'back\slash' ]; then
92
+    pass "Single quotes preserve backslash"
93
+else
94
+    fail "Single quotes preserve backslash" 'back\slash' "$result"
95
+fi
96
+
97
+result=$("$FORTSH_BIN" -c "echo 'has \"double\" quotes'" 2>&1)
98
+if [ "$result" = 'has "double" quotes' ]; then
99
+    pass "Single quotes preserve double quotes"
100
+else
101
+    fail "Single quotes preserve double quotes" 'has "double" quotes' "$result"
102
+fi
103
+
104
+result=$("$FORTSH_BIN" -c "echo '\$(echo no)'" 2>&1)
105
+if [ "$result" = '$(echo no)' ]; then
106
+    pass "Single quotes prevent command substitution"
107
+else
108
+    fail "Single quotes prevent command substitution" '$(echo no)' "$result"
109
+fi
110
+
111
+# =====================================
112
+section "447. DOUBLE QUOTES"
113
+# =====================================
114
+
115
+result=$("$FORTSH_BIN" -c 'echo "hello world"' 2>&1)
116
+if [ "$result" = "hello world" ]; then
117
+    pass "Double quotes preserve spaces"
118
+else
119
+    fail "Double quotes preserve spaces" "hello world" "$result"
120
+fi
121
+
122
+result=$("$FORTSH_BIN" -c 'x=test; echo "$x"' 2>&1)
123
+if [ "$result" = "test" ]; then
124
+    pass "Double quotes allow variable expansion"
125
+else
126
+    fail "Double quotes allow variable expansion" "test" "$result"
127
+fi
128
+
129
+result=$("$FORTSH_BIN" -c 'echo "$(echo hello)"' 2>&1)
130
+if [ "$result" = "hello" ]; then
131
+    pass "Double quotes allow command substitution"
132
+else
133
+    fail "Double quotes allow command substitution" "hello" "$result"
134
+fi
135
+
136
+result=$("$FORTSH_BIN" -c 'echo "has '\''single'\'' quotes"' 2>&1)
137
+if [ "$result" = "has 'single' quotes" ]; then
138
+    pass "Double quotes preserve single quotes"
139
+else
140
+    fail "Double quotes preserve single quotes" "has 'single' quotes" "$result"
141
+fi
142
+
143
+result=$("$FORTSH_BIN" -c 'echo "escaped \"quote\""' 2>&1)
144
+if [ "$result" = 'escaped "quote"' ]; then
145
+    pass "Double quotes with escaped quotes"
146
+else
147
+    fail "Double quotes with escaped quotes" 'escaped "quote"' "$result"
148
+fi
149
+
150
+result=$("$FORTSH_BIN" -c 'echo "back\\slash"' 2>&1)
151
+if [ "$result" = 'back\slash' ]; then
152
+    pass "Double quotes with escaped backslash"
153
+else
154
+    fail "Double quotes with escaped backslash" 'back\slash' "$result"
155
+fi
156
+
157
+result=$("$FORTSH_BIN" -c 'echo "dollar\$sign"' 2>&1)
158
+if [ "$result" = 'dollar$sign' ]; then
159
+    pass "Double quotes with escaped dollar"
160
+else
161
+    fail "Double quotes with escaped dollar" 'dollar$sign' "$result"
162
+fi
163
+
164
+# =====================================
165
+section "448. BACKSLASH ESCAPING"
166
+# =====================================
167
+
168
+result=$("$FORTSH_BIN" -c 'echo hello\ world' 2>&1)
169
+if [ "$result" = "hello world" ]; then
170
+    pass "Backslash escapes space"
171
+else
172
+    fail "Backslash escapes space" "hello world" "$result"
173
+fi
174
+
175
+result=$("$FORTSH_BIN" -c 'x=test; echo \$x' 2>&1)
176
+if [ "$result" = '$x' ]; then
177
+    pass "Backslash escapes dollar sign"
178
+else
179
+    fail "Backslash escapes dollar sign" '$x' "$result"
180
+fi
181
+
182
+result=$("$FORTSH_BIN" -c 'echo back\\slash' 2>&1)
183
+if [ "$result" = 'back\slash' ]; then
184
+    pass "Backslash escapes backslash"
185
+else
186
+    fail "Backslash escapes backslash" 'back\slash' "$result"
187
+fi
188
+
189
+result=$("$FORTSH_BIN" -c 'echo hello\
190
+world' 2>&1)
191
+if [ "$result" = "helloworld" ]; then
192
+    pass "Backslash-newline line continuation"
193
+else
194
+    fail "Backslash-newline line continuation" "helloworld" "$result"
195
+fi
196
+
197
+# =====================================
198
+section "449. MIXED QUOTING"
199
+# =====================================
200
+
201
+result=$("$FORTSH_BIN" -c 'echo "hello"'\''world'\''' 2>&1)
202
+# Adjacent quoted strings concatenate: "hello" + 'world' = helloworld
203
+if [ "$result" = "helloworld" ]; then
204
+    pass "Adjacent double and single quotes"
205
+else
206
+    fail "Adjacent double and single quotes" "helloworld" "$result"
207
+fi
208
+
209
+result=$("$FORTSH_BIN" -c 'x=val; echo "$x"'\''$x'\''' 2>&1)
210
+if [ "$result" = 'val$x' ]; then
211
+    pass "Mixed expansion and literal"
212
+else
213
+    fail "Mixed expansion and literal" 'val$x' "$result"
214
+fi
215
+
216
+result=$("$FORTSH_BIN" -c "echo 'don'\\''t'" 2>&1)
217
+if [ "$result" = "don't" ]; then
218
+    pass "Single quote in single quoted string"
219
+else
220
+    fail "Single quote in single quoted string" "don't" "$result"
221
+fi
222
+
223
+# =====================================
224
+section "450. QUOTING SPECIAL CHARACTERS"
225
+# =====================================
226
+
227
+result=$("$FORTSH_BIN" -c 'echo "semi;colon"' 2>&1)
228
+if [ "$result" = "semi;colon" ]; then
229
+    pass "Semicolon in double quotes"
230
+else
231
+    fail "Semicolon in double quotes" "semi;colon" "$result"
232
+fi
233
+
234
+result=$("$FORTSH_BIN" -c "echo 'pipe|char'" 2>&1)
235
+if [ "$result" = "pipe|char" ]; then
236
+    pass "Pipe in single quotes"
237
+else
238
+    fail "Pipe in single quotes" "pipe|char" "$result"
239
+fi
240
+
241
+result=$("$FORTSH_BIN" -c 'echo "ampersand&here"' 2>&1)
242
+if [ "$result" = "ampersand&here" ]; then
243
+    pass "Ampersand in double quotes"
244
+else
245
+    fail "Ampersand in double quotes" "ampersand&here" "$result"
246
+fi
247
+
248
+result=$("$FORTSH_BIN" -c "echo 'less<greater>'" 2>&1)
249
+if [ "$result" = "less<greater>" ]; then
250
+    pass "Angle brackets in single quotes"
251
+else
252
+    fail "Angle brackets in single quotes" "less<greater>" "$result"
253
+fi
254
+
255
+result=$("$FORTSH_BIN" -c 'echo "paren(here)"' 2>&1)
256
+if [ "$result" = "paren(here)" ]; then
257
+    pass "Parentheses in double quotes"
258
+else
259
+    fail "Parentheses in double quotes" "paren(here)" "$result"
260
+fi
261
+
262
+result=$("$FORTSH_BIN" -c "echo 'star*glob?'" 2>&1)
263
+if [ "$result" = "star*glob?" ]; then
264
+    pass "Glob chars in single quotes"
265
+else
266
+    fail "Glob chars in single quotes" "star*glob?" "$result"
267
+fi
268
+
269
+result=$("$FORTSH_BIN" -c 'echo "hash#comment"' 2>&1)
270
+if [ "$result" = "hash#comment" ]; then
271
+    pass "Hash in double quotes"
272
+else
273
+    fail "Hash in double quotes" "hash#comment" "$result"
274
+fi
275
+
276
+# =====================================
277
+section "451. EMPTY STRINGS"
278
+# =====================================
279
+
280
+result=$("$FORTSH_BIN" -c 'echo ""' 2>&1)
281
+if [ "$result" = "" ]; then
282
+    pass "Empty double-quoted string"
283
+else
284
+    fail "Empty double-quoted string" "(empty)" "$result"
285
+fi
286
+
287
+result=$("$FORTSH_BIN" -c "echo ''" 2>&1)
288
+if [ "$result" = "" ]; then
289
+    pass "Empty single-quoted string"
290
+else
291
+    fail "Empty single-quoted string" "(empty)" "$result"
292
+fi
293
+
294
+result=$("$FORTSH_BIN" -c 'x=""; echo "[$x]"' 2>&1)
295
+if [ "$result" = "[]" ]; then
296
+    pass "Empty variable in quotes"
297
+else
298
+    fail "Empty variable in quotes" "[]" "$result"
299
+fi
300
+
301
+result=$("$FORTSH_BIN" -c 'echo a "" b' 2>&1)
302
+if [ "$result" = "a  b" ]; then
303
+    pass "Empty string preserves word"
304
+else
305
+    fail "Empty string preserves word" "a  b" "$result"
306
+fi
307
+
308
+# =====================================
309
+section "452. WORD SPLITTING"
310
+# =====================================
311
+
312
+result=$("$FORTSH_BIN" -c 'x="a b c"; for w in $x; do echo "[$w]"; done' 2>&1)
313
+expected=$(printf "[a]\n[b]\n[c]")
314
+if [ "$result" = "$expected" ]; then
315
+    pass "Unquoted variable splits on whitespace"
316
+else
317
+    fail "Unquoted variable splits on whitespace" "$expected" "$result"
318
+fi
319
+
320
+result=$("$FORTSH_BIN" -c 'x="a b c"; for w in "$x"; do echo "[$w]"; done' 2>&1)
321
+if [ "$result" = "[a b c]" ]; then
322
+    pass "Quoted variable prevents splitting"
323
+else
324
+    fail "Quoted variable prevents splitting" "[a b c]" "$result"
325
+fi
326
+
327
+result=$("$FORTSH_BIN" -c 'x="a  b"; echo $x' 2>&1)
328
+if [ "$result" = "a b" ]; then
329
+    pass "Word splitting collapses spaces"
330
+else
331
+    fail "Word splitting collapses spaces" "a b" "$result"
332
+fi
333
+
334
+result=$("$FORTSH_BIN" -c 'x="a  b"; echo "$x"' 2>&1)
335
+if [ "$result" = "a  b" ]; then
336
+    pass "Quoting preserves multiple spaces"
337
+else
338
+    fail "Quoting preserves multiple spaces" "a  b" "$result"
339
+fi
340
+
341
+# =====================================
342
+section "453. GLOB PREVENTION"
343
+# =====================================
344
+
345
+result=$("$FORTSH_BIN" -c 'echo "*"' 2>&1)
346
+if [ "$result" = "*" ]; then
347
+    pass "Double quotes prevent glob expansion"
348
+else
349
+    fail "Double quotes prevent glob expansion" "*" "$result"
350
+fi
351
+
352
+result=$("$FORTSH_BIN" -c "echo '[a-z]*'" 2>&1)
353
+if [ "$result" = "[a-z]*" ]; then
354
+    pass "Single quotes prevent bracket expansion"
355
+else
356
+    fail "Single quotes prevent bracket expansion" "[a-z]*" "$result"
357
+fi
358
+
359
+result=$("$FORTSH_BIN" -c 'echo "?"' 2>&1)
360
+if [ "$result" = "?" ]; then
361
+    pass "Double quotes prevent ? expansion"
362
+else
363
+    fail "Double quotes prevent ? expansion" "?" "$result"
364
+fi
365
+
366
+# =====================================
367
+section "454. QUOTE IN VARIABLE"
368
+# =====================================
369
+
370
+result=$("$FORTSH_BIN" -c "x=\"has 'quotes'\"; echo \"\$x\"" 2>&1)
371
+if [ "$result" = "has 'quotes'" ]; then
372
+    pass "Single quotes inside double-quoted variable"
373
+else
374
+    fail "Single quotes inside double-quoted variable" "has 'quotes'" "$result"
375
+fi
376
+
377
+result=$("$FORTSH_BIN" -c 'x="has \"quotes\""; echo "$x"' 2>&1)
378
+if [ "$result" = 'has "quotes"' ]; then
379
+    pass "Double quotes inside variable"
380
+else
381
+    fail "Double quotes inside variable" 'has "quotes"' "$result"
382
+fi
383
+
384
+# =====================================
385
+section "455. QUOTING IN ASSIGNMENTS"
386
+# =====================================
387
+
388
+result=$("$FORTSH_BIN" -c 'x="hello world"; echo $x' 2>&1)
389
+if [ "$result" = "hello world" ]; then
390
+    pass "Quoted assignment with spaces"
391
+else
392
+    fail "Quoted assignment with spaces" "hello world" "$result"
393
+fi
394
+
395
+result=$("$FORTSH_BIN" -c "x='no \$expansion'; echo \"\$x\"" 2>&1)
396
+if [ "$result" = 'no $expansion' ]; then
397
+    pass "Single-quoted assignment"
398
+else
399
+    fail "Single-quoted assignment" 'no $expansion' "$result"
400
+fi
401
+
402
+result=$("$FORTSH_BIN" -c 'y=value; x="with $y"; echo "$x"' 2>&1)
403
+if [ "$result" = "with value" ]; then
404
+    pass "Variable expansion in assignment"
405
+else
406
+    fail "Variable expansion in assignment" "with value" "$result"
407
+fi
408
+
409
+# =====================================
410
+section "456. QUOTING IN COMMAND SUBSTITUTION"
411
+# =====================================
412
+
413
+result=$("$FORTSH_BIN" -c 'x=$(echo "hello world"); echo "$x"' 2>&1)
414
+if [ "$result" = "hello world" ]; then
415
+    pass "Quotes inside command substitution"
416
+else
417
+    fail "Quotes inside command substitution" "hello world" "$result"
418
+fi
419
+
420
+result=$("$FORTSH_BIN" -c 'x="$(echo "nested quotes")"; echo "$x"' 2>&1)
421
+if [ "$result" = "nested quotes" ]; then
422
+    pass "Nested quotes in command substitution"
423
+else
424
+    fail "Nested quotes in command substitution" "nested quotes" "$result"
425
+fi
426
+
427
+# =====================================
428
+section "457. BACKSLASH IN DOUBLE QUOTES"
429
+# =====================================
430
+
431
+# POSIX: In double quotes, \\\\ (4 backslashes) becomes \\ (2 backslashes)
432
+# Each pair of \\ in double quotes produces one literal backslash
433
+result=$("$FORTSH_BIN" -c 'echo "back\\\\slash"' 2>&1)
434
+if [ "$result" = 'back\\slash' ]; then
435
+    pass "Backslash-backslash in double quotes"
436
+else
437
+    fail "Backslash-backslash in double quotes" 'back\\slash' "$result"
438
+fi
439
+
440
+result=$("$FORTSH_BIN" -c 'echo "newline\\n"' 2>&1)
441
+if [ "$result" = 'newline\n' ]; then
442
+    pass "Backslash-n in double quotes (literal)"
443
+else
444
+    fail "Backslash-n in double quotes (literal)" 'newline\n' "$result"
445
+fi
446
+
447
+result=$("$FORTSH_BIN" -c 'echo "tab\\t"' 2>&1)
448
+if [ "$result" = 'tab\t' ]; then
449
+    pass "Backslash-t in double quotes (literal)"
450
+else
451
+    fail "Backslash-t in double quotes (literal)" 'tab\t' "$result"
452
+fi
453
+
454
+# =====================================
455
+section "458. DOLLAR IN QUOTES"
456
+# =====================================
457
+
458
+result=$("$FORTSH_BIN" -c 'echo "cost: \$5"' 2>&1)
459
+if [ "$result" = 'cost: $5' ]; then
460
+    pass "Escaped dollar in double quotes"
461
+else
462
+    fail "Escaped dollar in double quotes" 'cost: $5' "$result"
463
+fi
464
+
465
+result=$("$FORTSH_BIN" -c "echo 'cost: \$5'" 2>&1)
466
+if [ "$result" = 'cost: $5' ]; then
467
+    pass "Dollar in single quotes (literal)"
468
+else
469
+    fail "Dollar in single quotes (literal)" 'cost: $5' "$result"
470
+fi
471
+
472
+# =====================================
473
+section "459. NEWLINE IN QUOTES"
474
+# =====================================
475
+
476
+result=$("$FORTSH_BIN" -c 'echo "line1
477
+line2"' 2>&1)
478
+expected=$(printf "line1\nline2")
479
+if [ "$result" = "$expected" ]; then
480
+    pass "Literal newline in double quotes"
481
+else
482
+    fail "Literal newline in double quotes" "$expected" "$result"
483
+fi
484
+
485
+result=$("$FORTSH_BIN" -c "echo 'line1
486
+line2'" 2>&1)
487
+expected=$(printf "line1\nline2")
488
+if [ "$result" = "$expected" ]; then
489
+    pass "Literal newline in single quotes"
490
+else
491
+    fail "Literal newline in single quotes" "$expected" "$result"
492
+fi
493
+
494
+# =====================================
495
+section "460. TAB AND SPACE IN QUOTES"
496
+# =====================================
497
+
498
+result=$("$FORTSH_BIN" -c 'echo "	tab"' 2>&1)
499
+if printf "%s" "$result" | grep -q "	"; then
500
+    pass "Literal tab in double quotes"
501
+else
502
+    fail "Literal tab in double quotes" "string with tab" "$result"
503
+fi
504
+
505
+result=$("$FORTSH_BIN" -c 'echo "  spaces  "' 2>&1)
506
+if [ "$result" = "  spaces  " ]; then
507
+    pass "Multiple spaces in double quotes"
508
+else
509
+    fail "Multiple spaces in double quotes" "  spaces  " "$result"
510
+fi
511
+
512
+# =====================================
513
+section "461. QUOTING IN FOR LOOP"
514
+# =====================================
515
+
516
+result=$("$FORTSH_BIN" -c 'for x in "a b" c; do echo "[$x]"; done' 2>&1)
517
+expected=$(printf "[a b]\n[c]")
518
+if [ "$result" = "$expected" ]; then
519
+    pass "Quoted string in for list"
520
+else
521
+    fail "Quoted string in for list" "$expected" "$result"
522
+fi
523
+
524
+result=$("$FORTSH_BIN" -c 'list="a b c"; for x in $list; do echo "[$x]"; done' 2>&1)
525
+expected=$(printf "[a]\n[b]\n[c]")
526
+if [ "$result" = "$expected" ]; then
527
+    pass "Unquoted var splits in for"
528
+else
529
+    fail "Unquoted var splits in for" "$expected" "$result"
530
+fi
531
+
532
+result=$("$FORTSH_BIN" -c 'list="a b c"; for x in "$list"; do echo "[$x]"; done' 2>&1)
533
+if [ "$result" = "[a b c]" ]; then
534
+    pass "Quoted var no split in for"
535
+else
536
+    fail "Quoted var no split in for" "[a b c]" "$result"
537
+fi
538
+
539
+# =====================================
540
+section "462. QUOTING IN CASE STATEMENT"
541
+# =====================================
542
+
543
+result=$("$FORTSH_BIN" -c 'x="hello world"; case "$x" in "hello world") echo match;; esac' 2>&1)
544
+if [ "$result" = "match" ]; then
545
+    pass "Quoted string in case word"
546
+else
547
+    fail "Quoted string in case word" "match" "$result"
548
+fi
549
+
550
+result=$("$FORTSH_BIN" -c 'case "a b" in "a b") echo yes;; *) echo no;; esac' 2>&1)
551
+if [ "$result" = "yes" ]; then
552
+    pass "Quoted pattern in case"
553
+else
554
+    fail "Quoted pattern in case" "yes" "$result"
555
+fi
556
+
557
+# =====================================
558
+section "463. QUOTING SPECIAL SHELL CHARS"
559
+# =====================================
560
+
561
+result=$("$FORTSH_BIN" -c 'echo "hello; world"' 2>&1)
562
+if [ "$result" = "hello; world" ]; then
563
+    pass "Semicolon in double quotes"
564
+else
565
+    fail "Semicolon in double quotes" "hello; world" "$result"
566
+fi
567
+
568
+result=$("$FORTSH_BIN" -c "echo 'a && b'" 2>&1)
569
+if [ "$result" = "a && b" ]; then
570
+    pass "Double ampersand in single quotes"
571
+else
572
+    fail "Double ampersand in single quotes" "a && b" "$result"
573
+fi
574
+
575
+result=$("$FORTSH_BIN" -c 'echo "a || b"' 2>&1)
576
+if [ "$result" = "a || b" ]; then
577
+    pass "Double pipe in double quotes"
578
+else
579
+    fail "Double pipe in double quotes" "a || b" "$result"
580
+fi
581
+
582
+result=$("$FORTSH_BIN" -c "echo 'back\`tick'" 2>&1)
583
+if [ "$result" = 'back`tick' ]; then
584
+    pass "Backtick in single quotes"
585
+else
586
+    fail "Backtick in single quotes" 'back`tick' "$result"
587
+fi
588
+
589
+# =====================================
590
+section "464. QUOTING IN ARITHMETIC"
591
+# =====================================
592
+
593
+result=$("$FORTSH_BIN" -c 'x=5; echo $((x + 3))' 2>&1)
594
+if [ "$result" = "8" ]; then
595
+    pass "Unquoted var in arithmetic"
596
+else
597
+    fail "Unquoted var in arithmetic" "8" "$result"
598
+fi
599
+
600
+result=$("$FORTSH_BIN" -c 'x="5"; echo $((x + 3))' 2>&1)
601
+if [ "$result" = "8" ]; then
602
+    pass "Quoted assignment used in arithmetic"
603
+else
604
+    fail "Quoted assignment used in arithmetic" "8" "$result"
605
+fi
606
+
607
+# =====================================
608
+section "465. ESCAPE SEQUENCES"
609
+# =====================================
610
+
611
+result=$("$FORTSH_BIN" -c 'echo "hello\tworld"' 2>&1)
612
+if echo "$result" | grep -q "hello"; then
613
+    pass "Backslash-t in double quotes"
614
+else
615
+    fail "Backslash-t in double quotes"
616
+fi
617
+
618
+# Test backslash-n literal in single quotes (use double backslash for POSIX sh compatibility)
619
+result=$("$FORTSH_BIN" -c "echo 'hello\\nworld'" 2>&1)
620
+if printf '%s' "$result" | grep -q 'hello.nworld'; then
621
+    pass "Backslash-n literal in single quotes"
622
+else
623
+    fail "Backslash-n literal in single quotes"
624
+fi
625
+
626
+# =====================================
627
+section "466. QUOTING AND WORD SPLITTING"
628
+# =====================================
629
+
630
+result=$("$FORTSH_BIN" -c 'x="a b c"; set -- $x; echo $#' 2>&1)
631
+if [ "$result" = "3" ]; then
632
+    pass "Unquoted var splits on spaces"
633
+else
634
+    fail "Unquoted var splits on spaces" "3" "$result"
635
+fi
636
+
637
+result=$("$FORTSH_BIN" -c 'x="a b c"; set -- "$x"; echo $#' 2>&1)
638
+if [ "$result" = "1" ]; then
639
+    pass "Quoted var prevents splitting"
640
+else
641
+    fail "Quoted var prevents splitting" "1" "$result"
642
+fi
643
+
644
+result=$("$FORTSH_BIN" -c 'x=""; set -- $x; echo $#' 2>&1)
645
+if [ "$result" = "0" ]; then
646
+    pass "Empty unquoted var produces no args"
647
+else
648
+    fail "Empty unquoted var produces no args" "0" "$result"
649
+fi
650
+
651
+result=$("$FORTSH_BIN" -c 'x=""; set -- "$x"; echo $#' 2>&1)
652
+if [ "$result" = "1" ]; then
653
+    pass "Empty quoted var produces one empty arg"
654
+else
655
+    fail "Empty quoted var produces one empty arg" "1" "$result"
656
+fi
657
+
658
+# =====================================
659
+section "467. QUOTING SPECIAL PARAMETERS"
660
+# =====================================
661
+
662
+result=$("$FORTSH_BIN" -c 'set -- a b c; echo "$@" | wc -w' 2>&1)
663
+if [ "$result" -eq 3 ] 2>/dev/null; then
664
+    pass 'Quoted $@ preserves args'
665
+else
666
+    fail 'Quoted $@ preserves args' "3" "$result"
667
+fi
668
+
669
+result=$("$FORTSH_BIN" -c 'set -- a b c; echo "$*"' 2>&1)
670
+if [ "$result" = "a b c" ]; then
671
+    pass 'Quoted $* joins args'
672
+else
673
+    fail 'Quoted $* joins args' "a b c" "$result"
674
+fi
675
+
676
+result=$("$FORTSH_BIN" -c 'set -- a "b c" d; for x in "$@"; do echo "[$x]"; done' 2>&1)
677
+expected=$(printf "[a]\n[b c]\n[d]")
678
+if [ "$result" = "$expected" ]; then
679
+    pass 'Quoted $@ preserves quoting in original args'
680
+else
681
+    fail 'Quoted $@ preserves quoting in original args'
682
+fi
683
+
684
+# =====================================
685
+section "468. NESTED QUOTING"
686
+# =====================================
687
+
688
+result=$("$FORTSH_BIN" -c "echo 'single \"with\" double'" 2>&1)
689
+if [ "$result" = 'single "with" double' ]; then
690
+    pass "Double quotes inside single quotes"
691
+else
692
+    fail "Double quotes inside single quotes"
693
+fi
694
+
695
+result=$("$FORTSH_BIN" -c 'echo "double '\''with'\'' single"' 2>&1)
696
+if [ "$result" = "double 'with' single" ]; then
697
+    pass "Single quotes inside double quotes (escaped)"
698
+else
699
+    fail "Single quotes inside double quotes (escaped)"
700
+fi
701
+
702
+# =====================================
703
+section "469. QUOTING IN ASSIGNMENTS"
704
+# =====================================
705
+
706
+result=$("$FORTSH_BIN" -c 'x="hello world"; echo $x' 2>&1)
707
+if [ "$result" = "hello world" ]; then
708
+    pass "Quoted assignment with spaces"
709
+else
710
+    fail "Quoted assignment with spaces" "hello world" "$result"
711
+fi
712
+
713
+result=$("$FORTSH_BIN" -c "x='hello world'; echo \$x" 2>&1)
714
+if [ "$result" = "hello world" ]; then
715
+    pass "Single-quoted assignment with spaces"
716
+else
717
+    fail "Single-quoted assignment with spaces" "hello world" "$result"
718
+fi
719
+
720
+result=$("$FORTSH_BIN" -c 'x=hello\ world; echo $x' 2>&1)
721
+if [ "$result" = "hello world" ]; then
722
+    pass "Escaped space in assignment"
723
+else
724
+    fail "Escaped space in assignment" "hello world" "$result"
725
+fi
726
+
727
+# =====================================
728
+section "470. QUOTING IN COMMAND SUBSTITUTION"
729
+# =====================================
730
+
731
+result=$("$FORTSH_BIN" -c 'x=$(echo "hello world"); echo "$x"' 2>&1)
732
+if [ "$result" = "hello world" ]; then
733
+    pass "Quoted string in command substitution"
734
+else
735
+    fail "Quoted string in command substitution" "hello world" "$result"
736
+fi
737
+
738
+result=$("$FORTSH_BIN" -c 'x=`echo "hello world"`; echo "$x"' 2>&1)
739
+if [ "$result" = "hello world" ]; then
740
+    pass "Quoted string in backtick substitution"
741
+else
742
+    fail "Quoted string in backtick substitution" "hello world" "$result"
743
+fi
744
+
745
+# =====================================
746
+section "471. QUOTING AND GLOB PREVENTION"
747
+# =====================================
748
+
749
+result=$("$FORTSH_BIN" -c 'echo "*"' 2>&1)
750
+if [ "$result" = "*" ]; then
751
+    pass "Quoted asterisk is literal"
752
+else
753
+    fail "Quoted asterisk is literal" "*" "$result"
754
+fi
755
+
756
+result=$("$FORTSH_BIN" -c 'echo "?"' 2>&1)
757
+if [ "$result" = "?" ]; then
758
+    pass "Quoted question mark is literal"
759
+else
760
+    fail "Quoted question mark is literal" "?" "$result"
761
+fi
762
+
763
+result=$("$FORTSH_BIN" -c 'echo "[abc]"' 2>&1)
764
+if [ "$result" = "[abc]" ]; then
765
+    pass "Quoted brackets are literal"
766
+else
767
+    fail "Quoted brackets are literal" "[abc]" "$result"
768
+fi
769
+
770
+# =====================================
771
+section "472. QUOTING IN TESTS"
772
+# =====================================
773
+
774
+result=$("$FORTSH_BIN" -c 'x=""; [ -z "$x" ] && echo empty' 2>&1)
775
+if [ "$result" = "empty" ]; then
776
+    pass "Quoted empty var in test -z"
777
+else
778
+    fail "Quoted empty var in test -z" "empty" "$result"
779
+fi
780
+
781
+result=$("$FORTSH_BIN" -c 'x="hello"; [ -n "$x" ] && echo nonempty' 2>&1)
782
+if [ "$result" = "nonempty" ]; then
783
+    pass "Quoted var in test -n"
784
+else
785
+    fail "Quoted var in test -n" "nonempty" "$result"
786
+fi
787
+
788
+result=$("$FORTSH_BIN" -c 'x="a b"; [ "$x" = "a b" ] && echo match' 2>&1)
789
+if [ "$result" = "match" ]; then
790
+    pass "Quoted var with spaces in test ="
791
+else
792
+    fail "Quoted var with spaces in test =" "match" "$result"
793
+fi
794
+
795
+# =====================================
796
+section "473. QUOTING IN CASE PATTERNS"
797
+# =====================================
798
+
799
+result=$("$FORTSH_BIN" -c 'x="*"; case "$x" in "*") echo literal;; esac' 2>&1)
800
+if [ "$result" = "literal" ]; then
801
+    pass "Quoted asterisk matches literally in case"
802
+else
803
+    fail "Quoted asterisk matches literally in case" "literal" "$result"
804
+fi
805
+
806
+result=$("$FORTSH_BIN" -c 'x="a b"; case "$x" in "a b") echo match;; esac' 2>&1)
807
+if [ "$result" = "match" ]; then
808
+    pass "Quoted pattern with space in case"
809
+else
810
+    fail "Quoted pattern with space in case" "match" "$result"
811
+fi
812
+
813
+# =====================================
814
+section "474. QUOTING IN FUNCTION ARGS"
815
+# =====================================
816
+
817
+result=$("$FORTSH_BIN" -c 'f() { echo "[$1]"; }; f "hello world"' 2>&1)
818
+if [ "$result" = "[hello world]" ]; then
819
+    pass "Quoted arg to function"
820
+else
821
+    fail "Quoted arg to function" "[hello world]" "$result"
822
+fi
823
+
824
+result=$("$FORTSH_BIN" -c 'f() { echo $#; }; f "a b" "c d"' 2>&1)
825
+if [ "$result" = "2" ]; then
826
+    pass "Quoted args count in function"
827
+else
828
+    fail "Quoted args count in function" "2" "$result"
829
+fi
830
+
831
+# =====================================
832
+section "475. DOLLAR IN QUOTES"
833
+# =====================================
834
+
835
+result=$("$FORTSH_BIN" -c 'echo "\$HOME"' 2>&1)
836
+if [ "$result" = '$HOME' ]; then
837
+    pass "Escaped dollar in double quotes"
838
+else
839
+    fail "Escaped dollar in double quotes" "\$HOME" "$result"
840
+fi
841
+
842
+result=$("$FORTSH_BIN" -c "echo '\$HOME'" 2>&1)
843
+if [ "$result" = '$HOME' ]; then
844
+    pass "Dollar in single quotes"
845
+else
846
+    fail "Dollar in single quotes" "\$HOME" "$result"
847
+fi
848
+
849
+# =====================================
850
+section "476. BACKSLASH IN QUOTES"
851
+# =====================================
852
+
853
+result=$("$FORTSH_BIN" -c 'echo "\\"' 2>&1)
854
+if [ "$result" = '\' ]; then
855
+    pass "Escaped backslash in double quotes"
856
+else
857
+    fail "Escaped backslash in double quotes" "\\" "$result"
858
+fi
859
+
860
+result=$("$FORTSH_BIN" -c "echo '\\'" 2>&1)
861
+if [ "$result" = '\' ]; then
862
+    pass "Backslash in single quotes"
863
+else
864
+    fail "Backslash in single quotes" "\\" "$result"
865
+fi
866
+
867
+# =====================================
868
+section "477. QUOTE REMOVAL"
869
+# =====================================
870
+
871
+result=$("$FORTSH_BIN" -c 'echo ""hello""' 2>&1)
872
+if [ "$result" = "hello" ]; then
873
+    pass "Empty quotes around word"
874
+else
875
+    fail "Empty quotes around word" "hello" "$result"
876
+fi
877
+
878
+result=$("$FORTSH_BIN" -c 'echo "a""b""c"' 2>&1)
879
+if [ "$result" = "abc" ]; then
880
+    pass "Adjacent quoted strings concatenate"
881
+else
882
+    fail "Adjacent quoted strings concatenate" "abc" "$result"
883
+fi
884
+
885
+# =====================================
886
+section "478. MIXED QUOTE STYLES"
887
+# =====================================
888
+
889
+result=$("$FORTSH_BIN" -c "x=val; echo 'a'\"\$x\"'b'" 2>&1)
890
+if [ "$result" = "avalb" ]; then
891
+    pass "Mixed single and double quotes"
892
+else
893
+    fail "Mixed single and double quotes" "avalb" "$result"
894
+fi
895
+
896
+result=$("$FORTSH_BIN" -c "echo 'single'\"double\"'single'" 2>&1)
897
+if [ "$result" = "singledoublesingle" ]; then
898
+    pass "Alternating quote styles"
899
+else
900
+    fail "Alternating quote styles" "singledoublesingle" "$result"
901
+fi
902
+
903
+# =====================================
904
+section "479. QUOTING IN REDIRECTIONS"
905
+# =====================================
906
+
907
+result=$("$FORTSH_BIN" -c 'echo test > "/tmp/quote space test.txt"; cat "/tmp/quote space test.txt"; rm "/tmp/quote space test.txt"' 2>&1)
908
+if [ "$result" = "test" ]; then
909
+    pass "Quoted filename with space in redirect"
910
+else
911
+    fail "Quoted filename with space in redirect" "test" "$result"
912
+fi
913
+
914
+# =====================================
915
+section "480. EMPTY QUOTES"
916
+# =====================================
917
+
918
+result=$("$FORTSH_BIN" -c 'echo ""' 2>&1)
919
+if [ -z "$result" ]; then
920
+    pass "Empty double quotes produce empty output"
921
+else
922
+    fail "Empty double quotes produce empty output" "empty" "$result"
923
+fi
924
+
925
+result=$("$FORTSH_BIN" -c "echo ''" 2>&1)
926
+if [ -z "$result" ]; then
927
+    pass "Empty single quotes produce empty output"
928
+else
929
+    fail "Empty single quotes produce empty output" "empty" "$result"
930
+fi
931
+
932
+result=$("$FORTSH_BIN" -c 'x=""; echo "[$x]"' 2>&1)
933
+if [ "$result" = "[]" ]; then
934
+    pass "Empty var in quotes"
935
+else
936
+    fail "Empty var in quotes" "[]" "$result"
937
+fi
938
+
939
+# =====================================
940
+# Summary
941
+# =====================================
942
+printf "\n"
943
+printf "${BLUE}==========================================\n"
944
+printf "POSIX Quoting Test Summary\n"
945
+printf "==========================================${NC}\n"
946
+printf "Passed:  ${GREEN}%d${NC}\n" "$PASSED"
947
+printf "Failed:  ${RED}%d${NC}\n" "$FAILED"
948
+printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
949
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
950
+
951
+if [ -n "$FAILED_TESTS_LIST" ]; then
952
+    printf "\n${RED}Failed tests:${NC}\n"
953
+    printf "%b" "$FAILED_TESTS_LIST"
954
+fi
955
+
956
+if [ "$FAILED" -gt 0 ]; then
957
+    exit 1
958
+fi
959
+exit 0
suites/posix/posix_compliance_redirect.shadded
@@ -0,0 +1,950 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Redirection and Pipeline Test Suite for fortsh
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
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
28
+
29
+# Check if fortsh exists
30
+if [ ! -x "$FORTSH_BIN" ]; then
31
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
32
+    printf "Please run 'make' first or set FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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
+# Note: matches bash - POSIX redirections processed left-to-right
192
+# 1>&2 copies fd2 to fd1, then 2>/dev/null redirects fd2 to null
193
+# fd1 still points to original fd2 (stderr), so output appears
194
+result=$("$FORTSH_BIN" -c 'echo error 1>&2 2>/dev/null' 2>&1)
195
+if [ "$result" = "error" ]; then
196
+    pass "1>&2 with stderr suppressed (matches bash)"
197
+else
198
+    fail "1>&2 with stderr suppressed (matches bash)" "error" "$result"
199
+fi
200
+
201
+# =====================================
202
+section "434. COMBINED REDIRECTIONS"
203
+# =====================================
204
+
205
+result=$("$FORTSH_BIN" -c 'echo out > '"$TEST_DIR"'/combined.txt 2>&1; cat '"$TEST_DIR"'/combined.txt' 2>&1)
206
+if [ "$result" = "out" ]; then
207
+    pass "> file 2>&1 combination"
208
+else
209
+    fail "> file 2>&1 combination" "out" "$result"
210
+fi
211
+
212
+result=$("$FORTSH_BIN" -c '{ echo out; echo err >&2; } > '"$TEST_DIR"'/both.txt 2>&1; cat '"$TEST_DIR"'/both.txt' 2>&1)
213
+expected=$(printf "out\nerr")
214
+if [ "$result" = "$expected" ]; then
215
+    pass "Both streams to same file"
216
+else
217
+    fail "Both streams to same file" "$expected" "$result"
218
+fi
219
+
220
+# =====================================
221
+section "435. NOCLOBBER WITH >|"
222
+# =====================================
223
+
224
+echo "original" > "$TEST_DIR/noclobber.txt"
225
+
226
+result=$("$FORTSH_BIN" -c 'set -C; echo new >| '"$TEST_DIR"'/noclobber.txt; cat '"$TEST_DIR"'/noclobber.txt' 2>&1)
227
+if [ "$result" = "new" ]; then
228
+    pass ">| overrides noclobber"
229
+else
230
+    fail ">| overrides noclobber" "new" "$result"
231
+fi
232
+
233
+# =====================================
234
+section "436. SIMPLE PIPELINE"
235
+# =====================================
236
+
237
+result=$("$FORTSH_BIN" -c 'echo hello | cat' 2>&1)
238
+if [ "$result" = "hello" ]; then
239
+    pass "Simple two-stage pipeline"
240
+else
241
+    fail "Simple two-stage pipeline" "hello" "$result"
242
+fi
243
+
244
+result=$("$FORTSH_BIN" -c 'echo hello world | wc -w' 2>&1)
245
+if echo "$result" | grep -q "2"; then
246
+    pass "Pipeline with wc"
247
+else
248
+    fail "Pipeline with wc" "2" "$result"
249
+fi
250
+
251
+result=$("$FORTSH_BIN" -c 'printf "c\na\nb\n" | sort' 2>&1)
252
+expected=$(printf "a\nb\nc")
253
+if [ "$result" = "$expected" ]; then
254
+    pass "Pipeline with sort"
255
+else
256
+    fail "Pipeline with sort" "$expected" "$result"
257
+fi
258
+
259
+# =====================================
260
+section "437. MULTI-STAGE PIPELINE"
261
+# =====================================
262
+
263
+result=$("$FORTSH_BIN" -c 'echo hello | cat | cat | cat' 2>&1)
264
+if [ "$result" = "hello" ]; then
265
+    pass "Four-stage pipeline"
266
+else
267
+    fail "Four-stage pipeline" "hello" "$result"
268
+fi
269
+
270
+result=$("$FORTSH_BIN" -c 'printf "b\na\nc\nb\na\n" | sort | uniq' 2>&1)
271
+expected=$(printf "a\nb\nc")
272
+if [ "$result" = "$expected" ]; then
273
+    pass "sort | uniq pipeline"
274
+else
275
+    fail "sort | uniq pipeline" "$expected" "$result"
276
+fi
277
+
278
+result=$("$FORTSH_BIN" -c 'echo "hello world" | tr " " "\n" | wc -l' 2>&1)
279
+if echo "$result" | grep -q "2"; then
280
+    pass "tr | wc pipeline"
281
+else
282
+    fail "tr | wc pipeline" "2" "$result"
283
+fi
284
+
285
+# =====================================
286
+section "438. PIPELINE WITH REDIRECTION"
287
+# =====================================
288
+
289
+result=$("$FORTSH_BIN" -c 'echo hello | cat > '"$TEST_DIR"'/pipe_out.txt; cat '"$TEST_DIR"'/pipe_out.txt' 2>&1)
290
+if [ "$result" = "hello" ]; then
291
+    pass "Pipeline with output redirect"
292
+else
293
+    fail "Pipeline with output redirect" "hello" "$result"
294
+fi
295
+
296
+echo "from file" > "$TEST_DIR/pipe_in.txt"
297
+result=$("$FORTSH_BIN" -c 'cat < '"$TEST_DIR"'/pipe_in.txt | tr "a-z" "A-Z"' 2>&1)
298
+if [ "$result" = "FROM FILE" ]; then
299
+    pass "Input redirect into pipeline"
300
+else
301
+    fail "Input redirect into pipeline" "FROM FILE" "$result"
302
+fi
303
+
304
+# =====================================
305
+section "439. PIPELINE SUBSHELL"
306
+# =====================================
307
+
308
+result=$("$FORTSH_BIN" -c 'echo test | { read x; echo "got: $x"; }' 2>&1)
309
+if [ "$result" = "got: test" ]; then
310
+    pass "Pipeline to brace group"
311
+else
312
+    fail "Pipeline to brace group" "got: test" "$result"
313
+fi
314
+
315
+result=$("$FORTSH_BIN" -c 'echo test | (cat; echo done)' 2>&1)
316
+expected=$(printf "test\ndone")
317
+if [ "$result" = "$expected" ]; then
318
+    pass "Pipeline to subshell"
319
+else
320
+    fail "Pipeline to subshell" "$expected" "$result"
321
+fi
322
+
323
+# =====================================
324
+section "440. COMMAND SUBSTITUTION IN PIPELINE"
325
+# =====================================
326
+
327
+result=$("$FORTSH_BIN" -c 'echo $(echo hello | tr "a-z" "A-Z")' 2>&1)
328
+if [ "$result" = "HELLO" ]; then
329
+    pass "Command substitution with pipeline"
330
+else
331
+    fail "Command substitution with pipeline" "HELLO" "$result"
332
+fi
333
+
334
+result=$("$FORTSH_BIN" -c 'x=$(printf "a\nb\nc\n" | wc -l); echo $x' 2>&1)
335
+if echo "$result" | grep -q "3"; then
336
+    pass "Variable from pipeline in command sub"
337
+else
338
+    fail "Variable from pipeline in command sub" "3" "$result"
339
+fi
340
+
341
+# =====================================
342
+section "441. REDIRECTION ORDER"
343
+# =====================================
344
+
345
+# The order matters: 2>&1 before > vs after
346
+result=$("$FORTSH_BIN" -c '{ echo out; echo err >&2; } > '"$TEST_DIR"'/order1.txt 2>&1; cat '"$TEST_DIR"'/order1.txt | wc -l' 2>&1)
347
+if echo "$result" | grep -q "2"; then
348
+    pass "> file 2>&1 captures both"
349
+else
350
+    fail "> file 2>&1 captures both" "2 lines" "$result"
351
+fi
352
+
353
+# =====================================
354
+section "442. /dev/null REDIRECTION"
355
+# =====================================
356
+
357
+result=$("$FORTSH_BIN" -c 'echo hello > /dev/null; echo done' 2>&1)
358
+if [ "$result" = "done" ]; then
359
+    pass "> /dev/null discards output"
360
+else
361
+    fail "> /dev/null discards output" "done" "$result"
362
+fi
363
+
364
+result=$("$FORTSH_BIN" -c 'cat < /dev/null; echo empty' 2>&1)
365
+if [ "$result" = "empty" ]; then
366
+    pass "< /dev/null provides empty input"
367
+else
368
+    fail "< /dev/null provides empty input" "empty" "$result"
369
+fi
370
+
371
+result=$("$FORTSH_BIN" -c 'ls /nonexistent 2>/dev/null; echo $?' 2>&1)
372
+# Should show non-zero exit but no error output
373
+if echo "$result" | grep -qE "^[12]$"; then
374
+    pass "2>/dev/null with failing command"
375
+else
376
+    fail "2>/dev/null with failing command" "exit code only" "$result"
377
+fi
378
+
379
+# =====================================
380
+section "443. PROCESS SUBSTITUTION STYLE"
381
+# =====================================
382
+
383
+# Note: <() is a bash extension, but we test if basic redirects work with commands
384
+result=$("$FORTSH_BIN" -c 'diff <(echo a) <(echo a) 2>/dev/null; echo $?' 2>&1)
385
+# This may not be supported - just document if it works
386
+if [ "$result" = "0" ]; then
387
+    pass "Process substitution <() works"
388
+else
389
+    skip "Process substitution <() works" "bash extension"
390
+fi
391
+
392
+# =====================================
393
+section "444. CLOSING FILE DESCRIPTORS"
394
+# =====================================
395
+
396
+result=$("$FORTSH_BIN" -c 'echo hello >&-; echo done 2>/dev/null' 2>&1)
397
+# Closing stdout then trying to echo should either error or succeed
398
+if echo "$result" | grep -q "done"; then
399
+    pass ">&- closes stdout (recovered)"
400
+else
401
+    pass ">&- closes stdout (caused error)"
402
+fi
403
+
404
+# =====================================
405
+section "445. DUPLICATING INPUT"
406
+# =====================================
407
+
408
+result=$("$FORTSH_BIN" -c 'echo test | { cat; } 0<&0' 2>&1)
409
+if [ "$result" = "test" ]; then
410
+    pass "0<&0 duplicates stdin"
411
+else
412
+    fail "0<&0 duplicates stdin" "test" "$result"
413
+fi
414
+
415
+# =====================================
416
+section "446. PIPELINE EXIT STATUS"
417
+# =====================================
418
+
419
+# Last command determines exit status
420
+result=$("$FORTSH_BIN" -c 'false | true; echo $?' 2>&1)
421
+if [ "$result" = "0" ]; then
422
+    pass "Pipeline exit is last command (false|true=0)"
423
+else
424
+    fail "Pipeline exit is last command (false|true=0)" "0" "$result"
425
+fi
426
+
427
+result=$("$FORTSH_BIN" -c 'true | false; echo $?' 2>&1)
428
+if [ "$result" = "1" ]; then
429
+    pass "Pipeline exit is last command (true|false=1)"
430
+else
431
+    fail "Pipeline exit is last command (true|false=1)" "1" "$result"
432
+fi
433
+
434
+# Multi-stage pipeline
435
+result=$("$FORTSH_BIN" -c 'echo a | cat | cat | cat; echo $?' 2>&1)
436
+if echo "$result" | grep -q "0"; then
437
+    pass "Multi-stage pipeline success"
438
+else
439
+    fail "Multi-stage pipeline success" "0" "$result"
440
+fi
441
+
442
+# Pipeline with negation
443
+result=$("$FORTSH_BIN" -c '! false | true; echo $?' 2>&1)
444
+if [ "$result" = "1" ]; then
445
+    pass "! negates pipeline exit"
446
+else
447
+    fail "! negates pipeline exit" "1" "$result"
448
+fi
449
+
450
+# =====================================
451
+section "447. EXEC REDIRECTIONS"
452
+# =====================================
453
+
454
+# exec without command modifies shell FDs
455
+result=$("$FORTSH_BIN" -c '
456
+exec 3>"'"$TEST_DIR"'/exec_fd3.txt"
457
+echo "fd3 data" >&3
458
+exec 3>&-
459
+cat "'"$TEST_DIR"'/exec_fd3.txt"
460
+' 2>&1)
461
+if [ "$result" = "fd3 data" ]; then
462
+    pass "exec opens FD 3 for writing"
463
+else
464
+    fail "exec opens FD 3 for writing" "fd3 data" "$result"
465
+fi
466
+
467
+# exec read/write mode
468
+result=$("$FORTSH_BIN" -c '
469
+echo "initial" > "'"$TEST_DIR"'/rw_test.txt"
470
+exec 4<>"'"$TEST_DIR"'/rw_test.txt"
471
+read line <&4
472
+echo "read: $line"
473
+exec 4>&-
474
+' 2>&1)
475
+if [ "$result" = "read: initial" ]; then
476
+    pass "exec <> opens read-write"
477
+else
478
+    fail "exec <> opens read-write" "read: initial" "$result"
479
+fi
480
+
481
+# =====================================
482
+section "448. COMPOUND REDIRECTIONS"
483
+# =====================================
484
+
485
+# Redirect entire loop
486
+result=$("$FORTSH_BIN" -c '
487
+for i in 1 2 3; do
488
+    echo $i
489
+done > "'"$TEST_DIR"'/loop_out.txt"
490
+cat "'"$TEST_DIR"'/loop_out.txt"
491
+' 2>&1)
492
+expected=$(printf "1\n2\n3")
493
+if [ "$result" = "$expected" ]; then
494
+    pass "Redirect entire for loop"
495
+else
496
+    fail "Redirect entire for loop" "$expected" "$result"
497
+fi
498
+
499
+# Redirect if statement
500
+result=$("$FORTSH_BIN" -c '
501
+if true; then
502
+    echo inside
503
+fi > "'"$TEST_DIR"'/if_out.txt"
504
+cat "'"$TEST_DIR"'/if_out.txt"
505
+' 2>&1)
506
+if [ "$result" = "inside" ]; then
507
+    pass "Redirect entire if statement"
508
+else
509
+    fail "Redirect entire if statement" "inside" "$result"
510
+fi
511
+
512
+# Redirect case statement
513
+result=$("$FORTSH_BIN" -c '
514
+case "x" in
515
+    x) echo matched;;
516
+esac > "'"$TEST_DIR"'/case_out.txt"
517
+cat "'"$TEST_DIR"'/case_out.txt"
518
+' 2>&1)
519
+if [ "$result" = "matched" ]; then
520
+    pass "Redirect entire case statement"
521
+else
522
+    fail "Redirect entire case statement" "matched" "$result"
523
+fi
524
+
525
+# =====================================
526
+section "449. INPUT REDIRECTION VARIATIONS"
527
+# =====================================
528
+
529
+# cat multiple files
530
+echo "file1" > "$TEST_DIR/cat1.txt"
531
+echo "file2" > "$TEST_DIR/cat2.txt"
532
+result=$("$FORTSH_BIN" -c 'cat "'"$TEST_DIR"'/cat1.txt" "'"$TEST_DIR"'/cat2.txt"' 2>&1)
533
+expected=$(printf "file1\nfile2")
534
+if [ "$result" = "$expected" ]; then
535
+    pass "cat multiple files"
536
+else
537
+    fail "cat multiple files" "$expected" "$result"
538
+fi
539
+
540
+# Input from file
541
+echo "from file" > "$TEST_DIR/input.txt"
542
+result=$("$FORTSH_BIN" -c 'cat < "'"$TEST_DIR"'/input.txt"' 2>&1)
543
+if [ "$result" = "from file" ]; then
544
+    pass "< redirects stdin from file"
545
+else
546
+    fail "< redirects stdin from file" "from file" "$result"
547
+fi
548
+
549
+# =====================================
550
+section "450. OUTPUT APPEND BEHAVIOR"
551
+# =====================================
552
+
553
+# >> creates file if not exists
554
+rm -f "$TEST_DIR/append_new.txt"
555
+result=$("$FORTSH_BIN" -c 'echo first >> "'"$TEST_DIR"'/append_new.txt"; cat "'"$TEST_DIR"'/append_new.txt"' 2>&1)
556
+if [ "$result" = "first" ]; then
557
+    pass ">> creates file if not exists"
558
+else
559
+    fail ">> creates file if not exists" "first" "$result"
560
+fi
561
+
562
+# >> appends to existing
563
+echo "line1" > "$TEST_DIR/append_exist.txt"
564
+result=$("$FORTSH_BIN" -c 'echo line2 >> "'"$TEST_DIR"'/append_exist.txt"; cat "'"$TEST_DIR"'/append_exist.txt"' 2>&1)
565
+expected=$(printf "line1\nline2")
566
+if [ "$result" = "$expected" ]; then
567
+    pass ">> appends to existing file"
568
+else
569
+    fail ">> appends to existing file" "$expected" "$result"
570
+fi
571
+
572
+# Multiple appends
573
+rm -f "$TEST_DIR/multi_append.txt"
574
+result=$("$FORTSH_BIN" -c '
575
+echo a >> "'"$TEST_DIR"'/multi_append.txt"
576
+echo b >> "'"$TEST_DIR"'/multi_append.txt"
577
+echo c >> "'"$TEST_DIR"'/multi_append.txt"
578
+cat "'"$TEST_DIR"'/multi_append.txt"
579
+' 2>&1)
580
+expected=$(printf "a\nb\nc")
581
+if [ "$result" = "$expected" ]; then
582
+    pass "Multiple sequential appends"
583
+else
584
+    fail "Multiple sequential appends" "$expected" "$result"
585
+fi
586
+
587
+# =====================================
588
+section "451. REDIRECT WITH ASSIGNMENTS"
589
+# =====================================
590
+
591
+# Assignment with redirect
592
+result=$("$FORTSH_BIN" -c 'x=$(cat < /dev/null); echo "empty:[$x]"' 2>&1)
593
+if [ "$result" = "empty:[]" ]; then
594
+    pass "Assignment from empty file"
595
+else
596
+    fail "Assignment from empty file" "empty:[]" "$result"
597
+fi
598
+
599
+# Assignment captures command output despite redirects
600
+result=$("$FORTSH_BIN" -c 'x=$(echo hello 2>/dev/null); echo $x' 2>&1)
601
+if [ "$result" = "hello" ]; then
602
+    pass "Assignment captures stdout"
603
+else
604
+    fail "Assignment captures stdout" "hello" "$result"
605
+fi
606
+
607
+# =====================================
608
+section "452. HEREDOC VARIATIONS"
609
+# =====================================
610
+
611
+# Basic heredoc
612
+result=$("$FORTSH_BIN" -c 'cat <<END
613
+hello
614
+END' 2>&1)
615
+if [ "$result" = "hello" ]; then
616
+    pass "Basic heredoc"
617
+else
618
+    fail "Basic heredoc" "hello" "$result"
619
+fi
620
+
621
+# Heredoc with variable expansion
622
+result=$("$FORTSH_BIN" -c 'X=world; cat <<END
623
+hello $X
624
+END' 2>&1)
625
+if [ "$result" = "hello world" ]; then
626
+    pass "Heredoc with variable expansion"
627
+else
628
+    fail "Heredoc with variable expansion" "hello world" "$result"
629
+fi
630
+
631
+# Quoted heredoc prevents expansion
632
+result=$("$FORTSH_BIN" -c 'cat <<'\''END'\''
633
+$VAR
634
+END' 2>&1)
635
+if [ "$result" = '$VAR' ]; then
636
+    pass "Quoted heredoc prevents expansion"
637
+else
638
+    fail "Quoted heredoc prevents expansion" "\$VAR" "$result"
639
+fi
640
+
641
+# =====================================
642
+section "453. PIPELINE VARIATIONS"
643
+# =====================================
644
+
645
+# Long pipeline
646
+result=$("$FORTSH_BIN" -c 'echo test | cat | cat | cat | cat' 2>&1)
647
+if [ "$result" = "test" ]; then
648
+    pass "Long pipeline with multiple cats"
649
+else
650
+    fail "Long pipeline with multiple cats" "test" "$result"
651
+fi
652
+
653
+# Pipeline with head
654
+result=$("$FORTSH_BIN" -c 'printf "a\nb\nc\n" | head -1' 2>&1)
655
+if [ "$result" = "a" ]; then
656
+    pass "Pipeline with head"
657
+else
658
+    fail "Pipeline with head" "a" "$result"
659
+fi
660
+
661
+# Pipeline with tail
662
+result=$("$FORTSH_BIN" -c 'printf "a\nb\nc\n" | tail -1' 2>&1)
663
+if [ "$result" = "c" ]; then
664
+    pass "Pipeline with tail"
665
+else
666
+    fail "Pipeline with tail" "c" "$result"
667
+fi
668
+
669
+# Pipeline with sort
670
+result=$("$FORTSH_BIN" -c 'printf "c\na\nb\n" | sort | head -1' 2>&1)
671
+if [ "$result" = "a" ]; then
672
+    pass "Pipeline with sort"
673
+else
674
+    fail "Pipeline with sort" "a" "$result"
675
+fi
676
+
677
+# =====================================
678
+section "454. FILE DESCRIPTOR OPERATIONS"
679
+# =====================================
680
+
681
+# Dup stdout to stderr
682
+result=$("$FORTSH_BIN" -c 'echo test >&2' 2>&1)
683
+if [ "$result" = "test" ]; then
684
+    pass "Redirect stdout to stderr"
685
+else
686
+    fail "Redirect stdout to stderr" "test" "$result"
687
+fi
688
+
689
+# Close stdout after redirecting to stderr
690
+# Note: matches bash - >&2 redirects stdout to stderr, then 1>&- closes fd1
691
+# The write to closed fd1 fails with "Bad file descriptor"
692
+result=$("$FORTSH_BIN" -c 'echo test >&2 1>&-' 2>&1)
693
+if echo "$result" | grep -q "Bad file descriptor"; then
694
+    pass "Close stdout after redirect (matches bash - write error)"
695
+else
696
+    fail "Close stdout after redirect (matches bash - write error)" "Bad file descriptor error" "$result"
697
+fi
698
+
699
+# =====================================
700
+section "455. INPUT REDIRECTION VARIATIONS"
701
+# =====================================
702
+
703
+# Input from file
704
+echo "content" > "$TEST_DIR/input_test.txt"
705
+result=$("$FORTSH_BIN" -c 'cat < "'"$TEST_DIR"'/input_test.txt"' 2>&1)
706
+if [ "$result" = "content" ]; then
707
+    pass "Input redirection from file"
708
+else
709
+    fail "Input redirection from file" "content" "$result"
710
+fi
711
+
712
+# Input from /dev/null
713
+result=$("$FORTSH_BIN" -c 'cat < /dev/null | wc -c' 2>&1)
714
+if [ "$result" -eq 0 ] 2>/dev/null; then
715
+    pass "Input from /dev/null is empty"
716
+else
717
+    fail "Input from /dev/null is empty" "0" "$result"
718
+fi
719
+
720
+# =====================================
721
+section "456. OUTPUT TO /dev/null"
722
+# =====================================
723
+
724
+# Stdout to /dev/null
725
+result=$("$FORTSH_BIN" -c 'echo hidden > /dev/null; echo visible' 2>&1)
726
+if [ "$result" = "visible" ]; then
727
+    pass "Stdout to /dev/null suppresses output"
728
+else
729
+    fail "Stdout to /dev/null suppresses output" "visible" "$result"
730
+fi
731
+
732
+# Stderr to /dev/null
733
+result=$("$FORTSH_BIN" -c 'ls /nonexistent 2>/dev/null; echo done' 2>&1)
734
+if [ "$result" = "done" ]; then
735
+    pass "Stderr to /dev/null suppresses errors"
736
+else
737
+    fail "Stderr to /dev/null suppresses errors" "done" "$result"
738
+fi
739
+
740
+# Both to /dev/null
741
+result=$("$FORTSH_BIN" -c 'ls /nonexistent >/dev/null 2>&1; echo status=$?' 2>&1)
742
+if echo "$result" | grep -q "status="; then
743
+    pass "Both stdout and stderr to /dev/null"
744
+else
745
+    fail "Both stdout and stderr to /dev/null"
746
+fi
747
+
748
+# =====================================
749
+section "457. NOCLOBBER BEHAVIOR"
750
+# =====================================
751
+
752
+# Normal overwrite
753
+echo "old" > "$TEST_DIR/clobber_test.txt"
754
+result=$("$FORTSH_BIN" -c 'echo new > "'"$TEST_DIR"'/clobber_test.txt"; cat "'"$TEST_DIR"'/clobber_test.txt"' 2>&1)
755
+if [ "$result" = "new" ]; then
756
+    pass "Normal > overwrites file"
757
+else
758
+    fail "Normal > overwrites file" "new" "$result"
759
+fi
760
+
761
+# =====================================
762
+section "458. COMPOUND REDIRECTIONS"
763
+# =====================================
764
+
765
+# Redirect in if statement
766
+result=$("$FORTSH_BIN" -c 'if true; then echo yes; fi > "'"$TEST_DIR"'/if_redir.txt"; cat "'"$TEST_DIR"'/if_redir.txt"' 2>&1)
767
+if [ "$result" = "yes" ]; then
768
+    pass "Redirect if statement output"
769
+else
770
+    fail "Redirect if statement output" "yes" "$result"
771
+fi
772
+
773
+# Redirect in while loop
774
+result=$("$FORTSH_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)
775
+if [ "$result" -eq 3 ] 2>/dev/null; then
776
+    pass "Redirect while loop output"
777
+else
778
+    fail "Redirect while loop output" "3" "$result"
779
+fi
780
+
781
+# Redirect in for loop
782
+result=$("$FORTSH_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)
783
+if [ "$result" -eq 3 ] 2>/dev/null; then
784
+    pass "Redirect for loop output"
785
+else
786
+    fail "Redirect for loop output" "3" "$result"
787
+fi
788
+
789
+# =====================================
790
+section "459. PIPELINE EXIT STATUS"
791
+# =====================================
792
+
793
+# Last command determines exit
794
+result=$("$FORTSH_BIN" -c 'true | false; echo $?' 2>&1)
795
+if [ "$result" = "1" ]; then
796
+    pass "Pipeline exit from last command (false)"
797
+else
798
+    fail "Pipeline exit from last command (false)" "1" "$result"
799
+fi
800
+
801
+result=$("$FORTSH_BIN" -c 'false | true; echo $?' 2>&1)
802
+if [ "$result" = "0" ]; then
803
+    pass "Pipeline exit from last command (true)"
804
+else
805
+    fail "Pipeline exit from last command (true)" "0" "$result"
806
+fi
807
+
808
+# =====================================
809
+section "460. MULTIPLE REDIRECTIONS"
810
+# =====================================
811
+
812
+# Both input and output
813
+echo "input" > "$TEST_DIR/multi_in.txt"
814
+result=$("$FORTSH_BIN" -c 'cat < "'"$TEST_DIR"'/multi_in.txt" > "'"$TEST_DIR"'/multi_out.txt"; cat "'"$TEST_DIR"'/multi_out.txt"' 2>&1)
815
+if [ "$result" = "input" ]; then
816
+    pass "Both input and output redirect"
817
+else
818
+    fail "Both input and output redirect" "input" "$result"
819
+fi
820
+
821
+# Order of redirections
822
+result=$("$FORTSH_BIN" -c 'echo test > "'"$TEST_DIR"'/order1.txt" 2>&1; cat "'"$TEST_DIR"'/order1.txt"' 2>&1)
823
+if [ "$result" = "test" ]; then
824
+    pass "Redirect order: > then 2>&1"
825
+else
826
+    fail "Redirect order: > then 2>&1" "test" "$result"
827
+fi
828
+
829
+# =====================================
830
+section "461. PIPELINE WITH SUBSHELL"
831
+# =====================================
832
+
833
+# Subshell in pipeline
834
+result=$("$FORTSH_BIN" -c '(echo a; echo b) | wc -l' 2>&1)
835
+if [ "$result" -eq 2 ] 2>/dev/null; then
836
+    pass "Subshell in pipeline"
837
+else
838
+    fail "Subshell in pipeline" "2" "$result"
839
+fi
840
+
841
+# Brace group in pipeline
842
+result=$("$FORTSH_BIN" -c '{ echo x; echo y; } | wc -l' 2>&1)
843
+if [ "$result" -eq 2 ] 2>/dev/null; then
844
+    pass "Brace group in pipeline"
845
+else
846
+    fail "Brace group in pipeline" "2" "$result"
847
+fi
848
+
849
+# =====================================
850
+section "462. REDIRECT AND PIPELINE COMBO"
851
+# =====================================
852
+
853
+# Pipeline with final redirect
854
+result=$("$FORTSH_BIN" -c 'echo test | cat > "'"$TEST_DIR"'/pipe_redir.txt"; cat "'"$TEST_DIR"'/pipe_redir.txt"' 2>&1)
855
+if [ "$result" = "test" ]; then
856
+    pass "Pipeline with final redirect"
857
+else
858
+    fail "Pipeline with final redirect" "test" "$result"
859
+fi
860
+
861
+# Redirect within pipeline
862
+result=$("$FORTSH_BIN" -c 'echo test 2>/dev/null | cat' 2>&1)
863
+if [ "$result" = "test" ]; then
864
+    pass "Redirect within pipeline stage"
865
+else
866
+    fail "Redirect within pipeline stage" "test" "$result"
867
+fi
868
+
869
+# =====================================
870
+section "463. HERE STRING ALTERNATIVES"
871
+# =====================================
872
+
873
+# Echo pipe as here-string alternative
874
+result=$("$FORTSH_BIN" -c 'echo "test" | cat' 2>&1)
875
+if [ "$result" = "test" ]; then
876
+    pass "Echo pipe as here-string alternative"
877
+else
878
+    fail "Echo pipe as here-string alternative" "test" "$result"
879
+fi
880
+
881
+# Printf pipe
882
+result=$("$FORTSH_BIN" -c 'printf "%s" "test" | cat' 2>&1)
883
+if [ "$result" = "test" ]; then
884
+    pass "Printf pipe"
885
+else
886
+    fail "Printf pipe" "test" "$result"
887
+fi
888
+
889
+# =====================================
890
+section "464. REDIRECTION EDGE CASES"
891
+# =====================================
892
+
893
+# Redirect to same file
894
+result=$("$FORTSH_BIN" -c 'echo original > "'"$TEST_DIR"'/same.txt"; echo new > "'"$TEST_DIR"'/same.txt"; cat "'"$TEST_DIR"'/same.txt"' 2>&1)
895
+if [ "$result" = "new" ]; then
896
+    pass "Multiple redirects to same file"
897
+else
898
+    fail "Multiple redirects to same file" "new" "$result"
899
+fi
900
+
901
+# Empty redirect (creates empty file)
902
+rm -f "$TEST_DIR/empty_redir.txt"
903
+result=$("$FORTSH_BIN" -c '> "'"$TEST_DIR"'/empty_redir.txt"; [ -f "'"$TEST_DIR"'/empty_redir.txt" ] && echo exists' 2>&1)
904
+if [ "$result" = "exists" ]; then
905
+    pass "Empty redirect creates file"
906
+else
907
+    fail "Empty redirect creates file" "exists" "$result"
908
+fi
909
+
910
+# =====================================
911
+section "465. TPYE WITH REDIRECTION"
912
+# =====================================
913
+
914
+# Pipeline preserves data integrity
915
+result=$("$FORTSH_BIN" -c 'echo "hello world" | cat | cat | cat' 2>&1)
916
+if [ "$result" = "hello world" ]; then
917
+    pass "Pipeline preserves data integrity"
918
+else
919
+    fail "Pipeline preserves data integrity" "hello world" "$result"
920
+fi
921
+
922
+# Multiple pipes with tr
923
+result=$("$FORTSH_BIN" -c 'echo abc | tr a X | tr b Y | tr c Z' 2>&1)
924
+if [ "$result" = "XYZ" ]; then
925
+    pass "Multiple pipes with tr"
926
+else
927
+    fail "Multiple pipes with tr" "XYZ" "$result"
928
+fi
929
+
930
+# =====================================
931
+# Summary
932
+# =====================================
933
+printf "\n"
934
+printf "${BLUE}==========================================\n"
935
+printf "POSIX Redirection and Pipeline Summary\n"
936
+printf "==========================================${NC}\n"
937
+printf "Passed:  ${GREEN}%d${NC}\n" "$PASSED"
938
+printf "Failed:  ${RED}%d${NC}\n" "$FAILED"
939
+printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
940
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
941
+
942
+if [ -n "$FAILED_TESTS_LIST" ]; then
943
+    printf "\n${RED}Failed tests:${NC}\n"
944
+    printf "%b" "$FAILED_TESTS_LIST"
945
+fi
946
+
947
+if [ "$FAILED" -gt 0 ]; then
948
+    exit 1
949
+fi
950
+exit 0
suites/posix/posix_compliance_special.shadded
1396 lines changed — click to load
@@ -0,0 +1,1396 @@
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
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
28
+
29
+# Check if fortsh exists
30
+if [ ! -x "$FORTSH_BIN" ]; then
31
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
32
+    printf "Please run 'make' first or set FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_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=$("$FORTSH_BIN" -c 'IFS=":"; x="a:b"; set -- $x; unset IFS; y="c d"; set -- $y; echo $#' 2>&1)
480
+if [ "$result" = "2" ]; then
481
+    pass "unset IFS restores default splitting"
482
+else
483
+    fail "unset IFS restores default splitting" "2" "$result"
484
+fi
485
+
486
+# =====================================
487
+section "369. $$ IN SUBSHELLS"
488
+# =====================================
489
+
490
+# $$ in subshell should return parent shell PID per POSIX
491
+# Test within SAME shell instance (not separate invocations)
492
+result=$("$FORTSH_BIN" -c 'echo $$; (echo $$)' 2>&1)
493
+parent_pid=$(echo "$result" | sed -n '1p')
494
+subshell_pid=$(echo "$result" | sed -n '2p')
495
+if [ "$parent_pid" = "$subshell_pid" ]; then
496
+    pass "\$\$ in subshell returns parent shell PID"
497
+else
498
+    fail "\$\$ in subshell returns parent shell PID" "$parent_pid" "$subshell_pid"
499
+fi
500
+
501
+# $$ in command substitution
502
+result=$("$FORTSH_BIN" -c 'echo $$ $(echo $$)' 2>&1)
503
+# Both should be the same PID
504
+pid1=$(echo "$result" | awk '{print $1}')
505
+pid2=$(echo "$result" | awk '{print $2}')
506
+if [ "$pid1" = "$pid2" ]; then
507
+    pass "\$\$ in command substitution matches parent"
508
+else
509
+    fail "\$\$ in command substitution matches parent" "same" "$result"
510
+fi
511
+
512
+# $$ is numeric
513
+result=$("$FORTSH_BIN" -c 'echo $$' 2>&1)
514
+if [ "$result" -gt 0 ] 2>/dev/null; then
515
+    pass "\$\$ is positive integer"
516
+else
517
+    fail "\$\$ is positive integer" ">0" "$result"
518
+fi
519
+
520
+# =====================================
521
+section "370. \$@ VS \$* DIFFERENCES"
522
+# =====================================
523
+
524
+# $@ preserves separate arguments
525
+result=$("$FORTSH_BIN" -c 'set -- "a b" "c d"; for x in "$@"; do echo "[$x]"; done' 2>&1)
526
+expected=$(printf "[a b]\n[c d]")
527
+if [ "$result" = "$expected" ]; then
528
+    pass '"\$@" preserves argument boundaries'
529
+else
530
+    fail '"\$@" preserves argument boundaries' "$expected" "$result"
531
+fi
532
+
533
+# $* joins with IFS
534
+result=$("$FORTSH_BIN" -c 'IFS=":"; set -- a b c; echo "$*"' 2>&1)
535
+if [ "$result" = "a:b:c" ]; then
536
+    pass '"\$*" joins with first char of IFS'
537
+else
538
+    fail '"\$*" joins with first char of IFS' "a:b:c" "$result"
539
+fi
540
+
541
+# $@ unquoted splits
542
+result=$("$FORTSH_BIN" -c 'set -- "a b" "c d"; for x in $@; do echo "[$x]"; done' 2>&1)
543
+# Should split into 4 words: a, b, c, d
544
+expected=$(printf "[a]\n[b]\n[c]\n[d]")
545
+if [ "$result" = "$expected" ]; then
546
+    pass 'Unquoted $@ splits on whitespace'
547
+else
548
+    fail 'Unquoted $@ splits on whitespace' "$expected" "$result"
549
+fi
550
+
551
+# Empty $@ produces nothing
552
+result=$("$FORTSH_BIN" -c 'set --; for x in "$@"; do echo X; done; echo done' 2>&1)
553
+if [ "$result" = "done" ]; then
554
+    pass 'Empty "$@" produces no iterations'
555
+else
556
+    fail 'Empty "$@" produces no iterations' "done" "$result"
557
+fi
558
+
559
+# $# count
560
+result=$("$FORTSH_BIN" -c 'set -- a b c d e; echo $#' 2>&1)
561
+if [ "$result" = "5" ]; then
562
+    pass '$# counts positional parameters'
563
+else
564
+    fail '$# counts positional parameters' "5" "$result"
565
+fi
566
+
567
+# =====================================
568
+section "371. PWD AND OLDPWD"
569
+# =====================================
570
+
571
+# PWD is set
572
+result=$("$FORTSH_BIN" -c 'echo $PWD' 2>&1)
573
+if [ -n "$result" ] && [ -d "$result" ]; then
574
+    pass "PWD is set to valid directory"
575
+else
576
+    fail "PWD is set to valid directory" "directory path" "$result"
577
+fi
578
+
579
+# cd updates PWD
580
+result=$("$FORTSH_BIN" -c 'cd /tmp && echo $PWD' 2>&1)
581
+if echo "$result" | grep -q "tmp"; then
582
+    pass "cd updates PWD"
583
+else
584
+    fail "cd updates PWD" "/tmp" "$result"
585
+fi
586
+
587
+# cd updates OLDPWD
588
+result=$("$FORTSH_BIN" -c 'cd /; cd /tmp; echo $OLDPWD' 2>&1)
589
+if [ "$result" = "/" ]; then
590
+    pass "cd updates OLDPWD"
591
+else
592
+    fail "cd updates OLDPWD" "/" "$result"
593
+fi
594
+
595
+# cd - uses OLDPWD
596
+result=$("$FORTSH_BIN" -c 'cd /; cd /tmp; cd -' 2>&1)
597
+if echo "$result" | grep -q "/"; then
598
+    pass "cd - prints previous directory"
599
+else
600
+    fail "cd - prints previous directory" "/" "$result"
601
+fi
602
+
603
+# =====================================
604
+section "372. \$? EXIT STATUS"
605
+# =====================================
606
+
607
+# $? after successful command
608
+result=$("$FORTSH_BIN" -c 'true; echo $?' 2>&1)
609
+if [ "$result" = "0" ]; then
610
+    pass '$? is 0 after true'
611
+else
612
+    fail '$? is 0 after true' "0" "$result"
613
+fi
614
+
615
+# $? after failed command
616
+result=$("$FORTSH_BIN" -c 'false; echo $?' 2>&1)
617
+if [ "$result" = "1" ]; then
618
+    pass '$? is 1 after false'
619
+else
620
+    fail '$? is 1 after false' "1" "$result"
621
+fi
622
+
623
+# $? after exit N
624
+result=$("$FORTSH_BIN" -c '(exit 42); echo $?' 2>&1)
625
+if [ "$result" = "42" ]; then
626
+    pass '$? captures exit code from subshell'
627
+else
628
+    fail '$? captures exit code from subshell' "42" "$result"
629
+fi
630
+
631
+# $? after signal (128+signal)
632
+result=$("$FORTSH_BIN" -c 'sh -c "kill -9 \$\$" 2>/dev/null; echo $?' 2>&1)
633
+if [ "$result" -ge 128 ] 2>/dev/null; then
634
+    pass '$? is 128+ after signal death'
635
+else
636
+    fail '$? is 128+ after signal death' ">=128" "$result"
637
+fi
638
+
639
+# $? after command not found
640
+result=$("$FORTSH_BIN" -c 'nonexistent_cmd_xyz_12345 2>/dev/null; echo $?' 2>&1)
641
+if [ "$result" = "127" ]; then
642
+    pass '$? is 127 for command not found'
643
+else
644
+    fail '$? is 127 for command not found' "127" "$result"
645
+fi
646
+
647
+# =====================================
648
+section "373. \$0 SCRIPT NAME"
649
+# =====================================
650
+
651
+# $0 in -c command
652
+result=$("$FORTSH_BIN" -c 'echo $0' 2>&1)
653
+if [ -n "$result" ]; then
654
+    pass '$0 is set in -c command'
655
+else
656
+    fail '$0 is set in -c command' "non-empty" "$result"
657
+fi
658
+
659
+# $0 can be set with -c
660
+result=$("$FORTSH_BIN" -c 'echo $0' myname 2>&1)
661
+if [ "$result" = "myname" ]; then
662
+    pass '$0 can be set via argument after -c'
663
+else
664
+    fail '$0 can be set via argument after -c' "myname" "$result"
665
+fi
666
+
667
+# =====================================
668
+section "374. SHIFT EDGE CASES"
669
+# =====================================
670
+
671
+# shift removes first positional param
672
+result=$("$FORTSH_BIN" -c 'set -- a b c; shift; echo $1' 2>&1)
673
+if [ "$result" = "b" ]; then
674
+    pass 'shift removes first parameter'
675
+else
676
+    fail 'shift removes first parameter' "b" "$result"
677
+fi
678
+
679
+# shift N removes N params
680
+result=$("$FORTSH_BIN" -c 'set -- a b c d e; shift 2; echo $1' 2>&1)
681
+if [ "$result" = "c" ]; then
682
+    pass 'shift N removes N parameters'
683
+else
684
+    fail 'shift N removes N parameters' "c" "$result"
685
+fi
686
+
687
+# shift updates $#
688
+result=$("$FORTSH_BIN" -c 'set -- a b c d e; shift 3; echo $#' 2>&1)
689
+if [ "$result" = "2" ]; then
690
+    pass 'shift updates $#'
691
+else
692
+    fail 'shift updates $#' "2" "$result"
693
+fi
694
+
695
+# shift more than $# fails
696
+result=$("$FORTSH_BIN" -c 'set -- a b; shift 5 2>/dev/null; echo $?' 2>&1)
697
+if [ "$result" != "0" ]; then
698
+    pass 'shift beyond $# returns non-zero'
699
+else
700
+    fail 'shift beyond $# returns non-zero' "non-zero" "$result"
701
+fi
702
+
703
+# =====================================
704
+section "375. ENVIRONMENT VARIABLES"
705
+# =====================================
706
+
707
+# HOME is set
708
+result=$("$FORTSH_BIN" -c 'echo $HOME' 2>&1)
709
+if [ -n "$result" ] && [ -d "$result" ]; then
710
+    pass "HOME is set to valid directory"
711
+else
712
+    fail "HOME is set to valid directory" "directory" "$result"
713
+fi
714
+
715
+# PATH is set
716
+result=$("$FORTSH_BIN" -c 'echo $PATH' 2>&1)
717
+if [ -n "$result" ]; then
718
+    pass "PATH is set"
719
+else
720
+    fail "PATH is set" "non-empty" "$result"
721
+fi
722
+
723
+# PATH affects command lookup - use actual system paths
724
+LS_DIR=$(dirname "$(which ls 2>/dev/null)")
725
+result=$("$FORTSH_BIN" -c "PATH=$LS_DIR; ls / >/dev/null 2>&1; echo \$?" 2>&1)
726
+if [ "$result" = "0" ]; then
727
+    pass "PATH affects command resolution"
728
+else
729
+    fail "PATH affects command resolution" "0" "$result"
730
+fi
731
+
732
+# Empty PATH means current directory only
733
+result=$("$FORTSH_BIN" -c 'PATH=""; ls / 2>/dev/null; echo $?' 2>&1)
734
+if [ "$result" != "0" ]; then
735
+    pass "Empty PATH prevents finding commands"
736
+else
737
+    fail "Empty PATH prevents finding commands" "non-zero" "$result"
738
+fi
739
+
740
+# =====================================
741
+section "376. EVAL SPECIAL CASES"
742
+# =====================================
743
+
744
+# eval with variable containing command
745
+result=$("$FORTSH_BIN" -c 'cmd="echo hello"; eval "$cmd"' 2>&1)
746
+if [ "$result" = "hello" ]; then
747
+    pass "eval executes command in variable"
748
+else
749
+    fail "eval executes command in variable" "hello" "$result"
750
+fi
751
+
752
+# eval with multiple arguments
753
+result=$("$FORTSH_BIN" -c 'eval echo hello world' 2>&1)
754
+if [ "$result" = "hello world" ]; then
755
+    pass "eval concatenates arguments"
756
+else
757
+    fail "eval concatenates arguments" "hello world" "$result"
758
+fi
759
+
760
+# eval with variable expansion
761
+result=$("$FORTSH_BIN" -c 'x=y; y=z; eval echo \$$x' 2>&1)
762
+if [ "$result" = "z" ]; then
763
+    pass "eval performs double expansion"
764
+else
765
+    fail "eval performs double expansion" "z" "$result"
766
+fi
767
+
768
+# eval exit status
769
+result=$("$FORTSH_BIN" -c 'eval false; echo $?' 2>&1)
770
+if [ "$result" = "1" ]; then
771
+    pass "eval returns command exit status"
772
+else
773
+    fail "eval returns command exit status" "1" "$result"
774
+fi
775
+
776
+# =====================================
777
+section "377. COMMAND EXECUTION MODES"
778
+# =====================================
779
+
780
+# Command with -c flag
781
+result=$("$FORTSH_BIN" -c 'echo test' 2>&1)
782
+if [ "$result" = "test" ]; then
783
+    pass "fortsh -c executes command"
784
+else
785
+    fail "fortsh -c executes command" "test" "$result"
786
+fi
787
+
788
+# Command with environment var
789
+result=$(X=value "$FORTSH_BIN" -c 'echo $X' 2>&1)
790
+if [ "$result" = "value" ]; then
791
+    pass "env var passed to fortsh"
792
+else
793
+    fail "env var passed to fortsh" "value" "$result"
794
+fi
795
+
796
+# =====================================
797
+section "378. SPECIAL BUILTIN BEHAVIORS"
798
+# =====================================
799
+
800
+# break outside loop
801
+result=$("$FORTSH_BIN" -c 'break 2>/dev/null; echo $?' 2>&1)
802
+if echo "$result" | grep -qE '^[0-9]'; then
803
+    pass "break outside loop returns error status"
804
+else
805
+    fail "break outside loop returns error status"
806
+fi
807
+
808
+# continue outside loop
809
+result=$("$FORTSH_BIN" -c 'continue 2>/dev/null; echo $?' 2>&1)
810
+if echo "$result" | grep -qE '^[0-9]'; then
811
+    pass "continue outside loop returns error status"
812
+else
813
+    fail "continue outside loop returns error status"
814
+fi
815
+
816
+# return outside function
817
+result=$("$FORTSH_BIN" -c 'return 2>/dev/null; echo $?' 2>&1)
818
+if echo "$result" | grep -qE '^[0-9]'; then
819
+    pass "return outside function handled"
820
+else
821
+    fail "return outside function handled"
822
+fi
823
+
824
+# =====================================
825
+section "379. REDIRECTION EDGE CASES"
826
+# =====================================
827
+
828
+# Redirect to /dev/null
829
+result=$("$FORTSH_BIN" -c 'echo test > /dev/null; echo done' 2>&1)
830
+if [ "$result" = "done" ]; then
831
+    pass "redirect to /dev/null works"
832
+else
833
+    fail "redirect to /dev/null works" "done" "$result"
834
+fi
835
+
836
+# Redirect stderr to stdout
837
+result=$("$FORTSH_BIN" -c 'echo stderr >&2' 2>&1)
838
+if [ "$result" = "stderr" ]; then
839
+    pass "redirect stderr to stdout"
840
+else
841
+    fail "redirect stderr to stdout" "stderr" "$result"
842
+fi
843
+
844
+# =====================================
845
+section "380. POSITIONAL PARAMETERS EDGE CASES"
846
+# =====================================
847
+
848
+# More than 9 positional params
849
+result=$("$FORTSH_BIN" -c 'set -- a b c d e f g h i j k; echo $1 ${10} ${11}' 2>&1)
850
+if echo "$result" | grep -q "a j k"; then
851
+    pass "access to \${10} and beyond"
852
+else
853
+    fail "access to \${10} and beyond" "a j k" "$result"
854
+fi
855
+
856
+# shift with count
857
+result=$("$FORTSH_BIN" -c 'set -- a b c d e; shift 3; echo $1' 2>&1)
858
+if [ "$result" = "d" ]; then
859
+    pass "shift with count"
860
+else
861
+    fail "shift with count" "d" "$result"
862
+fi
863
+
864
+# =====================================
865
+section "381. ARITHMETIC EDGE CASES"
866
+# =====================================
867
+
868
+# Negative numbers
869
+result=$("$FORTSH_BIN" -c 'echo $((-5))' 2>&1)
870
+if [ "$result" = "-5" ]; then
871
+    pass "negative numbers in arithmetic"
872
+else
873
+    fail "negative numbers in arithmetic" "-5" "$result"
874
+fi
875
+
876
+# Parentheses for grouping
877
+result=$("$FORTSH_BIN" -c 'echo $(( (2+3) * 4 ))' 2>&1)
878
+if [ "$result" = "20" ]; then
879
+    pass "parentheses in arithmetic"
880
+else
881
+    fail "parentheses in arithmetic" "20" "$result"
882
+fi
883
+
884
+# Comparison operators return 0/1
885
+result=$("$FORTSH_BIN" -c 'echo $((5 > 3))' 2>&1)
886
+if [ "$result" = "1" ]; then
887
+    pass "arithmetic comparison returns 1 for true"
888
+else
889
+    fail "arithmetic comparison returns 1 for true" "1" "$result"
890
+fi
891
+
892
+result=$("$FORTSH_BIN" -c 'echo $((3 > 5))' 2>&1)
893
+if [ "$result" = "0" ]; then
894
+    pass "arithmetic comparison returns 0 for false"
895
+else
896
+    fail "arithmetic comparison returns 0 for false" "0" "$result"
897
+fi
898
+
899
+# =====================================
900
+section "382. VARIABLE ASSIGNMENT EDGE CASES"
901
+# =====================================
902
+
903
+# Assignment with empty value
904
+result=$("$FORTSH_BIN" -c 'X=; echo "[$X]"' 2>&1)
905
+if [ "$result" = "[]" ]; then
906
+    pass "empty variable assignment"
907
+else
908
+    fail "empty variable assignment" "[]" "$result"
909
+fi
910
+
911
+# Assignment with quotes
912
+result=$("$FORTSH_BIN" -c 'X="hello world"; echo $X' 2>&1)
913
+if [ "$result" = "hello world" ]; then
914
+    pass "quoted variable assignment"
915
+else
916
+    fail "quoted variable assignment" "hello world" "$result"
917
+fi
918
+
919
+# Multiple assignments on one line
920
+result=$("$FORTSH_BIN" -c 'A=1 B=2 C=3; echo $A $B $C' 2>&1)
921
+if [ "$result" = "1 2 3" ]; then
922
+    pass "multiple assignments"
923
+else
924
+    fail "multiple assignments" "1 2 3" "$result"
925
+fi
926
+
927
+# =====================================
928
+section "383. QUOTING EDGE CASES"
929
+# =====================================
930
+
931
+# Single quotes preserve literally
932
+result=$("$FORTSH_BIN" -c "echo '\$HOME'" 2>&1)
933
+if [ "$result" = '$HOME' ]; then
934
+    pass "single quotes preserve dollar"
935
+else
936
+    fail "single quotes preserve dollar" "\$HOME" "$result"
937
+fi
938
+
939
+# Double quotes allow expansion
940
+result=$("$FORTSH_BIN" -c 'X=test; echo "$X"' 2>&1)
941
+if [ "$result" = "test" ]; then
942
+    pass "double quotes allow expansion"
943
+else
944
+    fail "double quotes allow expansion" "test" "$result"
945
+fi
946
+
947
+# Mixed quoting
948
+result=$("$FORTSH_BIN" -c "X=val; echo 'literal'\"\$X\"'more'" 2>&1)
949
+if [ "$result" = "literalvalmore" ]; then
950
+    pass "mixed quoting works"
951
+else
952
+    fail "mixed quoting works" "literalvalmore" "$result"
953
+fi
954
+
955
+# =====================================
956
+section "384. PIPELINE EDGE CASES"
957
+# =====================================
958
+
959
+# Pipeline with three stages
960
+result=$("$FORTSH_BIN" -c 'echo test | cat | cat' 2>&1)
961
+if [ "$result" = "test" ]; then
962
+    pass "three-stage pipeline"
963
+else
964
+    fail "three-stage pipeline" "test" "$result"
965
+fi
966
+
967
+# Pipeline with grep
968
+result=$("$FORTSH_BIN" -c 'echo "hello world" | grep hello' 2>&1)
969
+if [ "$result" = "hello world" ]; then
970
+    pass "pipeline with grep"
971
+else
972
+    fail "pipeline with grep" "hello world" "$result"
973
+fi
974
+
975
+# =====================================
976
+section "385. CASE STATEMENT EDGE CASES"
977
+# =====================================
978
+
979
+# Case with wildcards
980
+result=$("$FORTSH_BIN" -c 'x=hello; case $x in h*) echo match;; esac' 2>&1)
981
+if [ "$result" = "match" ]; then
982
+    pass "case with wildcard pattern"
983
+else
984
+    fail "case with wildcard pattern" "match" "$result"
985
+fi
986
+
987
+# Case with multiple patterns
988
+result=$("$FORTSH_BIN" -c 'x=b; case $x in a|b|c) echo abc;; esac' 2>&1)
989
+if [ "$result" = "abc" ]; then
990
+    pass "case with multiple patterns"
991
+else
992
+    fail "case with multiple patterns" "abc" "$result"
993
+fi
994
+
995
+# Case with default
996
+result=$("$FORTSH_BIN" -c 'x=z; case $x in a) echo a;; *) echo default;; esac' 2>&1)
997
+if [ "$result" = "default" ]; then
998
+    pass "case with default pattern"
999
+else
1000
+    fail "case with default pattern" "default" "$result"
1001
+fi
1002
+
1003
+# =====================================
1004
+section "386. LOOP EDGE CASES"
1005
+# =====================================
1006
+
1007
+# For loop with break
1008
+result=$("$FORTSH_BIN" -c 'for i in 1 2 3 4 5; do [ $i -eq 3 ] && break; echo $i; done' 2>&1)
1009
+expected="1
1010
+2"
1011
+if [ "$result" = "$expected" ]; then
1012
+    pass "for loop with break"
1013
+else
1014
+    fail "for loop with break"
1015
+fi
1016
+
1017
+# For loop with continue
1018
+result=$("$FORTSH_BIN" -c 'for i in 1 2 3 4 5; do [ $i -eq 3 ] && continue; echo $i; done' 2>&1)
1019
+if echo "$result" | grep -q "1" && echo "$result" | grep -q "4" && ! echo "$result" | grep -q "3"; then
1020
+    pass "for loop with continue"
1021
+else
1022
+    fail "for loop with continue"
1023
+fi
1024
+
1025
+# =====================================
1026
+section "387. SUBSHELL EDGE CASES"
1027
+# =====================================
1028
+
1029
+# Subshell variable isolation
1030
+result=$("$FORTSH_BIN" -c 'X=outer; (X=inner; echo $X); echo $X' 2>&1)
1031
+expected="inner
1032
+outer"
1033
+if [ "$result" = "$expected" ]; then
1034
+    pass "subshell variable isolation"
1035
+else
1036
+    fail "subshell variable isolation"
1037
+fi
1038
+
1039
+# Subshell exit status
1040
+result=$("$FORTSH_BIN" -c '(exit 42); echo $?' 2>&1)
1041
+if [ "$result" = "42" ]; then
1042
+    pass "subshell exit status captured"
1043
+else
1044
+    fail "subshell exit status captured" "42" "$result"
1045
+fi
1046
+
1047
+# =====================================
1048
+section "388. FUNCTION EDGE CASES"
1049
+# =====================================
1050
+
1051
+# Function with local-like behavior (via subshell)
1052
+result=$("$FORTSH_BIN" -c 'X=global; f() { (X=local; echo $X); }; f; echo $X' 2>&1)
1053
+expected="local
1054
+global"
1055
+if [ "$result" = "$expected" ]; then
1056
+    pass "function with subshell for local vars"
1057
+else
1058
+    fail "function with subshell for local vars"
1059
+fi
1060
+
1061
+# Recursive function
1062
+result=$("$FORTSH_BIN" -c 'count() { if [ $1 -gt 0 ]; then echo $1; count $(($1-1)); fi; }; count 3' 2>&1)
1063
+expected="3
1064
+2
1065
+1"
1066
+if [ "$result" = "$expected" ]; then
1067
+    pass "recursive function"
1068
+else
1069
+    fail "recursive function"
1070
+fi
1071
+
1072
+# =====================================
1073
+section "389. HERE DOCUMENT EDGE CASES"
1074
+# =====================================
1075
+
1076
+# Heredoc with variable expansion
1077
+result=$("$FORTSH_BIN" -c 'X=value; cat <<EOF
1078
+$X
1079
+EOF' 2>&1)
1080
+if [ "$result" = "value" ]; then
1081
+    pass "heredoc variable expansion"
1082
+else
1083
+    fail "heredoc variable expansion" "value" "$result"
1084
+fi
1085
+
1086
+# Quoted heredoc (no expansion)
1087
+result=$("$FORTSH_BIN" -c 'cat <<'\''EOF'\''
1088
+$VAR
1089
+EOF' 2>&1)
1090
+if [ "$result" = '$VAR' ]; then
1091
+    pass "quoted heredoc no expansion"
1092
+else
1093
+    fail "quoted heredoc no expansion" "\$VAR" "$result"
1094
+fi
1095
+
1096
+# =====================================
1097
+section "390. COMMAND SUBSTITUTION EDGE CASES"
1098
+# =====================================
1099
+
1100
+# Nested command substitution
1101
+result=$("$FORTSH_BIN" -c 'echo $(echo $(echo nested))' 2>&1)
1102
+if [ "$result" = "nested" ]; then
1103
+    pass "nested command substitution"
1104
+else
1105
+    fail "nested command substitution" "nested" "$result"
1106
+fi
1107
+
1108
+# Command substitution with quotes
1109
+result=$("$FORTSH_BIN" -c 'echo "$(echo "hello world")"' 2>&1)
1110
+if [ "$result" = "hello world" ]; then
1111
+    pass "command substitution with quotes"
1112
+else
1113
+    fail "command substitution with quotes" "hello world" "$result"
1114
+fi
1115
+
1116
+# Backtick substitution
1117
+result=$("$FORTSH_BIN" -c 'echo `echo backtick`' 2>&1)
1118
+if [ "$result" = "backtick" ]; then
1119
+    pass "backtick command substitution"
1120
+else
1121
+    fail "backtick command substitution" "backtick" "$result"
1122
+fi
1123
+
1124
+# =====================================
1125
+section "391. EXPR COMMAND"
1126
+# =====================================
1127
+
1128
+result=$("$FORTSH_BIN" -c 'expr 5 + 3' 2>&1)
1129
+if [ "$result" = "8" ]; then
1130
+    pass "expr addition"
1131
+else
1132
+    fail "expr addition" "8" "$result"
1133
+fi
1134
+
1135
+result=$("$FORTSH_BIN" -c 'expr 10 - 4' 2>&1)
1136
+if [ "$result" = "6" ]; then
1137
+    pass "expr subtraction"
1138
+else
1139
+    fail "expr subtraction" "6" "$result"
1140
+fi
1141
+
1142
+result=$("$FORTSH_BIN" -c 'expr 6 \* 7' 2>&1)
1143
+if [ "$result" = "42" ]; then
1144
+    pass "expr multiplication"
1145
+else
1146
+    fail "expr multiplication" "42" "$result"
1147
+fi
1148
+
1149
+result=$("$FORTSH_BIN" -c 'expr 20 / 4' 2>&1)
1150
+if [ "$result" = "5" ]; then
1151
+    pass "expr division"
1152
+else
1153
+    fail "expr division" "5" "$result"
1154
+fi
1155
+
1156
+# =====================================
1157
+section "392. TEST COMMAND VARIATIONS"
1158
+# =====================================
1159
+
1160
+result=$("$FORTSH_BIN" -c 'test -f /etc/passwd && echo yes' 2>&1)
1161
+if [ "$result" = "yes" ]; then
1162
+    pass "test -f regular file"
1163
+else
1164
+    fail "test -f regular file" "yes" "$result"
1165
+fi
1166
+
1167
+result=$("$FORTSH_BIN" -c 'test -d /tmp && echo yes' 2>&1)
1168
+if [ "$result" = "yes" ]; then
1169
+    pass "test -d directory"
1170
+else
1171
+    fail "test -d directory" "yes" "$result"
1172
+fi
1173
+
1174
+result=$("$FORTSH_BIN" -c 'test 5 -eq 5 && echo yes' 2>&1)
1175
+if [ "$result" = "yes" ]; then
1176
+    pass "test numeric equal"
1177
+else
1178
+    fail "test numeric equal" "yes" "$result"
1179
+fi
1180
+
1181
+result=$("$FORTSH_BIN" -c 'test "abc" = "abc" && echo yes' 2>&1)
1182
+if [ "$result" = "yes" ]; then
1183
+    pass "test string equal"
1184
+else
1185
+    fail "test string equal" "yes" "$result"
1186
+fi
1187
+
1188
+# =====================================
1189
+section "393. BASENAME AND DIRNAME SIMULATION"
1190
+# =====================================
1191
+
1192
+result=$("$FORTSH_BIN" -c 'X=/path/to/file.txt; echo ${X##*/}' 2>&1)
1193
+if [ "$result" = "file.txt" ]; then
1194
+    pass "basename via parameter expansion"
1195
+else
1196
+    fail "basename via parameter expansion" "file.txt" "$result"
1197
+fi
1198
+
1199
+result=$("$FORTSH_BIN" -c 'X=/path/to/file.txt; echo ${X%/*}' 2>&1)
1200
+if [ "$result" = "/path/to" ]; then
1201
+    pass "dirname via parameter expansion"
1202
+else
1203
+    fail "dirname via parameter expansion" "/path/to" "$result"
1204
+fi
1205
+
1206
+result=$("$FORTSH_BIN" -c 'X=file.tar.gz; echo ${X%.gz}' 2>&1)
1207
+if [ "$result" = "file.tar" ]; then
1208
+    pass "remove extension"
1209
+else
1210
+    fail "remove extension" "file.tar" "$result"
1211
+fi
1212
+
1213
+result=$("$FORTSH_BIN" -c 'X=file.tar.gz; echo ${X%%.*}' 2>&1)
1214
+if [ "$result" = "file" ]; then
1215
+    pass "remove all extensions"
1216
+else
1217
+    fail "remove all extensions" "file" "$result"
1218
+fi
1219
+
1220
+# =====================================
1221
+section "394. COMPLEX PIPELINES"
1222
+# =====================================
1223
+
1224
+result=$("$FORTSH_BIN" -c 'echo "hello world" | tr " " "\n" | wc -l' 2>&1)
1225
+if [ "$result" -eq 2 ] 2>/dev/null; then
1226
+    pass "pipeline with tr and wc"
1227
+else
1228
+    fail "pipeline with tr and wc" "2" "$result"
1229
+fi
1230
+
1231
+result=$("$FORTSH_BIN" -c 'printf "c\na\nb\n" | sort | head -1' 2>&1)
1232
+if [ "$result" = "a" ]; then
1233
+    pass "pipeline sort and head"
1234
+else
1235
+    fail "pipeline sort and head" "a" "$result"
1236
+fi
1237
+
1238
+result=$("$FORTSH_BIN" -c 'echo "test" | cat | cat | cat' 2>&1)
1239
+if [ "$result" = "test" ]; then
1240
+    pass "triple cat pipeline"
1241
+else
1242
+    fail "triple cat pipeline" "test" "$result"
1243
+fi
1244
+
1245
+# =====================================
1246
+section "395. COMMAND GROUPING"
1247
+# =====================================
1248
+
1249
+result=$("$FORTSH_BIN" -c '{ echo a; echo b; echo c; } | wc -l' 2>&1)
1250
+if [ "$result" -eq 3 ] 2>/dev/null; then
1251
+    pass "brace group to pipeline"
1252
+else
1253
+    fail "brace group to pipeline" "3" "$result"
1254
+fi
1255
+
1256
+result=$("$FORTSH_BIN" -c '(echo x; echo y) | wc -l' 2>&1)
1257
+if [ "$result" -eq 2 ] 2>/dev/null; then
1258
+    pass "subshell to pipeline"
1259
+else
1260
+    fail "subshell to pipeline" "2" "$result"
1261
+fi
1262
+
1263
+result=$("$FORTSH_BIN" -c 'X=1; { X=2; echo $X; }; echo $X' 2>&1)
1264
+expected=$(printf "2\n2")
1265
+if [ "$result" = "$expected" ]; then
1266
+    pass "brace group modifies parent var"
1267
+else
1268
+    fail "brace group modifies parent var"
1269
+fi
1270
+
1271
+# =====================================
1272
+section "396. WORD EXPANSION ORDER"
1273
+# =====================================
1274
+
1275
+result=$("$FORTSH_BIN" -c 'X="a b c"; echo $X' 2>&1)
1276
+if [ "$result" = "a b c" ]; then
1277
+    pass "unquoted var word splits"
1278
+else
1279
+    fail "unquoted var word splits" "a b c" "$result"
1280
+fi
1281
+
1282
+result=$("$FORTSH_BIN" -c 'X="a b c"; echo "$X"' 2>&1)
1283
+if [ "$result" = "a b c" ]; then
1284
+    pass "quoted var preserves spaces"
1285
+else
1286
+    fail "quoted var preserves spaces" "a b c" "$result"
1287
+fi
1288
+
1289
+result=$("$FORTSH_BIN" -c 'echo ~ | grep -c "^/"' 2>&1)
1290
+if [ "$result" = "1" ]; then
1291
+    pass "tilde expands to home"
1292
+else
1293
+    fail "tilde expands to home" "1" "$result"
1294
+fi
1295
+
1296
+# =====================================
1297
+section "397. SIGNAL NAMES"
1298
+# =====================================
1299
+
1300
+result=$("$FORTSH_BIN" -c 'kill -l | grep -c HUP' 2>&1)
1301
+if [ "$result" -ge 1 ]; then
1302
+    pass "kill -l shows HUP"
1303
+else
1304
+    fail "kill -l shows HUP"
1305
+fi
1306
+
1307
+result=$("$FORTSH_BIN" -c 'kill -l | grep -c INT' 2>&1)
1308
+if [ "$result" -ge 1 ]; then
1309
+    pass "kill -l shows INT"
1310
+else
1311
+    fail "kill -l shows INT"
1312
+fi
1313
+
1314
+result=$("$FORTSH_BIN" -c 'kill -l | grep -c TERM' 2>&1)
1315
+if [ "$result" -ge 1 ]; then
1316
+    pass "kill -l shows TERM"
1317
+else
1318
+    fail "kill -l shows TERM"
1319
+fi
1320
+
1321
+# =====================================
1322
+section "398. EXEC WITH FD"
1323
+# =====================================
1324
+
1325
+result=$("$FORTSH_BIN" -c 'exec 3>&1; echo test >&3; exec 3>&-' 2>&1)
1326
+if [ "$result" = "test" ]; then
1327
+    pass "exec fd redirect"
1328
+else
1329
+    fail "exec fd redirect" "test" "$result"
1330
+fi
1331
+
1332
+# =====================================
1333
+section "399. COMPLEX FUNCTIONS"
1334
+# =====================================
1335
+
1336
+result=$("$FORTSH_BIN" -c 'max() { [ $1 -gt $2 ] && echo $1 || echo $2; }; max 5 3' 2>&1)
1337
+if [ "$result" = "5" ]; then
1338
+    pass "function max returns larger"
1339
+else
1340
+    fail "function max returns larger" "5" "$result"
1341
+fi
1342
+
1343
+result=$("$FORTSH_BIN" -c 'sum() { echo $(($1 + $2)); }; sum 10 20' 2>&1)
1344
+if [ "$result" = "30" ]; then
1345
+    pass "function sum"
1346
+else
1347
+    fail "function sum" "30" "$result"
1348
+fi
1349
+
1350
+result=$("$FORTSH_BIN" -c 'greet() { echo "Hello, $1!"; }; greet World' 2>&1)
1351
+if [ "$result" = "Hello, World!" ]; then
1352
+    pass "function with string interpolation"
1353
+else
1354
+    fail "function with string interpolation" "Hello, World!" "$result"
1355
+fi
1356
+
1357
+# =====================================
1358
+section "400. ADVANCED REDIRECTIONS"
1359
+# =====================================
1360
+
1361
+result=$("$FORTSH_BIN" -c 'echo out; echo err >&2' 2>&1)
1362
+expected=$(printf "out\nerr")
1363
+if [ "$result" = "$expected" ]; then
1364
+    pass "stdout and stderr"
1365
+else
1366
+    fail "stdout and stderr"
1367
+fi
1368
+
1369
+result=$("$FORTSH_BIN" -c '{ echo a; echo b >&2; } 2>&1 | wc -l' 2>&1)
1370
+if [ "$result" -eq 2 ] 2>/dev/null; then
1371
+    pass "redirect stderr to stdout in pipeline"
1372
+else
1373
+    fail "redirect stderr to stdout in pipeline" "2" "$result"
1374
+fi
1375
+
1376
+# =====================================
1377
+# Summary
1378
+# =====================================
1379
+printf "\n"
1380
+printf "${BLUE}==========================================\n"
1381
+printf "POSIX Special Features Test Summary\n"
1382
+printf "==========================================${NC}\n"
1383
+printf "Passed:  ${GREEN}%d${NC}\n" "$PASSED"
1384
+printf "Failed:  ${RED}%d${NC}\n" "$FAILED"
1385
+printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED"
1386
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
1387
+
1388
+if [ -n "$FAILED_TESTS_LIST" ]; then
1389
+    printf "\n${RED}Failed tests:${NC}\n"
1390
+    printf "%b" "$FAILED_TESTS_LIST"
1391
+fi
1392
+
1393
+if [ "$FAILED" -gt 0 ]; then
1394
+    exit 1
1395
+fi
1396
+exit 0
suites/posix/posix_compliance_test.shadded
@@ -0,0 +1,448 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Test Suite for fortsh
4
+# =====================================
5
+# Tests compliance with POSIX shell specification
6
+# Uses /bin/sh for comparison (typically dash or bash in POSIX mode)
7
+
8
+# Note: Using only POSIX-compliant constructs in this script
9
+# No bash-isms allowed!
10
+
11
+# Colors (POSIX-compliant way)
12
+RED='\033[0;31m'
13
+GREEN='\033[0;32m'
14
+YELLOW='\033[1;33m'
15
+BLUE='\033[0;34m'
16
+NC='\033[0m'
17
+
18
+# Test identification
19
+TEST_PREFIX="[posix-test]"
20
+CURRENT_SECTION=""
21
+TEST_NUM=0
22
+
23
+PASSED=0
24
+FAILED=0
25
+SKIPPED=0
26
+FAILED_TESTS_LIST=""
27
+
28
+# Get script directory (POSIX way)
29
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
30
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
31
+BASH_REF="${BASH_REF:-bash}"
32
+
33
+# Check if fortsh exists
34
+if [ ! -x "$FORTSH_BIN" ]; then
35
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
36
+    printf "Please run 'make' first or set FORTSH_BIN environment variable\n"
37
+    exit 1
38
+fi
39
+
40
+# Test result trackers
41
+pass() {
42
+    TEST_NUM=$((TEST_NUM + 1))
43
+    printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
44
+    PASSED=$((PASSED + 1))
45
+}
46
+
47
+fail() {
48
+    TEST_NUM=$((TEST_NUM + 1))
49
+    TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
50
+    printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
51
+    FAILED_TESTS_LIST="${FAILED_TESTS_LIST}  ${TEST_ID}: $1\n"
52
+    if [ -n "$2" ]; then
53
+        printf "  posix:  %s\n" "$2"
54
+    fi
55
+    if [ -n "$3" ]; then
56
+        printf "  fortsh: %s\n" "$3"
57
+    fi
58
+    FAILED=$((FAILED + 1))
59
+}
60
+
61
+skip() {
62
+    TEST_NUM=$((TEST_NUM + 1))
63
+    printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2"
64
+    SKIPPED=$((SKIPPED + 1))
65
+}
66
+
67
+section() {
68
+    # Extract section number from header like "3. POSIX PARAMETER EXPANSION"
69
+    CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
70
+    TEST_NUM=0
71
+    printf "\n"
72
+    printf "${BLUE}==========================================\n"
73
+    printf "%s\n" "$1"
74
+    printf "==========================================${NC}\n"
75
+}
76
+
77
+# Helper function to run command in both shells and compare
78
+compare_posix_output() {
79
+    test_name="$1"
80
+    command="$2"
81
+    posix_file="/tmp/posix_comp_$$_posix"
82
+    fortsh_file="/tmp/posix_comp_$$_fortsh"
83
+
84
+    # Run in POSIX shell (sh)
85
+    "$BASH_REF" -c "$command" > "$posix_file" 2>&1 || true
86
+
87
+    # Run in fortsh
88
+    "$FORTSH_BIN" -c "$command" > "$fortsh_file" 2>&1 || true
89
+
90
+    # Compare outputs
91
+    if diff -q "$posix_file" "$fortsh_file" > /dev/null 2>&1; then
92
+        pass "$test_name"
93
+    else
94
+        fail "$test_name" "$(cat "$posix_file")" "$(cat "$fortsh_file")"
95
+    fi
96
+
97
+    rm -f "$posix_file" "$fortsh_file"
98
+}
99
+
100
+# Helper function to compare exit codes
101
+compare_posix_exit_code() {
102
+    test_name="$1"
103
+    command="$2"
104
+
105
+    "$BASH_REF" -c "$command" > /dev/null 2>&1
106
+    posix_exit=$?
107
+
108
+    "$FORTSH_BIN" -c "$command" > /dev/null 2>&1
109
+    fortsh_exit=$?
110
+
111
+    if [ "$posix_exit" -eq "$fortsh_exit" ]; then
112
+        pass "$test_name"
113
+    else
114
+        fail "$test_name" "exit=$posix_exit" "exit=$fortsh_exit"
115
+    fi
116
+}
117
+
118
+# Cleanup
119
+cleanup() {
120
+    rm -f /tmp/posix_comp_$$_* 2>/dev/null
121
+    rm -f /tmp/posix_test_* 2>/dev/null
122
+}
123
+trap cleanup EXIT INT TERM
124
+
125
+section "1. POSIX BASIC COMMANDS"
126
+
127
+compare_posix_output "echo simple" "echo hello"
128
+compare_posix_output "echo with args" "echo one two three"
129
+compare_posix_output "printf basic" "printf 'test\n'"
130
+compare_posix_output "printf with args" "printf '%s %d\n' hello 42"
131
+
132
+section "2. POSIX VARIABLE EXPANSION"
133
+
134
+compare_posix_output "simple variable" "VAR=test; echo \$VAR"
135
+compare_posix_output "variable in quotes" 'VAR=test; echo "$VAR"'
136
+compare_posix_output "multiple vars" "A=hello; B=world; echo \$A \$B"
137
+compare_posix_output "undefined variable" "echo \$UNDEFINED_VAR_XYZ_987"
138
+
139
+section "3. POSIX PARAMETER EXPANSION"
140
+
141
+# Basic parameter expansion
142
+compare_posix_output "default value" 'echo "${UNSET:-default}"'
143
+compare_posix_output "assign default" 'UNSET=; echo "${UNSET:=assigned}"; echo $UNSET'
144
+compare_posix_output "error if unset" 'echo "${VAR:+alternative}"'
145
+compare_posix_output "string length" 'VAR=hello; echo "${#VAR}"'
146
+
147
+# Prefix removal (# and ##)
148
+compare_posix_output "remove shortest prefix" 'VAR=foo.bar.baz; echo "${VAR#*.}"'
149
+compare_posix_output "remove longest prefix" 'VAR=foo.bar.baz; echo "${VAR##*.}"'
150
+compare_posix_output "prefix no match" 'VAR=hello; echo "${VAR#x*}"'
151
+compare_posix_output "prefix remove slash" 'VAR=/usr/local/bin; echo "${VAR#/*/}"'
152
+
153
+# Suffix removal (% and %%)
154
+compare_posix_output "remove shortest suffix" 'VAR=foo.bar.baz; echo "${VAR%.*}"'
155
+compare_posix_output "remove longest suffix" 'VAR=foo.bar.baz; echo "${VAR%%.*}"'
156
+compare_posix_output "suffix no match" 'VAR=hello; echo "${VAR%x*}"'
157
+compare_posix_output "suffix remove extension" 'VAR=file.tar.gz; echo "${VAR%.gz}"'
158
+
159
+section "4. POSIX COMMAND SUBSTITUTION"
160
+
161
+compare_posix_output "backtick substitution" 'echo `echo test`'
162
+compare_posix_output "dollar paren substitution" "echo \$(echo test)"
163
+compare_posix_output "nested substitution" "echo \$(echo \$(echo nested))"
164
+
165
+section "5. POSIX ARITHMETIC"
166
+
167
+# POSIX arithmetic uses expr or $(( ))
168
+compare_posix_output "expr addition" "expr 5 + 3"
169
+compare_posix_output "expr multiplication" "expr 4 \* 3"
170
+compare_posix_output "expr division" "expr 15 / 3"
171
+
172
+section "6. POSIX REDIRECTION"
173
+
174
+compare_posix_output "output redirect" "echo test > /tmp/posix_test_out; cat /tmp/posix_test_out"
175
+compare_posix_output "append redirect" "echo line1 > /tmp/posix_test_app; echo line2 >> /tmp/posix_test_app; wc -l < /tmp/posix_test_app"
176
+compare_posix_output "input redirect" "echo input > /tmp/posix_test_in; cat < /tmp/posix_test_in"
177
+compare_posix_output "stderr redirect" "ls /nonexistent 2>&1 | grep -c 'cannot access\|No such\|not found'"
178
+
179
+section "7. POSIX PIPELINES"
180
+
181
+compare_posix_output "simple pipe" "echo hello | cat"
182
+compare_posix_output "two-stage pipe" "echo test | cat | tr t T"
183
+compare_posix_output "pipe with filter" "printf 'a\nb\nc\n' | grep b"
184
+
185
+section "8. POSIX TEST COMMAND"
186
+
187
+compare_posix_exit_code "test -f file" "touch /tmp/posix_test_file && test -f /tmp/posix_test_file"
188
+compare_posix_exit_code "test -d directory" "test -d /tmp"
189
+compare_posix_exit_code "test -n nonempty" "test -n 'hello'"
190
+compare_posix_exit_code "test -z empty" "test -z ''"
191
+compare_posix_exit_code "test string =" "test 'hello' = 'hello'"
192
+compare_posix_exit_code "test string !=" "test 'hello' != 'world'"
193
+compare_posix_exit_code "test number -eq" "test 5 -eq 5"
194
+compare_posix_exit_code "test number -ne" "test 5 -ne 3"
195
+compare_posix_exit_code "test number -gt" "test 5 -gt 3"
196
+compare_posix_exit_code "test number -ge" "test 5 -ge 5"
197
+compare_posix_exit_code "test number -lt" "test 3 -lt 5"
198
+compare_posix_exit_code "test number -le" "test 3 -le 3"
199
+
200
+section "9. POSIX CONDITIONALS"
201
+
202
+compare_posix_output "if true" "if true; then echo yes; fi"
203
+compare_posix_output "if false else" "if false; then echo no; else echo yes; fi"
204
+compare_posix_output "if-elif-else" "X=2; if [ \$X -eq 1 ]; then echo one; elif [ \$X -eq 2 ]; then echo two; else echo other; fi"
205
+
206
+section "10. POSIX LOOPS"
207
+
208
+compare_posix_output "for loop" "for i in a b c; do echo \$i; done"
209
+compare_posix_output "while loop" "i=3; while [ \$i -gt 0 ]; do echo \$i; i=\$((i - 1)); done"
210
+compare_posix_output "until loop" "i=1; until [ \$i -gt 3 ]; do echo \$i; i=\$((i + 1)); done"
211
+
212
+section "11. POSIX CASE STATEMENT"
213
+
214
+compare_posix_output "case exact match" "x=2; case \$x in 1) echo one;; 2) echo two;; esac"
215
+compare_posix_output "case pattern match" "x=hello; case \$x in h*) echo h_prefix;; esac"
216
+compare_posix_output "case default" "x=z; case \$x in a) echo a;; b) echo b;; *) echo default;; esac"
217
+compare_posix_output "case multiple patterns" "x=b; case \$x in a|b|c) echo abc;; *) echo other;; esac"
218
+
219
+section "12. POSIX FUNCTIONS"
220
+
221
+compare_posix_output "simple function" "func() { echo hello; }; func"
222
+compare_posix_output "function with args" "func() { echo \$1 \$2; }; func foo bar"
223
+compare_posix_output "function return" "func() { return 42; }; func; echo \$?"
224
+compare_posix_output "function \$# args" "func() { echo \$#; }; func a b c"
225
+
226
+section "13. POSIX SPECIAL VARIABLES"
227
+
228
+compare_posix_output "\$? exit status" "true; echo \$?"
229
+compare_posix_output "\$? after false" "false; echo \$?"
230
+compare_posix_output "\$# argument count" "set -- a b c; echo \$#"
231
+compare_posix_output "\$@ all arguments" "set -- a b c; echo \$@"
232
+compare_posix_output "\$* all arguments" "set -- a b c; echo \$*"
233
+compare_posix_output "\$0 script name" "echo \$0 | grep -c sh"
234
+
235
+section "14. POSIX LOGICAL OPERATORS"
236
+
237
+compare_posix_exit_code "true && true" "true && true"
238
+compare_posix_exit_code "true && false" "true && false"
239
+compare_posix_exit_code "false || true" "false || true"
240
+compare_posix_exit_code "false || false" "false || false"
241
+compare_posix_output "command && echo" "true && echo success"
242
+compare_posix_output "command || echo" "false || echo fallback"
243
+compare_posix_output "! negation" "! false && echo negated"
244
+
245
+section "15. POSIX QUOTING"
246
+
247
+compare_posix_output "single quote literal" "echo '\$VAR'"
248
+compare_posix_output "double quote expand" 'VAR=test; echo "$VAR"'
249
+compare_posix_output "escape in double" 'echo "test\$var"'
250
+compare_posix_output "backslash escape" 'echo test\ word'
251
+
252
+section "16. POSIX SUBSHELLS"
253
+
254
+compare_posix_output "subshell grouping" "(echo a; echo b) | wc -l"
255
+compare_posix_output "subshell var isolation" "(VAR=inner; echo \$VAR); echo \$VAR"
256
+
257
+section "17. POSIX COMPOUND COMMANDS"
258
+
259
+compare_posix_output "command grouping {}" "{ echo a; echo b; } | wc -l"
260
+compare_posix_output "command list ;" "echo a; echo b"
261
+
262
+section "18. POSIX HERE DOCUMENTS"
263
+
264
+compare_posix_output "simple heredoc" "cat <<EOF
265
+line1
266
+line2
267
+EOF"
268
+
269
+compare_posix_output "heredoc with vars" "VAR=test; cat <<EOF
270
+value=\$VAR
271
+EOF"
272
+
273
+compare_posix_output "quoted heredoc" "cat <<'EOF'
274
+\$VAR
275
+EOF"
276
+
277
+section "19. POSIX WORD EXPANSION ORDER"
278
+
279
+# POSIX specifies: tilde, parameter, command subst, arithmetic, field splitting, pathname, quote removal
280
+compare_posix_output "expansion order" "VAR='a b'; echo \$VAR"
281
+compare_posix_output "quoted expansion" 'VAR="a b"; echo "$VAR"'
282
+
283
+section "20. POSIX PATHNAME EXPANSION (GLOBBING)"
284
+
285
+# Setup test files
286
+mkdir -p /tmp/posix_test_glob
287
+touch /tmp/posix_test_glob/a.txt /tmp/posix_test_glob/b.txt /tmp/posix_test_glob/c.dat
288
+
289
+compare_posix_output "glob * pattern" "ls /tmp/posix_test_glob/*.txt 2>/dev/null | wc -l"
290
+compare_posix_output "glob ? pattern" "ls /tmp/posix_test_glob/?.txt 2>/dev/null | wc -l"
291
+compare_posix_output "glob [abc] pattern" "ls /tmp/posix_test_glob/[ab].txt 2>/dev/null | wc -l"
292
+
293
+section "21. POSIX FIELD SPLITTING (IFS)"
294
+
295
+compare_posix_output "default IFS" "VAR='a b c'; set -- \$VAR; echo \$#"
296
+compare_posix_output "custom IFS" "IFS=:; VAR='a:b:c'; set -- \$VAR; echo \$1"
297
+
298
+section "22. POSIX EXIT STATUS"
299
+
300
+compare_posix_exit_code "true exit status" "true"
301
+compare_posix_exit_code "false exit status" "false"
302
+compare_posix_exit_code "command not found" "nonexistent_command_xyz 2>/dev/null"
303
+compare_posix_exit_code "return from function" "func() { return 3; }; func"
304
+
305
+section "23. POSIX SET BUILTIN"
306
+
307
+compare_posix_output "set positional" "set -- a b c; echo \$1 \$2 \$3"
308
+compare_posix_output "set shift" "set -- a b c; shift; echo \$1"
309
+compare_posix_output "set shift n" "set -- a b c d; shift 2; echo \$1"
310
+
311
+section "24. POSIX EXPORT"
312
+
313
+compare_posix_output "export variable" "export VAR=test; sh -c 'echo \$VAR'"
314
+
315
+section "25. POSIX READONLY"
316
+
317
+compare_posix_exit_code "readonly assignment" "readonly VAR=test; VAR=new 2>/dev/null"
318
+
319
+section "26. POSIX UNSET"
320
+
321
+compare_posix_output "unset variable" "VAR=test; unset VAR; echo \${VAR:-empty}"
322
+compare_posix_output "unset nonexistent" "unset NONEXISTENT_VAR; echo ok"
323
+compare_posix_exit_code "unset readonly fails" "readonly X=1; unset X 2>/dev/null"
324
+compare_posix_output "unset function" "f() { echo hi; }; unset -f f; f 2>/dev/null || echo gone"
325
+
326
+section "27. POSIX EVAL"
327
+
328
+compare_posix_output "eval simple" "eval 'echo hello'"
329
+compare_posix_output "eval variable" "CMD='echo test'; eval \$CMD"
330
+compare_posix_output "eval assignment" "eval 'X=5'; echo \$X"
331
+compare_posix_output "eval command subst" "eval 'echo \$(echo nested)'"
332
+compare_posix_output "eval with semicolon" "eval 'echo a; echo b'"
333
+
334
+section "28. POSIX EXEC"
335
+
336
+compare_posix_output "exec replaces shell" "exec echo done"
337
+compare_posix_exit_code "exec nonexistent" "exec /nonexistent/command 2>/dev/null"
338
+
339
+section "29. POSIX COLON BUILTIN"
340
+
341
+compare_posix_output "colon no-op" ": ; echo ok"
342
+compare_posix_exit_code "colon exit status" ":"
343
+compare_posix_output "colon with args" ": arg1 arg2; echo ok"
344
+compare_posix_output "colon in if" "if :; then echo yes; fi"
345
+
346
+section "30. POSIX DOT/SOURCE"
347
+
348
+# Create temp script
349
+echo 'SOURCED_VAR=from_source' > /tmp/posix_test_source.sh
350
+compare_posix_output "dot source script" ". /tmp/posix_test_source.sh; echo \$SOURCED_VAR"
351
+compare_posix_exit_code "dot nonexistent" ". /nonexistent_file 2>/dev/null"
352
+
353
+section "31. POSIX CD AND PWD"
354
+
355
+compare_posix_output "cd and pwd" "cd /tmp && pwd"
356
+compare_posix_output "cd - returns to OLDPWD" "cd /tmp; cd /; cd -"
357
+compare_posix_exit_code "cd nonexistent" "cd /nonexistent_dir 2>/dev/null"
358
+compare_posix_output "pwd builtin" "pwd | grep -c /"
359
+
360
+section "32. POSIX UMASK"
361
+
362
+compare_posix_output "umask display" "umask | grep -E '^[0-9]{3,4}\$'"
363
+compare_posix_output "umask set and restore" "OLD=\$(umask); umask 077; umask \$OLD"
364
+
365
+section "33. POSIX WAIT"
366
+
367
+compare_posix_output "wait for background" "sleep 0.1 & wait; echo done"
368
+compare_posix_exit_code "wait no jobs" "wait"
369
+
370
+section "34. POSIX TIMES"
371
+
372
+# times is optional but common
373
+# times output contains variable timing values, so just verify format
374
+_times_out=$("$FORTSH_BIN" -c 'times' 2>/dev/null)
375
+if echo "$_times_out" | grep -qE '[0-9]+m[0-9]+\.[0-9]+s'; then
376
+    pass "times output exists"
377
+else
378
+    fail "times output exists" "NmN.NNNs NmN.NNNs" "$_times_out"
379
+fi
380
+
381
+section "35. POSIX BREAK AND CONTINUE"
382
+
383
+compare_posix_output "break in for" "for i in 1 2 3 4 5; do [ \$i -eq 3 ] && break; echo \$i; done"
384
+compare_posix_output "continue in for" "for i in 1 2 3 4 5; do [ \$i -eq 3 ] && continue; echo \$i; done"
385
+compare_posix_output "break in while" "i=0; while [ \$i -lt 10 ]; do i=\$((i+1)); [ \$i -eq 3 ] && break; echo \$i; done"
386
+compare_posix_output "break 2 nested" "for i in a b; do for j in 1 2 3; do [ \$j -eq 2 ] && break 2; echo \$i\$j; done; done"
387
+compare_posix_output "continue 2 nested" "for i in a b; do for j in 1 2 3; do [ \$j -eq 2 ] && continue 2; echo \$i\$j; done; done"
388
+
389
+section "36. POSIX SIGNAL HANDLING"
390
+
391
+# Note: trap output may vary by environment - test exit code
392
+compare_posix_exit_code "trap list" "trap >/dev/null 2>&1"
393
+compare_posix_output "trap on exit" "trap 'echo exiting' EXIT; exit 0"
394
+compare_posix_exit_code "trap reset" "trap - INT"
395
+
396
+section "37. POSIX BACKGROUND AND JOBS"
397
+
398
+compare_posix_output "background job" "sleep 0.1 & echo started; wait"
399
+# Note: $! returns different PIDs in each shell, so we check both output valid PIDs (exit code)
400
+compare_posix_exit_code "\$! last background pid" "sleep 0.1 & echo \$! | grep -qE '^[0-9]+\$'"
401
+
402
+section "38. POSIX ALIAS"
403
+
404
+compare_posix_output "alias definition" "alias ll='ls -l'; alias | grep ll"
405
+compare_posix_output "unalias" "alias x='echo test'; unalias x; alias | grep -c 'x=' || echo 0"
406
+
407
+section "39. POSIX COMMAND SEARCH"
408
+
409
+compare_posix_output "type builtin" "type echo | grep -c builtin"
410
+compare_posix_output "command -v" "command -v echo | grep -c echo"
411
+compare_posix_exit_code "command not found" "command -v nonexistent_xyz 2>/dev/null"
412
+
413
+section "40. POSIX COMPLEX EXPANSIONS"
414
+
415
+compare_posix_output "nested parameter expansion" 'A=hello; B=A; eval "echo \$$B"'
416
+compare_posix_output "expansion in assignment" 'X=$(echo test); echo $X'
417
+compare_posix_output "arithmetic in expansion" 'echo $((2 + 3 * 4))'
418
+compare_posix_output "multiple substitutions" 'A=1; B=2; echo $(echo $A) $(echo $B)'
419
+
420
+# Summary
421
+printf "\n"
422
+printf "==========================================\n"
423
+printf "POSIX COMPLIANCE TEST RESULTS ${TEST_PREFIX}\n"
424
+printf "==========================================\n"
425
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
426
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
427
+printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
428
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
429
+printf "==========================================\n"
430
+
431
+if [ $((PASSED + FAILED)) -gt 0 ]; then
432
+    PASS_RATE=$((PASSED * 100 / (PASSED + FAILED)))
433
+    printf "Pass rate: %d%%\n" "$PASS_RATE"
434
+fi
435
+
436
+if [ "$FAILED" -gt 0 ]; then
437
+    printf "\n${RED}Failed tests:${NC}\n"
438
+    printf "%b" "$FAILED_TESTS_LIST"
439
+    printf "==========================================\n"
440
+fi
441
+
442
+if [ "$FAILED" -eq 0 ]; then
443
+    printf "${GREEN}ALL POSIX COMPLIANCE TESTS PASSED!${NC} ✓\n"
444
+    exit 0
445
+else
446
+    printf "${RED}SOME TESTS FAILED${NC} ✗\n"
447
+    exit 1
448
+fi
suites/posix/posix_compliance_untested.shadded
@@ -0,0 +1,282 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance - Previously Untested Features
4
+# =====================================
5
+# Tests for POSIX features that are implemented but lack test coverage
6
+
7
+# Colors (POSIX-compliant way)
8
+RED='\033[0;31m'
9
+GREEN='\033[0;32m'
10
+YELLOW='\033[1;33m'
11
+BLUE='\033[0;34m'
12
+NC='\033[0m'
13
+
14
+# Test identification
15
+TEST_PREFIX="[posix-untested]"
16
+CURRENT_SECTION=""
17
+TEST_NUM=0
18
+
19
+PASSED=0
20
+FAILED=0
21
+SKIPPED=0
22
+FAILED_TESTS_LIST=""
23
+
24
+# Get script directory (POSIX way)
25
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
26
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
27
+BASH_REF="${BASH_REF:-bash}"
28
+
29
+# Check if fortsh exists
30
+if [ ! -x "$FORTSH_BIN" ]; then
31
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
32
+    printf "Please run 'make' first or set FORTSH_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 "  posix:         %s\n" "$2"
50
+    fi
51
+    if [ -n "$3" ]; then
52
+        printf "  fortsh:        %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\n" "$1"
60
+    SKIPPED=$((SKIPPED + 1))
61
+}
62
+
63
+section() {
64
+    # Extract section number from header like "131. COMMAND BUILTIN"
65
+    CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
66
+    TEST_NUM=0
67
+    printf "\n${BLUE}==========================================\n"
68
+    printf "%s\n" "$1"
69
+    printf "==========================================${NC}\n"
70
+}
71
+
72
+# Compare output between sh and fortsh
73
+compare_posix_output() {
74
+    test_name="$1"
75
+    test_cmd="$2"
76
+
77
+    # Run with POSIX sh
78
+    posix_output=$(FORTSH_RC_FILE=/dev/null "$BASH_REF" -c "$test_cmd" 2>&1)
79
+    posix_exit=$?
80
+
81
+    # Run with fortsh
82
+    fortsh_output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1)
83
+    fortsh_exit=$?
84
+
85
+    # Compare outputs
86
+    if [ "$posix_output" = "$fortsh_output" ] && [ "$posix_exit" = "$fortsh_exit" ]; then
87
+        pass "$test_name"
88
+    else
89
+        fail "$test_name" "$posix_output" "$fortsh_output"
90
+    fi
91
+}
92
+
93
+# Normalize shell error messages by stripping shell name and "line N: " prefix
94
+# POSIX doesn't mandate error message format, so we normalize for comparison
95
+normalize_error() {
96
+    echo "$1" | sed -e 's|^[^ ]*bash: |sh: |' -e 's|^[^ ]*fortsh: |sh: |' -e 's/line [0-9]*: //'
97
+}
98
+
99
+# Compare error output, normalizing line number differences
100
+compare_posix_error() {
101
+    test_name="$1"
102
+    test_cmd="$2"
103
+
104
+    posix_output=$(FORTSH_RC_FILE=/dev/null "$BASH_REF" -c "$test_cmd" 2>&1)
105
+    posix_exit=$?
106
+
107
+    fortsh_output=$(FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" 2>&1)
108
+    fortsh_exit=$?
109
+
110
+    posix_norm=$(normalize_error "$posix_output")
111
+    fortsh_norm=$(normalize_error "$fortsh_output")
112
+
113
+    if [ "$posix_norm" = "$fortsh_norm" ] && [ "$posix_exit" = "$fortsh_exit" ]; then
114
+        pass "$test_name"
115
+    else
116
+        fail "$test_name" "$posix_output" "$fortsh_output"
117
+    fi
118
+}
119
+
120
+# Test that fortsh accepts an option/command without error
121
+test_accepts() {
122
+    test_name="$1"
123
+    test_cmd="$2"
124
+
125
+    if FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" >/dev/null 2>&1; then
126
+        pass "$test_name"
127
+    else
128
+        fail "$test_name" "should succeed" "failed or not implemented"
129
+    fi
130
+}
131
+
132
+# Test that command fails as expected
133
+test_fails() {
134
+    test_name="$1"
135
+    test_cmd="$2"
136
+
137
+    if ! FORTSH_RC_FILE=/dev/null "$FORTSH_BIN" -c "$test_cmd" >/dev/null 2>&1; then
138
+        pass "$test_name"
139
+    else
140
+        fail "$test_name" "should fail" "succeeded unexpectedly"
141
+    fi
142
+}
143
+
144
+# =====================================
145
+# TESTS START HERE
146
+# =====================================
147
+
148
+section "131. COMMAND BUILTIN - PATH SEARCH"
149
+
150
+compare_posix_output "command -v ls" 'command -v ls >/dev/null && echo found'
151
+compare_posix_output "command -v nonexistent" 'command -v nonexistent >/dev/null || echo notfound'
152
+test_accepts "command -p ls" 'command -p ls / >/dev/null'
153
+compare_posix_output "command without options" 'command echo test'
154
+
155
+section "132. MULTI-FD REDIRECTIONS"
156
+
157
+# Create test file
158
+compare_posix_output "fd 3 redirect write" 'exec 3>/tmp/posix_fd3.txt; echo test >&3; exec 3>&-; cat /tmp/posix_fd3.txt; rm /tmp/posix_fd3.txt'
159
+compare_posix_output "fd 4 redirect read" 'echo data > /tmp/posix_fd4.txt; exec 4</tmp/posix_fd4.txt; read line <&4; echo $line; exec 4<&-; rm /tmp/posix_fd4.txt'
160
+compare_posix_output "fd duplication" 'exec 5>&1; echo stdout >&5; exec 5>&-'
161
+compare_posix_output "fd 3 and 4 together" 'exec 3>/tmp/posix_3.txt 4>/tmp/posix_4.txt; echo a >&3; echo b >&4; exec 3>&- 4>&-; cat /tmp/posix_3.txt /tmp/posix_4.txt; rm /tmp/posix_3.txt /tmp/posix_4.txt'
162
+
163
+section "133. EXEC WITH SHELL REDIRECTIONS"
164
+
165
+# Note: These redirect the shell itself, not just one command
166
+# Save stdout to FD 3, redirect, write, restore, then cat (otherwise cat hangs on file still open for writing)
167
+compare_posix_output "exec redirect stdout" 'exec 3>&1; exec >/tmp/posix_exec_out.txt; echo redirected; exec >&3; cat /tmp/posix_exec_out.txt; rm /tmp/posix_exec_out.txt'
168
+compare_posix_output "exec redirect stdin" 'echo testdata > /tmp/posix_exec_in.txt; exec </tmp/posix_exec_in.txt; read data; echo $data; rm /tmp/posix_exec_in.txt'
169
+
170
+section "134. SET OPTION INTERACTIONS"
171
+
172
+compare_posix_output "set -e with true" 'set -e; true; echo ok'
173
+compare_posix_output "set -u with unset var" 'set -u; echo ${UNSET_VAR:-default}'
174
+compare_posix_output "set -C noclobber" 'set -C; echo test > /tmp/posix_clobber.txt; echo ok; rm /tmp/posix_clobber.txt'
175
+compare_posix_output "set -C override with >|" 'set -C; echo test > /tmp/posix_clobber2.txt; echo override >| /tmp/posix_clobber2.txt; cat /tmp/posix_clobber2.txt; rm /tmp/posix_clobber2.txt'
176
+
177
+section "135. SPECIAL BUILTIN ERROR HANDLING"
178
+
179
+# POSIX: Special builtins must exit non-interactive shell on error
180
+# Testing with subshells to avoid killing the test script
181
+
182
+compare_posix_error "readonly error exits" '(readonly VAR=1; VAR=2 2>/dev/null; echo should not print) || echo exited'
183
+compare_posix_output "export invalid" '(export 123INVALID=value 2>/dev/null; echo should not print) || echo exited'
184
+compare_posix_output "set invalid option" '(set -@ 2>/dev/null; echo should not print) || echo exited'
185
+
186
+section "136. ULIMIT TESTS"
187
+
188
+test_accepts "ulimit display" 'ulimit'
189
+test_accepts "ulimit -a all" 'ulimit -a >/dev/null'
190
+test_accepts "ulimit -n files" 'ulimit -n >/dev/null'
191
+test_accepts "ulimit -s stack" 'ulimit -s >/dev/null 2>&1'
192
+
193
+section "137. NESTED PARAMETER EXPANSION"
194
+
195
+compare_posix_output "nested default" 'unset A B; echo ${A:-${B:-default}}'
196
+compare_posix_output "nested with set var" 'A=inner; unset B; echo ${B:-${A}}'
197
+compare_posix_output "double nested" 'unset A B C; echo ${A:-${B:-${C:-triple}}}'
198
+
199
+section "138. PARAMETER LENGTH EDGE CASES"
200
+
201
+compare_posix_output "length of $@" 'set -- a b c; echo ${#@}'
202
+compare_posix_output "length of $*" 'set -- a b c; echo ${#*}'
203
+compare_posix_output "length of empty" 'VAR=; echo ${#VAR}'
204
+compare_posix_output "length of unset" 'unset VAR; echo ${#VAR}'
205
+
206
+section "139. QUOTING EDGE CASES"
207
+
208
+compare_posix_output "empty double quotes" 'VAR=""; echo x${VAR}y'
209
+compare_posix_output "empty single quotes" "VAR=''; echo x\${VAR}y"
210
+compare_posix_output "adjacent quotes concat" 'echo "a"b"c"'
211
+compare_posix_output "quote in quote" 'echo "it'\''s"'
212
+
213
+section "140. BACKSLASH NEWLINE CONTINUATION"
214
+
215
+compare_posix_output "line continuation in string" 'echo "test\
216
+continuation"'
217
+compare_posix_output "line continuation in command" 'ec\
218
+ho test'
219
+
220
+section "141. COMMENT IN COMMAND SUBSTITUTION"
221
+
222
+compare_posix_output "comment in subshell" '$(# this is a comment
223
+echo test)'
224
+compare_posix_output "comment in backtick" '`# comment
225
+echo test`'
226
+
227
+section "142. REDIRECTION EDGE CASES"
228
+
229
+compare_posix_output "close stdin" 'cat <&- 2>&1 | head -1'
230
+compare_posix_error "close stdout" 'echo test >&- 2>&1'
231
+compare_posix_output "read/write mode" 'echo data > /tmp/posix_rw.txt; cat <> /tmp/posix_rw.txt; rm /tmp/posix_rw.txt'
232
+
233
+section "143. ERROR HANDLING EDGE CASES"
234
+
235
+test_fails "assign to positional param" '$1=value 2>/dev/null'
236
+test_fails "readonly reassign" 'readonly VAR=1; VAR=2 2>/dev/null'
237
+# Test that execution continues after arithmetic error
238
+# Check that "continued" appears in the output (error format may vary)
239
+test_accepts "division by zero" 'echo $((5/0)) | cat; echo continued 2>&1 | grep -q continued'
240
+
241
+section "144. SET -n (NOEXEC) TESTING"
242
+
243
+# This tests if set -n is implemented
244
+compare_posix_output "set -n parse only" 'set -n; echo "should not execute"; false' || skip "set -n not implemented"
245
+
246
+section "145. SET -m (MONITOR) TESTING"
247
+
248
+test_accepts "set -m monitor mode" 'set -m; set +m'
249
+compare_posix_output "set -m doesn't affect output" 'set -m; echo test; set +m'
250
+
251
+# =====================================
252
+# SUMMARY
253
+# =====================================
254
+
255
+printf "\n==========================================\n"
256
+printf "UNTESTED FEATURES TEST RESULTS ${TEST_PREFIX}\n"
257
+printf "==========================================\n"
258
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
259
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
260
+printf "${YELLOW}Skipped:${NC} %d\n" "$SKIPPED"
261
+printf "Total:   %d\n" "$((PASSED + FAILED + SKIPPED))"
262
+printf "==========================================\n"
263
+
264
+# Calculate pass rate
265
+if [ "$((PASSED + FAILED))" -gt 0 ]; then
266
+    pass_rate=$((PASSED * 100 / (PASSED + FAILED)))
267
+    printf "Pass rate: %d%%\n" "$pass_rate"
268
+fi
269
+
270
+if [ "$FAILED" -gt 0 ]; then
271
+    printf "\n${RED}Failed tests:${NC}\n"
272
+    printf "%b" "$FAILED_TESTS_LIST"
273
+    printf "==========================================\n"
274
+fi
275
+
276
+if [ "$FAILED" -eq 0 ]; then
277
+    printf "${GREEN}ALL UNTESTED FEATURES TESTS PASSED!${NC} ✓\n"
278
+    exit 0
279
+else
280
+    printf "${RED}SOME TESTS FAILED${NC} ✗\n"
281
+    exit 1
282
+fi
suites/posix/posix_gaps_arithmetic.shadded
@@ -0,0 +1,169 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Arithmetic Gap Tests
4
+# =====================================
5
+# Tests for POSIX arithmetic expansion
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-arithmetic]"
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|^[^ ]*fortsh: |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
+compare_posix_exit_code() {
70
+    test_name="$1"; command="$2"
71
+    "$BASH_REF" -c "$command" >/dev/null 2>&1; posix_code=$?
72
+    "$FORTSH_BIN" -c "$command" >/dev/null 2>&1; fortsh_code=$?
73
+    if [ "$posix_code" = "$fortsh_code" ]; then pass "$test_name"
74
+    else fail "$test_name" "exit $posix_code" "exit $fortsh_code"; fi
75
+}
76
+
77
+# ============================================================================
78
+# ARITHMETIC TESTS
79
+# ============================================================================
80
+
81
+section "1. BASIC OPERATORS"
82
+compare_posix_output "add" 'echo $((5+3))'
83
+compare_posix_output "sub" 'echo $((5-3))'
84
+compare_posix_output "mul" 'echo $((5*3))'
85
+compare_posix_output "div" 'echo $((15/3))'
86
+compare_posix_output "mod" 'echo $((17%5))'
87
+compare_posix_output "neg" 'echo $((-5))'
88
+
89
+section "2. SPACING"
90
+compare_posix_output "with spaces" 'echo $(( 1 + 2 ))'
91
+compare_posix_output "mixed spacing" 'echo $(( 5 +3 ))'
92
+
93
+section "3. COMPARISONS"
94
+compare_posix_output "lt" 'echo $((3<5))'
95
+compare_posix_output "gt" 'echo $((5>3))'
96
+compare_posix_output "le" 'echo $((5<=5))'
97
+compare_posix_output "ge" 'echo $((5>=5))'
98
+compare_posix_output "eq" 'echo $((5==5))'
99
+compare_posix_output "ne" 'echo $((5!=3))'
100
+
101
+section "4. LOGICAL"
102
+compare_posix_output "and" 'echo $((1&&1))'
103
+compare_posix_output "or" 'echo $((0||1))'
104
+compare_posix_output "not" 'echo $((!0))'
105
+
106
+section "5. BITWISE"
107
+compare_posix_output "band" 'echo $((12&10))'
108
+compare_posix_output "bor" 'echo $((12|10))'
109
+compare_posix_output "bxor" 'echo $((12^10))'
110
+compare_posix_output "lshift" 'echo $((1<<4))'
111
+compare_posix_output "rshift" 'echo $((16>>2))'
112
+
113
+section "6. TERNARY"
114
+compare_posix_output "ternary t" 'echo $((1?10:20))'
115
+compare_posix_output "ternary f" 'echo $((0?10:20))'
116
+compare_posix_output "ternary expr" 'echo $((5>3?1:0))'
117
+
118
+section "7. ASSIGNMENT OPS"
119
+compare_posix_output "pluseq" 'x=5; echo $((x+=3))'
120
+compare_posix_output "minuseq" 'x=5; echo $((x-=3))'
121
+compare_posix_output "muleq" 'x=5; echo $((x*=3))'
122
+compare_posix_output "diveq" 'x=15; echo $((x/=3))'
123
+
124
+section "8. PRECEDENCE"
125
+compare_posix_output "mul before add" 'echo $((2+3*4))'
126
+compare_posix_output "parens override" 'echo $(((2+3)*4))'
127
+compare_posix_output "div add" 'echo $((10/2+3))'
128
+compare_posix_output "nested parens" 'echo $(((2 + 3) * (4 + 5)))'
129
+
130
+section "9. VARIABLES"
131
+compare_posix_output "var simple" 'x=5; echo $((x))'
132
+compare_posix_output "var expr" 'a=3; b=4; echo $((a*a+b*b))'
133
+compare_posix_output "var unset" 'unset z; echo $((z))'
134
+compare_posix_output "var ref" 'X=10; echo $((X + 5))'
135
+
136
+section "10. NUMBER BASES"
137
+compare_posix_output "octal" 'echo $((010))'
138
+compare_posix_output "hex" 'echo $((0x10))'
139
+compare_posix_output "hex lowercase" 'echo $((0xa))'
140
+
141
+section "11. INCREMENT/DECREMENT"
142
+compare_posix_output "preinc" 'x=5; echo $((++x))'
143
+compare_posix_output "predec" 'x=5; echo $((--x))'
144
+compare_posix_output "postinc" 'x=5; echo $((x++))'
145
+compare_posix_output "postdec" 'x=5; echo $((x--))'
146
+
147
+section "12. EDGE CASES"
148
+compare_posix_output "negative numbers" "echo \$((-5 * -3))"
149
+compare_posix_output "large numbers" "echo \$((999999 + 1))"
150
+compare_posix_output "modulo negative" "echo \$((-17 % 5))"
151
+compare_posix_output "comparison chain" "echo \$((5 > 3 && 10 > 8))"
152
+compare_posix_output "unary minus" "X=5; echo \$((-X))"
153
+compare_posix_output "unary plus" "X=5; echo \$((+X))"
154
+compare_posix_output "zero" 'echo $((0))'
155
+compare_posix_output "nested arith" 'echo $(($((1 + 2)) + 3))'
156
+compare_posix_exit_code "division by zero" "echo \$((5 / 0)) 2>/dev/null"
157
+
158
+# Summary
159
+printf "\n==========================================\n"
160
+printf "ARITHMETIC GAP TEST RESULTS\n"
161
+printf "==========================================\n"
162
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
163
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
164
+printf "Total:   %d\n" "$((PASSED + FAILED))"
165
+if [ "$FAILED" -gt 0 ]; then
166
+    printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
167
+    exit 1
168
+fi
169
+exit 0
suites/posix/posix_gaps_builtins.shadded
@@ -0,0 +1,351 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Builtin Gap Tests
4
+# =====================================
5
+# Tests for POSIX shell builtins
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-builtins]"
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|^[^ ]*fortsh: |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
+compare_posix_exit_code() {
70
+    test_name="$1"; command="$2"
71
+    "$BASH_REF" -c "$command" >/dev/null 2>&1; posix_code=$?
72
+    "$FORTSH_BIN" -c "$command" >/dev/null 2>&1; fortsh_code=$?
73
+    if [ "$posix_code" = "$fortsh_code" ]; then pass "$test_name"
74
+    else fail "$test_name" "exit $posix_code" "exit $fortsh_code"; fi
75
+}
76
+
77
+compare_posix_error() {
78
+    test_name="$1"; command="$2"
79
+    posix_out=$("$BASH_REF" -c "$command" 2>&1 | normalize_output)
80
+    fortsh_out=$("$FORTSH_BIN" -c "$command" 2>&1 | normalize_output)
81
+    if [ "$posix_out" = "$fortsh_out" ]; then pass "$test_name"
82
+    else fail "$test_name" "$posix_out" "$fortsh_out"; fi
83
+}
84
+
85
+# ============================================================================
86
+# CD BUILTIN
87
+# ============================================================================
88
+
89
+section "1. CD BUILTIN"
90
+compare_posix_output "cd tmp" 'cd /tmp; pwd'
91
+compare_posix_output "cd home" 'cd ~; pwd | grep -c /'
92
+compare_posix_output "cd dash" 'cd /tmp; cd /; cd -'
93
+compare_posix_output "cd dotdot" 'cd /tmp; cd ..; pwd'
94
+
95
+# ============================================================================
96
+# PWD BUILTIN
97
+# ============================================================================
98
+
99
+section "2. PWD BUILTIN"
100
+compare_posix_output "pwd basic" 'pwd | grep -c /'
101
+compare_posix_output "pwd P" 'pwd -P | grep -c /'
102
+compare_posix_output "pwd L" 'pwd -L | grep -c /'
103
+
104
+# ============================================================================
105
+# ECHO BUILTIN
106
+# ============================================================================
107
+
108
+section "3. ECHO BUILTIN"
109
+compare_posix_output "echo basic" 'echo hello'
110
+compare_posix_output "echo multi" 'echo hello world'
111
+compare_posix_output "echo empty" 'echo ""'
112
+compare_posix_output "echo n" 'echo -n hello; echo world'
113
+compare_posix_output "echo special" 'echo "a\tb"'
114
+
115
+# ============================================================================
116
+# PRINTF BUILTIN
117
+# ============================================================================
118
+
119
+section "4. PRINTF BUILTIN"
120
+compare_posix_output "printf s" 'printf "%s\n" hello'
121
+compare_posix_output "printf d" 'printf "%d\n" 42'
122
+compare_posix_output "printf x" 'printf "%x\n" 255'
123
+compare_posix_output "printf o" 'printf "%o\n" 8'
124
+compare_posix_output "printf width" 'printf "%5d\n" 42'
125
+compare_posix_output "printf left" 'printf "%-5d|\n" 42'
126
+compare_posix_output "printf zero" 'printf "%05d\n" 42'
127
+
128
+# ============================================================================
129
+# TEST BUILTIN
130
+# ============================================================================
131
+
132
+section "5. TEST NUMERIC OPERATORS"
133
+compare_posix_output "test eq" '[ 5 -eq 5 ]; echo $?'
134
+compare_posix_output "test ne" '[ 5 -ne 3 ]; echo $?'
135
+compare_posix_output "test lt" '[ 3 -lt 5 ]; echo $?'
136
+compare_posix_output "test gt" '[ 5 -gt 3 ]; echo $?'
137
+compare_posix_output "test le" '[ 5 -le 5 ]; echo $?'
138
+compare_posix_output "test ge" '[ 5 -ge 5 ]; echo $?'
139
+
140
+section "6. TEST STRING OPERATORS"
141
+compare_posix_output "test z empty" '[ -z "" ]; echo $?'
142
+compare_posix_output "test z nonempty" '[ -z "x" ]; echo $?'
143
+compare_posix_output "test n empty" '[ -n "" ]; echo $?'
144
+compare_posix_output "test n nonempty" '[ -n "x" ]; echo $?'
145
+compare_posix_output "test str eq" '[ "a" = "a" ]; echo $?'
146
+compare_posix_output "test str ne" '[ "a" != "b" ]; echo $?'
147
+
148
+section "7. TEST FILE OPERATORS"
149
+compare_posix_output "test f" '[ -f /etc/passwd ]; echo $?'
150
+compare_posix_output "test d" '[ -d /tmp ]; echo $?'
151
+compare_posix_output "test e" '[ -e /tmp ]; echo $?'
152
+compare_posix_output "test r" '[ -r /etc/passwd ]; echo $?'
153
+compare_posix_output "test x" '[ -x /bin/sh ]; echo $?'
154
+compare_posix_output "test s" 'echo x > /tmp/ts$$; [ -s /tmp/ts$$ ]; echo $?; rm /tmp/ts$$'
155
+
156
+section "8. TEST COMPOUND"
157
+compare_posix_output "test and" '[ -d /tmp -a -f /etc/passwd ]; echo $?'
158
+compare_posix_output "test or" '[ -d /nonexistent -o -f /etc/passwd ]; echo $?'
159
+compare_posix_output "test not" '[ ! -d /nonexistent ]; echo $?'
160
+compare_posix_output "test paren" '[ \( -d /tmp \) ]; echo $?'
161
+
162
+# ============================================================================
163
+# TYPE AND COMMAND BUILTINS
164
+# ============================================================================
165
+
166
+section "9. TYPE BUILTIN"
167
+compare_posix_output "type builtin" "type echo | grep -ci 'builtin\\|built-in\\|shell builtin'"
168
+compare_posix_output "type function" "f() { :; }; type f | grep -c function"
169
+compare_posix_output "type external" "type cat | grep -c '/'"
170
+compare_posix_exit_code "type nonexistent" "type nonexistent_$$ 2>/dev/null"
171
+
172
+section "10. COMMAND BUILTIN"
173
+compare_posix_output "command v" 'command -v echo | grep -c echo'
174
+compare_posix_output "command V" 'command -V echo 2>/dev/null | grep -c echo'
175
+compare_posix_output "command p" 'command -p echo test'
176
+
177
+# ============================================================================
178
+# SET BUILTIN
179
+# ============================================================================
180
+
181
+section "11. SET BUILTIN"
182
+compare_posix_output "set -- clears positionals" "set -- a b; set --; echo \$#"
183
+compare_posix_error "set -- with empty" "set -- ''; echo \$# |\$1|"
184
+compare_posix_output "set -- with spaces" "set -- 'a b' 'c d'; echo \$1"
185
+compare_posix_output "set without args shows vars" "X=1; set | grep -c '^X='"
186
+compare_posix_output "set -o lists options" "set -o 2>&1 | wc -l"
187
+compare_posix_output "set args" 'set -- a b c; echo $1 $2 $3'
188
+compare_posix_output "set count" 'set -- a b c d e; echo $#'
189
+compare_posix_output "set all" 'set -- x y z; echo "$@"'
190
+compare_posix_output "set star" 'set -- x y z; echo "$*"'
191
+
192
+# ============================================================================
193
+# SHIFT BUILTIN
194
+# ============================================================================
195
+
196
+section "12. SHIFT BUILTIN"
197
+compare_posix_output "shift basic" 'set -- a b c; shift; echo $1'
198
+compare_posix_output "shift count" 'set -- a b c; shift; echo $#'
199
+compare_posix_output "shift 2" 'set -- a b c d; shift 2; echo $1'
200
+compare_posix_output "shift with count" "set -- a b c d e; shift 3; echo \$1"
201
+compare_posix_exit_code "shift too many" "set -- a b; shift 5 2>/dev/null"
202
+compare_posix_output "shift zero" "set -- a b c; shift 0; echo \$#"
203
+compare_posix_output "shift all" "set -- a b c; shift 3; echo \$#"
204
+compare_posix_exit_code "shift with no args" "set --; shift 2>/dev/null"
205
+
206
+# ============================================================================
207
+# EVAL BUILTIN
208
+# ============================================================================
209
+
210
+section "13. EVAL BUILTIN"
211
+compare_posix_output "eval with semicolons" "eval 'echo a; echo b' | wc -l"
212
+compare_posix_output "eval with pipes" "eval 'echo test | cat'"
213
+compare_posix_output "eval with redirects" "eval 'echo test > /tmp/posix_gaps_eval_$$'; cat /tmp/posix_gaps_eval_$$; rm -f /tmp/posix_gaps_eval_$$"
214
+compare_posix_output "eval double expansion" "VAR='echo \$HOME'; eval \$VAR | grep -c /"
215
+compare_posix_output "eval empty string" "eval ''; echo ok"
216
+compare_posix_output "nested eval" "eval eval echo nested"
217
+
218
+# ============================================================================
219
+# READONLY AND UNSET
220
+# ============================================================================
221
+
222
+section "14. READONLY"
223
+compare_posix_exit_code "readonly then unset fails" "readonly X=1; unset X 2>/dev/null"
224
+compare_posix_exit_code "export readonly" "readonly X=1; export X; sh -c 'echo \$X' | grep -c 1"
225
+compare_posix_output "readonly in subshell" "(readonly Y=2; echo \$Y); readonly | grep -c Y || echo 0"
226
+compare_posix_output "readonly basic" 'readonly Y=5; echo $Y'
227
+compare_posix_output "readonly list" 'readonly | grep -c .'
228
+
229
+section "15. UNSET"
230
+compare_posix_output "unset var" 'x=5; unset x; echo ${x:-unset}'
231
+compare_posix_output "unset func" 'f() { echo f; }; unset -f f; f 2>/dev/null || echo unset'
232
+compare_posix_output "unset v flag" 'x=5; unset -v x; echo ${x:-unset}'
233
+
234
+# ============================================================================
235
+# EXPORT
236
+# ============================================================================
237
+
238
+section "16. EXPORT"
239
+compare_posix_output "export basic" 'export X=5; sh -c "echo \$X"'
240
+compare_posix_output "export list" 'export | grep -c ='
241
+
242
+# ============================================================================
243
+# RETURN AND DOT
244
+# ============================================================================
245
+
246
+section "17. RETURN BUILTIN"
247
+compare_posix_output "return without function" "return 2>/dev/null || echo ok"
248
+compare_posix_output "return value preserved" "f() { return 42; }; f; echo \$?"
249
+compare_posix_output "return in sourced script" "echo 'return 7' > /tmp/posix_gaps_source_$$; . /tmp/posix_gaps_source_$$ 2>/dev/null || echo \$?; rm -f /tmp/posix_gaps_source_$$"
250
+
251
+section "18. DOT BUILTIN"
252
+compare_posix_output "source with PATH search" "echo 'echo sourced' > /tmp/posix_gaps_dot_$$; PATH=/tmp:\$PATH; . posix_gaps_dot_$$ 2>/dev/null || echo 'not found'; rm -f /tmp/posix_gaps_dot_$$"
253
+compare_posix_exit_code "source nonexistent" ". /tmp/posix_gaps_nonexistent_$$ 2>/dev/null"
254
+compare_posix_output "source preserves variables" "echo 'A=from_source' > /tmp/posix_gaps_dot2_$$; . /tmp/posix_gaps_dot2_$$; echo \$A; rm -f /tmp/posix_gaps_dot2_$$"
255
+
256
+# ============================================================================
257
+# GETOPTS
258
+# ============================================================================
259
+
260
+section "19. GETOPTS"
261
+compare_posix_output "getopts basic" "set -- -a test; getopts 'a:' opt; echo \$opt"
262
+compare_posix_output "getopts OPTARG" "set -- -a value; getopts 'a:' opt; echo \$OPTARG"
263
+compare_posix_output "getopts OPTIND" "set -- -a -b; getopts 'ab' opt; echo \$OPTIND"
264
+compare_posix_output "getopts invalid option" "set -- -z; getopts 'ab' opt 2>/dev/null; echo \$opt | grep -c '?'"
265
+
266
+# ============================================================================
267
+# UMASK
268
+# ============================================================================
269
+
270
+section "20. UMASK"
271
+compare_posix_output "umask get" "umask | grep -c '^[0-9]*\$'"
272
+compare_posix_output "umask set and get" "old=\$(umask); umask 022; umask; umask \$old | head -1"
273
+
274
+# ============================================================================
275
+# HASH
276
+# ============================================================================
277
+
278
+section "21. HASH"
279
+compare_posix_exit_code "hash command" "hash echo 2>/dev/null"
280
+compare_posix_exit_code "hash -r clears" "hash -r"
281
+compare_posix_exit_code "hash nonexistent" "hash nonexistent_cmd_$$ 2>/dev/null"
282
+
283
+# ============================================================================
284
+# TIMES
285
+# ============================================================================
286
+
287
+section "22. TIMES"
288
+compare_posix_output "times output format" "times | wc -l"
289
+compare_posix_exit_code "times exit status" "times >/dev/null"
290
+
291
+# ============================================================================
292
+# TRAP
293
+# ============================================================================
294
+
295
+section "23. TRAP"
296
+compare_posix_output "trap with signal number" "trap 'echo sig' 15; trap | grep -c 15"
297
+compare_posix_output "trap with multiple signals" "trap 'echo multi' INT TERM; trap | grep -c 'echo multi'"
298
+compare_posix_output "trap ignore signal" "trap '' INT; trap | grep INT | grep -c ''"
299
+
300
+# ============================================================================
301
+# EXIT
302
+# ============================================================================
303
+
304
+section "24. EXIT"
305
+compare_posix_exit_code "exit with status" "sh -c 'exit 42'"
306
+compare_posix_exit_code "exit in subshell" "(exit 7); echo \$?"
307
+
308
+# ============================================================================
309
+# VARIABLE OPERATIONS
310
+# ============================================================================
311
+
312
+section "25. VARIABLE ASSIGNMENT"
313
+compare_posix_output "var simple" 'x=5; echo $x'
314
+compare_posix_output "var empty" 'x=; echo "[$x]"'
315
+compare_posix_output "var quoted" 'x="a b"; echo "$x"'
316
+compare_posix_output "var concat" 'x=hel; y=lo; echo $x$y'
317
+compare_posix_output "var braces" 'x=val; echo ${x}'
318
+
319
+section "26. FUNCTION SCOPE"
320
+compare_posix_output "func global" 'x=global; f() { x=func; }; f; echo $x'
321
+compare_posix_output "func params" 'f() { echo $# $1 $2; }; f a b c'
322
+compare_posix_output "func shift" 'f() { shift; echo $1; }; f a b c'
323
+
324
+# ============================================================================
325
+# MISCELLANEOUS
326
+# ============================================================================
327
+
328
+section "27. MISCELLANEOUS EDGE CASES"
329
+compare_posix_output "empty command in list" ": ; echo ok"
330
+compare_posix_output "whitespace only" "   ; echo ok"
331
+compare_posix_output "multiple empty commands" ": ; : ; echo ok"
332
+compare_posix_output "empty string as command" "'' 2>/dev/null || echo ok"
333
+
334
+section "28. PIPELINES"
335
+compare_posix_output "five stage pipeline" "echo test | cat | cat | cat | cat"
336
+compare_posix_exit_code "pipeline with negation" "! false | false"
337
+compare_posix_output "pipeline with subshell" "(echo a; echo b) | wc -l"
338
+compare_posix_output "pipeline with brace group" "{ echo x; echo y; } | wc -l"
339
+
340
+# Summary
341
+printf "\n==========================================\n"
342
+printf "BUILTINS GAP TEST RESULTS\n"
343
+printf "==========================================\n"
344
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
345
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
346
+printf "Total:   %d\n" "$((PASSED + FAILED))"
347
+if [ "$FAILED" -gt 0 ]; then
348
+    printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
349
+    exit 1
350
+fi
351
+exit 0
suites/posix/posix_gaps_charclass.shadded
@@ -0,0 +1,162 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Character Class Gap Tests
4
+# =====================================
5
+# Tests for POSIX character classes in bracket expressions
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-charclass]"
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|^[^ ]*fortsh: |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
+# CHARACTER CLASS TESTS
71
+# ============================================================================
72
+
73
+section "1. CHARACTER CLASS ALNUM"
74
+compare_posix_output "alnum a" 'case a in [[:alnum:]]) echo yes;; *) echo no;; esac'
75
+compare_posix_output "alnum Z" 'case Z in [[:alnum:]]) echo yes;; *) echo no;; esac'
76
+compare_posix_output "alnum 5" 'case 5 in [[:alnum:]]) echo yes;; *) echo no;; esac'
77
+compare_posix_output "alnum excl" 'case "!" in [[:alnum:]]) echo yes;; *) echo no;; esac'
78
+
79
+section "2. CHARACTER CLASS ALPHA"
80
+compare_posix_output "alpha a" 'case a in [[:alpha:]]) echo yes;; *) echo no;; esac'
81
+compare_posix_output "alpha Z" 'case Z in [[:alpha:]]) echo yes;; *) echo no;; esac'
82
+compare_posix_output "alpha 5" 'case 5 in [[:alpha:]]) echo yes;; *) echo no;; esac'
83
+
84
+section "3. CHARACTER CLASS DIGIT"
85
+compare_posix_output "digit 0" 'case 0 in [[:digit:]]) echo yes;; *) echo no;; esac'
86
+compare_posix_output "digit 9" 'case 9 in [[:digit:]]) echo yes;; *) echo no;; esac'
87
+compare_posix_output "digit a" 'case a in [[:digit:]]) echo yes;; *) echo no;; esac'
88
+
89
+section "4. CHARACTER CLASS LOWER"
90
+compare_posix_output "lower a" 'case a in [[:lower:]]) echo yes;; *) echo no;; esac'
91
+compare_posix_output "lower z" 'case z in [[:lower:]]) echo yes;; *) echo no;; esac'
92
+compare_posix_output "lower A" 'case A in [[:lower:]]) echo yes;; *) echo no;; esac'
93
+
94
+section "5. CHARACTER CLASS UPPER"
95
+compare_posix_output "upper A" 'case A in [[:upper:]]) echo yes;; *) echo no;; esac'
96
+compare_posix_output "upper Z" 'case Z in [[:upper:]]) echo yes;; *) echo no;; esac'
97
+compare_posix_output "upper a" 'case a in [[:upper:]]) echo yes;; *) echo no;; esac'
98
+
99
+section "6. CHARACTER CLASS SPACE"
100
+compare_posix_output "space sp" 'case " " in [[:space:]]) echo yes;; *) echo no;; esac'
101
+compare_posix_output "space tab" 'case "	" in [[:space:]]) echo yes;; *) echo no;; esac'
102
+compare_posix_output "space a" 'case a in [[:space:]]) echo yes;; *) echo no;; esac'
103
+
104
+section "7. CHARACTER CLASS BLANK"
105
+compare_posix_output "blank sp" 'case " " in [[:blank:]]) echo yes;; *) echo no;; esac'
106
+compare_posix_output "blank tab" 'case "	" in [[:blank:]]) echo yes;; *) echo no;; esac'
107
+compare_posix_output "blank a" 'case a in [[:blank:]]) echo yes;; *) echo no;; esac'
108
+
109
+section "8. CHARACTER CLASS PUNCT"
110
+compare_posix_output "punct dot" 'case "." in [[:punct:]]) echo yes;; *) echo no;; esac'
111
+compare_posix_output "punct excl" 'case "!" in [[:punct:]]) echo yes;; *) echo no;; esac'
112
+compare_posix_output "punct a" 'case a in [[:punct:]]) echo yes;; *) echo no;; esac'
113
+
114
+section "9. CHARACTER CLASS XDIGIT"
115
+compare_posix_output "xdigit 0" 'case 0 in [[:xdigit:]]) echo yes;; *) echo no;; esac'
116
+compare_posix_output "xdigit a" 'case a in [[:xdigit:]]) echo yes;; *) echo no;; esac'
117
+compare_posix_output "xdigit F" 'case F in [[:xdigit:]]) echo yes;; *) echo no;; esac'
118
+compare_posix_output "xdigit g" 'case g in [[:xdigit:]]) echo yes;; *) echo no;; esac'
119
+
120
+section "10. CHARACTER CLASS PRINT GRAPH"
121
+compare_posix_output "print a" 'case a in [[:print:]]) echo yes;; *) echo no;; esac'
122
+compare_posix_output "print sp" 'case " " in [[:print:]]) echo yes;; *) echo no;; esac'
123
+compare_posix_output "graph a" 'case a in [[:graph:]]) echo yes;; *) echo no;; esac'
124
+compare_posix_output "graph sp" 'case " " in [[:graph:]]) echo yes;; *) echo no;; esac'
125
+
126
+section "11. CHARACTER CLASS CNTRL"
127
+compare_posix_output "cntrl a" 'case a in [[:cntrl:]]) echo yes;; *) echo no;; esac'
128
+
129
+section "12. COMBINED CHARACTER CLASSES"
130
+compare_posix_output "combo alpha digit a" 'case a in [[:alpha:][:digit:]]) echo yes;; *) echo no;; esac'
131
+compare_posix_output "combo alpha digit 5" 'case 5 in [[:alpha:][:digit:]]) echo yes;; *) echo no;; esac'
132
+compare_posix_output "combo alpha digit excl" 'case "!" in [[:alpha:][:digit:]]) echo yes;; *) echo no;; esac'
133
+
134
+section "13. NEGATED CHARACTER CLASSES"
135
+compare_posix_output "not digit a" 'case a in [^[:digit:]]) echo yes;; *) echo no;; esac'
136
+compare_posix_output "not digit 5" 'case 5 in [^[:digit:]]) echo yes;; *) echo no;; esac'
137
+compare_posix_output "not alpha bang" 'case a in [![:alpha:]]) echo yes;; *) echo no;; esac'
138
+
139
+section "14. RANGE EXPRESSIONS"
140
+compare_posix_output "range a-z m" 'case m in [a-z]) echo yes;; *) echo no;; esac'
141
+compare_posix_output "range A-Z M" 'case M in [A-Z]) echo yes;; *) echo no;; esac'
142
+compare_posix_output "range 0-9 5" 'case 5 in [0-9]) echo yes;; *) echo no;; esac'
143
+compare_posix_output "range combo" 'case M in [a-zA-Z]) echo yes;; *) echo no;; esac'
144
+
145
+section "15. BRACKET EDGE CASES"
146
+compare_posix_output "literal hyphen start" 'case "-" in [-abc]) echo yes;; *) echo no;; esac'
147
+compare_posix_output "literal hyphen end" 'case "-" in [abc-]) echo yes;; *) echo no;; esac'
148
+compare_posix_output "literal caret" 'case "^" in [a^b]) echo yes;; *) echo no;; esac'
149
+compare_posix_output "literal rbracket" 'case "]" in []abc]) echo yes;; *) echo no;; esac'
150
+
151
+# Summary
152
+printf "\n==========================================\n"
153
+printf "CHARACTER CLASS GAP TEST RESULTS\n"
154
+printf "==========================================\n"
155
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
156
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
157
+printf "Total:   %d\n" "$((PASSED + FAILED))"
158
+if [ "$FAILED" -gt 0 ]; then
159
+    printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
160
+    exit 1
161
+fi
162
+exit 0
suites/posix/posix_gaps_control.shadded
@@ -0,0 +1,256 @@
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|^[^ ]*fortsh: |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
suites/posix/posix_gaps_heredoc.shadded
@@ -0,0 +1,161 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Here-Document Gap Tests
4
+# =====================================
5
+# Tests for POSIX here-document functionality
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-heredoc]"
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|^[^ ]*fortsh: |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
+compare_posix_exit_code() {
70
+    test_name="$1"; command="$2"
71
+    "$BASH_REF" -c "$command" >/dev/null 2>&1; posix_code=$?
72
+    "$FORTSH_BIN" -c "$command" >/dev/null 2>&1; fortsh_code=$?
73
+    if [ "$posix_code" = "$fortsh_code" ]; then pass "$test_name"
74
+    else fail "$test_name" "exit $posix_code" "exit $fortsh_code"; fi
75
+}
76
+
77
+# ============================================================================
78
+# HEREDOC TESTS
79
+# ============================================================================
80
+
81
+section "1. BASIC HEREDOC"
82
+compare_posix_output "simple" 'cat <<EOF
83
+hello
84
+EOF'
85
+compare_posix_output "multiline" 'cat <<EOF
86
+line1
87
+line2
88
+EOF'
89
+compare_posix_output "empty" 'cat <<EOF
90
+EOF'
91
+
92
+section "2. VARIABLE EXPANSION"
93
+compare_posix_output "var expansion" 'x=value; cat <<EOF
94
+$x
95
+EOF'
96
+compare_posix_output "var with text" 'X=world; cat <<EOF
97
+hello $X
98
+EOF'
99
+
100
+section "3. COMMAND SUBSTITUTION"
101
+compare_posix_output "cmd sub" 'cat <<EOF
102
+$(echo hello)
103
+EOF'
104
+
105
+section "4. ARITHMETIC EXPANSION"
106
+compare_posix_output "arith" 'cat <<EOF
107
+$((1+2))
108
+EOF'
109
+
110
+section "5. QUOTED DELIMITER"
111
+compare_posix_output "single quoted" "cat <<'EOF'
112
+\$x \$(cmd)
113
+EOF"
114
+compare_posix_output "double quoted" 'cat <<"EOF"
115
+$x $(cmd)
116
+EOF'
117
+compare_posix_output "quoted no expand" "cat <<'EOF'
118
+\$notvar
119
+EOF"
120
+
121
+section "6. TAB STRIPPING (<<-)"
122
+compare_posix_output "dash strips tabs" "cat <<-EOF
123
+	line1
124
+		line2
125
+	line3
126
+EOF"
127
+compare_posix_output "dash mixed" "cat <<-EOF
128
+	tab_line
129
+    space_line
130
+EOF"
131
+compare_posix_output "dash with vars" "VAR=test; cat <<-EOF
132
+	value=\$VAR
133
+	end
134
+EOF"
135
+compare_posix_output "dash quoted" "cat <<-'EOF'
136
+	\$VAR
137
+	literal
138
+EOF"
139
+compare_posix_output "dash simple" 'cat <<-EOF
140
+	tabbed
141
+	EOF'
142
+
143
+section "7. MULTIPLE HEREDOCS"
144
+compare_posix_output "double heredoc" 'cat <<EOF1; cat <<EOF2
145
+first
146
+EOF1
147
+second
148
+EOF2'
149
+
150
+# Summary
151
+printf "\n==========================================\n"
152
+printf "HEREDOC GAP TEST RESULTS\n"
153
+printf "==========================================\n"
154
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
155
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
156
+printf "Total:   %d\n" "$((PASSED + FAILED))"
157
+if [ "$FAILED" -gt 0 ]; then
158
+    printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
159
+    exit 1
160
+fi
161
+exit 0
suites/posix/posix_gaps_quoting.shadded
@@ -0,0 +1,127 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Quoting Gap Tests
4
+# =====================================
5
+# Tests for POSIX quoting mechanisms
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-quoting]"
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|^[^ ]*fortsh: |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
+# QUOTING TESTS
71
+# ============================================================================
72
+
73
+section "1. SINGLE QUOTING"
74
+compare_posix_output "simple" "echo 'hello'"
75
+compare_posix_output "with space" "echo 'hello world'"
76
+compare_posix_output "dollar literal" "echo '\$x'"
77
+compare_posix_output "backtick literal" "echo '\`cmd\`'"
78
+compare_posix_output "backslash literal" "echo '\\'"
79
+
80
+section "2. DOUBLE QUOTING"
81
+compare_posix_output "simple" 'echo "hello"'
82
+compare_posix_output "with space" 'echo "hello world"'
83
+compare_posix_output "var expand" 'x=val; echo "$x"'
84
+compare_posix_output "escaped dollar" 'echo "\$x"'
85
+compare_posix_output "escaped quote" 'echo "hello\"world"'
86
+
87
+section "3. BACKSLASH ESCAPING"
88
+compare_posix_output "escape space" 'echo hello\ world'
89
+compare_posix_output "escape dollar" 'echo \$x'
90
+compare_posix_output "escape newline" 'echo hello\
91
+world'
92
+compare_posix_output "escape backslash" 'echo \\\\'
93
+
94
+section "4. MIXED QUOTING"
95
+compare_posix_output "single in double" 'echo "'"'"'"'
96
+compare_posix_output "double in single" "echo '\"'"
97
+compare_posix_output "concat quotes" "echo 'a'b'c'"
98
+compare_posix_output "quote switching" 'echo "a'"'"'b"'
99
+
100
+section "5. EMPTY QUOTES"
101
+compare_posix_output "empty single" "echo ''"
102
+compare_posix_output "empty double" 'echo ""'
103
+compare_posix_output "empty in string" 'echo a""b'
104
+compare_posix_output "empty as arg" 'set -- ""; echo $#'
105
+
106
+section "6. COMPLEX ESCAPING"
107
+compare_posix_output "backslash in dquotes" 'echo "test\\nword"'
108
+compare_posix_output "dollar in dquotes" 'echo "cost: \$5"'
109
+compare_posix_output "backtick in dquotes" 'echo "date: \`date +%Y\`" | grep -c date'
110
+compare_posix_output "mixed quoting" "echo 'single'\"double\"'single'"
111
+compare_posix_output "empty concat" "echo ''test''"
112
+compare_posix_output "quote removal" 'VAR="\"test\""; echo $VAR'
113
+compare_posix_output "backslash newline" "echo 'line1\
114
+line2' | wc -l"
115
+
116
+# Summary
117
+printf "\n==========================================\n"
118
+printf "QUOTING GAP TEST RESULTS\n"
119
+printf "==========================================\n"
120
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
121
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
122
+printf "Total:   %d\n" "$((PASSED + FAILED))"
123
+if [ "$FAILED" -gt 0 ]; then
124
+    printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
125
+    exit 1
126
+fi
127
+exit 0
suites/posix/posix_gaps_redirect.shadded
@@ -0,0 +1,156 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Redirection Gap Tests
4
+# =====================================
5
+# Tests for POSIX redirection operators
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-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
+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|^[^ ]*fortsh: |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
+compare_posix_exit_code() {
70
+    test_name="$1"; command="$2"
71
+    "$BASH_REF" -c "$command" >/dev/null 2>&1; posix_code=$?
72
+    "$FORTSH_BIN" -c "$command" >/dev/null 2>&1; fortsh_code=$?
73
+    if [ "$posix_code" = "$fortsh_code" ]; then pass "$test_name"
74
+    else fail "$test_name" "exit $posix_code" "exit $fortsh_code"; fi
75
+}
76
+
77
+# ============================================================================
78
+# BASIC REDIRECTIONS
79
+# ============================================================================
80
+
81
+section "1. OUTPUT REDIRECTION"
82
+compare_posix_output "redir out" 'echo test > /tmp/r$$; cat /tmp/r$$; rm /tmp/r$$'
83
+compare_posix_output "redir in" 'echo test > /tmp/r$$; cat < /tmp/r$$; rm /tmp/r$$'
84
+compare_posix_output "redir append" 'echo a > /tmp/r$$; echo b >> /tmp/r$$; cat /tmp/r$$; rm /tmp/r$$'
85
+compare_posix_output "redir stderr" 'ls /nonexistent$$ 2>/dev/null; echo done'
86
+
87
+section "2. READ/WRITE REDIRECTION"
88
+compare_posix_exit_code "redirect <> creates file" "echo test <> /tmp/posix_gaps_rw_$$; test -f /tmp/posix_gaps_rw_$$; rm -f /tmp/posix_gaps_rw_$$"
89
+compare_posix_exit_code "redirect <> opens existing" "echo data > /tmp/posix_gaps_rw2_$$; cat <> /tmp/posix_gaps_rw2_$$ 2>/dev/null; rm -f /tmp/posix_gaps_rw2_$$"
90
+
91
+# ============================================================================
92
+# ADVANCED REDIRECTIONS
93
+# ============================================================================
94
+
95
+section "3. FD REDIRECTION"
96
+compare_posix_output "redirect order matters" "echo test 2>&1 >/dev/null | wc -l"
97
+compare_posix_output "redirect to same fd" "echo test >&1 2>&1"
98
+compare_posix_output "redirect append" "echo a > /tmp/posix_gaps_redir_$$; echo b >> /tmp/posix_gaps_redir_$$; wc -l < /tmp/posix_gaps_redir_$$; rm -f /tmp/posix_gaps_redir_$$"
99
+
100
+section "4. HERE DOCUMENTS IN REDIRECT"
101
+compare_posix_output "redirect here-string alternative" "cat <<EOF
102
+test
103
+EOF"
104
+compare_posix_output "redirect duplicate stdin" "cat <&0 <<EOF
105
+input
106
+EOF"
107
+
108
+section "5. LOOP REDIRECTIONS"
109
+compare_posix_output "redirect in loop" 'for i in 1 2 3; do echo $i; done > /tmp/redir_test_$$; cat /tmp/redir_test_$$; rm /tmp/redir_test_$$'
110
+compare_posix_output "append multiple" 'echo a >> /tmp/app_test_$$; echo b >> /tmp/app_test_$$; cat /tmp/app_test_$$; rm /tmp/app_test_$$'
111
+compare_posix_output "stderr to file" 'ls /nonexistent 2>/tmp/err_test_$$ || cat /tmp/err_test_$$ | wc -l; rm -f /tmp/err_test_$$'
112
+
113
+# ============================================================================
114
+# IFS FIELD SPLITTING
115
+# ============================================================================
116
+
117
+section "6. IFS FIELD SPLITTING"
118
+compare_posix_output "IFS mixed ws and non-ws" "IFS=': \t'; VAR='a:b c:d'; set -- \$VAR; echo \$# \$1 \$2 \$3 \$4"
119
+compare_posix_output "IFS multiple delimiters" "IFS=',:'; VAR='a,b:c,d'; set -- \$VAR; echo \$#"
120
+compare_posix_output "IFS trailing delimiters" "IFS=:; VAR='a:b:c:'; set -- \$VAR; echo \$#"
121
+compare_posix_output "IFS leading and trailing" "IFS=:; VAR=':a:b:'; set -- \$VAR; echo \$# \$1 \$2"
122
+compare_posix_output "IFS consecutive delimiters" "IFS=:; VAR='a::b'; set -- \$VAR; echo \$# \$1 \$2 \$3"
123
+compare_posix_output "IFS whitespace collapsing" "IFS=' '; VAR='a  b   c'; set -- \$VAR; echo \$#"
124
+
125
+# ============================================================================
126
+# COMMENTS
127
+# ============================================================================
128
+
129
+section "7. COMMENTS"
130
+compare_posix_output "comment inline" 'echo yes # comment'
131
+compare_posix_output "comment in dquote" 'echo "not # comment"'
132
+compare_posix_output "comment in squote" "echo 'not # comment'"
133
+
134
+# ============================================================================
135
+# EXIT CODES
136
+# ============================================================================
137
+
138
+section "8. EXIT CODES"
139
+compare_posix_exit_code "true exits 0" "true"
140
+compare_posix_exit_code "false exits 1" "false"
141
+compare_posix_exit_code "exit 0" "(exit 0)"
142
+compare_posix_output "exit code chain" "true; echo \$?"
143
+compare_posix_output "backtick vs dollar-paren" "a=\`echo test\`; b=\$(echo test); test \"\$a\" = \"\$b\" && echo same"
144
+
145
+# Summary
146
+printf "\n==========================================\n"
147
+printf "REDIRECTION GAP TEST RESULTS\n"
148
+printf "==========================================\n"
149
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
150
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
151
+printf "Total:   %d\n" "$((PASSED + FAILED))"
152
+if [ "$FAILED" -gt 0 ]; then
153
+    printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
154
+    exit 1
155
+fi
156
+exit 0
suites/posix/posix_gaps_special.shadded
@@ -0,0 +1,225 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Special Parameters Gap Tests
4
+# =====================================
5
+# Tests for POSIX special parameters and expansion
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-special]"
17
+CURRENT_SECTION=""
18
+TEST_NUM=0
19
+
20
+PASSED=0
21
+FAILED=0
22
+SKIPPED=0
23
+FAILED_TESTS_LIST=""
24
+DEBUG_INFO=""
25
+
26
+# Get script directory (POSIX way)
27
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
28
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}"
29
+BASH_REF="${BASH_REF:-bash}"
30
+
31
+# Check if fortsh exists
32
+if [ ! -x "$FORTSH_BIN" ]; then
33
+    printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n"
34
+    printf "Please run 'make' first or set FORTSH_BIN environment variable\n"
35
+    exit 1
36
+fi
37
+
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 printf "  posix:  %s\n" "$2"; fi
50
+    if [ -n "$3" ]; then printf "  fortsh: %s\n" "$3"; fi
51
+    FAILED=$((FAILED + 1))
52
+}
53
+
54
+section() {
55
+    CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
56
+    TEST_NUM=0
57
+    printf "\n${BLUE}==========================================\n%s\n==========================================${NC}\n" "$1"
58
+}
59
+
60
+normalize_output() { sed -e 's|^[^ ]*bash: |sh: |' -e 's|^[^ ]*fortsh: |sh: |' -e 's/line [0-9]*: //'; }
61
+
62
+compare_posix_output() {
63
+    test_name="$1"; command="$2"
64
+    posix_out=$("$BASH_REF" -c "$command" 2>&1 | normalize_output)
65
+    fortsh_out=$("$FORTSH_BIN" -c "$command" 2>&1 | normalize_output)
66
+    if [ "$posix_out" = "$fortsh_out" ]; then pass "$test_name"
67
+    else
68
+        fail "$test_name" "$posix_out" "$fortsh_out"
69
+        # Accumulate debug for CI summary
70
+        DEBUG_INFO="${DEBUG_INFO}DEBUG [$test_name]: cmd='$command'\n"
71
+        DEBUG_INFO="${DEBUG_INFO}  bash: '${posix_out}' hex=$(printf '%s' "$posix_out" | od -A x -t x1z | head -1)\n"
72
+        DEBUG_INFO="${DEBUG_INFO}  fortsh: '${fortsh_out}' hex=$(printf '%s' "$fortsh_out" | od -A x -t x1z | head -1)\n"
73
+    fi
74
+}
75
+
76
+# ============================================================================
77
+# SPECIAL PARAMETERS
78
+# ============================================================================
79
+
80
+section "1. POSITIONAL PARAMETERS"
81
+compare_posix_output "dollar at" 'set -- a b c; echo "$@"'
82
+compare_posix_output "dollar star" 'set -- a b c; echo "$*"'
83
+compare_posix_output "dollar hash" 'set -- a b c; echo $#'
84
+compare_posix_output "dollar at with IFS" "IFS=:; set -- a b c; echo \"\$*\""
85
+compare_posix_output "dollar star vs at unquoted" "set -- a b c; for x in \$*; do echo \$x; done | wc -l"
86
+compare_posix_output "dollar at quoted iteration" "set -- 'a b' 'c d'; for x in \"\$@\"; do echo \$x; done | wc -l"
87
+compare_posix_output "dollar hash after shift" "set -- a b c; shift; echo \$#"
88
+
89
+section "2. SHELL STATUS PARAMETERS"
90
+compare_posix_output "dollar question" 'true; echo $?'
91
+compare_posix_output "dollar pid" 'echo $$ | grep -cE "^[0-9]+$"'
92
+compare_posix_output "dollar zero" 'echo $0 | grep -c .'
93
+compare_posix_output "dollar dash shows options" "echo \$- | grep -c '[a-z]'"
94
+compare_posix_output "dollar dollar is numeric" "echo \$\$ | grep -c '^[0-9]*\$'"
95
+compare_posix_output "dollar zero is set" "echo \${0:-none} | grep -c '.'"
96
+
97
+# ============================================================================
98
+# PARAMETER EXPANSION
99
+# ============================================================================
100
+
101
+section "3. DEFAULT VALUES"
102
+compare_posix_output "pe default" 'unset x; echo ${x:-default}'
103
+compare_posix_output "pe assign" 'unset x; echo ${x:=assigned}; echo $x'
104
+compare_posix_output "pe error" '(unset x; echo ${x:?msg}) 2>/dev/null; echo $?'
105
+compare_posix_output "pe alt" 'x=val; echo ${x:+alt}'
106
+compare_posix_output "default empty" 'x=""; echo ${x:-default}'
107
+compare_posix_output "default unset" 'unset x; echo ${x:-default}'
108
+compare_posix_output "default set" 'x="value"; echo ${x:-default}'
109
+
110
+section "4. STRING LENGTH"
111
+compare_posix_output "pe length" 'x=hello; echo ${#x}'
112
+compare_posix_output "length of empty" 'x=""; echo ${#x}'
113
+compare_posix_output "length of one" 'x="a"; echo ${#x}'
114
+compare_posix_output "length of special" 'x="a b c"; echo ${#x}'
115
+
116
+section "5. PATTERN REMOVAL"
117
+compare_posix_output "pe suffix" 'x=file.txt; echo ${x%.txt}'
118
+compare_posix_output "pe prefix" 'x=/path/file; echo ${x##*/}'
119
+compare_posix_output "suffix remove" 'x="file.txt"; echo ${x%.txt}'
120
+compare_posix_output "prefix remove" 'x="prefix_name"; echo ${x#prefix_}'
121
+compare_posix_output "longest suffix" 'x="a.b.c"; echo ${x%%.*}'
122
+compare_posix_output "longest prefix" 'x="a.b.c"; echo ${x##*.}'
123
+
124
+section "6. NESTED EXPANSION"
125
+compare_posix_output "nested default" "unset A; B=inner; echo \${A:-\${B}}"
126
+compare_posix_output "nested length" "VAR=hello; echo \${#VAR}"
127
+compare_posix_output "nested pattern removal" "VAR=/usr/local/bin; echo \${VAR#\${VAR%/*}}"
128
+compare_posix_output "multiple expansions" "A=foo; B=bar; echo \${A}\${B}"
129
+compare_posix_output "nested with quotes" "A='a b'; echo \"\${A}\""
130
+
131
+# ============================================================================
132
+# IFS SPLITTING
133
+# ============================================================================
134
+
135
+section "7. IFS SPLITTING"
136
+compare_posix_output "ifs default" 'x="a b c"; set -- $x; echo $#'
137
+compare_posix_output "ifs colon" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
138
+compare_posix_output "ifs empty" 'IFS=""; x="abc"; set -- $x; echo $#'
139
+compare_posix_output "ifs star" 'IFS=:; set -- a b c; echo "$*"'
140
+
141
+# ============================================================================
142
+# COMMAND SUBSTITUTION
143
+# ============================================================================
144
+
145
+section "8. COMMAND SUBSTITUTION"
146
+compare_posix_output "cmdsub basic" 'echo $(echo hello)'
147
+compare_posix_output "cmdsub backtick" 'echo `echo hello`'
148
+compare_posix_output "cmdsub nested" 'echo $(echo $(echo deep))'
149
+compare_posix_output "cmdsub quoted" 'echo "$(echo hello world)"'
150
+compare_posix_output "cmdsub multi" 'echo $(echo a; echo b)'
151
+
152
+section "9. COMMAND SUBSTITUTION VARIANTS"
153
+compare_posix_output "cmd sub echo" 'echo $(echo hello)'
154
+compare_posix_output "cmd sub pwd" 'echo $(pwd | grep -c "/")'
155
+compare_posix_output "cmd sub math" 'echo $(($(echo 5) + 3))'
156
+compare_posix_output "cmd sub in var" 'x=$(echo test); echo $x'
157
+compare_posix_output "backtick equiv" 'echo `echo hello`'
158
+compare_posix_output "cmd sub whitespace" 'echo "$(echo "  spaces  ")"'
159
+compare_posix_output "cmd sub multiline" 'echo "$(echo -e "a\nb")" | wc -l'
160
+compare_posix_output "cmd sub exit code" '$(exit 0); echo $?'
161
+compare_posix_output "cmd sub fail code" '$(exit 1); echo $?'
162
+
163
+# ============================================================================
164
+# TILDE EXPANSION
165
+# ============================================================================
166
+
167
+section "10. TILDE EXPANSION"
168
+compare_posix_output "tilde in assignment" "VAR=~/test; echo \$VAR | grep -c '^/'"
169
+compare_posix_output "tilde in middle no expand" "echo a~b | grep -c '~'"
170
+
171
+# ============================================================================
172
+# VARIABLE ASSIGNMENT CONTEXTS
173
+# ============================================================================
174
+
175
+section "11. VARIABLE ASSIGNMENT"
176
+compare_posix_output "simple assign" 'x=5; echo $x'
177
+compare_posix_output "multi assign" 'x=1 y=2 z=3; echo $x $y $z'
178
+compare_posix_output "assign in subshell" '(x=inner; echo $x); echo ${x:-unset}'
179
+compare_posix_output "assign export" 'export X=exported; printenv X 2>/dev/null || echo $X'
180
+compare_posix_output "assign readonly" 'readonly X=constant; echo $X'
181
+compare_posix_output "assign with cmd" 'X=$(echo value); echo $X'
182
+compare_posix_output "assign quoted" 'X="with spaces"; echo "$X"'
183
+compare_posix_output "assign empty" 'X=""; echo "[$X]"'
184
+compare_posix_output "assign special" 'X="$HOME"; echo ${X:+set}'
185
+
186
+# ============================================================================
187
+# PIPELINES
188
+# ============================================================================
189
+
190
+section "12. PIPELINES"
191
+compare_posix_output "pipe two" 'echo hello | cat'
192
+compare_posix_output "pipe three" 'echo hello | cat | cat'
193
+compare_posix_output "pipe with grep" 'echo hello | grep -o h'
194
+compare_posix_output "pipe word count" 'echo "a b c" | wc -w'
195
+compare_posix_output "pipe line count" 'printf "a\nb\nc\n" | wc -l'
196
+compare_posix_output "pipe sort" 'printf "c\na\nb\n" | sort | head -1'
197
+compare_posix_output "pipe uniq" 'printf "a\na\nb\n" | uniq | wc -l'
198
+compare_posix_output "pipe head" 'printf "1\n2\n3\n4\n5\n" | head -2'
199
+compare_posix_output "pipe tail" 'printf "1\n2\n3\n4\n5\n" | tail -2'
200
+compare_posix_output "pipe tr" 'echo abc | tr a-z A-Z'
201
+
202
+# ============================================================================
203
+# COMMAND NAME RESOLUTION
204
+# ============================================================================
205
+
206
+section "13. COMMAND NAME RESOLUTION"
207
+compare_posix_output "function overrides echo" "echo() { printf 'function\n'; }; echo test | grep -c function"
208
+compare_posix_output "command -v finds function" "func() { :; }; command -v func | grep -c func"
209
+compare_posix_output "command bypasses function" "echo() { printf 'func\n'; }; command echo test"
210
+
211
+# Summary
212
+printf "\n==========================================\n"
213
+printf "SPECIAL PARAMETERS GAP TEST RESULTS\n"
214
+printf "==========================================\n"
215
+printf "${GREEN}Passed:${NC}  %d\n" "$PASSED"
216
+printf "${RED}Failed:${NC}  %d\n" "$FAILED"
217
+printf "Total:   %d\n" "$((PASSED + FAILED))"
218
+if [ "$FAILED" -gt 0 ]; then
219
+    printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
220
+    if [ -n "$DEBUG_INFO" ]; then
221
+        printf "\n${YELLOW}Debug info:${NC}\n%b" "$DEBUG_INFO"
222
+    fi
223
+    exit 1
224
+fi
225
+exit 0
suites/posix/run_posix_tests.shadded
@@ -0,0 +1,118 @@
1
+#!/bin/sh
2
+# =====================================
3
+# POSIX Compliance Test Runner
4
+# =====================================
5
+# Runs all POSIX compliance test suites for fortsh
6
+# Automatically detects fortsh binary location
7
+
8
+set -e
9
+
10
+# Colors
11
+RED='\033[0;31m'
12
+GREEN='\033[0;32m'
13
+YELLOW='\033[1;33m'
14
+BLUE='\033[0;34m'
15
+CYAN='\033[0;36m'
16
+NC='\033[0m'
17
+
18
+# Get script directory
19
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
20
+
21
+# Find fortsh binary - check multiple locations
22
+if [ -n "$FORTSH_BIN" ] && [ -x "$FORTSH_BIN" ]; then
23
+    # Use environment variable if set
24
+    FORTSH_PATH="$FORTSH_BIN"
25
+elif [ -x "$SCRIPT_DIR/../bin/fortsh" ]; then
26
+    # Look in ../bin/fortsh (relative to tests/)
27
+    FORTSH_PATH="$SCRIPT_DIR/../bin/fortsh"
28
+elif [ -x "./bin/fortsh" ]; then
29
+    # Look in ./bin/fortsh (from project root)
30
+    FORTSH_PATH="./bin/fortsh"
31
+elif [ -x "$(pwd)/bin/fortsh" ]; then
32
+    # Look in current working directory
33
+    FORTSH_PATH="$(pwd)/bin/fortsh"
34
+else
35
+    printf "${RED}ERROR: fortsh binary not found!${NC}\n"
36
+    printf "Searched locations:\n"
37
+    printf "  - FORTSH_BIN environment variable\n"
38
+    printf "  - %s/bin/fortsh\n" "$SCRIPT_DIR/.."
39
+    printf "  - ./bin/fortsh\n"
40
+    printf "  - $(pwd)/bin/fortsh\n"
41
+    printf "\nPlease build fortsh first with 'make' or set FORTSH_BIN\n"
42
+    exit 1
43
+fi
44
+
45
+export FORTSH_BIN="$FORTSH_PATH"
46
+
47
+# Test suite files
48
+TEST_SUITES="
49
+posix_compliance_test.sh
50
+posix_compliance_extended.sh
51
+posix_compliance_builtins.sh
52
+posix_compliance_advanced.sh
53
+posix_compliance_gaps.sh
54
+posix_compliance_jobcontrol.sh
55
+posix_compliance_coverage.sh
56
+posix_compliance_untested.sh
57
+"
58
+
59
+# Print header
60
+printf "${CYAN}========================================\n"
61
+printf "POSIX Compliance Test Suite Runner\n"
62
+printf "========================================${NC}\n"
63
+printf "fortsh binary: ${GREEN}%s${NC}\n" "$FORTSH_BIN"
64
+printf "Test directory: %s\n" "$SCRIPT_DIR"
65
+printf "\n"
66
+
67
+# Counters
68
+TOTAL_SUITES=0
69
+PASSED_SUITES=0
70
+FAILED_SUITES=0
71
+TOTAL_TESTS=0
72
+
73
+# Run each test suite
74
+for suite in $TEST_SUITES; do
75
+    suite_path="$SCRIPT_DIR/$suite"
76
+
77
+    if [ ! -f "$suite_path" ]; then
78
+        printf "${YELLOW}⊘ SKIP${NC}: %s (not found)\n" "$suite"
79
+        continue
80
+    fi
81
+
82
+    if [ ! -x "$suite_path" ]; then
83
+        printf "${YELLOW}⊘ SKIP${NC}: %s (not executable)\n" "$suite"
84
+        continue
85
+    fi
86
+
87
+    TOTAL_SUITES=$((TOTAL_SUITES + 1))
88
+
89
+    printf "${BLUE}========================================\n"
90
+    printf "Running: %s\n" "$suite"
91
+    printf "========================================${NC}\n"
92
+
93
+    # Run the test suite
94
+    if "$suite_path"; then
95
+        printf "${GREEN}✓ PASSED${NC}: %s\n\n" "$suite"
96
+        PASSED_SUITES=$((PASSED_SUITES + 1))
97
+    else
98
+        printf "${RED}✗ FAILED${NC}: %s\n\n" "$suite"
99
+        FAILED_SUITES=$((FAILED_SUITES + 1))
100
+    fi
101
+done
102
+
103
+# Print summary
104
+printf "${CYAN}========================================\n"
105
+printf "OVERALL SUMMARY\n"
106
+printf "========================================${NC}\n"
107
+printf "Total test suites: %d\n" "$TOTAL_SUITES"
108
+printf "${GREEN}Passed suites:     %d${NC}\n" "$PASSED_SUITES"
109
+printf "${RED}Failed suites:     %d${NC}\n" "$FAILED_SUITES"
110
+printf "${CYAN}========================================${NC}\n"
111
+
112
+if [ "$FAILED_SUITES" -eq 0 ] && [ "$TOTAL_SUITES" -gt 0 ]; then
113
+    printf "${GREEN}\n✓ ALL TEST SUITES PASSED!\n${NC}"
114
+    exit 0
115
+else
116
+    printf "${RED}\n✗ SOME TEST SUITES FAILED\n${NC}"
117
+    exit 1
118
+fi