bashunit-testingsshbats-core

How to activate an ssh-account in Bash function in BATS test?


Context

As part of a Bash script that is tested using the BATS, I noticed that my tests are not terminated when I run a function that activates an ssh-account.

Code

The following function assumes a private and public ssh key pair exists in /home/<username>/.ssh/. If I run it manually using source src/the_bash_script.sh && activate_ssh_account <my_git_username>, it works and says Identity added: /home/name/.ssh/<my_git_email>:

#!/bin/bash
# Activates/enables the ssh for 
activate_ssh_account() {
    git_username=$1
    eval "$(ssh-agent -s)"
    ssh-add ~/.ssh/"$git_username"
}

However, when it is ran from the test with:

#!./test/libs/bats/bin/bats
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
# https://github.com/bats-core/bats-file#Index-of-all-functions
load 'libs/bats-file/load'
# https://github.com/bats-core/bats-assert#usage
load 'assert_utils'

source src/the_bash_script.sh


@test "Check if ssh-account is activated after activating it." {
    activate_ssh_account "some_git_username"
    assert_equal "Something" "Something_else"
}

It hangs indefinitely.

Question

How can I activate an ssh-account without causing the BATS tests to hang indefinitely?


Solution

  • The test hangs indefinitely because BATS waits for ssh-agent to terminate (which runs in the background once line eval "$(ssh-agent -s)" is executed). To be more specific, BATS waits for file descriptor 3 to be closed (which is being held open by ssh-agent).

    Thus, this can be solved by either implementing the workaround mentioned in the documentation or by killing ssh-agent.


    Workaround from documentation:

    #!/bin/bash
    # Activates/enables the ssh for 
    activate_ssh_account() {
        git_username=$1
        eval "$(ssh-agent -s 3>&-)"
        ssh-add ~/.ssh/"$git_username"
    }
    

    This closes fd 3 for ssh-agent and BATS will no longer hang. Note that this leaves ssh-agent running in the background, even after BATS exits. It is unclear from your question if that is desired or not. If it's not, use the alternative below.


    Kill ssh-agent:

    Add a cleanup trap to activate_ssh_account:

    #!/bin/bash
    # Activates/enables the ssh for 
    activate_ssh_account() {
        trap "trap - RETURN; kill \$SSH_AGENT_PID" RETURN
        git_username=$1
        eval "$(ssh-agent -s)"
        ssh-add ~/.ssh/"$git_username"
    }
    

    The trap is executed when the function exits and kills ssh-agent using the pid exported by eval "$(ssh-agent -s)" (i.e. variable SSH_AGENT_PID).

    If you don't want to use a trap for some reason, this will also work:

    #!/bin/bash
    # Activates/enables the ssh for
    activate_ssh_account() {
        git_username=$1
        eval "$(ssh-agent -s)"
        result=0
        ssh-add ~/.ssh/"$git_username" || result=$?
        kill $SSH_AGENT_PID
        return $result
    }
    

    Note that the || construct is necessary because BATS will stop executing the function's code once a command fails (i.e. without ||, kill will not be executed if ssh-add fails).


    As a side note, to actually test if activate_ssh_account succeeds or fails, you should use assert_success instead of assert_equal (unless there is more code that you omitted in your question).