#!/usr/bin/env bats setup() { PROMPT_COMMAND='' # in case the invoking shell has set this history -s fake command # preexec requires there be some history set -o nounset # in case the user has this set __bp_delay_install="true" source "${BATS_TEST_DIRNAME}/../bash-preexec.sh" } # Evaluates all the elements of PROMPT_COMMAND eval_PROMPT_COMMAND() { local lastexit=$? lastarg=$_ prompt_command for prompt_command in "${PROMPT_COMMAND[@]}"; do __bp_set_ret_value "$lastexit" "$lastarg" eval "$prompt_command" done } # Joins the elements of PROMPT_COMMAND with $'\n' join_PROMPT_COMMAND() { local IFS=$'\n' echo "${PROMPT_COMMAND[*]}" } bp_install() { __bp_install_after_session_init eval_PROMPT_COMMAND } test_echo() { echo "test echo" } test_preexec_echo() { printf "%s\n" "$1" } # Helper functions necessary because Bats' run doesn't preserve $? return_exit_code() { return $1 } set_exit_code_and_run_precmd() { return_exit_code "${1:-0}" "${2-$_}" __bp_precmd_invoke_cmd } @test "sourcing bash-preexec should exit with 1 if we're not using bash" { unset BASH_VERSION run source "${BATS_TEST_DIRNAME}/../bash-preexec.sh" [ $status -eq 1 ] [ -z "$output" ] } @test "sourcing bash-preexec should exit with 1 if we're using an older version of bash" { if type -p bash-3.0 &>/dev/null; then run bash-3.0 -c "source \"${BATS_TEST_DIRNAME}/../bash-preexec.sh\"" [ "$status" -eq 1 ] [ -z "$output" ] else skip fi } @test "__bp_install should exit if it's already installed" { bp_install run '__bp_install' [ $status -eq 1 ] [ -z "$output" ] } @test "__bp_install should remove trap logic and itself from PROMPT_COMMAND" { __bp_install_after_session_init # Assert that before running, the command contains the install string, and # afterwards it does not [[ "$(join_PROMPT_COMMAND)" == *"$__bp_install_string"* ]] || return 1 eval_PROMPT_COMMAND [[ "$(join_PROMPT_COMMAND)" != *"$__bp_install_string"* ]] || return 1 } @test "__bp_install should remove trap logic and itself from modified PROMPT_COMMAND" { PROMPT_COMMAND=() __bp_install_after_session_init PROMPT_COMMAND="$PROMPT_COMMAND; true" # Assert that before running, the command contains the install string, and # afterwards it does not [[ "$(join_PROMPT_COMMAND)" == *"$__bp_install_string"* ]] || return 1 eval_PROMPT_COMMAND [[ "$(join_PROMPT_COMMAND)" != *"$__bp_install_string"* ]] || return 1 } @test "__bp_install should preserve an existing DEBUG trap" { trap_invoked_count=0 foo() { (( trap_invoked_count += 1 )); } # note setting this causes BATS to mis-report the failure line when this test fails trap foo DEBUG original_debug_trap=$(trap -p DEBUG) [ "$(cut -d' ' -f3 <<< "$original_debug_trap")" == "'foo'" ] bp_install trap_count_snapshot=$trap_invoked_count if [[ $__bp_hook_preexec_proc == '__bp_hook_preexec_into_debug' ]]; then # We override the DEBUG trap with the DEBUG approach to preexec [ "$(trap -p DEBUG | cut -d' ' -f3)" == "'__bp_preexec_invoke_exec" ] [[ "${preexec_functions[*]}" == *"__bp_original_debug_trap"* ]] || return 1 [[ $(declare -f __bp_original_debug_trap) == *$'\n'" foo"$'\n'* ]] || return 1 else # We do not modify the DEBUG trap in the other approaches [ "$(trap -p DEBUG)" == "$original_debug_trap" ] fi __bp_interactive_mode # triggers the DEBUG trap # ensure the trap count is still being incremented after the trap's been overwritten (( trap_count_snapshot < trap_invoked_count )) } @test "__bp_install should preserve an existing DEBUG trap containing quotes" { trap_invoked_count=0 foo() { (( trap_invoked_count += 1 )); } # constants single_quote="'" single_quote_escape="'\''" original_trap_command="foo && echo 'hello' > /dev/null" # note setting this causes BATS to mis-report the failure line when this test fails trap "$original_trap_command" debug original_debug_trap=$(trap -p DEBUG) [ "$original_debug_trap" == "trap -- '${original_trap_command//$single_quote/$single_quote_escape}' DEBUG" ] bp_install trap_count_snapshot=$trap_invoked_count if [[ $__bp_hook_preexec_proc == '__bp_hook_preexec_into_debug' ]]; then # We override the DEBUG trap with the DEBUG approach to preexec [ "$(trap -p DEBUG | cut -d' ' -f3)" == "'__bp_preexec_invoke_exec" ] [[ "${preexec_functions[*]}" == *"__bp_original_debug_trap"* ]] || return 1 [[ $(declare -f __bp_original_debug_trap) == *$'\n'" $original_trap_command"$'\n'* ]] || return 1 else # We do not modify the DEBUG trap in the other approaches [ "$(trap -p DEBUG)" == "$original_debug_trap" ] fi __bp_interactive_mode # triggers the DEBUG trap # ensure the trap count is still being incremented after the trap's been overwritten (( trap_count_snapshot < trap_invoked_count )) } @test "__bp_install should preserve an existing PS0" { original_PS0=${PS0-} bp_install if [[ $__bp_hook_preexec_proc == '__bp_hook_preexec_into_ps0' ]]; then # We modify PS0 with the PS0 approach to preexec, but the original contents # of PS0 should be preserved. [ "${PS0-}" != "$original_PS0" ] [[ ${PS0-} == *"$original_PS0"* ]] || return 1 else # We do not modify PS0 in the other approaches [ "${PS0-}" == "$original_PS0" ] fi } @test "__bp_install_prompt_command should adjust modified PROMPT_COMMAND" { unset -v PROMPT_COMMAND PROMPT_COMMAND="echo PREHOOK" # First install __bp_install_prompt_command expected_result=$'__bp_precmd_invoke_cmd "$_"\necho PREHOOK\n__bp_interactive_mode' [ "$(join_PROMPT_COMMAND)" == "$expected_result" ] # User modification if __bp_use_array_prompt_command; then PROMPT_COMMAND+=('echo POSTHOOK') else PROMPT_COMMAND+=$'\necho POSTHOOK' fi expected_result=$'__bp_precmd_invoke_cmd "$_"\necho PREHOOK\n__bp_interactive_mode\necho POSTHOOK' [ "$(join_PROMPT_COMMAND)" == "$expected_result" ] # Re-adjust __bp_install_prompt_command expected_result=$'__bp_precmd_invoke_cmd "$_"\necho PREHOOK\necho POSTHOOK\n__bp_interactive_mode' [ "$(join_PROMPT_COMMAND)" == "$expected_result" ] } @test "__bp_install_prompt_command should be skipped when already set up" { unset -v PROMPT_COMMAND PROMPT_COMMAND="" # First install should succeed __bp_install_prompt_command || return 1 # Second install should skip processing and return 1 ! __bp_install_prompt_command || return 1 } @test "__bp_sanitize_string should remove semicolons and trim space" { __bp_sanitize_string output " true1; "$'\n' [ "$output" == "true1" ] __bp_sanitize_string output " ; true2; " [ "$output" == "true2" ] __bp_sanitize_string output $'\n'" ; true3; " [ "$output" == "true3" ] } @test "__bp_sanitize_string should remove no-op colons" { __bp_sanitize_string output ':' [ "$output" == "" ] __bp_sanitize_string output $':\n:' [ "$output" == "" ] __bp_sanitize_string output $':\n:;echo USER1' [ "$output" == "echo USER1" ] __bp_sanitize_string output $'echo USER2\n:\necho USER3' expected_result=$'echo USER2\necho USER3' [ "$output" == "$expected_result" ] __bp_sanitize_string output $'echo USER4;:;echo USER5' expected_result=$'echo USER4\necho USER5' [ "$output" == "$expected_result" ] __bp_sanitize_string output $'echo USER6;:\necho USER7' expected_result=$'echo USER6\necho USER7' [ "$output" == "$expected_result" ] __bp_sanitize_string output $':\n: ; echo USER8' [ "$output" == "echo USER8" ] __bp_sanitize_string output $':\n: ; echo USER9' [ "$output" == "echo USER9" ] __bp_sanitize_string output $'echo USER10 ; :\n: ; echo USER11' expected_result=$'echo USER10\necho USER11' [ "$output" == "$expected_result" ] } @test "Appending to PROMPT_COMMAND should work after bp_install" { bp_install PROMPT_COMMAND="$PROMPT_COMMAND; true" eval_PROMPT_COMMAND } @test "Appending or prepending to PROMPT_COMMAND should work after bp_install_after_session_init" { __bp_install_after_session_init nl=$'\n' # On Bash 5.1+ we append to the array properly, # so first element of PROMPT_COMMAND may still be empty. PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }true" PROMPT_COMMAND="$PROMPT_COMMAND $nl true" PROMPT_COMMAND="$PROMPT_COMMAND; true" PROMPT_COMMAND="true; $PROMPT_COMMAND" PROMPT_COMMAND="true; $PROMPT_COMMAND" PROMPT_COMMAND="true; $PROMPT_COMMAND" PROMPT_COMMAND="true $nl $PROMPT_COMMAND" eval_PROMPT_COMMAND } @test "Appending or prepending to PROMPT_COMMAND array should work after bp_install_after_session_init" { if __bp_use_array_prompt_command; then PROMPT_COMMAND=() __bp_install_after_session_init nl=$'\n' PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "true") PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "$nl true") PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "true") PROMPT_COMMAND=("true" "${PROMPT_COMMAND[@]}") PROMPT_COMMAND=("true" "${PROMPT_COMMAND[@]}") PROMPT_COMMAND=("true" "${PROMPT_COMMAND[@]}") PROMPT_COMMAND=("true $nl" "${PROMPT_COMMAND[@]}") eval_PROMPT_COMMAND else skip fi } # Case where a user is appending or prepending to PROMPT_COMMAND. # This can happen after 'source bash-preexec.sh' e.g. # source bash-preexec.sh; PROMPT_COMMAND="$PROMPT_COMMAND; other_prompt_command_hook" @test "Adding to PROMPT_COMMAND before and after initiating install" { PROMPT_COMMAND="echo before" PROMPT_COMMAND="$PROMPT_COMMAND; echo before2" __bp_install_after_session_init PROMPT_COMMAND="$PROMPT_COMMAND"$'\necho after' PROMPT_COMMAND="echo after2; $PROMPT_COMMAND;" eval_PROMPT_COMMAND expected_result=$'__bp_precmd_invoke_cmd "$_"\necho after2; echo before; echo before2\necho after\n__bp_interactive_mode' [ "$(join_PROMPT_COMMAND)" == "$expected_result" ] } @test "Adding to PROMPT_COMMAND after with semicolon" { PROMPT_COMMAND="echo before" __bp_install_after_session_init if __bp_use_array_prompt_command; then PROMPT_COMMAND[${#PROMPT_COMMAND[@]}-1]+='; echo after' else PROMPT_COMMAND+='; echo after' fi eval_PROMPT_COMMAND expected_result=$'__bp_precmd_invoke_cmd "$_"\necho before\necho after\n__bp_interactive_mode' [ "$(join_PROMPT_COMMAND)" == "$expected_result" ] } @test "during install PROMPT_COMMAND and precmd functions should be executed each once" { PROMPT_COMMAND="echo before" PROMPT_COMMAND="$PROMPT_COMMAND; echo before2" __bp_install_after_session_init PROMPT_COMMAND="$PROMPT_COMMAND; echo after" PROMPT_COMMAND="echo after2; $PROMPT_COMMAND;" precmd() { echo "inside precmd"; } run eval_PROMPT_COMMAND [ "${#lines[@]}" == '5' ] [ "${lines[0]}" == "after2" ] [ "${lines[1]}" == "before" ] [ "${lines[2]}" == "before2" ] if __bp_use_array_prompt_command; then [ "${lines[3]}" == "after" ] [ "${lines[4]}" == "inside precmd" ] else [ "${lines[3]}" == "inside precmd" ] [ "${lines[4]}" == "after" ] fi } @test "during install PROMPT_COMMAND and precmd functions should be executed each once (Bash 5.1+ PROMPT_COMMAND array)" { if __bp_use_array_prompt_command; then PROMPT_COMMAND=("echo before") PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "echo before2") __bp_install_after_session_init PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "echo after") PROMPT_COMMAND=("echo after2" "${PROMPT_COMMAND[@]}") precmd() { echo "inside precmd"; } run eval_PROMPT_COMMAND [ "${#lines[@]}" == '5' ] [ "${lines[0]}" == "after2" ] [ "${lines[1]}" == "before" ] [ "${lines[2]}" == "before2" ] [ "${lines[3]}" == "inside precmd" ] [ "${lines[4]}" == "after" ] else skip fi } @test "No functions defined for preexec should simply return" { __bp_interactive_mode run '__bp_preexec_invoke_exec' 'true' [ $status -eq 0 ] [ -z "$output" ] } @test "precmd should execute a function once" { precmd_functions+=(test_echo) run set_exit_code_and_run_precmd [ $status -eq 0 ] [ "$output" == "test echo" ] } @test "precmd should set \$? to be the previous exit code" { echo_exit_code() { echo "$?" } precmd_functions+=(echo_exit_code) run set_exit_code_and_run_precmd 251 [ $status -eq 251 ] [ "$output" == "251" ] } @test "__bp_precmd_invoke_cmd should preserve \$? when taking the reentry-guard early return" { # When (( __bp_inside_precmd > 0 )) is true, __bp_precmd_invoke_cmd returns # early without reaching the trailing __bp_set_ret_value call. Without the # explicit set_ret_value on this path, the bare `return` propagates the # exit status of the (( ... )) test (always 0 when the condition is true), # clobbering the user's actual last exit status. run_with_reentry_guard() { local __bp_inside_precmd=1 return_exit_code 7 __bp_precmd_invoke_cmd } run run_with_reentry_guard [ $status -eq 7 ] } @test "__bp_precmd_invoke_cmd should preserve \$? when taking the nested-call-frame early return" { # Once PROMPT_COMMAND already contains our hooks, # __bp_install_prompt_command returns 1, and if __bp_precmd_invoke_cmd is # invoked from inside another function (FUNCNAME depth > 1), the function # takes the "another framework wrapped our PROMPT_COMMAND" early # return. Without the explicit set_ret_value on this path the function used # to `return 0` and clobbered the caller's last exit status. # # The path's own guard short-circuits when BATS_VERSION is set so bats's # own scaffolding doesn't trip it; we clear BATS_VERSION inside the nested # call to actually exercise the path. bp_install run_nested() { BATS_VERSION="" return_exit_code 7 BATS_VERSION="" __bp_precmd_invoke_cmd } run run_nested [ $status -eq 7 ] } @test "__bp_precmd_invoke_cmd installed in PROMPT_COMMAND should preserve \$? and \$_" { unset -v PROMPT_COMMAND PROMPT_COMMAND='save_lastexit=$? save_lastarg=$_' __bp_install_prompt_command precmd_functions=(precmd) # Note: The DEBUG and ERR traps set by Bats overwrite $_, so we cannot # properly test the values of $_. We modify the DEBUG trap of Bats so that # it properly preserves the value of $_. When we are not sure that the # current DEBUG trap preserves $_, we skip the test. See # https://github.com/bats-core/bats-core/pull/1208 for details. if trap -p DEBUG | grep -qF \''bats_debug_trap "$BASH_SOURCE"'\'; then bats_debug_trap_modified=1 trap -- 'bats_debug_trap "$BASH_SOURCE" "$_"' DEBUG fi if ! trap -p DEBUG | grep -qF ' "$_"'\'; then # If Bats' DEBUG trap does not preserve $_, and if it is not # successfully updated to include "$_", we skip the test. skip elif [[ ${bats_debug_trap_modified-} && BASH_VERSINFO[0] -le 3 ]]; then # When Bats' DEBUG trap does not care about $_, even if we modify the # DEBUG trap, an issue still seems to remain in Bash 3.2. Therefore, # we skip the test in Bash 3.2 when Bat's DEBUG trap is modified. skip else run_prompt_command() { __bp_set_ret_value '77' 'lastarg!bzBtcQDLoM' eval_PROMPT_COMMAND } run_prompt_command || true [ "$save_lastexit" == '77' ] [ "$save_lastarg" == 'lastarg!bzBtcQDLoM' ] fi } @test "precmd should set \$BP_PIPESTATUS to the previous \$PIPESTATUS" { echo_pipestatus() { echo "${BP_PIPESTATUS[*]}" } # Helper function is necessary because Bats' run doesn't preserve $PIPESTATUS set_pipestatus_and_run_precmd() { false | true __bp_precmd_invoke_cmd } precmd_functions+=(echo_pipestatus) run 'set_pipestatus_and_run_precmd' [ $status -eq 0 ] [ "$output" == "1 0" ] } @test "precmd should set \$_ to be the previous last arg" { echo_last_arg() { echo "$_" } precmd_functions+=(echo_last_arg) bats_trap=$(trap -p DEBUG) trap DEBUG # remove the Bats stack-trace trap so $_ doesn't get overwritten : "last-arg" __bp_preexec_interactive_mode=1 __bp_preexec_invoke_exec "$_" eval "$bats_trap" # Restore trap run set_exit_code_and_run_precmd 0 "$__bp_last_argument_prev_command" [ $status -eq 0 ] [ "$output" == "last-arg" ] } @test "preexec should execute a function with the last command in our history" { preexec_functions+=(test_preexec_echo) __bp_interactive_mode git_command="git commit -a -m 'committing some stuff'" history -s $git_command run '__bp_preexec_invoke_exec' [ $status -eq 0 ] [ "$output" == "$git_command" ] } @test "preexec should execute multiple functions in the order added to their arrays" { fun_1() { echo "$1 one"; } fun_2() { echo "$1 two"; } preexec_functions+=(fun_1) preexec_functions+=(fun_2) __bp_interactive_mode run '__bp_preexec_invoke_exec' [ $status -eq 0 ] [ "${#lines[@]}" == '2' ] [ "${lines[0]}" == "fake command one" ] [ "${lines[1]}" == "fake command two" ] } @test "preexec_functions before initialization should be preserved" { fun_1() { reply_1="$1 one"; } fun_2() { reply_2="$1 two"; } # preexec registered before initialization preexec_functions+=(fun_1) # Initialization __bp_preexec_interactive_mode="" bp_install # preexec registered after initialization preexec_functions+=(fun_2) reply_1="" reply_2="" __bp_invoke_preexec_from_ps0 || return 1 [ "$reply_1" == "fake command one" ] [ "$reply_2" == "fake command two" ] } @test "precmd should execute multiple functions in the order added to their arrays" { fun_1() { echo "one"; } fun_2() { echo "two"; } precmd_functions+=(fun_1) precmd_functions+=(fun_2) run set_exit_code_and_run_precmd [ $status -eq 0 ] [ "${#lines[@]}" == '2' ] [ "${lines[0]}" == "one" ] [ "${lines[1]}" == "two" ] } @test "preexec should execute a function with IFS defined to local scope" { IFS=_ name_with_underscores_1() { parts=(1_2); echo $parts; } preexec_functions+=(name_with_underscores_1) __bp_interactive_mode run '__bp_preexec_invoke_exec' [ $status -eq 0 ] [ "$output" == "1 2" ] } @test "precmd should execute a function with IFS defined to local scope" { IFS=_ name_with_underscores_2() { parts=(2_2); echo $parts; } precmd_functions+=(name_with_underscores_2) run set_exit_code_and_run_precmd [ $status -eq 0 ] [ "$output" == "2 2" ] } @test "preexec should set \$? to be the exit code of preexec_functions" { return_nonzero() { return 1 } preexec_functions+=(return_nonzero) __bp_interactive_mode run '__bp_preexec_invoke_exec' [ $status -eq 1 ] } @test "__bp_invoke_precmd_functions should be transparent for \$? and \$_" { tester1() { test1_lastexit=$? test1_lastarg=$_; } tester2() { test2_lastexit=$? test2_lastarg=$_; } precmd_functions=(tester1 tester2) trap - DEBUG # remove the Bats stack-trace trap so $_ doesn't get overwritten __bp_invoke_precmd_functions 111 'vxxJlwNx9VPJDA' || true [ "$test1_lastexit" == 111 ] [ "$test1_lastarg" == 'vxxJlwNx9VPJDA' ] [ "$test2_lastexit" == 111 ] [ "$test2_lastarg" == 'vxxJlwNx9VPJDA' ] } @test "__bp_invoke_precmd_functions returns the last non-zero exit status" { tester1() { return 91; } tester2() { return 38; } tester3() { return 0; } precmd_functions=(tester1 tester2 tester3) status=0 __bp_invoke_precmd_functions 1 'lastarg' || status=$? [ "$status" == 38 ] precmd_functions=(tester3) status=0 __bp_invoke_precmd_functions 1 'lastarg' || status=$? [ "$status" == 0 ] } @test "__bp_invoke_preexec_functions should be transparent for \$? and \$_" { tester1() { test1_lastexit=$? test1_lastarg=$_; } tester2() { test2_lastexit=$? test2_lastarg=$_; } preexec_functions=(tester1 tester2) trap - DEBUG # remove the Bats stack-trace trap so $_ doesn't get overwritten __bp_invoke_preexec_functions 87 'ehQrzHTHtE2E7Q' 'command' || true [ "$test1_lastexit" == 87 ] [ "$test1_lastarg" == 'ehQrzHTHtE2E7Q' ] [ "$test2_lastexit" == 87 ] [ "$test2_lastarg" == 'ehQrzHTHtE2E7Q' ] } @test "__bp_invoke_preexec_functions returns the last non-zero exit status" { tester1() { return 52; } tester2() { return 112; } tester3() { return 0; } preexec_functions=(tester1 tester2 tester3) status=0 __bp_invoke_preexec_functions 1 'lastarg' 'command' || status=$? [ "$status" == 112 ] preexec_functions=(tester3) status=0 __bp_invoke_preexec_functions 1 'lastarg' 'command' || status=$? [ "$status" == 0 ] } @test "__bp_invoke_preexec_functions should supply a current command in the first argument" { tester1() { test1_bash_command=$1; } tester2() { test2_bash_command=$1; } preexec_functions=(tester1 tester2) __bp_invoke_preexec_functions 1 'lastarg' 'UEVkErELArSwjA' || true [ "$test1_bash_command" == 'UEVkErELArSwjA' ] [ "$test2_bash_command" == 'UEVkErELArSwjA' ] } @test "in_prompt_command should detect if a command is part of PROMPT_COMMAND" { PROMPT_COMMAND=$'precmd_invoke_cmd\n something; echo yo\n __bp_interactive_mode' run '__bp_in_prompt_command' "something" [ $status -eq 0 ] run '__bp_in_prompt_command' "something_else" [ $status -eq 1 ] # Should trim commands and arguments here. PROMPT_COMMAND=" precmd_invoke_cmd ; something ; some_stuff_here;" run '__bp_in_prompt_command' " precmd_invoke_cmd " [ $status -eq 0 ] PROMPT_COMMAND=" precmd_invoke_cmd ; something ; some_stuff_here;" run '__bp_in_prompt_command' " not_found" [ $status -eq 1 ] } @test "__bp_adjust_histcontrol should remove ignorespace and ignoreboth" { # Should remove ignorespace HISTCONTROL="ignorespace:ignoredups:*" __bp_adjust_histcontrol [ "$HISTCONTROL" == ":ignoredups:*" ] # Should remove ignoreboth and replace it with ignoredups HISTCONTROL="ignoreboth" __bp_adjust_histcontrol [ "$HISTCONTROL" == "ignoredups:" ] # Handle a few inputs HISTCONTROL="ignoreboth:ignorespace:some_thing_else" __bp_adjust_histcontrol echo "$HISTCONTROL" [ "$HISTCONTROL" == "ignoredups:::some_thing_else" ] } @test "preexec should respect HISTTIMEFORMAT" { preexec_functions+=(test_preexec_echo) __bp_interactive_mode git_command="git commit -a -m 'committing some stuff'" HISTTIMEFORMAT='%F %T ' history -s $git_command run '__bp_preexec_invoke_exec' [ $status -eq 0 ] [ "$output" == "$git_command" ] } @test "preexec should not strip whitespace from commands" { preexec_functions+=(test_preexec_echo) __bp_interactive_mode history -s " this command has whitespace " run '__bp_preexec_invoke_exec' [ $status -eq 0 ] [ "$output" == " this command has whitespace " ] } @test "preexec should preserve multi-line strings in commands" { preexec_functions+=(test_preexec_echo) __bp_interactive_mode history -s "this 'command contains a multiline string'" run '__bp_preexec_invoke_exec' [ $status -eq 0 ] [ "$output" == "this 'command contains a multiline string'" ] } @test "preexec should work on options to 'echo' commands" { preexec_functions+=(test_preexec_echo) __bp_interactive_mode history -s -- '-n' run '__bp_preexec_invoke_exec' [ $status -eq 0 ] [ "$output" == '-n' ] }