🏡 index : quine-global-labs/mirror-rcaloras-bash-preexec.git

#!/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' ]
}