| 1 | #!/bin/sh |
| 2 | TEST_PREFIX="[int-subshell]" |
| 3 | SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) |
| 4 | SHELL_BIN="${SHELL_BIN:?ERROR: SHELL_BIN must be set}" |
| 5 | export SHELL_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 |