638 lines
15 KiB
YAML
638 lines
15 KiB
YAML
name: "Call stack"
|
|
common_test_files:
|
|
- path: "callstack_helpers.sh"
|
|
contents: |
|
|
# Helper to dump frame-independent call stack state.
|
|
# Optionally takes a prefix string to use in all output.
|
|
dump_source_info() {
|
|
local prefix="${1:-}"
|
|
|
|
# Normalize shell name to make sure it matches.
|
|
# TODO: Once all tests are run on 5.3, this will need to be revisited.
|
|
echo "${prefix} caller: $(caller 2>&1 || echo '<failed>')" | sed -e "s|$0|main|g" | sed -e "s/environment/main/g"
|
|
|
|
echo "${prefix} LINENO: ${LINENO}"
|
|
echo "${prefix} FUNCNAME[@]: ${FUNCNAME[*]}"
|
|
|
|
# Normalize shell name to make sure it matches.
|
|
# TODO: Once all tests are run on 5.3, this will need to be revisited.
|
|
echo "${prefix} BASH_SOURCE[@]: ${BASH_SOURCE[*]}" | sed -e "s|$0|main|g" | sed -e "s/environment/main/g"
|
|
|
|
# TODO(BASH_LINENO): implement correct BASH_LINENO tracking
|
|
# echo "${prefix} BASH_LINENO[@]: ${BASH_LINENO[*]}"
|
|
}
|
|
|
|
# Helper to dump frame-specific call stack state
|
|
dump_frame() {
|
|
local prefix="${1:-}"
|
|
local frame="${2:-0}"
|
|
|
|
echo "${prefix}[Frame $frame]"
|
|
|
|
# Normalize shell name to make sure it matches.
|
|
# TODO: Once all tests are run on 5.3, this will need to be revisited.
|
|
echo "${prefix} caller N: $(caller $frame 2>&1 || echo '<failed>')" | sed -e "s|$0|main|g" | sed -e "s/environment/main/g"
|
|
}
|
|
|
|
# Helper to dump all stack info
|
|
dump_stack() {
|
|
local prefix="${1:-}"
|
|
|
|
dump_source_info "$prefix"
|
|
|
|
local max_frames="${2:-20}"
|
|
local i
|
|
for ((i=0; i<max_frames; i++)); do
|
|
if ! caller $i >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
dump_frame "$prefix" $i
|
|
done
|
|
}
|
|
|
|
cases:
|
|
- name: "No calls - top level"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
echo "=== Top level ==="
|
|
dump_stack
|
|
|
|
- name: "Simple function call"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_function() {
|
|
echo "=== In my_function ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
my_function
|
|
|
|
- name: "Nested function calls"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
level3() {
|
|
echo "=== In level3 ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
level2() {
|
|
echo "=== In level2 ==="
|
|
dump_stack " "
|
|
level3
|
|
}
|
|
|
|
level1() {
|
|
echo "=== In level1 ==="
|
|
dump_stack " "
|
|
level2
|
|
}
|
|
|
|
level1
|
|
|
|
- name: "Function call from sourced script"
|
|
test_files:
|
|
- path: "sourced.sh"
|
|
contents: |
|
|
sourced_function() {
|
|
echo "=== In sourced_function ==="
|
|
dump_stack " "
|
|
}
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
source sourced.sh
|
|
sourced_function
|
|
|
|
- name: "Nested sourcing"
|
|
test_files:
|
|
- path: "outer.sh"
|
|
contents: |
|
|
echo "=== In outer.sh top level ==="
|
|
dump_stack " "
|
|
source inner.sh
|
|
- path: "inner.sh"
|
|
contents: |
|
|
echo "=== In inner.sh top level ==="
|
|
dump_stack " "
|
|
|
|
inner_func() {
|
|
echo "=== In inner_func ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
inner_func
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
command source outer.sh
|
|
|
|
- name: "Command substitution in top level"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
result=$(dump_stack " [CmdSub] ")
|
|
echo "$result"
|
|
|
|
- name: "Command substitution in function"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_function() {
|
|
echo "=== In my_function ==="
|
|
result=$(dump_stack " [CmdSub] ")
|
|
echo "$result"
|
|
}
|
|
|
|
my_function
|
|
|
|
- name: "Command substitution invoking function"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_function() {
|
|
echo "=== In my_function via cmdSub ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
result=$(my_function)
|
|
echo "$result"
|
|
|
|
- name: "Nested command substitutions"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
inner_func() {
|
|
echo "=== In inner_func ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
outer_func() {
|
|
echo "=== In outer_func ==="
|
|
result=$(inner_func)
|
|
echo "$result"
|
|
}
|
|
|
|
final=$(outer_func)
|
|
echo "$final"
|
|
|
|
- name: "eval at top level"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
eval 'echo "=== In eval ==="; dump_stack " "'
|
|
|
|
- name: "eval in function"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_function() {
|
|
echo "=== In my_function ==="
|
|
eval 'echo "=== In eval ==="; dump_stack " "'
|
|
}
|
|
|
|
my_function
|
|
|
|
- name: "eval calling function"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_function() {
|
|
echo "=== In my_function via eval ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
eval 'my_function'
|
|
|
|
- name: "Nested evals"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_function() {
|
|
echo "=== In my_function ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
eval 'eval "my_function"'
|
|
|
|
- name: "eval with command substitution"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_function() {
|
|
echo "=== In my_function ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
result=$(eval 'my_function')
|
|
echo "$result"
|
|
|
|
- name: "Trap handler - DEBUG"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
trap_handler() {
|
|
echo "=== In DEBUG trap handler ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
trap 'trap_handler' DEBUG
|
|
|
|
echo "Command 1"
|
|
echo "Command 2"
|
|
|
|
- name: "Trap handler - DEBUG in function"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
trap_handler() {
|
|
echo "=== In DEBUG trap handler ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
trap 'trap_handler' DEBUG
|
|
|
|
my_function() {
|
|
echo "In my_function"
|
|
echo "Still in my_function"
|
|
}
|
|
|
|
my_function
|
|
|
|
- name: "Trap handler - EXIT"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
trap 'echo "=== In EXIT trap ==="; dump_stack " "' EXIT
|
|
|
|
echo "Main script"
|
|
|
|
- name: "Trap handler calling function"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
trap_function() {
|
|
echo "=== In trap_function ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
trap 'trap_function' EXIT
|
|
|
|
echo "Main script"
|
|
|
|
- name: "LINENO tracking across contexts"
|
|
stdin: |
|
|
echo "Line 1: LINENO=${LINENO}"
|
|
echo "Line 2: LINENO=${LINENO}"
|
|
|
|
my_function() {
|
|
echo "In function line 1: LINENO=${LINENO}"
|
|
echo "In function line 2: LINENO=${LINENO}"
|
|
}
|
|
|
|
my_function
|
|
|
|
echo "Line 3: LINENO=${LINENO}"
|
|
|
|
- name: "LINENO in eval"
|
|
stdin: |
|
|
echo "Before eval: LINENO=${LINENO}"
|
|
eval 'echo "In eval: LINENO=${LINENO}"'
|
|
echo "After eval: LINENO=${LINENO}"
|
|
|
|
- name: "LINENO in command substitution"
|
|
stdin: |
|
|
echo "Before cmdSub: LINENO=${LINENO}"
|
|
result=$(echo "In cmdSub: LINENO=${LINENO}")
|
|
echo "$result"
|
|
echo "After cmdSub: LINENO=${LINENO}"
|
|
|
|
- name: "LINENO in sourced script"
|
|
test_files:
|
|
- path: "script.sh"
|
|
contents: |
|
|
echo "Sourced line 1: LINENO=${LINENO}"
|
|
echo "Sourced line 2: LINENO=${LINENO}"
|
|
|
|
sourced_func() {
|
|
echo "In sourced_func: LINENO=${LINENO}"
|
|
}
|
|
|
|
sourced_func
|
|
stdin: |
|
|
echo "Main line 1: LINENO=${LINENO}"
|
|
source script.sh
|
|
echo "Main line 2: LINENO=${LINENO}"
|
|
|
|
- name: "different depths"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
level3() {
|
|
dump_stack
|
|
}
|
|
|
|
level2() {
|
|
level3
|
|
}
|
|
|
|
level1() {
|
|
level2
|
|
}
|
|
|
|
level1
|
|
|
|
- name: "BASH_SOURCE and FUNCNAME arrays"
|
|
test_files:
|
|
- path: "outer.sh"
|
|
contents: |
|
|
outer_func() {
|
|
echo "=== In outer_func ==="
|
|
echo " FUNCNAME[@]: ${FUNCNAME[*]}"
|
|
echo " BASH_SOURCE[@]: ${BASH_SOURCE[*]}"
|
|
source inner.sh
|
|
}
|
|
- path: "inner.sh"
|
|
contents: |
|
|
inner_func() {
|
|
echo "=== In inner_func ==="
|
|
echo " FUNCNAME[@]: ${FUNCNAME[*]}"
|
|
echo " BASH_SOURCE[@]: ${BASH_SOURCE[*]}"
|
|
}
|
|
|
|
inner_func
|
|
stdin: |
|
|
source outer.sh
|
|
outer_func
|
|
|
|
- name: "Mixed: eval in sourced function with command substitution"
|
|
test_files:
|
|
- path: "script.sh"
|
|
contents: |
|
|
script_func() {
|
|
echo "=== In script_func ==="
|
|
result=$(eval 'dump_stack " [Eval->CmdSub] "')
|
|
echo "$result"
|
|
}
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
source script.sh
|
|
script_func
|
|
|
|
- name: "Complex nesting: function -> eval -> cmdSub -> function"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
inner_func() {
|
|
echo "=== In inner_func ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
outer_func() {
|
|
echo "=== In outer_func ==="
|
|
eval 'result=$(inner_func); echo "$result"'
|
|
}
|
|
|
|
outer_func
|
|
|
|
# Tests with -c flag
|
|
- name: "Call stack with -c flag"
|
|
args:
|
|
["-c", "source callstack_helpers.sh; echo '=== Via -c ==='; dump_stack"]
|
|
|
|
- name: "Function call with -c flag"
|
|
args:
|
|
[
|
|
"-c",
|
|
"my_func() { echo '=== In my_func via -c ==='; dump_stack; }; source callstack_helpers.sh; my_func",
|
|
]
|
|
|
|
- name: "Nested functions with -c flag"
|
|
args:
|
|
[
|
|
"-c",
|
|
"f2() { echo 'In f2'; dump_stack; }; f1() { echo 'In f1'; f2; }; source callstack_helpers.sh; f1",
|
|
]
|
|
|
|
- name: "eval with -c flag"
|
|
args:
|
|
[
|
|
"-c",
|
|
"eval 'source callstack_helpers.sh; echo In eval via -c; dump_stack'",
|
|
]
|
|
|
|
- name: "Command substitution with -c flag"
|
|
args:
|
|
[
|
|
"-c",
|
|
"source callstack_helpers.sh; result=$(echo LINENO=${LINENO}; dump_stack); echo $result",
|
|
]
|
|
|
|
# Interactive mode tests
|
|
- name: "Interactive mode - simple function"
|
|
known_failure: true # TODO(LINENO): implement accurate interactive mode tracking
|
|
args: ["-i"]
|
|
ignore_stderr: true
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
f() { dump_stack; }
|
|
f
|
|
|
|
- name: "Interactive mode - FUNCNAME and BASH_SOURCE"
|
|
args: ["-i"]
|
|
ignore_stderr: true
|
|
stdin: |
|
|
f() { echo "FUNCNAME: ${FUNCNAME[@]}"; echo "BASH_SOURCE: ${BASH_SOURCE[@]}"; }
|
|
f
|
|
|
|
# PROMPT_COMMAND tests
|
|
- name: "PROMPT_COMMAND with call stack"
|
|
known_failure: true # TODO(LINENO): implement accurate PROMPT_COMMAND line tracking
|
|
args: ["-i"]
|
|
ignore_stderr: true
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
PROMPT_COMMAND='dump_stack "[PC] "'
|
|
echo test
|
|
|
|
- name: "PROMPT_COMMAND calling function"
|
|
known_failure: true # TODO(LINENO): implement accurate PROMPT_COMMAND line tracking
|
|
args: ["-i"]
|
|
ignore_stderr: true
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
pc_func() { echo "[PC_FUNC] In function"; dump_stack; }
|
|
PROMPT_COMMAND='pc_func'
|
|
echo test
|
|
|
|
- name: "PROMPT_COMMAND with nested calls"
|
|
known_failure: true # TODO(LINENO): implement accurate PROMPT_COMMAND line tracking
|
|
args: ["-i"]
|
|
ignore_stderr: true
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
inner() { dump_stack; }
|
|
outer() { inner; }
|
|
PROMPT_COMMAND='outer'
|
|
echo test
|
|
|
|
# Additional edge cases
|
|
- name: "Recursive function call stack"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
recursive_func() {
|
|
local depth=$1
|
|
echo "=== Depth $depth ==="
|
|
|
|
if [ $depth -gt 0 ]; then
|
|
recursive_func $((depth - 1))
|
|
else
|
|
dump_stack " "
|
|
fi
|
|
}
|
|
|
|
recursive_func 3
|
|
|
|
- name: "Call stack with process substitution"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_func() {
|
|
echo "=== In my_func ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
# Process substitution context
|
|
cat <(my_func)
|
|
|
|
- name: "Call stack across pipe"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_func() {
|
|
echo "=== In my_func ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
my_func | cat
|
|
|
|
- name: "Call stack in subshell"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_func() {
|
|
echo "=== In my_func (subshell) ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
(my_func)
|
|
|
|
- name: "Call stack with background job"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_func() {
|
|
echo "=== In my_func (background) ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
my_func &
|
|
wait
|
|
|
|
- name: "BASH_SOURCE indexing"
|
|
test_files:
|
|
- path: "script.sh"
|
|
contents: |
|
|
echo "In script.sh"
|
|
echo " BASH_SOURCE[0]: ${BASH_SOURCE[0]}"
|
|
echo " BASH_SOURCE[1]: ${BASH_SOURCE[1]}"
|
|
|
|
script_func() {
|
|
echo "In script_func"
|
|
echo " BASH_SOURCE[0]: ${BASH_SOURCE[0]}"
|
|
echo " BASH_SOURCE[1]: ${BASH_SOURCE[1]}"
|
|
echo " BASH_SOURCE[2]: ${BASH_SOURCE[2]}"
|
|
}
|
|
|
|
script_func
|
|
stdin: |
|
|
echo "Main script"
|
|
echo " BASH_SOURCE[0]: ${BASH_SOURCE[0]}"
|
|
source script.sh
|
|
|
|
- name: "FUNCNAME at top level"
|
|
stdin: |
|
|
echo "Top level FUNCNAME: '${FUNCNAME}'"
|
|
echo "Top level FUNCNAME[@]: '${FUNCNAME[@]}'"
|
|
echo "Top level FUNCNAME[*]: '${FUNCNAME[*]}'"
|
|
|
|
- name: "Empty BASH_LINENO at top level"
|
|
stdin: |
|
|
echo "Top level BASH_LINENO[@]: '${BASH_LINENO[@]}'"
|
|
echo "Top level BASH_LINENO[*]: '${BASH_LINENO[*]}'"
|
|
|
|
- name: "Call stack in trap with function call"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
trap_func() {
|
|
echo "=== In trap_func ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
my_func() {
|
|
echo "=== In my_func ==="
|
|
echo "About to trigger trap"
|
|
}
|
|
|
|
trap 'trap_func' DEBUG
|
|
my_func
|
|
|
|
- name: "Call stack: source within eval within function"
|
|
test_files:
|
|
- path: "script.sh"
|
|
contents: |
|
|
echo "=== In sourced script ==="
|
|
dump_stack " "
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_func() {
|
|
echo "=== In my_func ==="
|
|
eval 'source script.sh'
|
|
}
|
|
|
|
my_func
|
|
|
|
- name: "Multiple eval levels"
|
|
stdin: |
|
|
source callstack_helpers.sh
|
|
|
|
my_func() {
|
|
echo "=== In my_func ==="
|
|
dump_stack " "
|
|
}
|
|
|
|
eval 'eval "eval \"my_func\""'
|
|
|
|
- name: "LINENO persistence in function"
|
|
stdin: |
|
|
my_func() {
|
|
local line1=${LINENO}
|
|
local line2=${LINENO}
|
|
local line3=${LINENO}
|
|
echo "line1: $line1"
|
|
echo "line2: $line2"
|
|
echo "line3: $line3"
|
|
}
|
|
|
|
my_func
|