Search code examples
bashtestingexceptionbats-core

BATS assert_failure in nested function


Context

While writing a test that expects an exception/error to be thrown, I am experiencing some difficulties detecting the error.

The code installs various software packages and tests each installation command separately. There is one function that does some preprocessing before each function and then calls the installation function, this managing function is called: run_main_functions, and it passes along arguments if they are entered. For completenes, the code of run_main_functions consists of:

#!/bin/bash
run_main_functions() {
    local SCRIPT_NAME=$1
    local EXTRA_ARGUMENT=$2
    SCRIPT_PATH=src/"$SCRIPT_NAME".sh
    local LOG_PATH=$LOG_LOCATION"$SCRIPT_NAME".txt
    
    chmod +x $SCRIPT_PATH
    
    # Remove old log files if exist
    if [ -f "$LOG_PATH" ] ; then
        rm "$LOG_PATH"
    fi
    
    # run the function that performs a single installation command
    if [ "$EXTRA_ARGUMENT" == "" ]; then
        source ./$SCRIPT_PATH $LOG_PATH
    else
        source ./$SCRIPT_PATH $LOG_PATH $EXTRA_ARGUMENT
    fi
}

This run_main_functions then calls a function count_nr_of_lines which I expect to throw an error if it is fed an invalid filepath. It consists of:

#!/bin/bash
# Count the total nr of lines in an incoming file and export it.
count_nr_of_lines() {
    source src/hardcoded_variables.txt
    
    local LOG_PATH=$1
    local INPUT_PATH=$2
    
    # read content from file
    text_content=$(<$INPUT_PATH)
    
    # count the number of lines in that app
    total_nr_of_lines=$(echo "$text_content" | wc -l)
    
    # 4. Write the result of the content check to a log file.
    echo $total_nr_of_lines > "${TOTAL_NR_OF_LINES_IN_TARGET_FILEPATH}"
}
count_nr_of_lines "$@"

And the test that checks whether the count_nr_of_lines throws an error if the INPUT_PATH is of a non-existant filepath is:

#!./test/libs/bats/bin/bats

load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'libs/bats-file/load'

source test/helper.sh
source src/hardcoded_variables.txt
source src/helper.sh

mkdir -p src/logs

# Method that executes all tested main code before running tests.
setup() {
    
    # print test filename to screen.
    if [ "${BATS_TEST_NUMBER}" = 1 ];then
        echo "# Testfile: $(basename ${BATS_TEST_FILENAME})-" >&3
    fi
    
    # Declare filenames of files that perform commands
    declare -a script_names=("custom_install_4_energizedprotection")
    
    # Specify an additional array with arguments
    declare -a additional_arguments=("test/testfiles/nonexistant_filename.txt")

    # Loop through files that perform commands
    for i in "${!script_names[@]}"; do
        run_main_functions "${script_names[i]}" "${additional_arguments[i]}"    
    done
}

#---------------------------------------------------------------------------------------------------------------------------
@test "Tests whether an exception is thrown when computing the total nr of lines, if the file is not found." {
    ACTUAL_RESULT=$(<$TOTAL_NR_OF_LINES_IN_TARGET_FILEPATH)
    EXPECTED_OUTPUT="1"

    assert_failure
    #assert_equal "$ACTUAL_RESULT" "$EXPECTED_OUTPUT"
}

Where the filepath to the non-existant file is fed as:"test/testfiles/nonexistant_filename.txt". The test fails with error:

✗ Tests whether an exception is thrown when computing the total nr of lines, if the file is not found.
   (from function `apt_update' in file ./src/custom_install_4_energizedprotection.sh, line 10,
    from function `source' in file ./src/custom_install_4_energizedprotection.sh, line 18,
    from function `run_main_functions' in file src/helper.sh, line 19,
    from function `setup' in test file test/test_custom_install_4_1_energizedprotection.bats, line 28)
     `run_main_functions "${script_names[i]}" "${additional_arguments[i]}"' failed
   ./src/custom_install_4_energizedprotection.sh: line 10: test/testfiles/nonexistant_filename.txt: No such file or directory

So in some sense, an error is thrown, which is expected, but I would expect the bats test to catch that error/exception, and yield a passed test.

Question

How could I make the bats test pass succesfully by catching/registering the error that is thrown if the file does not exist?

Attempts

I also tried to include an invalid path detection inside the count_nr_of_lines function with:

if [ ! -f "$INPUT_PATH" ] ; then
    exit 1
fi

to throw an explicit error, however, that removes the verbosity/error messages from the test failure and only results in a failing test.


Solution

  • I don't understand why you're running the test code in the setup. How about this

    setup() {
        # print test filename to screen.
        if [ "${BATS_TEST_NUMBER}" = 1 ];then
            echo "# Testfile: $(basename ${BATS_TEST_FILENAME})-" >&3
        fi
    }
    
    #---------------------------------------------------------------------------------------------------------------------------
    @test "Tests whether an exception is thrown when computing the total nr of lines, if the file is not found." {
        #ACTUAL_RESULT=$(<$TOTAL_NR_OF_LINES_IN_TARGET_FILEPATH)
        #EXPECTED_OUTPUT="1"
    
        # this will report an error if it has a non-zero return status
        run_main_functions "custom_install_4_energizedprotection" "test/testfiles/nonexistant_filename.txt"
    
        #assert_equal "$ACTUAL_RESULT" "$EXPECTED_OUTPUT"
    }
    

    I missed the point that you are explicitly expecting failure. You could

        ! run_main_functions ...
    

    which will error if run_main_functions returns OK, or use the run command

        run run_main_functions
        expect_failure
    

    which I think will work, but I have a vague recollection about having to export sourced functions (I can't find any reference about that in the docs though)


    And after re-reading the docs, instead of setup use setup_file:

    setup_file() {
        echo "# Testfile: $(basename ${BATS_TEST_FILENAME})-" >&3
    }