Bash · 23454 bytes Raw Blame History
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 "$@"