Files
sausage/userland/brush/source/brush-shell/tests/cases/compat/errors.yaml
T
2026-11-11 22:04:16 +00:00

745 lines
24 KiB
YAML

name: "Error handling"
cases:
- name: "Fatal error (interactive/subshell)"
ignore_stderr: true
args: ["-i"]
stdin: |
echo "before expansion"
(echo "${non_existent_var:?missing parameter}")
echo "after expansion"
- name: "Fatal error (non-interactive/subshell)"
ignore_stderr: true
stdin: |
echo "before expansion"
(echo "${non_existent_var:?missing parameter}")
echo "after expansion"
- name: "Fatal error (interactive/command substitution)"
ignore_stderr: true
args: ["-i"]
stdin: |
echo "before expansion"
x=$(echo "${non_existent_var:?missing parameter}")
echo "after expansion"
- name: "Fatal error (non-interactive/command substitution)"
ignore_stderr: true
stdin: |
echo "before expansion"
x=$(echo "${non_existent_var:?missing parameter}")
echo "after expansion"
- name: "Fatal error (interactive/sourced file)"
ignore_stderr: true
args: ["-i"]
test_files:
- path: error.sh
contents: |
echo "before expansion"
echo "${non_existent_var:?missing parameter}"
echo "after expansion"
stdin: |
source error.sh
echo "after sourcing script with error"
- name: "Fatal error (non-interactive/sourced file)"
ignore_stderr: true
test_files:
- path: error.sh
contents: |
echo "before expansion"
echo "${non_existent_var:?missing parameter}"
echo "after expansion"
stdin: |
source error.sh
echo "after sourcing script with error"
- name: "Fatal error (interactive/executed script)"
ignore_stderr: true
args: ["-i", "error.sh"]
test_files:
- path: error.sh
contents: |
echo "before expansion"
echo "${non_existent_var:?missing parameter}"
echo "after expansion"
- name: "Fatal error (non-interactive/executed script)"
ignore_stderr: true
args: ["error.sh"]
test_files:
- path: error.sh
contents: |
echo "before expansion"
echo "${non_existent_var:?missing parameter}"
echo "after expansion"
- name: "Shell language syntax error (command line)"
incompatible_os: ["azurelinux"] # Needs investigation
ignore_stderr: true
args:
- "-c"
- |
[[ -% 3 ]]
echo "after syntax error"
- name: "Shell language syntax error (non-interactive)"
incompatible_os: ["azurelinux"] # Needs investigation
ignore_stderr: true
stdin: |
[[ -% 3 ]]
echo "after syntax error"
- name: "Shell language syntax error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
[[ -% 3 ]]
echo "after syntax error"
- name: "Special built-in error (command line)"
ignore_stderr: true
args:
- "-c"
- |
echo "before shift"
set -- foo
shift 2
echo "after shift"
- name: "Special built-in error (non-interactive)"
ignore_stderr: true
stdin: |
echo "before shift"
set -- foo
shift 2
echo "after shift"
- name: "Special built-in error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
echo "before shift"
set -- foo
shift 2
echo "after shift"
- name: "Regular utility error (command line)"
ignore_stderr: true
args:
- "-c"
- |
echo "before cat"
cat /no/such/utility/path 2>/dev/null
echo "after cat (status $?)"
- name: "Regular utility error (non-interactive)"
ignore_stderr: true
stdin: |
echo "before cat"
cat /no/such/utility/path 2>/dev/null
echo "after cat (status $?)"
- name: "Regular utility error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
echo "before cat"
cat /no/such/utility/path 2>/dev/null
echo "after cat (status $?)"
- name: "Special built-in redirection error (command line)"
ignore_stderr: true
args:
- "-c"
- |
echo "before special redirection"
: > missing_special_dir/out.txt
echo "after special redirection"
- name: "Special built-in redirection error (non-interactive)"
ignore_stderr: true
stdin: |
echo "before special redirection"
: > missing_special_dir/out.txt
echo "after special redirection"
- name: "Special built-in redirection error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
echo "before special redirection"
: > missing_special_dir/out.txt
echo "after special redirection"
- name: "Compound command redirection error (command line)"
ignore_stderr: true
args:
- "-c"
- |
echo "before compound redirection"
if true; then echo inside; fi > missing_compound_dir/out.txt
echo "after compound redirection"
- name: "Compound command redirection error (non-interactive)"
ignore_stderr: true
stdin: |
echo "before compound redirection"
if true; then echo inside; fi > missing_compound_dir/out.txt
echo "after compound redirection"
- name: "Compound command redirection error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
echo "before compound redirection"
if true; then echo inside; fi > missing_compound_dir/out.txt
echo "after compound redirection"
- name: "Function redirection error (command line)"
ignore_stderr: true
args:
- "-c"
- |
demo_func() { echo inside; }
echo "before function redirection"
demo_func > missing_function_dir/out.txt
echo "after function redirection"
- name: "Function redirection error (non-interactive)"
ignore_stderr: true
stdin: |
demo_func() { echo inside; }
echo "before function redirection"
demo_func > missing_function_dir/out.txt
echo "after function redirection"
- name: "Function redirection error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
demo_func() { echo inside; }
echo "before function redirection"
demo_func > missing_function_dir/out.txt
echo "after function redirection"
- name: "Utility redirection error (command line)"
ignore_stderr: true
args:
- "-c"
- |
echo "before cat redirection"
cat < missing_utility_dir/input.txt
echo "after cat redirection"
- name: "Utility redirection error (non-interactive)"
ignore_stderr: true
stdin: |
echo "before cat redirection"
cat < missing_utility_dir/input.txt
echo "after cat redirection"
- name: "Utility redirection error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
echo "before cat redirection"
cat < missing_utility_dir/input.txt
echo "after cat redirection"
- name: "Variable assignment error (command line)"
ignore_stderr: true
args:
- "-c"
- |
readonly value_guard=1
echo "before readonly assignment"
value_guard=2
echo "after readonly assignment"
- name: "Variable assignment error (non-interactive)"
ignore_stderr: true
stdin: |
readonly value_guard=1
echo "before readonly assignment"
value_guard=2
echo "after readonly assignment"
- name: "A special builtin named by an expansion keeps its redirection error fatal in POSIX mode"
known_failure: true # TODO(posix): a special builtin's failed redirection doesn't end the shell when an expansion names the command
ignore_stderr: true
stdin: |
set -o posix
cmd=:
$cmd > /nonexistent-dir/file
echo "not reached"
- name: "local of a readonly variable fails"
known_failure: true # TODO(local): local shadows a readonly variable rather than failing with status 1
ignore_stderr: true
stdin: |
readonly r=1
f() { local r=2; echo "in f: $r"; }
f
echo "f: $?"
- name: "A special builtin's misuse in the EXIT trap sets the shell's status in POSIX mode"
known_failure: true # TODO(posix): the EXIT trap's misuse leaves the shell's status as it was
ignore_stderr: true
stdin: |
(set -o posix; trap "set -o bogus" EXIT; echo "in subshell")
echo "after subshell: $?"
- name: "Variable assignment error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
readonly value_guard=1
echo "before readonly assignment"
value_guard=2
echo "after readonly assignment"
- name: "Expansion error (command line)"
known_failure: true # Needs investigation
ignore_stderr: true
args:
- "-c"
- |
echo "before expansion"
echo "${non_existent_var:?missing parameter}"
echo "after expansion"
- name: "Expansion error (non-interactive)"
ignore_stderr: true
stdin: |
echo "before expansion"
echo "${UNSET_REQUIRED:?missing parameter}"
echo "after expansion"
- name: "Expansion error (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
echo "before expansion"
echo "${UNSET_REQUIRED:?missing parameter}"
echo "after expansion"
- name: "Command not found (command line)"
ignore_stderr: true
args:
- "-c"
- |
missing_command
echo "after missing command (status $?)"
- name: "Command not found (non-interactive)"
ignore_stderr: true
stdin: |
missing_command
echo "after missing command (status $?)"
- name: "Command not found (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
missing_command
echo "after missing command (status $?)"
- name: "Unrecoverable read error (command line)"
ignore_stderr: true
args:
- "-c"
- |
echo "before closing stdin"
exec 0<&-
echo "after closing stdin"
- name: "Unrecoverable read error (non-interactive)"
known_failure: true
ignore_stderr: true
stdin: |
echo "before closing stdin"
exec 0<&-
echo "after closing stdin"
- name: "Unrecoverable read error (interactive)"
known_failure: true
args: ["-i"]
ignore_stderr: true
stdin: |
echo "before closing stdin"
exec 0<&-
echo "after closing stdin"
- name: "Failing special built-in (non-POSIX mode, non-interactive)"
ignore_stderr: true
stdin: |
. ./non-existent-file
echo "after builtin call (status $?)"
- name: "Failing special built-in (non-POSIX mode, interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
. ./non-existent-file
echo "after builtin call (status $?)"
- name: "Failing special built-in (POSIX mode, non-interactive)"
ignore_stderr: true
stdin: |
set -o posix
. ./non-existent-file
echo "after builtin call (status $?)"
- name: "Failing special built-in (POSIX mode, interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
set -o posix
. ./non-existent-file
echo "after builtin call (status $?)"
- name: "A special builtin's misuse doesn't end a POSIX shell when its status is tested, and an assignment error does"
ignore_stderr: true
stdin: |
set -o posix
(set -o bogus || echo "ran ||"; echo "survived set -o bogus ||")
(if shift x; then :; fi; echo "survived if shift x")
(readonly r=1; readonly r=2 || echo "ran ||"; echo "survived readonly r=2 ||")
(. /nonexistent-file || echo "ran ||"; echo "survived . ||")
- name: "Prefix assignment to a readonly variable (POSIX mode, non-interactive)"
known_failure: true # in POSIX mode, bash doesn't run the command; brush does
ignore_stderr: true
stdin: |
set -o posix
readonly R=1
R=2 printf 'ran\n'
echo "after"
- name: "errexit with failing command (non-interactive)"
stdin: |
set -e
false
echo "after false"
- name: "errexit with failing command (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
set -e
echo "before false"
false
echo "after false"
- name: "errexit with unbound variable access (interactive)"
args: ["-i", "-e", "-u", "-o", "pipefail"]
ignore_stderr: true
stdin: |
echo "before false"
false
echo "after false"
# Only the presence of escape sequences is compared, not the (differing) wording. The harness
# passes --disable-color by default, which would hide the behavior under test.
- name: "Error messages are uncolored when stderr is not a terminal"
known_failure: true # brush colors its error messages unless given --disable-color
removed_default_args: ["--disable-color"]
stdin: |
out=$(missing_command 2>&1)
[[ $out == *$'\e'* ]] && echo "command not found: colored" || echo "command not found: plain"
out=$(cd /no/such/dir 2>&1)
[[ $out == *$'\e'* ]] && echo "builtin error: colored" || echo "builtin error: plain"
- name: "Expansion error in a redirected group"
known_failure: true # TODO(errors): the error leaves the group before it's shown, so it's shown on the shell's stderr despite the group's redirect, and the shell exits with status 1 rather than 127
stdin: |
unset zz
{ echo "${zz?unset}"; } 2>/dev/null
echo "not reached"
- name: "Builtin diagnostics carry the script location"
known_failure: true # TODO(errors): brush prints no "file: line N:" location
stdin: |
export 1x=2
- name: "Builtin usage errors are reported without an error prefix"
known_failure: true # TODO(errors): brush's error formatter prints "error: " and no location
stdin: |
{ export -Z; } 2>&1 | head -n 1
- name: "Builtin I/O errors are reported as bash reports them"
known_failure: true # TODO(errors): brush reports "i/o error: ... (os error 2)"
stdin: |
cd /nonexistent-dir
# The leading "file: line N:" a shell prefixes is stripped.
- name: "Parameter expansion errors name the parameter"
known_failure: true # TODO(errors): brush reports "expansion error: <message>" or "expanding unset variable: ${name}"
stdin: |
strip() { sed -E 's/^[^ ]*: line [0-9]+: //'; }
unset y zz arr
(echo ${y:?}) 2>&1 | strip
(echo ${y?}) 2>&1 | strip
(echo ${y:?custom message}) 2>&1 | strip
(set -u; echo $zz) 2>&1 | strip
(set -u; echo ${arr[1]}) 2>&1 | strip
- name: "Arithmetic errors name the expression and the token at fault"
known_failure: true # TODO(errors): brush reports "arithmetic evaluation error: <message>" without the expression
min_oracle_version: "5.3" # bash 5.2 says "syntax error", not "arithmetic syntax error"
stdin: |
strip() { sed -E 's/^[^ ]*: line [0-9]+: //'; }
(echo $((1/0))) 2>&1 | strip
(echo $((5%0))) 2>&1 | strip
(echo $((1+))) 2>&1 | strip
((1/0)) 2>&1 | strip
- name: "Special builtin runtime error is fatal in POSIX mode"
min_oracle_version: "5.3" # bash 5.2 reports the error and goes on
known_failure: true # TODO(posix): this special builtin error doesn't end a non-interactive POSIX shell
ignore_stderr: true
stdin: |
set -o posix
export 1x=2
echo "after export: $?"
- name: "Unsetting a readonly variable is fatal in POSIX mode"
min_oracle_version: "5.3" # bash 5.2 reports the error and goes on
ignore_stderr: true
stdin: |
(set -o posix; readonly rx=1; unset rx; echo "after unset: $?"); echo "unset: $?"
readonly ry=1
unset ry
echo "outside POSIX mode: $?"
- name: "unset -v's invalid name is fatal in POSIX mode, and plain unset's isn't"
known_failure: true # TODO(unset): an invalid name is accepted silently, so it can't end the shell
ignore_stderr: true
stdin: |
set -o posix
(unset -v 1x; echo "survived: $?"); echo "unset -v 1x: $?"
(unset -v 'a[1'; echo "survived: $?"); echo "unset -v 'a[1': $?"
(unset 1x; echo "survived: $?"); echo "unset 1x: $?"
(unset -f 1x; echo "survived: $?"); echo "unset -f 1x: $?"
- name: "Builtin errors in a pipeline are reported on the command's stderr"
known_failure: true # TODO(pipeline): a builtin's error in a stage escapes the stage's stderr
stdin: |
strip() { sed -E 's/\x1b\[[0-9;]*m//g; s/^[^ ]*: line [0-9]+: //; s/^error: //'; }
shopt -s -u extglob 2>&1 | strip
echo "shopt: ${PIPESTATUS[*]}"
export -Z 2>/dev/null | cat
echo "export: ${PIPESTATUS[*]}"
cd /nonexistent-dir 2>/dev/null | cat
echo "cd: ${PIPESTATUS[*]}"
true | cd /nonexistent-dir 2>/dev/null
echo "last stage: ${PIPESTATUS[*]}"
- name: "A special builtin's usage and assignment errors are fatal in POSIX mode"
known_failure: true # TODO(posix): a special builtin's usage and assignment errors don't end a non-interactive POSIX shell
ignore_stderr: true
stdin: |
set -o posix
(set -o bogus; echo "survived: $?")
echo "status $?: set -o bogus"
(set --bogus; echo "survived: $?")
echo "status $?: set --bogus"
(trap -Z; echo "survived: $?")
echo "status $?: trap -Z"
(shift abc; echo "survived: $?")
echo "status $?: shift abc"
(readonly 1x; echo "survived: $?")
echo "status $?: readonly 1x"
(export 1x=2 y=3; echo "survived: $?")
echo "status $?: export 1x=2 y=3"
(readonly r=1; readonly r=2; echo "survived: $?")
echo "status $?: readonly r=1; readonly r=2"
(readonly r=1; unset r; echo "survived: $?")
echo "status $?: readonly r=1; unset r"
(readonly r=1; export r=2; echo "survived: $?")
echo "status $?: readonly r=1; export r=2"
(. /nonexistent-file; echo "survived: $?")
echo "status $?: . /nonexistent-file"
(f() { return abc; }; f; echo "survived: $?")
echo "status $?: f() { return abc; }; f"
(return 3; echo "survived: $?")
echo "status $?: return 3"
(exec -Z; echo "survived: $?")
echo "status $?: exec -Z"
(export --help >/dev/null; echo "survived: $?")
echo "status $?: export --help >/dev/null"
(for i in 1; do break abc; done; echo "survived: $?")
echo "status $?: for i in 1; do break abc; done"
- name: "A special builtin's ordinary failures are not fatal in POSIX mode"
known_failure: true # TODO(posix): any error a special builtin returns ends a non-interactive POSIX shell
ignore_stderr: true
stdin: |
set -o posix
for cmd in 'trap x BOGUS' 'set -- a; shift 3' 'export -f nofunc' 'unset -f -v x' \
'unset 1x' 'for i in 1; do break 0; done' 'break' 'continue 2'; do
(eval "$cmd"; echo "survived $?: $cmd")
done
- name: "A failed exec ends a non-interactive shell unless execfail is set"
known_failure: true # TODO(exec): a failed exec doesn't end a non-interactive shell
ignore_stderr: true
stdin: |
(exec /nonexistent-command; echo "survived")
echo "missing: $?"
(exec /etc/passwd; echo "survived")
echo "not executable: $?"
(shopt -s execfail; exec /nonexistent-command; echo "survived execfail: $?")
- name: "A special builtin's redirection error is fatal in POSIX mode"
known_failure: true # TODO(posix): the shell reports a redirection error before it knows the command
ignore_stderr: true
stdin: |
set -o posix
exec 3</nonexistent-file
echo "survived: $?"
- name: "Builtins attribute their diagnostics as bash does"
known_failure: true # TODO(errors): some builtins print diagnostics without bash's attribution or wording
stdin: |
strip() { sed -E 's/\x1b\[[0-9;]*m//g; s/^[^ ]*: line [0-9]+: //; s/^error: //'; }
enable nosuch 2>&1 | strip
(unset HOME; cd) 2>&1 | strip
[ 1 = 1 2>&1 | strip
help nosuchtopic 2>&1 | strip
kill -s BOGUS $$ 2>&1 | strip
type nosuch 2>&1 | strip
unalias nosuch 2>&1 | strip
- name: "An assignment error drops the rest of the line"
known_failure: true # TODO(errors): runs the rest of the line
ignore_stderr: true
stdin: |
readonly r=1
r=2; echo "same line: $?"
echo "next line: $?"
f() { r=3; }; f; echo "same line after f: $?"
echo "next line after f: $?"
# An error that bash abandons the current line for stays that way when the command it happens
# in is a compound command with redirections: the redirections only decide where the message
# goes (the cases above and below check that), not what the error does to the enclosing list.
- name: "An error in a redirected group drops the rest of the line"
ignore_stderr: true
stdin: |
readonly r=1
{ r=2; } 2>/dev/null; echo "same line after assignment: $?"
echo "next line: $?"
{ echo $((1/0)); } 2>/dev/null; echo "same line after arithmetic: $?"
echo "next line: $?"
shopt -s failglob
{ echo /no-such-dir-*; } 2>/dev/null; echo "same line after failglob: $?"
echo "next line: $?"
- name: "An error in a redirected compound command drops the rest of the line"
ignore_stderr: true
stdin: |
readonly r=1
if true; then r=2; fi 2>/dev/null; echo "same line after if: $?"
echo "next line: $?"
for i in 1; do r=2; done 2>/dev/null; echo "same line after for: $?"
echo "next line: $?"
while true; do r=2; done 2>/dev/null; echo "same line after while: $?"
echo "next line: $?"
- name: "An error in a redirected group isn't a failure for ||"
ignore_stderr: true
stdin: |
readonly r=1
{ r=2; } 2>/dev/null || echo "ran ||"; echo "same line"
echo "next line"
- name: "An error in a redirected group isn't a false condition"
ignore_stderr: true
stdin: |
readonly r=1
if { r=2; } 2>/dev/null; then echo "then"; else echo "else"; fi; echo "same line after if"
echo "next line"
! { r=2; } 2>/dev/null && echo "negated"; echo "same line after !"
echo "next line"
- name: "An error in a redirected group abandons the enclosing loop"
ignore_stderr: true
stdin: |
readonly r=1
for i in 1 2 3; do { r=2; } 2>/dev/null; echo "iteration $i"; done
echo "after for"
i=0
until { r=2; } 2>/dev/null; do i=$((i + 1)); [ $i -ge 3 ] && break; echo "tick $i"; done
echo "after until"
i=0
while true; do { r=2; } 2>/dev/null || break; i=$((i + 1)); [ $i -ge 3 ] && break; done; echo "same line after while"
echo "after while"
- name: "An error in a redirected group abandons the function it's in"
ignore_stderr: true
stdin: |
readonly r=1
f() { { r=2; } 2>/dev/null; echo "still in f"; }
f
echo "next line"
- name: "An error in a redirected group doesn't run the ERR trap"
ignore_stderr: true
stdin: |
trap 'echo "ERR trap"' ERR
readonly r=1
{ r=2; } 2>/dev/null; echo "same line"
echo "next line"
# Not observable as a shell exit yet: an assignment error doesn't end a POSIX shell (see
# "A special builtin's usage and assignment errors are fatal in POSIX mode"), so this only
# checks that the rest of the line is still dropped.
- name: "An error in a redirected group drops the rest of the line (POSIX mode)"
ignore_stderr: true
stdin: |
set -o posix
readonly r=1
{ r=2; } 2>/dev/null; echo "same line"
- name: "A fatal error in a redirected group drops the rest of the line (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
unset zz
{ echo "${zz?missing parameter}"; } 2>/dev/null; echo "same line"
echo "next line"
- name: "An error in a redirected group drops the rest of the line (interactive)"
args: ["-i"]
ignore_stderr: true
stdin: |
readonly r=1
{ r=2; } 2>/dev/null; echo "same line"
echo "next line"
- name: "A syntax error in a sourced file fails the source and goes on"
known_failure: true # TODO(errors): the syntax error ends the shell
ignore_stderr: true
test_files:
- path: "bad.sh"
contents: |
echo )
stdin: |
. ./bad.sh
echo "after: $?"
- name: "A syntax error in a sourced file ends a POSIX shell"
ignore_stderr: true
test_files:
- path: "bad.sh"
contents: |
echo )
stdin: |
set -o posix
. ./bad.sh
echo "after: $?"