tenseleyflow/bensch / 009ce4c

Browse files

extract integration tests

12 cross-feature integration tests: expansion, redirection, builtin
cross-interaction, control flow, error handling, functions, pipelines,
scope/env, subshells, job control, signals/traps.

Source: fortsh/tests/builtins/integration/
Authored by espadonne
SHA
009ce4cea364bfc4f26bf2009f6121ec74957177
Parents
ba8e9bc
Tree
2d39055

12 changed files

StatusFile+-
A suites/builtins/integration/run_integration_tests.sh 130 0
A suites/builtins/integration/test_int_builtin_cross.sh 77 0
A suites/builtins/integration/test_int_control_flow.sh 83 0
A suites/builtins/integration/test_int_error_handling.sh 73 0
A suites/builtins/integration/test_int_expansion.sh 99 0
A suites/builtins/integration/test_int_function.sh 71 0
A suites/builtins/integration/test_int_job_control.sh 55 0
A suites/builtins/integration/test_int_pipeline.sh 67 0
A suites/builtins/integration/test_int_redirection.sh 90 0
A suites/builtins/integration/test_int_scope_env.sh 61 0
A suites/builtins/integration/test_int_signal_trap.sh 54 0
A suites/builtins/integration/test_int_subshell.sh 63 0
suites/builtins/integration/run_integration_tests.shadded
@@ -0,0 +1,130 @@
1
+#!/bin/sh
2
+# =====================================
3
+# Fortsh Integration Test Runner
4
+# =====================================
5
+# Runs all integration test suites and aggregates results.
6
+# Can be called standalone or from the main run_all_tests.sh
7
+
8
+# Portable timeout: macOS lacks GNU timeout
9
+if command -v timeout >/dev/null 2>&1; then
10
+    run_with_timeout() { timeout "$@"; }
11
+elif command -v gtimeout >/dev/null 2>&1; then
12
+    run_with_timeout() { gtimeout "$@"; }
13
+else
14
+    run_with_timeout() {
15
+        _rwt_secs="$1"; shift
16
+        _rwt_tmp=$(mktemp /tmp/fortsh_timeout.XXXXXX)
17
+        ( "$@" ) > "$_rwt_tmp" 2>&1 &
18
+        _rwt_pid=$!
19
+        ( sleep "$_rwt_secs"; kill "$_rwt_pid" 2>/dev/null ) &
20
+        _rwt_wd=$!
21
+        wait "$_rwt_pid" 2>/dev/null
22
+        _rwt_rc=$?
23
+        kill "$_rwt_wd" 2>/dev/null
24
+        wait "$_rwt_wd" 2>/dev/null
25
+        cat "$_rwt_tmp"
26
+        rm -f "$_rwt_tmp"
27
+        return $_rwt_rc
28
+    }
29
+fi
30
+
31
+RED='\033[0;31m'
32
+GREEN='\033[0;32m'
33
+YELLOW='\033[1;33m'
34
+BLUE='\033[0;34m'
35
+CYAN='\033[0;36m'
36
+BOLD='\033[1m'
37
+NC='\033[0m'
38
+
39
+VERBOSE=0
40
+for arg in "$@"; do
41
+    case "$arg" in
42
+        -v|--verbose) VERBOSE=1 ;;
43
+    esac
44
+done
45
+
46
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
47
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
48
+export FORTSH_BIN
49
+
50
+if [ ! -x "$FORTSH_BIN" ]; then
51
+    printf "${RED}ERROR${NC}: fortsh binary not found at %s\n" "$FORTSH_BIN"
52
+    printf "Please run 'make' first or set FORTSH_BIN environment variable\n"
53
+    exit 1
54
+fi
55
+
56
+TOTAL_PASSED=0
57
+TOTAL_FAILED=0
58
+TOTAL_SKIPPED=0
59
+SUITES_PASSED=0
60
+SUITES_FAILED=0
61
+FAILED_SUITES=""
62
+
63
+for test_file in "$SCRIPT_DIR"/test_int_*.sh; do
64
+    [ ! -x "$test_file" ] && continue
65
+    suite_name=$(basename "$test_file")
66
+
67
+    if [ "$VERBOSE" -eq 1 ]; then
68
+        printf "\n${CYAN}── %s ──${NC}\n" "$suite_name"
69
+        output=$(run_with_timeout 120 "$test_file" 2>&1)
70
+        exit_code=$?
71
+        echo "$output"
72
+    else
73
+        output=$(run_with_timeout 120 "$test_file" 2>&1)
74
+        exit_code=$?
75
+    fi
76
+
77
+    # Strip ANSI codes for parsing
78
+    clean=$(echo "$output" | sed 's/\x1b\[[0-9;]*m//g')
79
+    passed=$(echo "$clean" | grep -i "^Passed:" | tail -1 | awk '{print $2}')
80
+    failed=$(echo "$clean" | grep -i "^Failed:" | tail -1 | awk '{print $2}')
81
+    skipped=$(echo "$clean" | grep -i "^Skipped:" | tail -1 | awk '{print $2}')
82
+
83
+    passed=${passed:-0}
84
+    failed=${failed:-0}
85
+    skipped=${skipped:-0}
86
+
87
+    TOTAL_PASSED=$((TOTAL_PASSED + passed))
88
+    TOTAL_FAILED=$((TOTAL_FAILED + failed))
89
+    TOTAL_SKIPPED=$((TOTAL_SKIPPED + skipped))
90
+
91
+    if [ "$exit_code" -eq 0 ]; then
92
+        printf "${GREEN}✓${NC} %-35s  ${GREEN}%d passed${NC}" "$suite_name" "$passed"
93
+        [ "$skipped" -gt 0 ] && printf "  ${YELLOW}%d skipped${NC}" "$skipped"
94
+        printf "\n"
95
+        SUITES_PASSED=$((SUITES_PASSED + 1))
96
+    else
97
+        printf "${RED}✗${NC} %-35s  ${GREEN}%d passed${NC}  ${RED}%d failed${NC}" "$suite_name" "$passed" "$failed"
98
+        [ "$skipped" -gt 0 ] && printf "  ${YELLOW}%d skipped${NC}" "$skipped"
99
+        printf "\n"
100
+        SUITES_FAILED=$((SUITES_FAILED + 1))
101
+        FAILED_SUITES="${FAILED_SUITES}  - ${suite_name}\n"
102
+    fi
103
+done
104
+
105
+printf "\n==========================================\n"
106
+printf "INTEGRATION TEST RESULTS\n"
107
+printf "==========================================\n"
108
+printf "Passed:  %d\n" "$TOTAL_PASSED"
109
+printf "Failed:  %d\n" "$TOTAL_FAILED"
110
+printf "Skipped: %d\n" "$TOTAL_SKIPPED"
111
+printf "Total:   %d\n" "$((TOTAL_PASSED + TOTAL_FAILED + TOTAL_SKIPPED))"
112
+printf "==========================================\n"
113
+
114
+if [ $((TOTAL_PASSED + TOTAL_FAILED)) -gt 0 ]; then
115
+    PASS_RATE=$((TOTAL_PASSED * 100 / (TOTAL_PASSED + TOTAL_FAILED)))
116
+    printf "Pass rate: %d%%\n" "$PASS_RATE"
117
+fi
118
+
119
+if [ "$SUITES_FAILED" -gt 0 ]; then
120
+    printf "\n${RED}Failed suites:${NC}\n"
121
+    printf "%b" "$FAILED_SUITES"
122
+    printf "==========================================\n"
123
+fi
124
+
125
+if [ "$TOTAL_FAILED" -eq 0 ]; then
126
+    printf "ALL INTEGRATION TESTS PASSED!\n"
127
+    exit 0
128
+else
129
+    exit 1
130
+fi
suites/builtins/integration/test_int_builtin_cross.shadded
@@ -0,0 +1,77 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-builtin-cross]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: builtin-to-builtin interactions
9
+
10
+section "1. declare/export/readonly interaction"
11
+compare_output "declare -x is export" 'declare -x BDX=exported; echo $BDX'
12
+compare_output "declare -r is readonly" 'declare -r BDR=readonly; { BDR=other; } 2>/dev/null; echo $BDR'
13
+compare_output "export then readonly" 'export BER=val; readonly BER; { BER=other; } 2>/dev/null; echo $BER'
14
+compare_output "declare -ix integer export" 'declare -ix BDI=5; echo $BDI'
15
+compare_output "declare -a then export" 'declare -a BDA=(1 2 3); export BDA; echo ${BDA[@]}'
16
+compare_output "readonly then unset fails" 'readonly BRU=val; unset BRU 2>/dev/null; echo $? $BRU'
17
+compare_output "export -p shows exported" 'export BEP=val; export -p | grep BEP | head -1'
18
+
19
+section "2. set/shift interaction"
20
+compare_output "set then shift" 'set -- a b c; shift; echo $@'
21
+compare_output "set then shift 2" 'set -- a b c; shift 2; echo $@'
22
+compare_output "set then count" 'set -- a b c; echo $#; shift; echo $#'
23
+compare_output "set resets positional" 'set -- a b c; set -- x y; echo $@'
24
+compare_output "set empty clears" 'set -- a b c; set --; echo $#'
25
+compare_output "shift all" 'set -- a b c; shift 3; echo $#'
26
+
27
+section "3. pushd/popd/dirs stack"
28
+compare_output "round trip" 'ORIG=$(pwd); pushd /tmp >/dev/null; pushd /var >/dev/null; popd >/dev/null; popd >/dev/null; test "$(pwd)" = "$ORIG" && echo same'
29
+compare_output "deep round trip" 'ORIG=$(pwd); pushd /tmp >/dev/null; pushd /var >/dev/null; pushd / >/dev/null; popd >/dev/null; popd >/dev/null; popd >/dev/null; test "$(pwd)" = "$ORIG" && echo same'
30
+compare_output "dirs shows stack" 'pushd /tmp >/dev/null; pushd /var >/dev/null; dirs 2>/dev/null'
31
+compare_output "dirs -v numbered" 'pushd /tmp >/dev/null; dirs -v 2>/dev/null'
32
+compare_output "pushd swap top two" 'pushd /tmp >/dev/null; pushd /var >/dev/null; pushd >/dev/null 2>/dev/null; pwd'
33
+compare_output "dirs -c clears stack" 'pushd /tmp >/dev/null; dirs -c 2>/dev/null; dirs 2>/dev/null'
34
+
35
+section "4. alias/eval interaction"
36
+compare_output "alias in eval" 'alias bgreet="echo hi" 2>/dev/null; eval bgreet 2>/dev/null || echo no_expand'
37
+compare_output "alias then unalias" 'alias bua="echo test" 2>/dev/null; unalias bua 2>/dev/null; echo $?'
38
+compare_output "alias overwrite" 'alias bao="echo old" 2>/dev/null; alias bao="echo new" 2>/dev/null; eval bao 2>/dev/null || echo no_expand'
39
+
40
+section "5. trap/exit interaction"
41
+compare_output "EXIT trap fires on exit" 'trap "echo bye" EXIT; exit 0'
42
+compare_output "EXIT trap fires on implicit exit" 'trap "echo bye" EXIT; true'
43
+compare_output "trap then trap replaces" 'trap "echo a" EXIT; trap "echo b" EXIT'
44
+compare_output "trap then clear" 'trap "echo a" EXIT; trap - EXIT; echo done'
45
+compare_output "EXIT trap sees last status" 'trap "echo \$?" EXIT; false'
46
+
47
+section "6. hash/command interaction"
48
+compare_output "hash -r then command -v" 'hash -r 2>/dev/null; command -v ls'
49
+compare_output "command -v builtin" 'command -v echo'
50
+compare_output "command -v not found" 'command -v nonexistent_xyz_123; echo $?'
51
+
52
+section "7. type/command interaction"
53
+compare_output "type echo" 'type echo 2>/dev/null | head -1'
54
+compare_output "command -V echo" 'command -V echo 2>/dev/null | head -1'
55
+compare_output "type external" 'type ls 2>/dev/null | head -1'
56
+compare_output "type not found" 'type nonexistent_xyz_123 2>/dev/null; echo $?'
57
+
58
+section "8. read/echo round-trip"
59
+compare_output "echo pipe read" 'echo "hello world" | { read A B; echo "$A $B"; }'
60
+compare_output "printf pipe while read" 'printf "a\nb\nc\n" | { while read line; do echo "[$line]"; done; }'
61
+compare_output "echo to file and read back" "echo 'data here' > $TEST_TMPDIR/brt1; read VAR < $TEST_TMPDIR/brt1; echo \$VAR"
62
+compare_output "printf format and read" 'printf "%d\n" 42 | { read N; echo "got:$N"; }'
63
+
64
+section "9. eval/source chain"
65
+compare_output "eval source" "echo 'BES=sourced' > $TEST_TMPDIR/bchain.sh; eval 'source $TEST_TMPDIR/bchain.sh'; echo \$BES"
66
+compare_output "nested eval" 'eval "eval \"echo deep\""'
67
+compare_output "eval with variable" 'CMD="echo hello"; eval "$CMD"'
68
+compare_output "eval with special chars" 'eval "echo hello world"'
69
+
70
+section "10. let/declare -i interaction"
71
+compare_output "declare -i then let" 'declare -i BDL=0; let BDL+=5; echo $BDL'
72
+compare_output "let standalone" 'let X=3+4 2>/dev/null; echo $X'
73
+compare_output "declare -i auto-arithmetic" 'declare -i BDA2=3+4; echo $BDA2'
74
+compare_output "let comparison" 'let "5 > 3" 2>/dev/null; echo $?'
75
+compare_output "let false comparison" 'let "3 > 5" 2>/dev/null; echo $?'
76
+
77
+print_summary
suites/builtins/integration/test_int_control_flow.shadded
@@ -0,0 +1,83 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-control-flow]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: builtins in loops, conditionals, and control flow
9
+
10
+section "1. builtins as loop conditions"
11
+compare_output "while read as condition" 'printf "a\nb\nc\n" | while read line; do echo "got:$line"; done'
12
+compare_output "while test as condition" 'i=0; while test $i -lt 3; do echo $i; i=$((i+1)); done'
13
+compare_output "while bracket as condition" 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done'
14
+compare_output "until test as condition" 'i=0; until test $i -ge 3; do echo $i; i=$((i+1)); done'
15
+compare_output "until bracket as condition" 'i=0; until [ $i -ge 3 ]; do echo $i; i=$((i+1)); done'
16
+compare_output "while true with break" 'i=0; while true; do i=$((i+1)); [ $i -ge 3 ] && break; echo $i; done'
17
+compare_output "while command as condition" 'echo -e "a\nb" > /tmp/test_cf_$$; while read line; do echo $line; done < /tmp/test_cf_$$; rm -f /tmp/test_cf_$$'
18
+
19
+section "2. break and continue"
20
+compare_output "basic break" 'for i in 1 2 3 4 5; do echo $i; test $i -eq 3 && break; done'
21
+compare_output "basic continue" 'for i in 1 2 3 4 5; do test $i -eq 3 && continue; echo $i; done'
22
+compare_output "break in nested for - inner" 'for i in 1 2; do for j in a b c; do [ $j = b ] && break; echo $i$j; done; done'
23
+compare_output "continue in nested for" 'for i in 1 2; do for j in a b c; do [ $j = b ] && continue; echo $i$j; done; done'
24
+compare_output "break 2 exits both loops" 'for i in 1 2; do for j in a b; do [ $i = 2 ] && break 2; echo $i$j; done; done'
25
+compare_output "continue 2 skips outer" 'for i in 1 2 3; do for j in a b; do [ $i = 2 ] && continue 2; echo $i$j; done; done'
26
+compare_output "break in while" 'i=0; while true; do i=$((i+1)); [ $i -gt 3 ] && break; echo $i; done'
27
+compare_output "continue in while" 'i=0; while [ $i -lt 5 ]; do i=$((i+1)); [ $i -eq 3 ] && continue; echo $i; done'
28
+
29
+section "3. if/elif with builtins"
30
+compare_output "if test gt" 'if test 5 -gt 3; then echo yes; fi'
31
+compare_output "if bracket dir" 'if [ -d /tmp ]; then echo dir; fi'
32
+compare_output "if double bracket glob" 'if [[ "hello" == h* ]]; then echo match; fi'
33
+compare_output "if command -v" 'if command -v ls >/dev/null; then echo found; fi'
34
+compare_output "elif chain" 'X=2; if [ $X -eq 1 ]; then echo one; elif [ $X -eq 2 ]; then echo two; else echo other; fi'
35
+compare_output "triple elif" 'X=3; if [ $X -eq 1 ]; then echo one; elif [ $X -eq 2 ]; then echo two; elif [ $X -eq 3 ]; then echo three; else echo other; fi'
36
+compare_output "if false elif false else" 'if false; then echo no; elif false; then echo no2; else echo default; fi'
37
+compare_output "if type builtin" 'if type echo >/dev/null 2>&1; then echo builtin; fi'
38
+compare_output "if with negation" 'if ! false; then echo negated; fi'
39
+
40
+section "4. case with builtins"
41
+compare_output "case with echo result" 'X=hello; case $X in h*) echo starts_h;; *) echo other;; esac'
42
+compare_output "case with cmd sub" 'case $(echo hello) in h*) echo match;; esac'
43
+compare_output "case on exit status" 'true; case $? in 0) echo ok;; *) echo fail;; esac'
44
+compare_output "case multiple patterns" 'X=banana; case $X in apple|pear) echo fruit1;; banana|grape) echo fruit2;; esac'
45
+compare_output "case with variable" 'PAT=hello; case $PAT in hello) echo matched;; esac'
46
+compare_output "case fallthrough default" 'X=unknown; case $X in a) echo a;; b) echo b;; *) echo default;; esac'
47
+
48
+section "5. compound conditions"
49
+compare_output "and chain both true" 'test -d /tmp && test -d /var && echo both'
50
+compare_output "or chain first false" 'false || echo fallback'
51
+compare_output "and-or chain" 'true && false || echo recovered'
52
+compare_output "negation" '! false && echo negated'
53
+compare_output "double negation" '! ! true; echo $?'
54
+compare_output "complex condition" '[ -d /tmp ] && [ -d /var ] || echo missing'
55
+compare_output "grouped conditions" '{ true && true; } && echo both'
56
+
57
+section "6. builtins in loops"
58
+compare_output "for in list" 'for i in 1 2 3; do echo $i; done'
59
+compare_output "for with cmd sub in list" 'for f in $(echo a b c); do echo $f; done'
60
+compare_output "c-style for" 'for ((i=0; i<3; i++)); do echo $i; done'
61
+compare_output "echo in while loop" 'i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done'
62
+compare_output "variable survives non-pipeline loop" 'i=0; while [ $i -lt 3 ]; do i=$((i+1)); done; echo $i'
63
+compare_output "read loop from heredoc" 'while read line; do echo "got:$line"; done <<EOF
64
+x
65
+y
66
+z
67
+EOF'
68
+compare_output "for with brace expansion" 'for i in {1..5}; do echo $i; done'
69
+compare_output "for with glob" "touch $TEST_TMPDIR/cf_a.txt $TEST_TMPDIR/cf_b.txt; for f in $TEST_TMPDIR/cf_*.txt; do basename \$f; done | sort"
70
+
71
+section "7. loop exit status"
72
+compare_output "exit status from loop body" 'for cmd in true false true; do $cmd; echo $?; done'
73
+compare_output "while false never runs" 'while false; do echo never; done; echo $?'
74
+compare_output "for loop exit status" 'for i in 1; do true; done; echo $?'
75
+compare_output "for loop last cmd status" 'for i in 1; do false; done; echo $?'
76
+
77
+section "8. nested control flow"
78
+compare_output "if inside for" 'for i in 1 2 3; do if [ $i -eq 2 ]; then echo even; else echo odd; fi; done'
79
+compare_output "for inside if" 'if true; then for i in 1 2; do echo $i; done; fi'
80
+compare_output "while inside for" 'for i in 1 2; do j=0; while [ $j -lt 2 ]; do echo "$i.$j"; j=$((j+1)); done; done'
81
+compare_output "case inside for" 'for x in a b c; do case $x in a) echo first;; b) echo second;; *) echo other;; esac; done'
82
+
83
+print_summary
suites/builtins/integration/test_int_error_handling.shadded
@@ -0,0 +1,73 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-error-handling]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: error handling with set -e, pipefail, and error propagation
9
+
10
+section "1. set -e (errexit) basics"
11
+compare_output "no error continues" 'set -e; true; echo reached'
12
+compare_exit "error exits" 'set -e; false; echo unreachable'
13
+compare_output "OR suppresses errexit" 'set -e; false || true; echo reached'
14
+compare_output "if condition doesnt trigger" 'set -e; if false; then :; fi; echo reached'
15
+compare_output "while condition doesnt trigger" 'set -e; while false; do :; done; echo reached'
16
+compare_output "negation doesnt trigger" 'set -e; ! false; echo reached'
17
+compare_output "AND left fail doesnt exit alone" 'set -e; false && true; echo reached'
18
+compare_output "set +e disables" 'set -e; set +e; false; echo reached'
19
+compare_output "errexit with command sub" 'set -e; echo $(true); echo reached'
20
+
21
+section "2. set -e in subshells"
22
+compare_output "subshell error exits subshell" 'set -e; (false) || echo recovered'
23
+compare_exit "errexit in subshell" '(set -e; false; echo unreachable)'
24
+compare_output "errexit subshell status" '(set -e; false; echo unreachable); echo $?'
25
+compare_output "parent errexit child fails" 'set -e; (false) || true; echo reached'
26
+
27
+section "3. set -e in functions"
28
+compare_exit "errexit function fails" 'set -e; f() { false; echo unreachable; }; f; echo after'
29
+compare_exit "errexit inside function" 'f() { set -e; false; echo unreachable; }; f'
30
+compare_output "errexit function in conditional" 'set -e; f() { false; }; if f; then echo yes; else echo no; fi; echo after'
31
+compare_output "errexit with return" 'set -e; f() { return 1; }; f || echo caught; echo after'
32
+
33
+section "4. pipefail"
34
+compare_output "pipefail first fails" 'set -o pipefail; false | true; echo $?'
35
+compare_output "pipefail middle fails" 'set -o pipefail; true | false | true; echo $?'
36
+compare_output "pipefail all succeed" 'set -o pipefail; true | true; echo $?'
37
+compare_output "pipefail all fail" 'set -o pipefail; false | false; echo $?'
38
+compare_exit "errexit plus pipefail" 'set -eo pipefail; false | true; echo unreachable'
39
+compare_output "pipefail disabled by default" 'false | true; echo $?'
40
+compare_output "pipefail with echo" 'set -o pipefail; echo hello | true; echo $?'
41
+
42
+section "5. error propagation"
43
+compare_output "function return status" 'f() { return 1; }; f; echo $?'
44
+compare_output "subshell exit status" '(exit 2); echo $?'
45
+compare_output "eval preserves status" 'eval "false"; echo $?'
46
+compare_output "eval true status" 'eval "true"; echo $?'
47
+compare_output "source preserves status" "echo 'false' > $TEST_TMPDIR/efalse.sh; source $TEST_TMPDIR/efalse.sh; echo \$?"
48
+compare_output "cmd sub status" 'X=$(false); echo $?'
49
+compare_output "cmd sub true status" 'X=$(true); echo $?'
50
+compare_output "last in pipeline" 'true | false; echo $?'
51
+compare_output "negated status" '! true; echo $?'
52
+compare_output "negated false status" '! false; echo $?'
53
+
54
+section "6. builtin error handling"
55
+compare_output "cd failure" 'cd /nonexistent_xyz 2>/dev/null; echo $?'
56
+compare_output "source failure" 'source /nonexistent_file.sh 2>/dev/null; echo $?'
57
+compare_exit "readonly violation" 'readonly ERX=1; ERX=2 2>/dev/null'
58
+compare_output "unset readonly" 'readonly ERY=1; unset ERY 2>/dev/null; echo $?'
59
+compare_output "command not found" 'nonexistent_cmd_xyz 2>/dev/null; echo $?'
60
+compare_output "read with no input" 'read VAR < /dev/null; echo $?'
61
+compare_output "test failure" 'test 1 -eq 2; echo $?'
62
+compare_output "false builtin" 'false; echo $?'
63
+compare_output "true builtin" 'true; echo $?'
64
+
65
+section "7. set -e with conditionals (should NOT exit)"
66
+compare_output "set -e if false" 'set -e; if false; then echo yes; fi; echo reached'
67
+compare_output "set -e while false" 'set -e; while false; do echo no; done; echo reached'
68
+compare_output "set -e until true" 'set -e; until true; do echo no; done; echo reached'
69
+compare_output "set -e test in if" 'set -e; if test 1 -eq 2; then echo no; fi; echo reached'
70
+compare_output "set -e bracket in if" 'set -e; if [ 1 -eq 2 ]; then echo no; fi; echo reached'
71
+compare_output "set -e negation" 'set -e; ! false; echo reached'
72
+
73
+print_summary
suites/builtins/integration/test_int_expansion.shadded
@@ -0,0 +1,99 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-expansion]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: builtins with parameter, arithmetic, and command substitution
9
+
10
+section "1. parameter expansion with builtins"
11
+compare_output "default value" 'X=hello; echo ${X:-default}'
12
+compare_output "unset default" 'echo ${UNSET_VAR_XYZ:-fallback}'
13
+compare_output "alternate value set" 'X=hello; echo ${X:+alternate}'
14
+compare_output "alternate value unset" 'echo ${UNSET_VAR_XYZ:+alternate}'
15
+compare_output "prefix strip" 'X=hello; echo ${X#h}'
16
+compare_output "prefix strip greedy" 'X=hello; echo ${X##*l}'
17
+compare_output "suffix strip" 'X=hello; echo ${X%o}'
18
+compare_output "suffix strip greedy" 'X=hello; echo ${X%%l*}'
19
+compare_output "substitution" 'X=hello_world; echo ${X/world/earth}'
20
+compare_output "substitution global" 'X=ababa; echo ${X//a/x}'
21
+compare_output "string length" 'X=hello; echo ${#X}'
22
+compare_output "assign default" 'unset ADEF; echo ${ADEF:=assigned}; echo $ADEF'
23
+compare_output "substring" 'X=hello; echo ${X:1:3}'
24
+compare_output "uppercase" 'X=hello; echo ${X^^}'
25
+compare_output "lowercase" 'X=HELLO; echo ${X,,}'
26
+compare_output "first char upper" 'X=hello; echo ${X^}'
27
+compare_output "first char lower" 'X=HELLO; echo ${X,}'
28
+
29
+section "2. array expansion with builtins"
30
+compare_output "all elements" 'arr=(a b c); echo ${arr[@]}'
31
+compare_output "element count" 'arr=(a b c); echo ${#arr[@]}'
32
+compare_output "index access" 'arr=(a b c); echo ${arr[1]}'
33
+compare_output "first element" 'arr=(a b c); echo ${arr[0]}'
34
+compare_output "last element" 'arr=(a b c); echo ${arr[-1]}'
35
+compare_output "array slice" 'arr=(a b c d e); echo ${arr[@]:1:3}'
36
+compare_output "iterate array" 'arr=(a b c); for x in "${arr[@]}"; do echo $x; done'
37
+compare_output "array length of element" 'arr=(hello world); echo ${#arr[0]}'
38
+compare_output "assoc array access" 'declare -A map; map[key]=value; echo ${map[key]}'
39
+compare_output "assoc array keys" 'declare -A m; m[a]=1; m[b]=2; echo ${!m[@]} | tr " " "\n" | sort | tr "\n" " "; echo'
40
+compare_output "array append" 'arr=(a b); arr+=(c); echo ${arr[@]}'
41
+compare_output "array unset element" 'arr=(a b c); unset arr[1]; echo ${arr[@]}'
42
+
43
+section "3. arithmetic expansion"
44
+compare_output "basic add" 'echo $((3 + 4))'
45
+compare_output "variable in arithmetic" 'X=5; echo $((X * 2))'
46
+compare_output "exponent" 'echo $((2 ** 10))'
47
+compare_output "grouping" 'echo $(( (3 + 4) * 2 ))'
48
+compare_output "modulo" 'echo $((17 % 5))'
49
+compare_output "division" 'echo $((10 / 3))'
50
+compare_output "comparison in arithmetic" 'echo $((5 > 3))'
51
+compare_output "ternary" 'X=5; echo $(( X > 3 ? 1 : 0 ))'
52
+compare_output "increment" 'X=5; echo $((++X)); echo $X'
53
+compare_output "decrement" 'X=5; echo $((--X)); echo $X'
54
+compare_output "compound assignment" 'X=10; echo $((X += 5)); echo $X'
55
+compare_output "bitwise and" 'echo $((12 & 10))'
56
+compare_output "bitwise or" 'echo $((12 | 10))'
57
+compare_output "left shift" 'echo $((1 << 4))'
58
+
59
+section "4. command substitution"
60
+compare_output "basic cmd sub" 'echo "$(echo hello)"'
61
+compare_output "nested cmd sub" 'echo "$(echo $(echo nested))"'
62
+compare_output "cd in cmd sub" 'X=$(cd /tmp && pwd); echo $X'
63
+compare_output "exit status from cmd sub" 'X=$(true); echo $?'
64
+compare_output "false status from cmd sub" 'X=$(false); echo $?'
65
+compare_output "backtick form" 'echo `echo backtick`'
66
+compare_output "cmd sub preserves newlines in var" 'X=$(printf "a\nb"); echo "$X"'
67
+compare_output "cmd sub strips trailing newline" 'X=$(echo hello); echo $X'
68
+compare_output "cmd sub in arithmetic" 'echo $(( $(echo 3) + $(echo 4) ))'
69
+compare_output "nested cmd sub with processing" 'echo $(echo $(echo hello) | tr a-z A-Z)'
70
+
71
+section "5. tilde expansion"
72
+compare_output "tilde expands to HOME" 'echo ~ | grep -q / && echo yes'
73
+compare_output "tilde with path" 'echo ~/subdir | grep -q / && echo yes'
74
+compare_output "cd with tilde" 'cd ~ && echo $? || echo failed'
75
+
76
+section "6. word splitting and IFS"
77
+compare_output "IFS colon split" 'IFS=:; read A B C <<< "one:two:three"; echo "$A $B $C"'
78
+compare_output "custom IFS in for" 'IFS=,; for x in $(echo "a,b,c"); do echo $x; done'
79
+compare_output "word splitting with set" 'X="a  b  c"; set -- $X; echo $#'
80
+compare_output "IFS default splitting" 'X="a b c"; set -- $X; echo $1 $2 $3'
81
+compare_output "IFS empty no splitting" 'IFS=""; X="a b c"; set -- $X; echo $#'
82
+
83
+section "7. brace expansion"
84
+compare_output "comma brace" 'echo {a,b,c}'
85
+compare_output "range brace" 'echo {1..5}'
86
+compare_output "range with step" 'echo {1..10..2}'
87
+compare_output "brace with prefix" 'echo file.{txt,md,sh}'
88
+compare_output "nested brace" 'echo {a,b}{1,2}'
89
+
90
+section "8. quoting interaction"
91
+compare_output "double quotes preserve" 'X="hello world"; echo "$X"'
92
+compare_output "single quotes literal" 'X="hello world"; echo '"'"'$X'"'"''
93
+compare_output "nested quoting in cmd sub" 'echo "$(echo "nested quotes")"'
94
+compare_output "escaped dollar" 'echo \$HOME'
95
+compare_output "escaped backtick" 'echo \`echo hi\`'
96
+compare_output "mixed quoting" "echo 'single' \"double\" plain"
97
+compare_output "quote in variable" 'X="it'"'"'s"; echo "$X"'
98
+
99
+print_summary
suites/builtins/integration/test_int_function.shadded
@@ -0,0 +1,71 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-function]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: builtins in/with functions
9
+
10
+section "1. scope interaction"
11
+compare_output "local variable scope" 'x=global; f() { local x=local; echo $x; }; f; echo $x'
12
+compare_output "declare scope in function" 'f() { declare -i n=3+4; echo $n; }; f; echo ${n:-unset}'
13
+compare_output "export from function persists" 'f() { export FE=val; }; f; echo $FE'
14
+compare_output "local array scope" 'f() { local -a arr=(1 2 3); echo ${arr[@]}; }; f; echo ${arr:-unset}'
15
+compare_output "readonly from function persists" 'f() { readonly FR=frozen; }; f; echo $FR'
16
+compare_output "declare -g in function" 'f() { declare -g GV=global_val; }; f; echo $GV'
17
+compare_output "local doesnt affect global" 'X=outer; f() { local X=inner; }; f; echo $X'
18
+compare_output "unset local reveals global" 'X=global; f() { local X=local; unset X; echo ${X:-gone}; }; f; echo $X'
19
+compare_output "multiple locals" 'f() { local A=1 B=2 C=3; echo $A $B $C; }; f; echo ${A:-x} ${B:-y} ${C:-z}'
20
+
21
+section "2. return and exit status"
22
+compare_output "return 0" 'f() { return 0; }; f; echo $?'
23
+compare_output "return 42" 'f() { return 42; }; f; echo $?'
24
+compare_output "early return" 'f() { echo before; return; echo after; }; f'
25
+compare_output "implicit return status" 'f() { false; }; f; echo $?'
26
+compare_output "return in conditional" 'f() { return 0; }; f && echo yes'
27
+compare_output "return from nested if" 'f() { if true; then return 5; fi; echo unreachable; }; f; echo $?'
28
+compare_output "return preserves value" 'f() { true; return; }; f; echo $?'
29
+
30
+section "3. nested functions"
31
+compare_output "simple nested call" 'a() { echo a; }; b() { a; echo b; }; b'
32
+compare_output "inner function def" 'outer() { inner() { echo inner; }; inner; }; outer'
33
+compare_output "scope in nested" 'f() { local x=1; g() { echo $x; }; g; }; f'
34
+compare_output "chain of three" 'a() { echo 1; }; b() { a; echo 2; }; c() { b; echo 3; }; c'
35
+compare_output "mutual call" 'a() { echo a; }; b() { echo b; a; }; b'
36
+compare_output "function overwrite" 'f() { echo old; }; f; f() { echo new; }; f'
37
+
38
+section "4. recursion"
39
+compare_output "countdown" 'countdown() { [ $1 -le 0 ] && return; echo $1; countdown $(($1 - 1)); }; countdown 5'
40
+compare_output "recursive sum" 'sum() { [ $1 -le 0 ] && echo 0 && return; echo $(( $1 + $(sum $(($1 - 1))) )); }; sum 5'
41
+compare_output "fibonacci" 'fib() { [ $1 -le 1 ] && echo $1 && return; echo $(( $(fib $(($1 - 1))) + $(fib $(($1 - 2))) )); }; fib 6'
42
+
43
+section "5. positional parameters"
44
+compare_output "dollar-hash dollar-1 dollar-2" 'f() { echo $# $1 $2; }; f a b c'
45
+compare_output "shift in function" 'f() { echo $1; shift; echo $1; }; f first second'
46
+compare_output "set in function" 'f() { set -- x y z; echo $@; }; f a b c'
47
+compare_output "set doesnt leak" 'set -- a b; f() { set -- x y z; echo $@; }; f; echo $@'
48
+compare_output "dollar-at quoted" 'f() { for arg in "$@"; do echo "arg:$arg"; done; }; f "hello world" foo'
49
+compare_output "dollar-star" 'f() { echo $*; }; f a b c'
50
+
51
+section "6. trap in functions"
52
+compare_output "RETURN trap" 'f() { trap "echo cleaned" RETURN; echo body; }; f'
53
+compare_output "ERR trap in function" 'f() { trap "echo err" ERR; false; }; f 2>/dev/null'
54
+compare_output "function as trap handler" 'cleanup() { echo bye; }; trap cleanup EXIT; echo main'
55
+compare_output "trap reset in function" 'f() { trap "echo trap1" RETURN; trap - RETURN; echo body; }; f'
56
+
57
+section "7. functions with builtins"
58
+compare_output "cd in function affects caller" 'ORIG=$(pwd); f() { cd /tmp && pwd; }; f; cd "$ORIG"'
59
+compare_output "eval in function" 'f() { eval "echo hello"; }; f'
60
+compare_output "source in function" "echo 'echo sourced' > $TEST_TMPDIR/fsrc.sh; f() { source $TEST_TMPDIR/fsrc.sh; }; f"
61
+compare_output "hash in function" 'f() { hash -r 2>/dev/null; echo ok; }; f'
62
+compare_output "type in function" 'f() { type echo 2>/dev/null | head -1; }; f'
63
+compare_output "printf in function" 'f() { printf "%d %d %d\n" 1 2 3; }; f'
64
+compare_output "test in function" 'f() { test 5 -gt 3 && echo yes; }; f'
65
+
66
+section "8. getopts in functions"
67
+compare_output "getopts basic in function" 'f() { OPTIND=1; while getopts "ab:" opt; do echo "$opt $OPTARG"; done; }; f -a -b val'
68
+compare_output "getopts called twice" 'f() { OPTIND=1; while getopts "x" opt; do echo $opt; done; }; f -x; f -x'
69
+compare_output "getopts with args after" 'f() { OPTIND=1; while getopts "a" opt; do echo $opt; done; shift $((OPTIND-1)); echo $1; }; f -a rest'
70
+
71
+print_summary
suites/builtins/integration/test_int_job_control.shadded
@@ -0,0 +1,55 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-job-control]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: job control, background processes, wait, kill
9
+# TTY-dependent tests are skipped when not connected to a terminal
10
+
11
+HAS_TTY=false
12
+[ -t 0 ] && HAS_TTY=true
13
+
14
+section "1. background execution"
15
+compare_output "background and wait" 'echo bg & wait; echo done'
16
+compare_output "background exit status via wait" 'true & wait $!; echo $?'
17
+compare_output "background false status" 'false & wait $!; echo $?'
18
+# Output order of concurrent background jobs is non-deterministic — sort to normalize
19
+TEST_NUM=$((TEST_NUM + 1))
20
+_jc_expected=$(run_with_timeout "$TEST_TIMEOUT" "$BASH_REF" -c 'echo a & echo b & wait; echo done' 2>&1 | sort)
21
+_jc_actual=$(run_with_timeout "$TEST_TIMEOUT" "$FORTSH_BIN" -c 'echo a & echo b & wait; echo done' 2>&1 | sort)
22
+if [ "$_jc_expected" = "$_jc_actual" ]; then pass "multiple background wait all"
23
+else fail "multiple background wait all" "$_jc_expected" "$_jc_actual"; fi
24
+compare_output "background dollar-bang" 'sleep 0.01 & [ -n "$!" ] && echo has_pid'
25
+compare_output "background preserves parent" 'X=before; (X=after) & wait; echo $X'
26
+
27
+section "2. wait"
28
+compare_output "wait specific pid" 'sleep 0.01 & PID=$!; wait $PID; echo $?'
29
+compare_output "wait captures exit" 'false & wait $!; echo $?'
30
+compare_output "wait for true" 'true & wait $!; echo $?'
31
+compare_output "wait no children" 'wait 2>/dev/null; echo $?'
32
+compare_output "wait nonexistent pid" 'wait 99999 2>/dev/null; echo $?'
33
+compare_output "wait all children" 'sleep 0.01 & sleep 0.01 & wait; echo $?'
34
+
35
+section "3. kill"
36
+compare_output "kill background process" 'sleep 10 & PID=$!; kill $PID 2>/dev/null; wait $PID 2>/dev/null; echo done'
37
+compare_output "kill -0 checks existence" 'sleep 10 & PID=$!; kill -0 $PID 2>/dev/null && echo alive; kill $PID 2>/dev/null; wait $PID 2>/dev/null'
38
+compare_output "kill nonexistent pid" 'kill 99999 2>/dev/null; echo $?'
39
+compare_output "kill -l lists signals" 'kill -l | head -1 | grep -q "[A-Z]" && echo has_signals'
40
+
41
+section "4. job control (TTY-dependent)"
42
+if $HAS_TTY; then
43
+    compare_output "jobs empty" 'jobs 2>/dev/null; echo $?'
44
+else
45
+    skip "jobs empty" "no TTY"
46
+    skip "fg no jobs" "no TTY"
47
+    skip "bg no jobs" "no TTY"
48
+fi
49
+
50
+section "5. coproc"
51
+compare_output "coproc with brace group" 'coproc { cat; }; echo hello >&${COPROC[1]}; sleep 0.1; read line <&${COPROC[0]}; echo $line'
52
+compare_output "named coproc" 'coproc MYCAT { cat; }; echo test >&${MYCAT[1]}; sleep 0.1; read line <&${MYCAT[0]}; echo $line'
53
+compare_output "coproc output" 'coproc { echo from_coproc; sleep 10; }; sleep 0.1; read line <&${COPROC[0]}; echo $line; kill $COPROC_PID 2>/dev/null; wait $COPROC_PID 2>/dev/null'
54
+
55
+print_summary
suites/builtins/integration/test_int_pipeline.shadded
@@ -0,0 +1,67 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-pipeline]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: builtins interacting with pipelines
9
+
10
+section "1. builtins as pipeline source"
11
+compare_output "echo into pipeline" 'echo hello world | tr a-z A-Z'
12
+compare_output "printf into pipeline" 'printf "%s\n" a b c | sort -r'
13
+compare_output "echo pipe to wc" 'echo "one two three" | wc -w | tr -d " "'
14
+compare_output "pwd in pipeline" 'pwd | grep -q / && echo yes'
15
+compare_output "type in pipeline" 'type echo | head -1'
16
+compare_output "printf format in pipeline" 'printf "%d\n" 3 1 2 | sort -n'
17
+compare_output "set outputs to pipeline" 'X=hello; set | grep "^X=" | head -1'
18
+compare_output "declare -p in pipeline" 'X=42; declare -p X 2>/dev/null | head -1'
19
+compare_output "export -p in pipeline" 'export EX=val; export -p | grep "^declare -x EX=" | head -1'
20
+compare_output "alias in pipeline" 'alias myalias="echo test" 2>/dev/null; alias | grep myalias | head -1; unalias myalias 2>/dev/null'
21
+
22
+section "2. builtins as pipeline sink"
23
+compare_output "read from pipeline" 'echo hello | read VAR; echo $VAR'
24
+compare_output "read line from pipeline" 'printf "a\nb\nc\n" | while read line; do echo "got:$line"; done'
25
+compare_output "read multiple vars from pipeline" 'echo "one two three" | { read A B C; echo "$A $B $C"; }'
26
+compare_output "read in while from seq" 'seq 3 | while read n; do echo "num:$n"; done'
27
+compare_output "multi-stage to read" 'printf "c\na\nb\n" | sort | while read x; do echo "sorted:$x"; done'
28
+compare_output "read with IFS from pipeline" 'echo "a:b:c" | { IFS=: read A B C; echo "$A $B $C"; }'
29
+
30
+section "3. builtins mid-pipeline"
31
+compare_output "builtin mid-pipeline sort head" 'echo -e "b\na\nc" | sort | head -1'
32
+compare_output "echo tr wc chain" 'echo "hello world" | tr a-z A-Z | wc -c | tr -d " "'
33
+compare_output "printf sort tail chain" 'printf "%d\n" 5 1 3 2 4 | sort -n | tail -1'
34
+compare_output "command substitution in pipeline" 'echo $(echo hello) | tr a-z A-Z'
35
+
36
+section "4. pipeline exit status"
37
+compare_output "last cmd status true" 'false | true; echo $?'
38
+compare_output "last cmd status false" 'true | false; echo $?'
39
+compare_output "pipefail first fails" 'set -o pipefail; false | true; echo $?'
40
+compare_output "pipefail middle fails" 'set -o pipefail; true | false | true; echo $?'
41
+compare_output "pipefail all succeed" 'set -o pipefail; true | true; echo $?'
42
+compare_output "negated pipeline" '! true; echo $?'
43
+compare_output "negated pipeline false" '! false; echo $?'
44
+compare_output "PIPESTATUS basic" 'true | false | true; echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]}'
45
+compare_output "PIPESTATUS all zero" 'true | true | true; echo ${PIPESTATUS[@]}'
46
+
47
+section "5. pipeline subshell semantics"
48
+compare_output "var set in pipeline doesnt leak" 'X=before; echo y | read X; echo $X'
49
+compare_output "cd in pipeline doesnt affect parent" 'cd /tmp | cat; pwd'
50
+# Filter out non-deterministic "Broken pipe" errors from comparison
51
+TEST_NUM=$((TEST_NUM + 1))
52
+_pp_expected=$(run_with_timeout "$TEST_TIMEOUT" "$BASH_REF" -c 'echo test | { export PVAR=pipe; echo $PVAR; }; echo ${PVAR:-unset}' 2>&1 | grep -v 'Broken pipe')
53
+_pp_actual=$(run_with_timeout "$TEST_TIMEOUT" "$FORTSH_BIN" -c 'echo test | { export PVAR=pipe; echo $PVAR; }; echo ${PVAR:-unset}' 2>&1 | grep -v 'Broken pipe')
54
+if [ "$_pp_expected" = "$_pp_actual" ]; then pass "export in pipeline segment"
55
+else fail "export in pipeline segment" "$_pp_expected" "$_pp_actual"; fi
56
+compare_output "cmd sub in pipeline" 'echo "$(echo hello)" | tr h H'
57
+compare_output "subshell in pipeline" '(echo from_sub) | tr a-z A-Z'
58
+
59
+section "6. pipeline edge cases"
60
+compare_output "empty pipeline source" 'echo -n "" | wc -c | tr -d " "'
61
+compare_output "large data through pipeline" 'printf "%0100s\n" | wc -c | tr -d " "'
62
+compare_output "multiple echo into pipeline" '{ echo a; echo b; echo c; } | sort -r'
63
+compare_output "pipeline with command group" '{ echo 1; echo 2; } | { while read n; do echo "got:$n"; done; }'
64
+compare_output "pipeline preserves whitespace" 'echo "  hello  " | cat'
65
+compare_output "pipeline with exit in subshell" '(echo before; exit 0; echo after) | cat'
66
+
67
+print_summary
suites/builtins/integration/test_int_redirection.shadded
@@ -0,0 +1,90 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-redirection]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: builtins with I/O redirections
9
+
10
+section "1. output redirections"
11
+compare_output "echo to file" "echo hello > $TEST_TMPDIR/out1; cat $TEST_TMPDIR/out1"
12
+compare_output "echo append" "echo first > $TEST_TMPDIR/out2; echo second >> $TEST_TMPDIR/out2; cat $TEST_TMPDIR/out2"
13
+compare_output "printf to file" "printf '%s\n' a b > $TEST_TMPDIR/out3; cat $TEST_TMPDIR/out3"
14
+compare_output "echo to stderr" 'echo err >&2 2>/dev/null; echo $?'
15
+compare_output "echo to dev null" 'echo test > /dev/null; echo $?'
16
+compare_output "multiple echo to same file" "echo a > $TEST_TMPDIR/out4; echo b >> $TEST_TMPDIR/out4; echo c >> $TEST_TMPDIR/out4; cat $TEST_TMPDIR/out4"
17
+compare_output "overwrite existing file" "echo first > $TEST_TMPDIR/out5; echo second > $TEST_TMPDIR/out5; cat $TEST_TMPDIR/out5"
18
+
19
+section "2. input redirections"
20
+compare_output "read from file" "echo data > $TEST_TMPDIR/in1; read VAR < $TEST_TMPDIR/in1; echo \$VAR"
21
+compare_output "cat from redirect" "echo hello > $TEST_TMPDIR/in2; cat < $TEST_TMPDIR/in2"
22
+compare_output "sort from redirect" "printf 'c\na\nb\n' > $TEST_TMPDIR/in3; sort < $TEST_TMPDIR/in3"
23
+compare_output "while read from redirect" "printf 'a\nb\nc\n' > $TEST_TMPDIR/in4; while read line; do echo \"got:\$line\"; done < $TEST_TMPDIR/in4"
24
+compare_output "read multiple vars from file" "echo 'one two three' > $TEST_TMPDIR/in5; read A B C < $TEST_TMPDIR/in5; echo \$A \$B \$C"
25
+
26
+section "3. file descriptor manipulation"
27
+compare_output "exec open write close" "exec 3>$TEST_TMPDIR/fd1; echo hello >&3; exec 3>&-; cat $TEST_TMPDIR/fd1"
28
+compare_output "exec open read close" "echo data > $TEST_TMPDIR/fd2; exec 3<$TEST_TMPDIR/fd2; read VAR <&3; exec 3<&-; echo \$VAR"
29
+compare_output "multiple fd open" "exec 4>$TEST_TMPDIR/fd3; exec 5>$TEST_TMPDIR/fd4; echo a >&4; echo b >&5; exec 4>&- 5>&-; cat $TEST_TMPDIR/fd3 $TEST_TMPDIR/fd4"
30
+compare_output "dup stdout to fd" "exec 3>&1; echo hello >&3; exec 3>&-"
31
+compare_output "stderr to file" "echo err >&2 2>$TEST_TMPDIR/fd5; cat $TEST_TMPDIR/fd5"
32
+compare_output "stderr and stdout to same file" "echo out; echo err >&2 > $TEST_TMPDIR/fd6 2>&1; cat $TEST_TMPDIR/fd6"
33
+
34
+section "4. here-documents"
35
+compare_output "read from heredoc" 'read VAR <<EOF
36
+hello
37
+EOF
38
+echo $VAR'
39
+compare_output "cat with heredoc" 'cat <<EOF
40
+line1
41
+line2
42
+EOF'
43
+compare_output "while read from heredoc" 'while read line; do echo "got:$line"; done <<EOF
44
+alpha
45
+beta
46
+gamma
47
+EOF'
48
+compare_output "heredoc with variable expansion" 'X=world; cat <<EOF
49
+hello $X
50
+EOF'
51
+compare_output "quoted heredoc no expansion" 'X=world; cat <<'"'"'EOF'"'"'
52
+hello $X
53
+EOF'
54
+compare_output "heredoc with command sub" 'cat <<EOF
55
+today is $(echo wednesday)
56
+EOF'
57
+compare_output "heredoc with arithmetic" 'cat <<EOF
58
+result is $((3 + 4))
59
+EOF'
60
+compare_output "empty heredoc" 'cat <<EOF
61
+EOF'
62
+
63
+section "5. here-strings"
64
+compare_output "read from here-string" 'read VAR <<< "hello"; echo $VAR'
65
+compare_output "read multiple from here-string" 'read A B <<< "one two"; echo $A $B'
66
+compare_output "cat from here-string" 'cat <<< "hello world"'
67
+compare_output "here-string with expansion" 'X=world; cat <<< "hello $X"'
68
+compare_output "here-string with cmd sub" 'cat <<< "$(echo computed)"'
69
+compare_output "here-string whitespace" 'read VAR <<< "  spaces  "; echo "[$VAR]"'
70
+
71
+section "6. redirections with builtin output"
72
+compare_output "type to file" "type echo > $TEST_TMPDIR/rtype 2>&1; head -1 $TEST_TMPDIR/rtype"
73
+compare_output "set to dev null" 'X=test; set > /dev/null; echo $?'
74
+compare_output "alias to file" "alias myalias='echo hi' 2>/dev/null; alias > $TEST_TMPDIR/ralias 2>/dev/null; echo done"
75
+compare_output "redirect error messages" 'cd /nonexistent_xyz 2>/dev/null; echo $?'
76
+compare_output "test with file redirect" "echo content > $TEST_TMPDIR/rtst; test -s $TEST_TMPDIR/rtst && echo nonempty"
77
+compare_output "declare -p redirect" "X=42; declare -p X > $TEST_TMPDIR/rdecl 2>/dev/null; cat $TEST_TMPDIR/rdecl 2>/dev/null"
78
+
79
+section "7. noclobber"
80
+compare_output "noclobber prevents overwrite" "echo first > $TEST_TMPDIR/noclob; set -C; { echo second > $TEST_TMPDIR/noclob; } 2>/dev/null; echo \$?; set +C; cat $TEST_TMPDIR/noclob"
81
+compare_output "noclobber allows append" "echo first > $TEST_TMPDIR/noclob2; set -C; echo second >> $TEST_TMPDIR/noclob2; set +C; cat $TEST_TMPDIR/noclob2"
82
+compare_output "noclobber force override" "echo first > $TEST_TMPDIR/noclob3; set -C; echo second >| $TEST_TMPDIR/noclob3; set +C; cat $TEST_TMPDIR/noclob3"
83
+
84
+section "8. process substitution"
85
+compare_output "input process sub" 'cat <(echo hello)'
86
+compare_output "diff two process subs" 'diff <(echo a) <(echo a) && echo same'
87
+compare_output "while read from process sub" 'while read line; do echo "got:$line"; done < <(printf "a\nb\n")'
88
+compare_output "process sub with sort" 'sort <(printf "c\na\nb\n")'
89
+
90
+print_summary
suites/builtins/integration/test_int_scope_env.shadded
@@ -0,0 +1,61 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-scope-env]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: variable scope, export, source, environment inheritance
9
+
10
+section "1. export inheritance"
11
+compare_output "exported var in subshell" 'export SEX=hello; (echo $SEX)'
12
+compare_output "non-exported not in subshell cmd" 'SEX2=hello; echo ${SEX2:-unset}'
13
+compare_output "export from function" 'f() { export SEF=val; }; f; echo $SEF'
14
+compare_output "export -n removes export" 'export SEN=val; export -n SEN; echo $SEN'
15
+compare_output "export with value" 'export SEV=hello; echo $SEV'
16
+compare_output "export existing var" 'SEE=existing; export SEE; echo $SEE'
17
+
18
+section "2. source/dot scope"
19
+compare_output "var from source" "echo 'SSV=sourced' > $TEST_TMPDIR/scope1.sh; source $TEST_TMPDIR/scope1.sh; echo \$SSV"
20
+compare_output "source overwrites var" "X_SC=before; echo 'X_SC=after' > $TEST_TMPDIR/scope2.sh; source $TEST_TMPDIR/scope2.sh; echo \$X_SC"
21
+compare_output "return from source" "echo 'return 42' > $TEST_TMPDIR/scope3.sh; source $TEST_TMPDIR/scope3.sh; echo \$?"
22
+compare_output "cd from source" "echo 'cd /tmp' > $TEST_TMPDIR/scope4.sh; ORIG=\$(pwd); source $TEST_TMPDIR/scope4.sh; pwd; cd \"\$ORIG\""
23
+compare_output "function def from source" "echo 'sf() { echo sourced_func; }' > $TEST_TMPDIR/scope5.sh; source $TEST_TMPDIR/scope5.sh; sf"
24
+compare_output "multiple source" "echo 'A_SC=1' > $TEST_TMPDIR/scope6a.sh; echo 'B_SC=2' > $TEST_TMPDIR/scope6b.sh; source $TEST_TMPDIR/scope6a.sh; source $TEST_TMPDIR/scope6b.sh; echo \$A_SC \$B_SC"
25
+compare_output "source with args" "echo 'echo \$1 \$2' > $TEST_TMPDIR/scope7.sh; source $TEST_TMPDIR/scope7.sh hello world"
26
+compare_output "dot command same as source" "echo 'DSV=dotted' > $TEST_TMPDIR/scope8.sh; . $TEST_TMPDIR/scope8.sh; echo \$DSV"
27
+
28
+section "3. eval scope"
29
+compare_output "eval modifies scope" 'eval "ESV=from_eval"; echo $ESV'
30
+compare_output "eval export" 'eval "export ESE=eval_export"; echo $ESE'
31
+compare_output "eval readonly" 'eval "readonly ESR=eval_readonly"; echo $ESR'
32
+compare_output "eval function def" 'eval "esf() { echo eval_func; }"; esf'
33
+compare_output "eval preserves vars" 'X_EV=before; eval "echo \$X_EV"'
34
+compare_output "nested eval" 'eval "eval \"echo nested_eval\""'
35
+
36
+section "4. declare -g"
37
+compare_output "declare -g from function" 'f() { declare -g DGV=global_val; }; f; echo $DGV'
38
+compare_output "declare without -g stays local" 'f() { declare DLV=local_val; }; f; echo ${DLV:-unset}'
39
+compare_output "declare -g overwrites" 'DGO=old; f() { declare -g DGO=new; }; f; echo $DGO'
40
+
41
+section "5. IFS manipulation scope"
42
+compare_output "IFS in function local" 'f() { local IFS=:; read A B <<< "x:y"; echo $A $B; }; f; echo "${IFS:-default}" | cat -v'
43
+compare_output "IFS change persists" 'IFS=:; read A B <<< "x:y"; echo $A; IFS=" "'
44
+compare_output "IFS in subshell" '(IFS=:; read A B <<< "x:y"; echo $A $B)'
45
+compare_output "IFS reset" 'IFS=:; unset IFS; echo "a b c" | { read A B C; echo $A; }'
46
+
47
+section "6. readonly scope"
48
+compare_output "readonly visible in functions" 'readonly SRV=fixed; f() { echo $SRV; }; f'
49
+compare_output "readonly visible in subshells" 'readonly SRS=fixed; (echo $SRS)'
50
+compare_output "readonly from function persists" 'f() { readonly SRF=in_func; }; f; echo $SRF'
51
+compare_output "readonly blocks reassignment" 'readonly SRB=fixed; { SRB=other; } 2>/dev/null; echo $SRB'
52
+compare_output "readonly blocks unset" 'readonly SRU=fixed; unset SRU 2>/dev/null; echo $SRU'
53
+
54
+section "7. variable cleanup"
55
+compare_output "unset removes var" 'SUV=temp; unset SUV; echo ${SUV:-gone}'
56
+compare_output "unset array" 'SUA=(1 2 3); unset SUA; echo ${SUA:-gone}'
57
+compare_output "unset function" 'suf() { echo hi; }; unset -f suf; suf 2>/dev/null; echo $?'
58
+compare_output "unset in function" 'SUF2=outer; f() { unset SUF2; }; f; echo ${SUF2:-gone}'
59
+compare_output "unset exported var" 'export SUE=val; unset SUE; echo ${SUE:-gone}'
60
+
61
+print_summary
suites/builtins/integration/test_int_signal_trap.shadded
@@ -0,0 +1,54 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-signal-trap]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: traps and signal handling
9
+
10
+section "1. EXIT trap"
11
+compare_output "basic EXIT trap" 'trap "echo bye" EXIT; echo main'
12
+compare_output "EXIT with explicit exit" 'trap "echo bye" EXIT; exit 0'
13
+compare_output "function as EXIT handler" 'f() { echo cleaned; }; trap f EXIT; echo main'
14
+compare_output "subshell has own EXIT" 'trap "echo outer" EXIT; (trap "echo inner" EXIT; echo sub)'
15
+compare_output "EXIT trap sees exit status" 'trap "echo status=$?" EXIT; exit 42'
16
+compare_output "EXIT trap on implicit exit" 'trap "echo bye" EXIT; true'
17
+compare_output "EXIT trap with multiple commands" 'trap "echo a; echo b" EXIT; echo main'
18
+
19
+section "2. ERR trap"
20
+compare_output "ERR on false" 'trap "echo error" ERR; false; echo after'
21
+compare_output "ERR not in OR chain" 'trap "echo error" ERR; false || true; echo ok'
22
+compare_output "ERR not in if condition" 'trap "echo error" ERR; if false; then echo no; fi; echo ok'
23
+compare_output "ERR not in while condition" 'trap "echo error" ERR; while false; do :; done; echo ok'
24
+compare_output "ERR not with negation" 'trap "echo error" ERR; ! false; echo ok'
25
+compare_output "ERR on command failure" 'trap "echo err" ERR; /nonexistent_xyz 2>/dev/null; echo after'
26
+
27
+section "3. RETURN trap"
28
+compare_output "RETURN in function" 'f() { trap "echo returned" RETURN; echo body; }; f'
29
+compare_output "RETURN fires on return" 'f() { trap "echo returned" RETURN; echo body; return 0; }; f'
30
+compare_output "RETURN on source" "echo 'echo sourced' > $TEST_TMPDIR/trap_src.sh; trap 'echo returned' RETURN; source $TEST_TMPDIR/trap_src.sh"
31
+compare_output "RETURN multiple calls" 'f() { trap "echo ret" RETURN; echo body; }; f; f; f'
32
+
33
+section "4. signal traps"
34
+compare_output "trap USR1" 'trap "echo caught" USR1; kill -USR1 $$; echo after'
35
+compare_output "ignore signal" 'trap "" TERM; kill -TERM $$ 2>/dev/null; echo survived'
36
+compare_output "trap INT" 'trap "echo interrupted" INT; kill -INT $$; echo after'
37
+compare_output "trap HUP" 'trap "echo hangup" HUP; kill -HUP $$; echo after'
38
+
39
+section "5. trap management"
40
+compare_output "overwrite trap" 'trap "echo a" EXIT; trap "echo b" EXIT'
41
+compare_output "clear trap" 'trap "echo a" EXIT; trap - EXIT; echo done'
42
+compare_output "display trap" 'trap "echo hello" EXIT; trap -p EXIT'
43
+compare_output "trap multiple signals" 'trap "echo caught" USR1 USR2; kill -USR1 $$; kill -USR2 $$; echo done'
44
+compare_output "trap -p shows all" 'trap "echo a" USR1; trap "echo b" USR2; trap -p | grep -cE "USR[12]" | tr -d " "'
45
+compare_output "trap list signals" 'trap -l >/dev/null 2>&1; echo $?'
46
+
47
+section "6. trap interaction with builtins"
48
+compare_output "trap doesnt change exit status" 'trap "echo bye" EXIT; false; echo $?'
49
+compare_output "trap with eval" 'trap "echo bye" EXIT; eval "echo evaled"'
50
+compare_output "trap in subshell" '(trap "echo sub_bye" EXIT; echo sub)'
51
+compare_output "trap preserved across commands" 'trap "echo bye" EXIT; echo a; echo b; echo c'
52
+compare_output "nested trap scopes" 'trap "echo outer_bye" EXIT; (trap "echo inner_bye" EXIT; echo inner); echo between'
53
+
54
+print_summary
suites/builtins/integration/test_int_subshell.shadded
@@ -0,0 +1,63 @@
1
+#!/bin/sh
2
+TEST_PREFIX="[int-subshell]"
3
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
4
+FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../../../bin/fortsh}"
5
+export FORTSH_BIN
6
+. "$SCRIPT_DIR/../test_harness.sh"
7
+
8
+# Integration tests: builtins in subshells
9
+
10
+section "1. variable isolation"
11
+compare_output "assignment doesnt leak" 'X=outer; (X=inner); echo $X'
12
+compare_output "export doesnt leak" '(export FOO=bar); echo ${FOO:-unset}'
13
+compare_output "unset doesnt affect parent" 'X=val; (unset X); echo $X'
14
+compare_output "declare doesnt leak" '(declare -i N=5); echo ${N:-unset}'
15
+compare_output "readonly persists in parent" 'readonly RO=val; (echo $RO)'
16
+compare_output "array in subshell doesnt leak" '(arr=(a b c)); echo ${arr:-unset}'
17
+compare_output "multiple vars in subshell" '(A=1; B=2; C=3); echo ${A:-x} ${B:-y} ${C:-z}'
18
+
19
+section "2. directory isolation"
20
+compare_output "cd doesnt affect parent" '(cd /tmp); pwd'
21
+compare_output "pushd doesnt affect parent" '(pushd /tmp >/dev/null 2>&1); pwd'
22
+compare_output "multiple cd in subshell" '(cd /tmp; cd /var); pwd'
23
+compare_output "pwd reflects subshell cd" '(cd /tmp; pwd)'
24
+compare_output "cd and var in subshell" '(cd /tmp; X=$(pwd)); echo ${X:-unset}'
25
+
26
+section "3. option isolation"
27
+compare_output "set -e doesnt leak" '(set -e); echo ok'
28
+compare_output "set in subshell only" '(set -- a b c; echo $#); echo $#'
29
+compare_output "shopt doesnt leak" '(shopt -s nullglob 2>/dev/null); echo ok'
30
+compare_output "set -u in subshell" '(set -u; echo ${SETVAR:-default})'
31
+
32
+section "4. trap isolation"
33
+compare_output "separate EXIT traps" 'trap "echo parent_bye" EXIT; (trap "echo child_bye" EXIT; echo sub_body)'
34
+compare_output "subshell EXIT fires at end" '(trap "echo inner_bye" EXIT; echo body); echo after'
35
+compare_output "trap in subshell independent" '(trap "echo sub_bye" EXIT; echo sub)'
36
+compare_output "parent trap survives subshell" 'trap "echo parent_exit" EXIT; (echo sub); echo main'
37
+
38
+section "5. nesting"
39
+compare_output "double nested subshell" '( (echo deep) )'
40
+compare_output "cmd sub inside subshell" '(echo $(echo nested))'
41
+compare_output "layered scope" '(X=1; (X=2; echo $X); echo $X)'
42
+compare_output "triple nested cmd sub" 'echo $(echo $(echo triple))'
43
+compare_output "nested with variable" '(A=outer; (A=inner; echo $A); echo $A)'
44
+compare_output "three level nesting" '(echo 1; (echo 2; (echo 3)))'
45
+
46
+section "6. exit status"
47
+compare_output "zero exit" '(exit 0); echo $?'
48
+compare_output "non-zero exit" '(exit 42); echo $?'
49
+compare_output "implicit exit status" '(false); echo $?'
50
+compare_output "OR chain with subshell failure" '(exit 1) || echo recovered'
51
+compare_output "AND chain with subshell success" '(exit 0) && echo continued'
52
+compare_output "nested subshell exit status" '( (exit 3) ); echo $?'
53
+compare_output "exit in nested" '(exit 0); (exit 1); echo $?'
54
+
55
+section "7. command substitution as implicit subshell"
56
+compare_output "cmd sub scope" 'X=outer; Y=$(X=inner; echo $X); echo $X $Y'
57
+compare_output "cd in cmd sub" 'Y=$(cd /tmp; pwd); pwd; echo $Y'
58
+compare_output "export in cmd sub" 'Y=$(export EV=val; echo $EV); echo ${EV:-unset} $Y'
59
+compare_output "cmd sub exit status" 'X=$(true); echo $?'
60
+compare_output "cmd sub false status" 'X=$(false); echo $?'
61
+compare_output "nested cmd sub" 'echo $(echo $(echo nested))'
62
+
63
+print_summary