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

289 lines
10 KiB
YAML

name: "Interactive"
incompatible_configs: ["sh"]
cases:
- name: "Basic interactive test"
pty: true
ignore_stdout: true # Needs investigation
ignore_whitespace: true # There's an extra newline for now in test output
stdin: |
#expect-prompt
echo hi
#send:Enter
#expect:hi
#expect-prompt
#send:Ctrl+D
- name: "SIGINT ends the command an interactive shell is running"
known_failure: true # TODO(signals): an interactive brush goes on with the command it's running
pty: true
ignore_stdout: true # Prompts don't line up
stdin: |
#expect-prompt
kill -INT $$; touch went-on.txt
#send:Enter
#expect-prompt
#send:Ctrl+D
- name: "Programs inherit an ignored SIGINT after the shell first waits for one"
skip: true # TODO(signals): flaky; tokio's SIGINT listener replaces the inherited ignore, but a wait for a program sets it up only once its select! polls it (in random order), so the ignore survives when the first program has already exited
pty: true
ignore_stdout: true # Prompts don't line up
# The shell replaces itself with one started ignoring SIGINT, given the flags the harness
# gives brush; it waits for one program, and then another sends itself SIGINT.
stdin: |
#expect-prompt
exec sh -c 'p=$1; shift; trap "" INT; exec env PS1="$p" "$0" --norc --noprofile -i "$@"' "$BASH" "$PS1" ${BRUSH_VERSION:+--no-config --disable-bracketed-paste --disable-color --input-backend=basic}
#send:Enter
#expect-prompt
sh -c 'exit 0'; sh -c 'kill -INT $$; touch survived.txt'; echo "done-$((2 + 2))"
#send:Enter
#expect:done-4
#expect-prompt
#send:Ctrl+D
- name: "An interactive shell's inherited _ isn't exported by its first prompt"
pty: true
ignore_stdout: true # Prompts don't line up
min_oracle_version: "5.3" # bash 5.2 keeps the inherited `_` exported until a command updates it
# The shell replaces itself with an interactive one that inherits `_`, given the flags the
# harness gives brush; that one's first command passes on whatever `_` it exports then.
stdin: |
#expect-prompt
exec env _=/inherited/underscore PS1="$PS1" "$BASH" --norc --noprofile -i ${BRUSH_VERSION:+--no-config --disable-bracketed-paste --disable-color --input-backend=basic}
#send:Enter
#expect-prompt
exec printenv _ > out.txt
#send:Enter
- name: "Simple tab completion"
skip: true # TODO(input): Need to find the right way to enable this with a rich input backend.
pty: true
ignore_stdout: true
test_files:
- path: "test-file.txt"
contents: |
Hello, world.
stdin: |
#expect-prompt
cat test-file.
#send:Tab
#send:Enter
#expect:Hello, world.
#expect-prompt
#send:Ctrl+D
- name: "PROMPT_COMMAND"
ignore_stderr: true
args: ["-i"]
stdin: |
declare -a calls=()
command=0
PROMPT_COMMAND='calls+=("$command")'
command=first
command=second
command=third
unset PROMPT_COMMAND
declare -p calls
# Reading commands from a pipe doesn't make a shell interactive, so no prompt is
# displayed and PROMPT_COMMAND never runs.
- name: "PROMPT_COMMAND in a non-interactive shell reading stdin"
args: ["-s"]
stdin: |
PROMPT_COMMAND='echo PROMPT-COMMAND-RAN'
echo one
echo two
# Nor is the prompt itself expanded there. PS1 is left single-quoted so the command
# substitution runs at prompt time rather than at assignment time, and it writes to stderr
# to escape its own capture -- expanding this prompt is otherwise silent.
- name: "prompt expansion in a non-interactive shell reading stdin"
args: ["-s"]
stdin: |
PS1='$(echo PS1-EXPANDED >&2)$ '
echo one
echo two
# PS0 is expanded after a command is read and before it runs, and only by an interactive
# shell -- so its command substitution has no more business running here than PS1's does.
- name: "PS0 expansion in a non-interactive shell reading stdin"
args: ["-s"]
stdin: |
PS0='$(echo PS0-EXPANDED >&2)'
echo one
echo two
# PROMPT_COMMAND runs between commands, so what it leaves in $?, PIPESTATUS, and $_ must
# not be visible to the next command.
- name: "PROMPT_COMMAND preserves the last argument in $_"
ignore_stderr: true # bash echoes prompts and job-control warnings on a non-tty `-i` shell
args: ["-i"]
stdin: |
PROMPT_COMMAND='true PC-ARG'
true USER-ARG
echo "UNDERSCORE[$_]"
unset PROMPT_COMMAND
# bash accepts a string or an indexed array in PROMPT_COMMAND and ignores any other type.
- name: "PROMPT_COMMAND as an indexed array"
ignore_stderr: true # bash echoes prompts and job-control warnings on a non-tty `-i` shell
args: ["-i"]
stdin: |
PROMPT_COMMAND=('echo INDEXED-0' 'echo INDEXED-1')
echo command-ran
unset PROMPT_COMMAND
# Each element is saved/restored on its own, so element N sees the status of the last
# *user* command rather than whatever element N-1 left behind. `(exit 6)` is what makes
# the two distinguishable: a leak from `false` would print 1 here, not 6.
- name: "PROMPT_COMMAND array elements each see the last command's status"
ignore_stderr: true # bash echoes prompts and job-control warnings on a non-tty `-i` shell
args: ["-i"]
stdin: |
PROMPT_COMMAND=('false' 'echo PC-SAW[$?]')
(exit 6)
echo "AFTER[$?]"
unset PROMPT_COMMAND
# An `exit` from PROMPT_COMMAND ends the shell then and there: the remaining array
# elements don't run, the command waiting on stdin never does, and the shell exits with
# the status `exit` was given.
- name: "exit from PROMPT_COMMAND ends the shell with its status"
ignore_stderr: true # bash echoes prompts and job-control warnings on a non-tty `-i` shell
args: ["-i"]
stdin: |
PROMPT_COMMAND=('echo PC-A' 'exit 7' 'echo PC-C')
echo NEVER-RUNS
- name: "PROMPT_COMMAND as an associative array is ignored"
ignore_stderr: true # bash echoes prompts and job-control warnings on a non-tty `-i` shell
args: ["-i"]
stdin: |
declare -A PROMPT_COMMAND
PROMPT_COMMAND[x]='echo ASSOC-X'
PROMPT_COMMAND[y]='echo ASSOC-Y'
echo command-ran
unset PROMPT_COMMAND
# `return` outside a function or sourced script requests no control flow at all -- the
# builtin only does so when `in_function()` or `in_sourced_script()` -- so neither a
# PROMPT_COMMAND entry nor a line typed at the prompt can end the shell this way. bash
# reports the error and carries on, and the remaining array elements still run.
- name: "return does not exit the shell, from PROMPT_COMMAND or the prompt"
ignore_stderr: true # bash echoes prompts and job-control warnings on a non-tty `-i` shell
args: ["-i"]
stdin: |
PROMPT_COMMAND=('echo PC-A' 'return' 'echo PC-B')
echo STILL-ALIVE
unset PROMPT_COMMAND
return
echo STILL-ALIVE-AFTER-BARE-RETURN
# Expanding the prompt runs commands too (command substitutions in PS1), so what it leaves
# behind must not be visible to the next command either.
- name: "prompt expansion preserves the last argument in $_"
ignore_stderr: true # bash echoes prompts and job-control warnings on a non-tty `-i` shell
args: ["-i"]
stdin: |
PS1='$(true PS1-ARG)$ '
true USER-ARG
echo "UNDERSCORE[$_]"
- name: "prompt expansion preserves $? and PIPESTATUS"
ignore_stderr: true # bash echoes prompts and job-control warnings on a non-tty `-i` shell
args: ["-i"]
stdin: |
PS1='$( (exit 9) | (exit 8) )$ '
(exit 4) | (exit 5)
echo "AFTER[$?][${PIPESTATUS[*]}]"
- name: "Interactive -c running a program"
known_failure: true # TODO(jobs): the program takes the terminal's foreground from a background process group, and is stopped by SIGTTOU for good
pty: true
timeout_in_seconds: 5
args: ["-i", "-c", "cat /dev/null; echo done"]
stdin: |
#expect:done
- name: "A fatal error in a function drops the rest of the interactive line"
known_failure: true # TODO(interactive): runs the rest of the line
pty: true
ignore_stdout: true # Prompts don't line up
stdin: |
#expect-prompt
f() { set -u; echo "$unset_var"; }; f; touch went-on.txt
#send:Enter
#expect-prompt
#send:Ctrl+D
- name: "A fatal error in a builtin drops the rest of the interactive line"
known_failure: true # TODO(interactive): runs the rest of the line
pty: true
ignore_stdout: true # Prompts don't line up
stdin: |
#expect-prompt
set -u; compgen -W '$unset_var'; touch went-on.txt
#send:Enter
#expect-prompt
#send:Ctrl+D
- name: "A fatal error in a builtin ends an interactive shell's subshell"
known_failure: true # TODO(interactive): the subshell carries on
pty: true
ignore_stdout: true # Prompts don't line up
stdin: |
#expect-prompt
( set -u; compgen -W '$unset_var'; touch went-on.txt )
#send:Enter
#expect-prompt
#send:Ctrl+D
- name: "A fatal expansion error drops the rest of the interactive line"
pty: true
ignore_stdout: true # Prompts don't line up
stdin: |
#expect-prompt
set -u; echo "$unset_var"; touch went-on.txt
#send:Enter
#expect-prompt
#send:Ctrl+D
- name: "A fatal expansion error ends an interactive shell's subshell"
pty: true
ignore_stdout: true # Prompts don't line up
stdin: |
#expect-prompt
( set -u; echo "$unset_var"; touch went-on.txt )
#send:Enter
#expect-prompt
#send:Ctrl+D
- name: "A fatal error in eval drops only the eval'd code in an interactive shell"
pty: true
ignore_stdout: true # Prompts don't line up
stdin: |
#expect-prompt
set -u; eval 'echo "$unset_var"; touch skipped.txt'; touch went-on.txt
#send:Enter
#expect-prompt
#send:Ctrl+D
- name: "A fatal error in a sourced file drops only that file in an interactive shell"
pty: true
ignore_stdout: true # Prompts don't line up
test_files:
- path: "script.sh"
contents: |
echo "$unset_var"
touch skipped.txt
stdin: |
#expect-prompt
set -u; . ./script.sh; touch went-on.txt
#send:Enter
#expect-prompt
#send:Ctrl+D