| 1 | #!/bin/sh |
| 2 | TEST_PREFIX="[int-function]" |
| 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/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 |