1#!/bin/bash 2# Copyright 2022 Google LLC 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15################################################################################ 16 17# Set of utilities to run unit tests for bash scripts. 18# 19# Example usage: 20# From your test script: 21# source some/path/to/test_utils.sh 22# 23# # Test functions must be defined as follows: 24# test_<Test Name>_<Test Case Name>() { 25# # Do some ground work. 26# # Run the test script. 27# ./path/to/script_to_test <input1> <input2> ... 28# ASSERT_CMD_SUCCEEDED 29# ASSERT_FILE_EQUALS <file1> <file2> 30# } 31# 32# # Finds all the functions starting with `test_`, extracts test name and 33# # test case, and run them. 34# run_all_tests "$@" 35# 36 37# This is either set by Bazel or generated. 38: "${TEST_TMPDIR:="$(mktemp -td test.XXXXX)"}" 39readonly TEST_TMPDIR 40 41# Temporary directory for the testcase to use. 42TEST_CASE_TMPDIR= 43 44# Current test name. 45_CURRENT_TEST_SCOPE= 46 47# Current test case. 48_CURRENT_TEST_CASE= 49 50# True if at least one of the test cases terminated with an error. 51_HAS_ERROR="false" 52 53_print_testcase_failed_and_exit() { 54 echo "[ FAILED ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" 55 exit 1 56} 57 58####################################### 59# Starts a new test case. 60# 61# Globals: 62# _CURRENT_TEST_SCOPE 63# _CURRENT_TEST_CASE 64####################################### 65_start_test_case() { 66 echo "[ RUN ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" 67 # Create a tmp dir for the test case. 68 TEST_CASE_TMPDIR="${TEST_TMPDIR}/${_CURRENT_TEST_SCOPE}/${_CURRENT_TEST_CASE}" 69 mkdir -p "${TEST_CASE_TMPDIR}" 70} 71 72####################################### 73# Ends a test case printing a success message. 74# 75# Globals: 76# _CURRENT_TEST_SCOPE 77# _CURRENT_TEST_CASE 78####################################### 79_end_test_case_with_success() { 80 test_case="$1" 81 echo "[ OK ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" 82} 83 84####################################### 85# Returns the list of tests defined in the test script. 86# 87# A test case is a function of the form: 88# test_<Test Name>_<Test Case> 89# 90# This function returns all the functions starting with `test_`. 91# 92# Globals: 93# None 94# Arguments: 95# None 96####################################### 97_get_all_tests() { 98 declare -F | 99 while read line; do 100 case "${line}" in "declare -f test_"*) 101 echo "${line#declare -f }" 102 ;; 103 esac 104 done 105} 106 107####################################### 108# Runs a given test function. 109# 110# A test case is a function of the form: 111# test_<Test Name>_<Test Case> 112# 113# This script extracts test name and test case from the name. 114# 115# Globals: 116# _CURRENT_TEST_SCOPE 117# Arguments: 118# None 119####################################### 120_do_run_test() { 121 test_function="$1" 122 IFS=_ read _CURRENT_TEST_SCOPE _CURRENT_TEST_CASE <<< "${test_function#test_}" 123 _start_test_case 124 ( 125 # Make sure we exit only when assertions fail. 126 set +e 127 "${test_function}" 128 ) 129 local -r result=$? 130 if (( $result == 0 )); then 131 _end_test_case_with_success 132 else 133 _HAS_ERROR="true" 134 fi 135} 136 137####################################### 138# Runs all the test cases defined in the test script file. 139# Globals: 140# None 141# Arguments: 142# None 143# 144####################################### 145run_all_tests() { 146 for test in $(_get_all_tests); do 147 _do_run_test "${test}" 148 done 149 # Make sure we return an error code for the failing test 150 if [[ "${_HAS_ERROR}" == "true" ]]; then 151 exit 1 152 fi 153} 154 155ASSERT_CMD_SUCCEEDED() { 156 if (( $? != 0 )); then 157 _print_testcase_failed_and_exit 158 fi 159} 160 161ASSERT_CMD_FAILED() { 162 if (( $? == 0 )); then 163 _print_testcase_failed_and_exit 164 fi 165} 166 167ASSERT_FILE_EQUALS() { 168 input_file="$1" 169 expected_file="$2" 170 if ! diff "${input_file}" "${expected_file}"; then 171 _print_testcase_failed_and_exit 172 fi 173} 174