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