Skip to content

Commit

Permalink
ask/choose/confirm: fix broken tests, although ask test has a clear…
Browse files Browse the repository at this point in the history
…ing issue when testing

get-terminal-position-support: fix crash when stdin not accessible

get-terminal-tty-support: expand debug information to look for ways to ensure ask is only clearing input if it needed to

is-interactive: simplify conditional logic

read-key, is-shapeshifter: add (ctrl|alt)+(left|right) macos keys
  • Loading branch information
balupton committed Oct 24, 2024
1 parent 099bb6b commit 7d60221
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 62 deletions.
4 changes: 4 additions & 0 deletions commands.beta/is-shapeshifter
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ function is_shapeshifter_test() (
$'\e[2~' # insert
$'\e[3~' # delete
$'\e[5~' # page-up
$'\e[1;5D' # page-up
$'\eb' # page-up
$'\e[6~' # page-down
$'\e[1;5C' # page-down
$'\ef' # page-down
$'\177' # backspace
$'\b' # backspace
# $'\n' # enter <-- don't do enter, as that is normal
Expand Down
6 changes: 4 additions & 2 deletions commands/ask
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ function ask_test() (
eval-tester --name='receive default response by timeout and no input' --stdout='a default response' \
-- ask --skip-default --question='What is your response?' --default='a default response'

local down=$'\e[B'

{
sleep 3
__print_line
Expand Down Expand Up @@ -51,7 +53,7 @@ function ask_test() (

# move down and change to custom response
sleep 3
printf $'\eOB'
__print_string "$down"
sleep 3
__print_line

Expand Down Expand Up @@ -289,7 +291,7 @@ function ask_() (
# treat empty string and newline as default
:
elif [[ $__input_result =~ ^[[:cntrl:][:space:]]*$ ]]; then
# if it is only control characters, e.g. escape, and whitespace characters, then treat as empty
# if it is only control characters (e.g. escape) and whitespace characters, then treat as empty
RESULT=''
else
# treat everything else as manual __input_result
Expand Down
45 changes: 25 additions & 20 deletions commands/choose
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ function choose_test() (

local up=$'\e[A' down=$'\e[B' right=$'\e[C' left=$'\e[D' home=$'\e[H' end=$'\e[F' insert=$'\e[2~' delete=$'\e[3~' page_up=$'\e[5~' page_down=$'\e[6~' backspace=$'\177' escape=$'\e' tab=$'\t' backtab=$'\e[Z' all=$'\x01' # enter=$'\x0a' space=' '

local \
timeout_optional='[ timed out: not required ]' \
timeout_required='[ input failure: timed out: required ]' \
timeout_defaults='[ timed out: used default ]'

# TESTS WITHOUT INTERACTION
# as we are checking stderr, do not use color for these tests, as gets too complicated

Expand All @@ -29,66 +34,66 @@ function choose_test() (
-- env COLOR=no choose 'q' 'd' --multi --index --skip-default --default=b --default=c -- a b c

# timeout optional
eval-tester --name='receive no response by timeout with no input and optional' --stderr='q [timed out: not required]' \
eval-tester --name='receive no response by timeout with no input and optional' --stderr="q $timeout_optional" \
-- env COLOR=no choose 'q' 'd' --timeout=5 -- a b c

eval-tester --name='receive no --index response by timeout with no input and optional' --stderr='q [timed out: not required]' \
eval-tester --name='receive no --index response by timeout with no input and optional' --stderr="q $timeout_optional" \
-- env COLOR=no choose 'q' 'd' --index --timeout=5 -- a b c

# timeout required
eval-tester --name='receive timeout response by timeout with no input and reqyured' --status='60' --stderr='q [input failure: timed out: required]' \
eval-tester --name='receive timeout response by timeout with no input and reqyured' --status='60' --stderr="q $timeout_required" \
-- env COLOR=no choose 'q' 'd' --required --timeout=5 -- a b c

eval-tester --name='receive timeout --index response by timeout with no input and reqyured' --status='60' --stderr='q [input failure: timed out: required]' \
eval-tester --name='receive timeout --index response by timeout with no input and reqyured' --status='60' --stderr="q $timeout_required" \
-- env COLOR=no choose 'q' 'd' --index --required --timeout=5 -- a b c

# single mode, single defalut
eval-tester --name='receive default response by timeout with no input and optional' --stdout=b --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default response by timeout with no input and optional' --stdout=b --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --timeout=5 --default=b -- a b c

eval-tester --name='receive default --index response by timeout with no input and optional' --stdout=1 --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default --index response by timeout with no input and optional' --stdout=1 --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --index --timeout=5 --default=b -- a b c

eval-tester --name='receive default response by timeout with no input and required' --stdout=b --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default response by timeout with no input and required' --stdout=b --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --timeout=5 --default=b --required -- a b c

eval-tester --name='receive default --index response by timeout with no input and required' --stdout=1 --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default --index response by timeout with no input and required' --stdout=1 --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --index --timeout=5 --default=b --required -- a b c

# single mode, so only the first match is selected
eval-tester --name='receive default first response by timeout with no input and optional' --stdout=b --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default first response by timeout with no input and optional' --stdout=b --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --timeout=5 --default-fuzzy=b -- a b bb c

eval-tester --name='receive default first --index response by timeout with no input and optional' --stdout=1 --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default first --index response by timeout with no input and optional' --stdout=1 --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --index --timeout=5 --default-fuzzy=b -- a b bb c

eval-tester --name='receive default first response by timeout with no input and required' --stdout=b --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default first response by timeout with no input and required' --stdout=b --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --timeout=5 --default-fuzzy=b --required -- a b bb c

eval-tester --name='receive default first --index response by timeout with no input and required' --stdout=1 --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default first --index response by timeout with no input and required' --stdout=1 --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --index --timeout=5 --default-fuzzy=b --required -- a b bb c

# multi mode
eval-tester --name='receive default responses by timeout with no input and multi and optional' --stdout=$'b\nbb' --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default responses by timeout with no input and multi and optional' --stdout=$'b\nbb' --stderr="q $timeout_defaults" \
-- env COLOR=no choose --multi 'q' 'd' --timeout=5 --default-fuzzy=b -- a b bb c

eval-tester --name='receive default --index responses by timeout with no input and multi and optional' --stdout=$'1\n2' --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default --index responses by timeout with no input and multi and optional' --stdout=$'1\n2' --stderr="q $timeout_defaults" \
-- env COLOR=no choose --multi 'q' 'd' --index --timeout=5 --default-fuzzy=b -- a b bb c

eval-tester --name='receive default responses by timeout with no input and multi and required' --stdout=$'b\nbb' --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default responses by timeout with no input and multi and required' --stdout=$'b\nbb' --stderr="q $timeout_defaults" \
-- env COLOR=no choose --multi 'q' 'd' --timeout=5 --default-fuzzy=b --required -- a b bb c

eval-tester --name='receive default --index responses by timeout with no input and multi and required' --stdout=$'1\n2' --stderr='q [timed out: using defaults]' \
eval-tester --name='receive default --index responses by timeout with no input and multi and required' --stdout=$'1\n2' --stderr="q $timeout_defaults" \
-- env COLOR=no choose --multi 'q' 'd' --index --timeout=5 --default-fuzzy=b --required -- a b bb c

# multiline defaults
eval-tester --name='receive multi-line default first response by timeout with no input' --stdout=$'b\nB' --stderr='q [timed out: using defaults]' \
eval-tester --name='receive multi-line default first response by timeout with no input' --stdout=$'b\nB' --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --timeout=2 --default=$'b\nB' --defaults=$'c\nd' -- a $'b\nB' c d
eval-tester --name='receive multi-line default first --index response by timeout with no input' --stdout=1 --stderr='q [timed out: using defaults]' \
eval-tester --name='receive multi-line default first --index response by timeout with no input' --stdout=1 --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --index --timeout=2 --default=$'b\nB' --defaults=$'c\nd' -- a $'b\nB' c d
eval-tester --name='receive multi-line default responses by timeout with no input' --stdout=$'b\nB\nc\nd' --stderr='q [timed out: using defaults]' \
eval-tester --name='receive multi-line default responses by timeout with no input' --stdout=$'b\nB\nc\nd' --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --multi --timeout=2 --default=$'b\nB' --defaults=$'c\nd' -- a $'b\nB' c d
eval-tester --name='receive multi-line default responses by timeout with no input' --stdout=$'1\n2\n3' --stderr='q [timed out: using defaults]' \
eval-tester --name='receive multi-line default responses by timeout with no input' --stdout=$'1\n2\n3' --stderr="q $timeout_defaults" \
-- env COLOR=no choose 'q' 'd' --multi --index --timeout=2 --default=$'b\nB' --defaults=$'c\nd' -- a $'b\nB' c d

# TESTS WITH INTERACTION
Expand Down
4 changes: 3 additions & 1 deletion commands/confirm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ function confirm_test() (

# --ignore-stderr otherwise only \n^[[68;1R is output

local escape=$'\e'

{
sleep 3
__print_line
Expand Down Expand Up @@ -53,7 +55,7 @@ function confirm_test() (
{
# press escape key
sleep 3
printf $'\x1b'
__print_string "$escape"
} | eval-tester --name='receive abort response by escape key' --status=125 --ignore-stderr \
-- confirm --ppid=-1 --bool --timeout=5 -- 'What is your response?'

Expand Down
2 changes: 1 addition & 1 deletion commands/get-terminal-position-support
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function get_terminal_position_support() (
# shorter timeouts aren't suitable as slower machines take a while for the response
# the read will complete immediately upon a response thanks to [-d R] which completes reading when the R is read, which is the final character of the response
local _ line column
IFS='[;' read -t 2 -srd R -p $'\e[6n' _ line column <"$option_device_file"
IFS='[;' read -t 2 -srd R -p $'\e[6n' _ line column 2>/dev/null <"$option_device_file" || :
if test "$option_quiet" = 'yes'; then
if test -n "${line-}" -a -n "${column-}"; then
return 0
Expand Down
112 changes: 81 additions & 31 deletions commands/get-terminal-tty-support
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,64 @@ function get_terminal_tty_support_test() (
ps -p "$$" -o tty= || :
fi

__print_lines '/dev/fd/0:'
ls -l /dev/fd/0 || :
file /dev/fd/0 || :

# https://stackoverflow.com/a/54668834/130638
if ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0; then
echo proc_pass
__print_lines proc_pass
else
echo proc_fail
__print_lines proc_fail
fi

( (tty -s && echo tty_s_pass) || echo tty_s_fail) || :
# attempts at detection

# -c /dev/stdin to handle background in ssh -T
# note that if everything is captured, such as eval_capture with stdin modification, then this appears true
( ([[ (-p /dev/stdin || -c /dev/stdin) && -p /dev/stdout && -p /dev/stderr ]] && ! (: >/dev/stdin) && __print_lines is_ssh_T_or_all_custom) || __print_lines is_neither_ssh_T_or_all_custom) || :
( (! [[ -c /dev/stdin ]] && (: >/dev/stdin) && __print_lines is_CI) || __print_lines is_not_CI) || :

( (tty -s && __print_lines tty_s_pass) || __print_lines tty_s_fail) || :

( (test -t 0 && echo test_t_stdin_pass) || echo test_t_stdin_fail) || :
( (test -t 1 && echo test_t_stdout_pass) || echo test_t_stdout_fail) || :
( (test -t 2 && echo test_t_stderr_pass) || echo test_t_stderr_fail) || :
( (test -t 0 && __print_lines test_t_stdin_pass) || __print_lines test_t_stdin_fail) || :
( (test -t 1 && __print_lines test_t_stdout_pass) || __print_lines test_t_stdout_fail) || :
( (test -t 2 && __print_lines test_t_stderr_pass) || __print_lines test_t_stderr_fail) || :

( (test -p /dev/stdin && echo test_p_stdin_pass) || echo test_p_stdin_fail) || :
( (test -p /dev/stdout && echo test_p_stdout_pass) || echo test_p_stdout_fail) || :
( (test -p /dev/stderr && echo test_p_stderr_pass) || echo test_p_stderr_fail) || :
( (test -p /dev/tty && echo test_p_tty_pass) || echo test_p_tty_fail) || :
( (test -p /dev/stdin && __print_lines test_p_stdin_pass) || __print_lines test_p_stdin_fail) || :
( (test -p /dev/stdout && __print_lines test_p_stdout_pass) || __print_lines test_p_stdout_fail) || :
( (test -p /dev/stderr && __print_lines test_p_stderr_pass) || __print_lines test_p_stderr_fail) || :
( (test -p /dev/tty && __print_lines test_p_tty_pass) || __print_lines test_p_tty_fail) || :

( (test -c /dev/stdin && echo test_c_stdin_pass) || echo test_c_stdin_fail) || :
( (test -c /dev/stdout && echo test_c_stdout_pass) || echo test_c_stdout_fail) || :
( (test -c /dev/stderr && echo test_c_stderr_pass) || echo test_c_stderr_fail) || :
( (test -c /dev/tty && echo test_c_tty_pass) || echo test_c_tty_fail) || :
( (test -c /dev/stdin && __print_lines test_c_stdin_pass) || __print_lines test_c_stdin_fail) || :
( (test -c /dev/stdout && __print_lines test_c_stdout_pass) || __print_lines test_c_stdout_fail) || :
( (test -c /dev/stderr && __print_lines test_c_stderr_pass) || __print_lines test_c_stderr_fail) || :
( (test -c /dev/tty && __print_lines test_c_tty_pass) || __print_lines test_c_tty_fail) || :

# check if reading is even possible (If TIMEOUT is 0, read returns immediately, without trying to read any data, returning success only if input is available on the specified file descriptor.)
( (read -t 0 && echo read_default_pass) || echo read_default_fail) || :
( (read -t 0 </dev/stdin && echo read_stdin_pass) || echo read_stdin_fail) || :
( (read -t 0 </dev/stdout && echo read_stdout_pass) || echo read_stdout_fail) || :
( (read -t 0 </dev/stderr && echo read_stderr_pass) || echo read_stderr_fail) || :
( (read -t 0 </dev/tty && echo read_tty_pass) || echo read_tty_fail) || :
( (read -t 0 && __print_lines read_default_pass) || __print_lines read_default_fail) || :
( (read -t 0 </dev/stdin && __print_lines read_stdin_pass) || __print_lines read_stdin_fail) || :
( (read -t 0 </dev/stdout && __print_lines read_stdout_pass) || __print_lines read_stdout_fail) || :
( (read -t 0 </dev/stderr && __print_lines read_stderr_pass) || __print_lines read_stderr_fail) || :
( (read -t 0 </dev/tty && __print_lines read_tty_pass) || __print_lines read_tty_fail) || :

# don't use exec, don't use printf, they interefered with the read-key tests, as read-key would read [exec]
# : is the same check but it is superior because it is a proper noop, unlike exec and printf

( (: </dev/stdin && echo noop_from_stdin_pass) || echo noop_from_stdin_fail) || :
( (: </dev/stdout && echo noop_from_stdout_pass) || echo noop_from_stdout_fail) || :
( (: </dev/stderr && echo noop_from_stderr_pass) || echo noop_from_stderr_fail) || :
( (: </dev/tty && echo noop_from_tty_pass) || echo noop_from_tty_fail) || :
( (: </dev/stdin && __print_lines noop_from_stdin_pass) || __print_lines noop_from_stdin_fail) || :
( (: </dev/stdout && __print_lines noop_from_stdout_pass) || __print_lines noop_from_stdout_fail) || :
( (: </dev/stderr && __print_lines noop_from_stderr_pass) || __print_lines noop_from_stderr_fail) || :
( (: </dev/tty && __print_lines noop_from_tty_pass) || __print_lines noop_from_tty_fail) || :

( (: >/dev/stdin && echo noop_to_stdin_pass) || echo noop_to_stdin_fail) || :
( (: >/dev/stdout && echo noop_to_stdout_pass) || echo noop_to_stdout_fail) || :
( (: >/dev/stderr && echo noop_to_stderr_pass) || echo noop_to_stderr_fail) || :
( (: >/dev/tty && echo noop_to_tty_pass) || echo noop_to_tty_fail) || :
( (: >/dev/stdin && __print_lines noop_to_stdin_pass) || __print_lines noop_to_stdin_fail) || :
( (: >/dev/stdout && __print_lines noop_to_stdout_pass) || __print_lines noop_to_stdout_fail) || :
( (: >/dev/stderr && __print_lines noop_to_stderr_pass) || __print_lines noop_to_stderr_fail) || :
( (: >/dev/tty && __print_lines noop_to_tty_pass) || __print_lines noop_to_tty_fail) || :

( (: </dev/stdin >/dev/stdin && echo noop_bidirectonal_stdin_pass) || echo noop_bidirectonal_stdin_fail) || :
( (: </dev/stdout >/dev/stdout && echo noop_bidirectonal_stdout_pass) || echo noop_bidirectonal_stdout_fail) || :
( (: </dev/stderr >/dev/stderr && echo noop_bidirectonal_stderr_pass) || echo noop_bidirectonal_stderr_fail) || :
( (: </dev/tty >/dev/tty && echo noop_bidirectonal_tty_pass) || echo noop_bidirectonal_tty_fail) || :
( (: </dev/stdin >/dev/stdin && __print_lines noop_bidirectonal_stdin_pass) || __print_lines noop_bidirectonal_stdin_fail) || :
( (: </dev/stdout >/dev/stdout && __print_lines noop_bidirectonal_stdout_pass) || __print_lines noop_bidirectonal_stdout_fail) || :
( (: </dev/stderr >/dev/stderr && __print_lines noop_bidirectonal_stderr_pass) || __print_lines noop_bidirectonal_stderr_fail) || :
( (: </dev/tty >/dev/tty && __print_lines noop_bidirectonal_tty_pass) || __print_lines noop_bidirectonal_tty_fail) || :
}

local stdout stderr
Expand Down Expand Up @@ -113,6 +124,45 @@ function get_terminal_tty_support_test() (
__print_lines 'stderr ='
echo-lines --indent=' ' --stdin <<<"$stderr"

__print_lines '' '### pipe to --stdoutvar --stderrvar ###'
stdout=''
stderr=''
__print_lines 'testing pipe' | eval_capture --stdoutvar=stdout --stderrvar=stderr -- __do_test
__print_lines 'stdout ='
echo-lines --indent=' ' --stdin <<<"$stdout"
__print_lines 'stderr ='
echo-lines --indent=' ' --stdin <<<"$stderr"

__print_lines '' '### delayed pipe to --stdoutvar --stderrvar ###'
stdout=''
stderr=''
{
sleep 3
__print_line
} | eval_capture --stdoutvar=stdout --stderrvar=stderr -- __do_test || :
__print_lines 'stdout ='
echo-lines --indent=' ' --stdin <<<"$stdout"
__print_lines 'stderr ='
echo-lines --indent=' ' --stdin <<<"$stderr"

__print_lines '' '### <<< to --stdoutvar --stderrvar ###'
stdout=''
stderr=''
eval_capture --stdoutvar=stdout --stderrvar=stderr -- __do_test <<<'testing <<<'
__print_lines 'stdout ='
echo-lines --indent=' ' --stdin <<<"$stdout"
__print_lines 'stderr ='
echo-lines --indent=' ' --stdin <<<"$stderr"

__print_lines '' '### < <(...) to --stdoutvar --stderrvar ###'
stdout=''
stderr=''
eval_capture --stdoutvar=stdout --stderrvar=stderr -- __do_test < <(__print_lines 'testing <<<')
__print_lines 'stdout ='
echo-lines --indent=' ' --stdin <<<"$stdout"
__print_lines 'stderr ='
echo-lines --indent=' ' --stdin <<<"$stderr"

echo-style --g1="TEST: $0"
return 0
)
Expand Down
7 changes: 2 additions & 5 deletions commands/is-interactive
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,8 @@ function is_interactive() (
# =====================================
# Action

if get-terminal-stdin-tty-support --quiet && ! is-ci; then
return 0
else
return 1
fi
get-terminal-stdin-tty-support --quiet && ! is-ci
return
)

# fire if invoked standalone
Expand Down
8 changes: 6 additions & 2 deletions commands/read-key
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,15 @@ function read_key() (

# page up
# [$'\e[5~'] is [⇞] [numlock 9] ubuntu, [🌐 ⇧ ↑] macos, [🌐 ↑] screen/vt macos
$'\e[5~'*) __match_special_and_trim_once 'page-up' $'\e[5~' ;;
# [$'\E[1;5D'] is [⌃ ←] macos
# [$'\eb'] is [⌥ ←] macos
$'\e[5~'* | $'\e[1;5D'* | $'\eb'*) __match_special_and_trim_once 'page-up' $'\e[5~' $'\e[1;5D' $'\eb' ;;

# page down
# [$'\e[6~'] is [⇟] [numlock 3] ubuntu, [🌐 ⇧ ↓] macos, [🌐 ↑] screen/vt macos
$'\e[6~'*) __match_special_and_trim_once 'page-down' $'\e[6~' ;;
# [$'\e[1;5C'] is [⌃ →] macos
# [$'\ef'] is [⌥ →] macos
$'\e[6~'* | $'\e[1;5C'* | $'\ef'*) __match_special_and_trim_once 'page-down' $'\e[6~' $'\e[1;5C' $'\ef' ;;

# backspace
# [$'\x7f' = $'\177'] is [⌫] ubuntu, macos
Expand Down

0 comments on commit 7d60221

Please sign in to comment.