#!/bin/bash # Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ################################################################################ # Set of utilities to run unit tests for bash scripts. # # Example usage: # From your test script: # source some/path/to/test_utils.sh # # # Test functions must be defined as follows: # test__() { # # Do some ground work. # # Run the test script. # ./path/to/script_to_test ... # ASSERT_CMD_SUCCEEDED # ASSERT_FILE_EQUALS # } # # # Finds all the functions starting with `test_`, extracts test name and # # test case, and run them. # run_all_tests "$@" # # This is either set by Bazel or generated. : "${TEST_TMPDIR:="$(mktemp -td test.XXXXX)"}" readonly TEST_TMPDIR # Temporary directory for the testcase to use. TEST_CASE_TMPDIR= # Current test name. _CURRENT_TEST_SCOPE= # Current test case. _CURRENT_TEST_CASE= # True if at least one of the test cases terminated with an error. _HAS_ERROR="false" _print_testcase_failed_and_exit() { echo "[ FAILED ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" exit 1 } ####################################### # Starts a new test case. # # Globals: # _CURRENT_TEST_SCOPE # _CURRENT_TEST_CASE ####################################### _start_test_case() { echo "[ RUN ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" # Create a tmp dir for the test case. TEST_CASE_TMPDIR="${TEST_TMPDIR}/${_CURRENT_TEST_SCOPE}/${_CURRENT_TEST_CASE}" mkdir -p "${TEST_CASE_TMPDIR}" } ####################################### # Ends a test case printing a success message. # # Globals: # _CURRENT_TEST_SCOPE # _CURRENT_TEST_CASE ####################################### _end_test_case_with_success() { test_case="$1" echo "[ OK ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" } ####################################### # Returns the list of tests defined in the test script. # # A test case is a function of the form: # test__ # # This function returns all the functions starting with `test_`. # # Globals: # None # Arguments: # None ####################################### _get_all_tests() { declare -F | while read line; do case "${line}" in "declare -f test_"*) echo "${line#declare -f }" ;; esac done } ####################################### # Runs a given test function. # # A test case is a function of the form: # test__ # # This script extracts test name and test case from the name. # # Globals: # _CURRENT_TEST_SCOPE # Arguments: # None ####################################### _do_run_test() { test_function="$1" IFS=_ read _CURRENT_TEST_SCOPE _CURRENT_TEST_CASE <<< "${test_function#test_}" _start_test_case ( # Make sure we exit only when assertions fail. set +e "${test_function}" ) local -r result=$? if (( $result == 0 )); then _end_test_case_with_success else _HAS_ERROR="true" fi } ####################################### # Runs all the test cases defined in the test script file. # Globals: # None # Arguments: # None # ####################################### run_all_tests() { for test in $(_get_all_tests); do _do_run_test "${test}" done # Make sure we return an error code for the failing test if [[ "${_HAS_ERROR}" == "true" ]]; then exit 1 fi } ASSERT_CMD_SUCCEEDED() { if (( $? != 0 )); then _print_testcase_failed_and_exit fi } ASSERT_CMD_FAILED() { if (( $? == 0 )); then _print_testcase_failed_and_exit fi } ASSERT_FILE_EQUALS() { input_file="$1" expected_file="$2" if ! diff "${input_file}" "${expected_file}"; then _print_testcase_failed_and_exit fi }