725 lines
18 KiB
YAML
725 lines
18 KiB
YAML
name: "Builtins: trap"
|
|
cases:
|
|
#
|
|
# trap -l signal listing
|
|
#
|
|
- name: "trap -l - lists all signal names"
|
|
stdin: |
|
|
# Just verify it outputs something reasonable - check common signals exist
|
|
trap -l | grep -qE 'HUP|SIGHUP' && echo "has HUP"
|
|
trap -l | grep -qE 'INT|SIGINT' && echo "has INT"
|
|
trap -l | grep -qE 'TERM|SIGTERM' && echo "has TERM"
|
|
|
|
#
|
|
# Basic trap registration
|
|
#
|
|
- name: "trap registration"
|
|
stdin: |
|
|
trap "echo 1" SIGINT
|
|
trap "echo 2" SIGINT
|
|
trap -p INT
|
|
|
|
trap "echo 3" int
|
|
trap -p INT
|
|
|
|
trap "echo 4" 2
|
|
trap -p INT
|
|
|
|
- name: "trap unregistering"
|
|
stdin: |
|
|
echo "[Case 1]"
|
|
trap "echo 1" SIGINT
|
|
trap SIGINT
|
|
trap -p INT
|
|
|
|
echo "[Case 2]"
|
|
trap "echo 2" SIGINT
|
|
trap - SIGINT
|
|
trap -p INT
|
|
|
|
- name: "trap EXIT"
|
|
stdin: |
|
|
trap 'echo "[exit]"' EXIT
|
|
trap -p EXIT
|
|
|
|
- name: "trap EXIT: status code handling"
|
|
stdin: |
|
|
trap 'echo "[exit]: \$?: $?"' EXIT
|
|
trap -p EXIT
|
|
false
|
|
|
|
- name: "trap DEBUG"
|
|
stdin: |
|
|
trap 'echo [command: ${BASH_COMMAND}]' DEBUG
|
|
trap -p DEBUG
|
|
|
|
echo First
|
|
echo Second
|
|
false
|
|
|
|
- name: "trap ERR - registration only"
|
|
stdin: |
|
|
trap "echo [err]" ERR
|
|
trap -p ERR
|
|
|
|
- name: "trap ERR - basic invocation"
|
|
stdin: |
|
|
trap 'echo "ERR: $?"' ERR
|
|
false
|
|
echo "continues"
|
|
false
|
|
echo "continues again"
|
|
|
|
- name: "trap ERR - not triggered in conditional"
|
|
stdin: |
|
|
trap 'echo "ERR trapped"' ERR
|
|
if false; then
|
|
echo "then"
|
|
else
|
|
echo "else"
|
|
fi
|
|
echo "after"
|
|
|
|
- name: "trap ERR - not triggered with && short-circuit"
|
|
stdin: |
|
|
trap 'echo "ERR trapped"' ERR
|
|
false && echo "not printed"
|
|
echo "after"
|
|
|
|
- name: "trap ERR - not triggered with ||"
|
|
stdin: |
|
|
trap 'echo "ERR trapped"' ERR
|
|
false || echo "fallback"
|
|
echo "after"
|
|
|
|
- name: "trap ERR - not triggered with negation"
|
|
stdin: |
|
|
trap 'echo "ERR trapped"' ERR
|
|
! false
|
|
echo "after"
|
|
|
|
- name: "trap ERR - not triggered in while condition"
|
|
stdin: |
|
|
trap 'echo "ERR"' ERR
|
|
while false; do
|
|
echo "loop body"
|
|
done
|
|
echo "after while"
|
|
|
|
- name: "trap ERR - not triggered in until condition"
|
|
stdin: |
|
|
trap 'echo "ERR"' ERR
|
|
count=0
|
|
until false; do
|
|
((count++))
|
|
if ((count > 2)); then break; fi
|
|
done
|
|
echo "after until"
|
|
|
|
- name: "trap ERR - not triggered as part of || list"
|
|
stdin: |
|
|
trap 'echo "ERR"' ERR
|
|
false || true || false
|
|
echo "after"
|
|
|
|
- name: "trap ERR - not triggered for negated command"
|
|
stdin: |
|
|
trap 'echo "ERR"' ERR
|
|
if ! true; then
|
|
echo "then"
|
|
fi
|
|
! false
|
|
echo "after"
|
|
|
|
- name: "trap ERR - triggered after && chain completes then fails"
|
|
stdin: |
|
|
trap 'echo "ERR trapped"' ERR
|
|
true && true
|
|
false
|
|
echo "after"
|
|
|
|
- name: "trap ERR - execution doesn't affect control flow"
|
|
stdin: |
|
|
trap 'echo "ERR"' ERR
|
|
result=0
|
|
false || result=$?
|
|
echo "result: $result"
|
|
|
|
- name: "trap ERR - multiple failures trigger multiple traps"
|
|
stdin: |
|
|
count=0
|
|
trap '((count++)); echo "ERR $count"' ERR
|
|
false
|
|
false
|
|
false
|
|
echo "total: $count"
|
|
|
|
- name: "trap ERR - has access to exit status"
|
|
stdin: |
|
|
trap 'echo "ERR with status: $?"' ERR
|
|
(exit 42)
|
|
echo "after"
|
|
|
|
- name: "trap ERR - has access to BASH_COMMAND"
|
|
known_failure: true # TODO(traps): ERR trap basic execution not implemented
|
|
stdin: |
|
|
trap 'echo "ERR for: $BASH_COMMAND"' ERR
|
|
nonexistent_command_xyz 2>/dev/null
|
|
echo "after"
|
|
|
|
- name: "trap ERR - has access to LINENO"
|
|
known_failure: true # TODO(traps): ERR trap basic execution not implemented
|
|
stdin: |
|
|
trap 'echo "ERR at line: $LINENO"' ERR
|
|
false
|
|
echo "after"
|
|
|
|
- name: "return in EXIT trap within function"
|
|
known_failure: true # TODO(traps): return in trap handler behavior differs
|
|
stdin: |
|
|
myfunc() {
|
|
trap 'return 0' EXIT
|
|
false
|
|
echo "should not print in func"
|
|
}
|
|
myfunc
|
|
echo "after func"
|
|
|
|
- name: "subshell inherits signal traps (for trap -p display)"
|
|
stdin: |
|
|
trap 'echo "[parent int]"' INT
|
|
trap 'echo "[parent term]"' TERM
|
|
trap -p INT
|
|
echo "---"
|
|
(trap -p INT; trap -p TERM)
|
|
|
|
- name: "subshell can override inherited trap"
|
|
stdin: |
|
|
trap 'echo "[parent int]"' INT
|
|
trap -p INT
|
|
echo "---"
|
|
(trap 'echo "[subshell int]"' INT; trap -p INT)
|
|
|
|
- name: "subshell inherits EXIT trap"
|
|
stdin: |
|
|
trap 'echo "[exit]"' EXIT
|
|
trap -p EXIT
|
|
echo "---"
|
|
(trap -p EXIT)
|
|
|
|
- name: "subshell inherits DEBUG trap"
|
|
stdin: |
|
|
trap 'echo "[debug]"' DEBUG
|
|
trap -p DEBUG
|
|
echo "---"
|
|
(trap -p DEBUG)
|
|
|
|
- name: "subshell does not execute inherited DEBUG trap"
|
|
stdin: |
|
|
trap 'echo "[debug]"' DEBUG
|
|
echo "parent"
|
|
echo "---"
|
|
(echo "subshell")
|
|
echo "back in parent"
|
|
|
|
- name: "subshell can set its own EXIT trap"
|
|
known_failure: true # TODO(traps): EXIT trap in subshell
|
|
stdin: |
|
|
trap 'echo "[parent exit]"' EXIT
|
|
echo "parent"
|
|
(trap 'echo "[subshell exit]"' EXIT; echo "subshell")
|
|
echo "back in parent"
|
|
|
|
- name: "subshell inherited EXIT trap does not fire"
|
|
stdin: |
|
|
trap 'echo "[exit]"' EXIT
|
|
echo "parent"
|
|
(echo "subshell")
|
|
echo "back in parent"
|
|
|
|
#
|
|
# Subshell trap inheritance in command substitution
|
|
#
|
|
- name: "command substitution inherits signal trap (trap -p display)"
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
result=$(trap -p INT)
|
|
echo "$result"
|
|
|
|
- name: "command substitution does not fire inherited signal trap"
|
|
known_failure: true # TODO(signals): signal handling in command substitution
|
|
stdin: |
|
|
trap 'echo "[int]" >&2' INT
|
|
result=$(kill -INT $$; echo "after kill")
|
|
echo "result: $result"
|
|
|
|
- name: "command substitution can set its own trap"
|
|
stdin: |
|
|
trap 'echo "[parent int]"' INT
|
|
result=$(trap 'echo "[subshell int]"' INT; trap -p INT)
|
|
echo "$result"
|
|
|
|
- name: "command substitution EXIT trap not inherited from parent"
|
|
stdin: |
|
|
trap 'echo "[parent exit]"' EXIT
|
|
result=$(trap -p EXIT; echo "inside")
|
|
echo "result: $result"
|
|
|
|
- name: "command substitution can have its own EXIT trap"
|
|
known_failure: true # TODO: EXIT trap in command substitution subshell
|
|
stdin: |
|
|
trap 'echo "[parent exit]"' EXIT
|
|
result=$(trap 'echo "[sub exit]"' EXIT; echo "inside")
|
|
echo "result: $result"
|
|
|
|
- name: "nested command substitution trap inheritance"
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
outer=$(inner=$(trap -p INT); echo "inner: $inner")
|
|
echo "outer: $outer"
|
|
|
|
#
|
|
# Subshell trap inheritance in pipelines
|
|
#
|
|
- name: "pipeline component inherits signal trap (trap -p display)"
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
trap -p INT | cat
|
|
|
|
- name: "pipeline component can override trap"
|
|
stdin: |
|
|
trap 'echo "[parent int]"' INT
|
|
{ trap 'echo "[pipe int]"' INT; trap -p INT; } | cat
|
|
|
|
- name: "pipeline EXIT trap behavior"
|
|
stdin: |
|
|
trap 'echo "[exit]"' EXIT
|
|
{ trap -p EXIT; echo "in pipe"; } | cat
|
|
echo "after pipe"
|
|
|
|
- name: "multiple pipeline stages trap inheritance"
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
trap -p INT | cat | cat
|
|
|
|
#
|
|
# Subshell trap inheritance in process substitution
|
|
#
|
|
- name: "process substitution inherits signal trap"
|
|
known_failure: true # TODO: process substitution trap inheritance
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
cat <(trap -p INT)
|
|
|
|
- name: "process substitution output trap inheritance"
|
|
skip: true # TODO: process substitution trap inheritance / inconsistent across distros
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
echo "data" > >(trap -p INT; cat > /dev/null)
|
|
# Give time for the process substitution to complete
|
|
wait
|
|
|
|
- name: "process substitution can set own EXIT trap"
|
|
known_failure: true # TODO: EXIT trap in process substitution
|
|
stdin: |
|
|
trap 'echo "[parent exit]"' EXIT
|
|
cat <(trap 'echo "[sub exit]"' EXIT; echo "inside")
|
|
echo "after"
|
|
|
|
#
|
|
# Subshell trap inheritance in background jobs
|
|
#
|
|
- name: "background job inherits signal trap (trap -p display)"
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
trap -p INT &
|
|
wait
|
|
|
|
- name: "background job does not fire inherited signal trap"
|
|
known_failure: true # TODO(signals): background job signal handling
|
|
stdin: |
|
|
trap 'echo "[int]" >&2' INT
|
|
{ kill -INT $$; echo "after kill"; } &
|
|
wait
|
|
echo "main done"
|
|
|
|
- name: "background job EXIT trap behavior"
|
|
stdin: |
|
|
trap 'echo "[exit]"' EXIT
|
|
{ trap -p EXIT; echo "bg job"; } &
|
|
wait
|
|
echo "main continues"
|
|
|
|
- name: "background job can set own traps"
|
|
stdin: |
|
|
trap 'echo "[parent int]"' INT
|
|
{ trap 'echo "[bg int]"' INT; trap -p INT; } &
|
|
wait
|
|
trap -p INT
|
|
|
|
#
|
|
# Coproc trap inheritance (all coproc tests consolidated here)
|
|
#
|
|
# Note: "coproc - inherits signal trap" test is skipped due to bash version
|
|
# differences. Older bash resets traps in coproc, newer bash (5.3.9) inherits them.
|
|
# This causes inconsistent test results across CI runners.
|
|
- name: "coproc - EXIT trap behavior"
|
|
skip: true # TODO: this test is too inconsistent
|
|
min_oracle_version: "5.3.0"
|
|
incompatible_os:
|
|
- fedora
|
|
- arch
|
|
stdin: |
|
|
trap 'echo "[exit]"' EXIT
|
|
coproc { trap -p EXIT; echo "coproc"; }
|
|
cat <&${COPROC[0]}
|
|
wait
|
|
echo "main continues"
|
|
|
|
- name: "coproc - DEBUG trap inheritance"
|
|
skip: true # TODO: this test is too inconsistent
|
|
min_oracle_version: "5.3.0"
|
|
incompatible_os:
|
|
- fedora
|
|
stdin: |
|
|
trap 'echo "[debug]"' DEBUG
|
|
coproc { trap -p DEBUG; echo "coproc"; }
|
|
cat <&${COPROC[0]}
|
|
wait
|
|
|
|
- name: "coproc - ERR trap with errtrace"
|
|
skip: true # TODO: this test is too inconsistent
|
|
min_oracle_version: "5.3.0"
|
|
incompatible_os:
|
|
- fedora
|
|
- arch
|
|
stdin: |
|
|
set -E
|
|
trap 'echo "[err]"' ERR
|
|
coproc { trap -p ERR; false; echo "after false"; }
|
|
cat <&${COPROC[0]}
|
|
wait
|
|
|
|
#
|
|
# RETURN trap - basic behavior
|
|
#
|
|
- name: "RETURN trap - fires on function return"
|
|
stdin: |
|
|
trap 'echo "[return]"' RETURN
|
|
myfunc() {
|
|
echo "in func"
|
|
}
|
|
myfunc
|
|
echo "after func"
|
|
|
|
- name: "RETURN trap - fires with return value"
|
|
stdin: |
|
|
trap 'echo "[return: $?]"' RETURN
|
|
myfunc() {
|
|
echo "in func"
|
|
return 42
|
|
}
|
|
myfunc
|
|
echo "after func"
|
|
|
|
- name: "RETURN trap - fires on sourced script completion"
|
|
known_failure: true # TODO(traps): RETURN trap not firing for sourced scripts
|
|
test_files:
|
|
- path: "sourced.sh"
|
|
contents: |
|
|
echo "in sourced"
|
|
stdin: |
|
|
trap 'echo "[return]"' RETURN
|
|
. ./sourced.sh
|
|
echo "after source"
|
|
|
|
- name: "RETURN trap - not inherited in function without functrace"
|
|
stdin: |
|
|
set +T
|
|
trap 'echo "[return]"' RETURN
|
|
outer() {
|
|
inner() {
|
|
echo "in inner"
|
|
}
|
|
inner
|
|
echo "after inner"
|
|
}
|
|
outer
|
|
echo "done"
|
|
|
|
- name: "RETURN trap - inherited with functrace"
|
|
known_failure: true # TODO(traps): RETURN trap inheritance with functrace not implemented
|
|
stdin: |
|
|
set -T
|
|
trap 'echo "[return: ${FUNCNAME[0]:-main}]"' RETURN
|
|
outer() {
|
|
inner() {
|
|
echo "in inner"
|
|
}
|
|
inner
|
|
echo "after inner"
|
|
}
|
|
outer
|
|
echo "done"
|
|
|
|
- name: "RETURN trap - in explicit subshell"
|
|
stdin: |
|
|
trap 'echo "[return]"' RETURN
|
|
(
|
|
myfunc() { echo "in func"; }
|
|
myfunc
|
|
)
|
|
echo "after subshell"
|
|
|
|
- name: "RETURN trap - does not fire for regular commands"
|
|
stdin: |
|
|
trap 'echo "[return]"' RETURN
|
|
echo "cmd1"
|
|
echo "cmd2"
|
|
true
|
|
echo "done"
|
|
|
|
- name: "RETURN trap - does not fire for subshell completion"
|
|
stdin: |
|
|
trap 'echo "[return]"' RETURN
|
|
(echo "in subshell")
|
|
echo "after subshell"
|
|
|
|
#
|
|
# Signal trap execution verification
|
|
#
|
|
- name: "trap SIGUSR1"
|
|
known_failure: true # TODO(signals): signal handling
|
|
stdin: |
|
|
trap 'echo "[usr1 received]"' USR1
|
|
trap -p USR1
|
|
kill -USR1 $$
|
|
echo "after signal"
|
|
|
|
- name: "trap SIGUSR2"
|
|
known_failure: true # TODO(signals): signal handling
|
|
stdin: |
|
|
trap 'echo "[usr2 received]"' SIGUSR2
|
|
trap -p USR2
|
|
kill -USR2 $$
|
|
echo "after signal"
|
|
|
|
- name: "trap multiple signals with same handler"
|
|
known_failure: true # TODO(signals): signal handling
|
|
stdin: |
|
|
trap 'echo "[signal received]"' USR1 USR2
|
|
kill -USR1 $$
|
|
kill -USR2 $$
|
|
echo "done"
|
|
|
|
- name: "trap handler can inspect signal"
|
|
known_failure: true # TODO(signals): signal handling
|
|
stdin: |
|
|
handler() {
|
|
echo "handler called"
|
|
}
|
|
trap handler USR1
|
|
kill -USR1 $$
|
|
echo "after"
|
|
|
|
#
|
|
# Trap reset and unset
|
|
#
|
|
- name: "trap - clears trap"
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
trap -p INT
|
|
trap - INT
|
|
trap -p INT
|
|
|
|
- name: "trap '' ignores signal"
|
|
known_failure: true # TODO(signals): signal handling
|
|
stdin: |
|
|
trap '' INT
|
|
trap -p INT
|
|
kill -INT $$
|
|
echo "survived"
|
|
|
|
- name: "trap with empty string can be cleared"
|
|
stdin: |
|
|
trap '' INT
|
|
trap -p INT
|
|
trap - INT
|
|
trap -p INT
|
|
|
|
#
|
|
# trap -p edge cases
|
|
#
|
|
- name: "trap -p - with no args shows all traps"
|
|
stdin: |
|
|
trap 'echo a' INT
|
|
trap 'echo b' TERM
|
|
trap 'echo c' EXIT
|
|
trap -p | sort
|
|
|
|
- name: "trap -p - with specific signal"
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
trap 'echo "[term]"' TERM
|
|
trap -p INT
|
|
trap -p TERM
|
|
|
|
- name: "trap -p - with multiple signals"
|
|
stdin: |
|
|
trap 'echo "[int]"' INT
|
|
trap 'echo "[term]"' TERM
|
|
trap 'echo "[usr1]"' USR1
|
|
trap -p INT TERM USR1
|
|
|
|
- name: "trap -p - for unset trap shows nothing"
|
|
stdin: |
|
|
trap -p INT
|
|
echo "done"
|
|
|
|
- name: "trap -p - output is re-executable"
|
|
stdin: |
|
|
trap 'echo "hello world"' INT
|
|
eval "$(trap -p INT)"
|
|
trap -p INT
|
|
|
|
#
|
|
# Trap handler quoting
|
|
#
|
|
- name: "trap handler - single quotes preserved"
|
|
known_failure: true # TODO(traps): quote escaping in trap -p output differs
|
|
stdin: |
|
|
trap 'echo '\''quoted'\''' INT
|
|
trap -p INT
|
|
|
|
- name: "trap handler - double quotes in handler"
|
|
stdin: |
|
|
trap 'echo "double quoted"' INT
|
|
trap -p INT
|
|
|
|
- name: "trap handler - special characters"
|
|
stdin: |
|
|
trap 'echo $HOME; echo "done"' INT
|
|
trap -p INT
|
|
|
|
- name: "trap handler - newlines in handler"
|
|
stdin: |
|
|
trap 'echo line1
|
|
echo line2' INT
|
|
trap -p INT
|
|
|
|
#
|
|
# DEBUG trap - basic behavior
|
|
#
|
|
- name: "DEBUG trap - sees BASH_COMMAND"
|
|
known_failure: true # TODO(BASH_COMMAND): not showing assignment, quotes differ
|
|
stdin: |
|
|
trap 'echo "cmd: $BASH_COMMAND"' DEBUG
|
|
x=1
|
|
echo "x is $x"
|
|
|
|
- name: "DEBUG trap - sees LINENO"
|
|
known_failure: true # TODO(traps): LINENO in DEBUG trap not accurate
|
|
stdin: |
|
|
trap 'echo "line: $LINENO"' DEBUG
|
|
echo first
|
|
echo second
|
|
|
|
- name: "DEBUG trap - fires before each simple command"
|
|
known_failure: true # TODO(traps): DEBUG trap not firing for trap command itself
|
|
stdin: |
|
|
count=0
|
|
trap '((count++))' DEBUG
|
|
echo a
|
|
echo b
|
|
echo c
|
|
echo "count: $count"
|
|
|
|
- name: "DEBUG trap - in loop"
|
|
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
|
stdin: |
|
|
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
|
for i in 1 2; do
|
|
echo "iter $i"
|
|
done
|
|
|
|
#
|
|
# EXIT trap - advanced cases
|
|
#
|
|
- name: "EXIT trap - has access to exit status"
|
|
stdin: |
|
|
trap 'echo "exiting with: $?"' EXIT
|
|
exit 42
|
|
|
|
- name: "EXIT trap - runs even after errexit"
|
|
stdin: |
|
|
set -e
|
|
trap 'echo "[exit trap ran]"' EXIT
|
|
false
|
|
echo "not reached"
|
|
|
|
- name: "EXIT trap - can modify exit status"
|
|
known_failure: true # TODO(traps): EXIT trap exit doesn't modify status
|
|
stdin: |
|
|
trap 'exit 0' EXIT
|
|
exit 99
|
|
|
|
- name: "EXIT trap - multiple assignments last one wins"
|
|
stdin: |
|
|
trap 'echo "first"' EXIT
|
|
trap 'echo "second"' EXIT
|
|
echo "done"
|
|
|
|
#
|
|
# EXIT trap in -c mode
|
|
#
|
|
- name: "EXIT trap fires in -c mode"
|
|
args:
|
|
- "-c"
|
|
- "trap 'echo exiting' EXIT; echo hello"
|
|
expected_stdout: |
|
|
hello
|
|
exiting
|
|
|
|
- name: "EXIT trap in -c mode has access to exit status"
|
|
args:
|
|
- "-c"
|
|
- "trap 'echo \"status: $?\"' EXIT; exit 42"
|
|
expected_stdout: "status: 42\n"
|
|
|
|
- name: "EXIT trap in -c mode runs after errexit"
|
|
args:
|
|
- "-c"
|
|
- "set -e; trap 'echo trapped' EXIT; false"
|
|
expected_stdout: "trapped\n"
|
|
|
|
- name: "EXIT trap in -c mode with multiple assignments last one wins"
|
|
args:
|
|
- "-c"
|
|
- "trap 'echo first' EXIT; trap 'echo second' EXIT; echo done"
|
|
expected_stdout: |
|
|
done
|
|
second
|
|
|
|
#
|
|
# Trap with multiple signals
|
|
#
|
|
- name: "trap - clear multiple signals at once"
|
|
stdin: |
|
|
trap 'echo "[handler]"' INT TERM USR1
|
|
trap -p INT
|
|
trap -p TERM
|
|
trap -p USR1
|
|
echo "---"
|
|
trap - INT TERM USR1
|
|
trap -p INT
|
|
trap -p TERM
|
|
trap -p USR1
|
|
echo "done"
|
|
|
|
- name: "trap - ignore multiple signals"
|
|
stdin: |
|
|
trap '' INT TERM
|
|
trap -p INT
|
|
trap -p TERM
|
|
echo "done"
|