1*bcb5dc79SHONG Yifan#!/usr/bin/env bash 2*bcb5dc79SHONG Yifan# 3*bcb5dc79SHONG Yifan# Copyright 2015 The Bazel Authors. All rights reserved. 4*bcb5dc79SHONG Yifan# 5*bcb5dc79SHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License"); 6*bcb5dc79SHONG Yifan# you may not use this file except in compliance with the License. 7*bcb5dc79SHONG Yifan# You may obtain a copy of the License at 8*bcb5dc79SHONG Yifan# 9*bcb5dc79SHONG Yifan# http://www.apache.org/licenses/LICENSE-2.0 10*bcb5dc79SHONG Yifan# 11*bcb5dc79SHONG Yifan# Unless required by applicable law or agreed to in writing, software 12*bcb5dc79SHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS, 13*bcb5dc79SHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*bcb5dc79SHONG Yifan# See the License for the specific language governing permissions and 15*bcb5dc79SHONG Yifan# limitations under the License. 16*bcb5dc79SHONG Yifan# 17*bcb5dc79SHONG Yifan# Common utility file for Bazel shell tests 18*bcb5dc79SHONG Yifan# 19*bcb5dc79SHONG Yifan# unittest.bash: a unit test framework in Bash. 20*bcb5dc79SHONG Yifan# 21*bcb5dc79SHONG Yifan# A typical test suite looks like so: 22*bcb5dc79SHONG Yifan# 23*bcb5dc79SHONG Yifan# ------------------------------------------------------------------------ 24*bcb5dc79SHONG Yifan# #!/usr/bin/env bash 25*bcb5dc79SHONG Yifan# 26*bcb5dc79SHONG Yifan# source path/to/unittest.bash || exit 1 27*bcb5dc79SHONG Yifan# 28*bcb5dc79SHONG Yifan# # Test that foo works. 29*bcb5dc79SHONG Yifan# function test_foo() { 30*bcb5dc79SHONG Yifan# foo >$TEST_log || fail "foo failed"; 31*bcb5dc79SHONG Yifan# expect_log "blah" "Expected to see 'blah' in output of 'foo'." 32*bcb5dc79SHONG Yifan# } 33*bcb5dc79SHONG Yifan# 34*bcb5dc79SHONG Yifan# # Test that bar works. 35*bcb5dc79SHONG Yifan# function test_bar() { 36*bcb5dc79SHONG Yifan# bar 2>$TEST_log || fail "bar failed"; 37*bcb5dc79SHONG Yifan# expect_not_log "ERROR" "Unexpected error from 'bar'." 38*bcb5dc79SHONG Yifan# ... 39*bcb5dc79SHONG Yifan# assert_equals $x $y 40*bcb5dc79SHONG Yifan# } 41*bcb5dc79SHONG Yifan# 42*bcb5dc79SHONG Yifan# run_suite "Test suite for blah" 43*bcb5dc79SHONG Yifan# ------------------------------------------------------------------------ 44*bcb5dc79SHONG Yifan# 45*bcb5dc79SHONG Yifan# Each test function is considered to pass iff fail() is not called 46*bcb5dc79SHONG Yifan# while it is active. fail() may be called directly, or indirectly 47*bcb5dc79SHONG Yifan# via other assertions such as expect_log(). run_suite must be called 48*bcb5dc79SHONG Yifan# at the very end. 49*bcb5dc79SHONG Yifan# 50*bcb5dc79SHONG Yifan# A test function may redefine functions "set_up" and/or "tear_down"; 51*bcb5dc79SHONG Yifan# these functions are executed before and after each test function, 52*bcb5dc79SHONG Yifan# respectively. Similarly, "cleanup" and "timeout" may be redefined, 53*bcb5dc79SHONG Yifan# and these function are called upon exit (of any kind) or a timeout. 54*bcb5dc79SHONG Yifan# 55*bcb5dc79SHONG Yifan# The user can pass --test_arg to bazel test to select specific tests 56*bcb5dc79SHONG Yifan# to run. Specifying --test_arg multiple times allows to select several 57*bcb5dc79SHONG Yifan# tests to be run in the given order. Additionally the user may define 58*bcb5dc79SHONG Yifan# TESTS=(test_foo test_bar ...) to specify a subset of test functions to 59*bcb5dc79SHONG Yifan# execute, for example, a working set during debugging. By default, all 60*bcb5dc79SHONG Yifan# functions called test_* will be executed. 61*bcb5dc79SHONG Yifan# 62*bcb5dc79SHONG Yifan# This file provides utilities for assertions over the output of a 63*bcb5dc79SHONG Yifan# command. The output of the command under test is directed to the 64*bcb5dc79SHONG Yifan# file $TEST_log, and then the expect_log* assertions can be used to 65*bcb5dc79SHONG Yifan# test for the presence of certain regular expressions in that file. 66*bcb5dc79SHONG Yifan# 67*bcb5dc79SHONG Yifan# The test framework is responsible for restoring the original working 68*bcb5dc79SHONG Yifan# directory before each test. 69*bcb5dc79SHONG Yifan# 70*bcb5dc79SHONG Yifan# The order in which test functions are run is not defined, so it is 71*bcb5dc79SHONG Yifan# important that tests clean up after themselves. 72*bcb5dc79SHONG Yifan# 73*bcb5dc79SHONG Yifan# Each test will be run in a new subshell. 74*bcb5dc79SHONG Yifan# 75*bcb5dc79SHONG Yifan# Functions named __* are not intended for use by clients. 76*bcb5dc79SHONG Yifan# 77*bcb5dc79SHONG Yifan# This framework implements the "test sharding protocol". 78*bcb5dc79SHONG Yifan# 79*bcb5dc79SHONG Yifan 80*bcb5dc79SHONG Yifan[ -n "$BASH_VERSION" ] || 81*bcb5dc79SHONG Yifan { echo "unittest.bash only works with bash!" >&2; exit 1; } 82*bcb5dc79SHONG Yifan 83*bcb5dc79SHONG YifanDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) 84*bcb5dc79SHONG Yifan 85*bcb5dc79SHONG Yifan#### Configuration variables (may be overridden by testenv.sh or the suite): 86*bcb5dc79SHONG Yifan 87*bcb5dc79SHONG Yifan# This function may be called by testenv.sh or a test suite to enable errexit 88*bcb5dc79SHONG Yifan# in a way that enables us to print pretty stack traces when something fails. 89*bcb5dc79SHONG Yifanfunction enable_errexit() { 90*bcb5dc79SHONG Yifan set -o errtrace 91*bcb5dc79SHONG Yifan set -eu 92*bcb5dc79SHONG Yifan trap __test_terminated_err ERR 93*bcb5dc79SHONG Yifan} 94*bcb5dc79SHONG Yifan 95*bcb5dc79SHONG Yifanfunction disable_errexit() { 96*bcb5dc79SHONG Yifan set +o errtrace 97*bcb5dc79SHONG Yifan set +eu 98*bcb5dc79SHONG Yifan trap - ERR 99*bcb5dc79SHONG Yifan} 100*bcb5dc79SHONG Yifan 101*bcb5dc79SHONG Yifan#### Set up the test environment, branched from the old shell/testenv.sh 102*bcb5dc79SHONG Yifan 103*bcb5dc79SHONG Yifan# Enable errexit with pretty stack traces. 104*bcb5dc79SHONG Yifanenable_errexit 105*bcb5dc79SHONG Yifan 106*bcb5dc79SHONG Yifan# Print message in "$1" then exit with status "$2" 107*bcb5dc79SHONG Yifandie () { 108*bcb5dc79SHONG Yifan # second argument is optional, defaulting to 1 109*bcb5dc79SHONG Yifan local status_code=${2:-1} 110*bcb5dc79SHONG Yifan # Stop capturing stdout/stderr, and dump captured output 111*bcb5dc79SHONG Yifan if [ "$CAPTURED_STD_ERR" -ne 0 -o "$CAPTURED_STD_OUT" -ne 0 ]; then 112*bcb5dc79SHONG Yifan restore_outputs 113*bcb5dc79SHONG Yifan if [ "$CAPTURED_STD_OUT" -ne 0 ]; then 114*bcb5dc79SHONG Yifan cat "${TEST_TMPDIR}/captured.out" 115*bcb5dc79SHONG Yifan CAPTURED_STD_OUT=0 116*bcb5dc79SHONG Yifan fi 117*bcb5dc79SHONG Yifan if [ "$CAPTURED_STD_ERR" -ne 0 ]; then 118*bcb5dc79SHONG Yifan cat "${TEST_TMPDIR}/captured.err" 1>&2 119*bcb5dc79SHONG Yifan CAPTURED_STD_ERR=0 120*bcb5dc79SHONG Yifan fi 121*bcb5dc79SHONG Yifan fi 122*bcb5dc79SHONG Yifan 123*bcb5dc79SHONG Yifan if [ -n "${1-}" ] ; then 124*bcb5dc79SHONG Yifan echo "$1" 1>&2 125*bcb5dc79SHONG Yifan fi 126*bcb5dc79SHONG Yifan if [ -n "${BASH-}" ]; then 127*bcb5dc79SHONG Yifan local caller_n=0 128*bcb5dc79SHONG Yifan while [ $caller_n -lt 4 ] && caller_out=$(caller $caller_n 2>/dev/null); do 129*bcb5dc79SHONG Yifan test $caller_n -eq 0 && echo "CALLER stack (max 4):" 130*bcb5dc79SHONG Yifan echo " $caller_out" 131*bcb5dc79SHONG Yifan let caller_n=caller_n+1 132*bcb5dc79SHONG Yifan done 1>&2 133*bcb5dc79SHONG Yifan fi 134*bcb5dc79SHONG Yifan if [ x"$status_code" != x -a x"$status_code" != x"0" ]; then 135*bcb5dc79SHONG Yifan exit "$status_code" 136*bcb5dc79SHONG Yifan else 137*bcb5dc79SHONG Yifan exit 1 138*bcb5dc79SHONG Yifan fi 139*bcb5dc79SHONG Yifan} 140*bcb5dc79SHONG Yifan 141*bcb5dc79SHONG Yifan# Print message in "$1" then record that a non-fatal error occurred in ERROR_COUNT 142*bcb5dc79SHONG YifanERROR_COUNT="${ERROR_COUNT:-0}" 143*bcb5dc79SHONG Yifanerror () { 144*bcb5dc79SHONG Yifan if [ -n "$1" ] ; then 145*bcb5dc79SHONG Yifan echo "$1" 1>&2 146*bcb5dc79SHONG Yifan fi 147*bcb5dc79SHONG Yifan ERROR_COUNT=$(($ERROR_COUNT + 1)) 148*bcb5dc79SHONG Yifan} 149*bcb5dc79SHONG Yifan 150*bcb5dc79SHONG Yifan# Die if "$1" != "$2", print $3 as death reason 151*bcb5dc79SHONG Yifancheck_eq () { 152*bcb5dc79SHONG Yifan [ "$1" = "$2" ] || die "Check failed: '$1' == '$2' ${3:+ ($3)}" 153*bcb5dc79SHONG Yifan} 154*bcb5dc79SHONG Yifan 155*bcb5dc79SHONG Yifan# Die if "$1" == "$2", print $3 as death reason 156*bcb5dc79SHONG Yifancheck_ne () { 157*bcb5dc79SHONG Yifan [ "$1" != "$2" ] || die "Check failed: '$1' != '$2' ${3:+ ($3)}" 158*bcb5dc79SHONG Yifan} 159*bcb5dc79SHONG Yifan 160*bcb5dc79SHONG Yifan# The structure of the following if statements is such that if '[' fails 161*bcb5dc79SHONG Yifan# (e.g., a non-number was passed in) then the check will fail. 162*bcb5dc79SHONG Yifan 163*bcb5dc79SHONG Yifan# Die if "$1" > "$2", print $3 as death reason 164*bcb5dc79SHONG Yifancheck_le () { 165*bcb5dc79SHONG Yifan [ "$1" -gt "$2" ] || die "Check failed: '$1' <= '$2' ${3:+ ($3)}" 166*bcb5dc79SHONG Yifan} 167*bcb5dc79SHONG Yifan 168*bcb5dc79SHONG Yifan# Die if "$1" >= "$2", print $3 as death reason 169*bcb5dc79SHONG Yifancheck_lt () { 170*bcb5dc79SHONG Yifan [ "$1" -lt "$2" ] || die "Check failed: '$1' < '$2' ${3:+ ($3)}" 171*bcb5dc79SHONG Yifan} 172*bcb5dc79SHONG Yifan 173*bcb5dc79SHONG Yifan# Die if "$1" < "$2", print $3 as death reason 174*bcb5dc79SHONG Yifancheck_ge () { 175*bcb5dc79SHONG Yifan [ "$1" -ge "$2" ] || die "Check failed: '$1' >= '$2' ${3:+ ($3)}" 176*bcb5dc79SHONG Yifan} 177*bcb5dc79SHONG Yifan 178*bcb5dc79SHONG Yifan# Die if "$1" <= "$2", print $3 as death reason 179*bcb5dc79SHONG Yifancheck_gt () { 180*bcb5dc79SHONG Yifan [ "$1" -gt "$2" ] || die "Check failed: '$1' > '$2' ${3:+ ($3)}" 181*bcb5dc79SHONG Yifan} 182*bcb5dc79SHONG Yifan 183*bcb5dc79SHONG Yifan# Die if $2 !~ $1; print $3 as death reason 184*bcb5dc79SHONG Yifancheck_match () 185*bcb5dc79SHONG Yifan{ 186*bcb5dc79SHONG Yifan expr match "$2" "$1" >/dev/null || \ 187*bcb5dc79SHONG Yifan die "Check failed: '$2' does not match regex '$1' ${3:+ ($3)}" 188*bcb5dc79SHONG Yifan} 189*bcb5dc79SHONG Yifan 190*bcb5dc79SHONG Yifan# Run command "$1" at exit. Like "trap" but multiple atexits don't 191*bcb5dc79SHONG Yifan# overwrite each other. Will break if someone does call trap 192*bcb5dc79SHONG Yifan# directly. So, don't do that. 193*bcb5dc79SHONG YifanATEXIT="${ATEXIT-}" 194*bcb5dc79SHONG Yifanatexit () { 195*bcb5dc79SHONG Yifan if [ -z "$ATEXIT" ]; then 196*bcb5dc79SHONG Yifan ATEXIT="$1" 197*bcb5dc79SHONG Yifan else 198*bcb5dc79SHONG Yifan ATEXIT="$1 ; $ATEXIT" 199*bcb5dc79SHONG Yifan fi 200*bcb5dc79SHONG Yifan trap "$ATEXIT" EXIT 201*bcb5dc79SHONG Yifan} 202*bcb5dc79SHONG Yifan 203*bcb5dc79SHONG Yifan## TEST_TMPDIR 204*bcb5dc79SHONG Yifanif [ -z "${TEST_TMPDIR:-}" ]; then 205*bcb5dc79SHONG Yifan export TEST_TMPDIR="$(mktemp -d ${TMPDIR:-/tmp}/bazel-test.XXXXXXXX)" 206*bcb5dc79SHONG Yifanfi 207*bcb5dc79SHONG Yifanif [ ! -e "${TEST_TMPDIR}" ]; then 208*bcb5dc79SHONG Yifan mkdir -p -m 0700 "${TEST_TMPDIR}" 209*bcb5dc79SHONG Yifan # Clean TEST_TMPDIR on exit 210*bcb5dc79SHONG Yifan atexit "rm -fr ${TEST_TMPDIR}" 211*bcb5dc79SHONG Yifanfi 212*bcb5dc79SHONG Yifan 213*bcb5dc79SHONG Yifan# Functions to compare the actual output of a test to the expected 214*bcb5dc79SHONG Yifan# (golden) output. 215*bcb5dc79SHONG Yifan# 216*bcb5dc79SHONG Yifan# Usage: 217*bcb5dc79SHONG Yifan# capture_test_stdout 218*bcb5dc79SHONG Yifan# ... do something ... 219*bcb5dc79SHONG Yifan# diff_test_stdout "$TEST_SRCDIR/path/to/golden.out" 220*bcb5dc79SHONG Yifan 221*bcb5dc79SHONG Yifan# Redirect a file descriptor to a file. 222*bcb5dc79SHONG YifanCAPTURED_STD_OUT="${CAPTURED_STD_OUT:-0}" 223*bcb5dc79SHONG YifanCAPTURED_STD_ERR="${CAPTURED_STD_ERR:-0}" 224*bcb5dc79SHONG Yifan 225*bcb5dc79SHONG Yifancapture_test_stdout () { 226*bcb5dc79SHONG Yifan exec 3>&1 # Save stdout as fd 3 227*bcb5dc79SHONG Yifan exec 4>"${TEST_TMPDIR}/captured.out" 228*bcb5dc79SHONG Yifan exec 1>&4 229*bcb5dc79SHONG Yifan CAPTURED_STD_OUT=1 230*bcb5dc79SHONG Yifan} 231*bcb5dc79SHONG Yifan 232*bcb5dc79SHONG Yifancapture_test_stderr () { 233*bcb5dc79SHONG Yifan exec 6>&2 # Save stderr as fd 6 234*bcb5dc79SHONG Yifan exec 7>"${TEST_TMPDIR}/captured.err" 235*bcb5dc79SHONG Yifan exec 2>&7 236*bcb5dc79SHONG Yifan CAPTURED_STD_ERR=1 237*bcb5dc79SHONG Yifan} 238*bcb5dc79SHONG Yifan 239*bcb5dc79SHONG Yifan# Force XML_OUTPUT_FILE to an existing path 240*bcb5dc79SHONG Yifanif [ -z "${XML_OUTPUT_FILE:-}" ]; then 241*bcb5dc79SHONG Yifan XML_OUTPUT_FILE=${TEST_TMPDIR}/output.xml 242*bcb5dc79SHONG Yifanfi 243*bcb5dc79SHONG Yifan 244*bcb5dc79SHONG Yifan#### Global variables: 245*bcb5dc79SHONG Yifan 246*bcb5dc79SHONG YifanTEST_name="" # The name of the current test. 247*bcb5dc79SHONG Yifan 248*bcb5dc79SHONG YifanTEST_log=$TEST_TMPDIR/log # The log file over which the 249*bcb5dc79SHONG Yifan # expect_log* assertions work. Must 250*bcb5dc79SHONG Yifan # be absolute to be robust against 251*bcb5dc79SHONG Yifan # tests invoking 'cd'! 252*bcb5dc79SHONG Yifan 253*bcb5dc79SHONG YifanTEST_passed="true" # The result of the current test; 254*bcb5dc79SHONG Yifan # failed assertions cause this to 255*bcb5dc79SHONG Yifan # become false. 256*bcb5dc79SHONG Yifan 257*bcb5dc79SHONG Yifan# These variables may be overridden by the test suite: 258*bcb5dc79SHONG Yifan 259*bcb5dc79SHONG YifanTESTS=() # A subset or "working set" of test 260*bcb5dc79SHONG Yifan # functions that should be run. By 261*bcb5dc79SHONG Yifan # default, all tests called test_* are 262*bcb5dc79SHONG Yifan # run. 263*bcb5dc79SHONG Yifanif [ $# -gt 0 ]; then 264*bcb5dc79SHONG Yifan # Legacy behavior is to ignore missing regexp, but with errexit 265*bcb5dc79SHONG Yifan # the following line fails without || true. 266*bcb5dc79SHONG Yifan # TODO(dmarting): maybe we should revisit the way of selecting 267*bcb5dc79SHONG Yifan # test with that framework (use Bazel's environment variable instead). 268*bcb5dc79SHONG Yifan TESTS=($(for i in $@; do echo $i; done | grep ^test_ || true)) 269*bcb5dc79SHONG Yifan if (( ${#TESTS[@]} == 0 )); then 270*bcb5dc79SHONG Yifan echo "WARNING: Arguments do not specifies tests!" >&2 271*bcb5dc79SHONG Yifan fi 272*bcb5dc79SHONG Yifanfi 273*bcb5dc79SHONG Yifan 274*bcb5dc79SHONG YifanTEST_verbose="true" # Whether or not to be verbose. A 275*bcb5dc79SHONG Yifan # command; "true" or "false" are 276*bcb5dc79SHONG Yifan # acceptable. The default is: true. 277*bcb5dc79SHONG Yifan 278*bcb5dc79SHONG YifanTEST_script="$(pwd)/$0" # Full path to test script 279*bcb5dc79SHONG Yifan 280*bcb5dc79SHONG Yifan#### Internal functions 281*bcb5dc79SHONG Yifan 282*bcb5dc79SHONG Yifanfunction __show_log() { 283*bcb5dc79SHONG Yifan echo "-- Test log: -----------------------------------------------------------" 284*bcb5dc79SHONG Yifan [[ -e $TEST_log ]] && cat $TEST_log || echo "(Log file did not exist.)" 285*bcb5dc79SHONG Yifan echo "------------------------------------------------------------------------" 286*bcb5dc79SHONG Yifan} 287*bcb5dc79SHONG Yifan 288*bcb5dc79SHONG Yifan# Usage: __pad <title> <pad-char> 289*bcb5dc79SHONG Yifan# Print $title padded to 80 columns with $pad_char. 290*bcb5dc79SHONG Yifanfunction __pad() { 291*bcb5dc79SHONG Yifan local title=$1 292*bcb5dc79SHONG Yifan local pad=$2 293*bcb5dc79SHONG Yifan { 294*bcb5dc79SHONG Yifan echo -n "$pad$pad $title " 295*bcb5dc79SHONG Yifan printf "%80s" " " | tr ' ' "$pad" 296*bcb5dc79SHONG Yifan } | head -c 80 297*bcb5dc79SHONG Yifan echo 298*bcb5dc79SHONG Yifan} 299*bcb5dc79SHONG Yifan 300*bcb5dc79SHONG Yifan#### Exported functions 301*bcb5dc79SHONG Yifan 302*bcb5dc79SHONG Yifan# Usage: init_test ... 303*bcb5dc79SHONG Yifan# Deprecated. Has no effect. 304*bcb5dc79SHONG Yifanfunction init_test() { 305*bcb5dc79SHONG Yifan : 306*bcb5dc79SHONG Yifan} 307*bcb5dc79SHONG Yifan 308*bcb5dc79SHONG Yifan 309*bcb5dc79SHONG Yifan# Usage: set_up 310*bcb5dc79SHONG Yifan# Called before every test function. May be redefined by the test suite. 311*bcb5dc79SHONG Yifanfunction set_up() { 312*bcb5dc79SHONG Yifan : 313*bcb5dc79SHONG Yifan} 314*bcb5dc79SHONG Yifan 315*bcb5dc79SHONG Yifan# Usage: tear_down 316*bcb5dc79SHONG Yifan# Called after every test function. May be redefined by the test suite. 317*bcb5dc79SHONG Yifanfunction tear_down() { 318*bcb5dc79SHONG Yifan : 319*bcb5dc79SHONG Yifan} 320*bcb5dc79SHONG Yifan 321*bcb5dc79SHONG Yifan# Usage: cleanup 322*bcb5dc79SHONG Yifan# Called upon eventual exit of the test suite. May be redefined by 323*bcb5dc79SHONG Yifan# the test suite. 324*bcb5dc79SHONG Yifanfunction cleanup() { 325*bcb5dc79SHONG Yifan : 326*bcb5dc79SHONG Yifan} 327*bcb5dc79SHONG Yifan 328*bcb5dc79SHONG Yifan# Usage: timeout 329*bcb5dc79SHONG Yifan# Called upon early exit from a test due to timeout. 330*bcb5dc79SHONG Yifanfunction timeout() { 331*bcb5dc79SHONG Yifan : 332*bcb5dc79SHONG Yifan} 333*bcb5dc79SHONG Yifan 334*bcb5dc79SHONG Yifan# Usage: fail <message> [<message> ...] 335*bcb5dc79SHONG Yifan# Print failure message with context information, and mark the test as 336*bcb5dc79SHONG Yifan# a failure. The context includes a stacktrace including the longest sequence 337*bcb5dc79SHONG Yifan# of calls outside this module. (We exclude the top and bottom portions of 338*bcb5dc79SHONG Yifan# the stack because they just add noise.) Also prints the contents of 339*bcb5dc79SHONG Yifan# $TEST_log. 340*bcb5dc79SHONG Yifanfunction fail() { 341*bcb5dc79SHONG Yifan __show_log >&2 342*bcb5dc79SHONG Yifan echo "$TEST_name FAILED:" "$@" "." >&2 343*bcb5dc79SHONG Yifan echo "$@" >$TEST_TMPDIR/__fail 344*bcb5dc79SHONG Yifan TEST_passed="false" 345*bcb5dc79SHONG Yifan __show_stack 346*bcb5dc79SHONG Yifan # Cleanup as we are leaving the subshell now 347*bcb5dc79SHONG Yifan tear_down 348*bcb5dc79SHONG Yifan exit 1 349*bcb5dc79SHONG Yifan} 350*bcb5dc79SHONG Yifan 351*bcb5dc79SHONG Yifan# Usage: warn <message> 352*bcb5dc79SHONG Yifan# Print a test warning with context information. 353*bcb5dc79SHONG Yifan# The context includes a stacktrace including the longest sequence 354*bcb5dc79SHONG Yifan# of calls outside this module. (We exclude the top and bottom portions of 355*bcb5dc79SHONG Yifan# the stack because they just add noise.) 356*bcb5dc79SHONG Yifanfunction warn() { 357*bcb5dc79SHONG Yifan __show_log >&2 358*bcb5dc79SHONG Yifan echo "$TEST_name WARNING: $1." >&2 359*bcb5dc79SHONG Yifan __show_stack 360*bcb5dc79SHONG Yifan 361*bcb5dc79SHONG Yifan if [ -n "${TEST_WARNINGS_OUTPUT_FILE:-}" ]; then 362*bcb5dc79SHONG Yifan echo "$TEST_name WARNING: $1." >> "$TEST_WARNINGS_OUTPUT_FILE" 363*bcb5dc79SHONG Yifan fi 364*bcb5dc79SHONG Yifan} 365*bcb5dc79SHONG Yifan 366*bcb5dc79SHONG Yifan# Usage: show_stack 367*bcb5dc79SHONG Yifan# Prints the portion of the stack that does not belong to this module, 368*bcb5dc79SHONG Yifan# i.e. the user's code that called a failing assertion. Stack may not 369*bcb5dc79SHONG Yifan# be available if Bash is reading commands from stdin; an error is 370*bcb5dc79SHONG Yifan# printed in that case. 371*bcb5dc79SHONG Yifan__show_stack() { 372*bcb5dc79SHONG Yifan local i=0 373*bcb5dc79SHONG Yifan local trace_found=0 374*bcb5dc79SHONG Yifan 375*bcb5dc79SHONG Yifan # Skip over active calls within this module: 376*bcb5dc79SHONG Yifan while (( i < ${#FUNCNAME[@]} )) && [[ ${BASH_SOURCE[i]:-} == ${BASH_SOURCE[0]} ]]; do 377*bcb5dc79SHONG Yifan (( ++i )) 378*bcb5dc79SHONG Yifan done 379*bcb5dc79SHONG Yifan 380*bcb5dc79SHONG Yifan # Show all calls until the next one within this module (typically run_suite): 381*bcb5dc79SHONG Yifan while (( i < ${#FUNCNAME[@]} )) && [[ ${BASH_SOURCE[i]:-} != ${BASH_SOURCE[0]} ]]; do 382*bcb5dc79SHONG Yifan # Read online docs for BASH_LINENO to understand the strange offset. 383*bcb5dc79SHONG Yifan # Undefined can occur in the BASH_SOURCE stack apparently when one exits from a subshell 384*bcb5dc79SHONG Yifan echo "${BASH_SOURCE[i]:-"Unknown"}:${BASH_LINENO[i - 1]:-"Unknown"}: in call to ${FUNCNAME[i]:-"Unknown"}" >&2 385*bcb5dc79SHONG Yifan (( ++i )) 386*bcb5dc79SHONG Yifan trace_found=1 387*bcb5dc79SHONG Yifan done 388*bcb5dc79SHONG Yifan 389*bcb5dc79SHONG Yifan [ $trace_found = 1 ] || echo "[Stack trace not available]" >&2 390*bcb5dc79SHONG Yifan} 391*bcb5dc79SHONG Yifan 392*bcb5dc79SHONG Yifan# Usage: expect_log <regexp> [error-message] 393*bcb5dc79SHONG Yifan# Asserts that $TEST_log matches regexp. Prints the contents of 394*bcb5dc79SHONG Yifan# $TEST_log and the specified (optional) error message otherwise, and 395*bcb5dc79SHONG Yifan# returns non-zero. 396*bcb5dc79SHONG Yifanfunction expect_log() { 397*bcb5dc79SHONG Yifan local pattern=$1 398*bcb5dc79SHONG Yifan local message=${2:-Expected regexp "$pattern" not found} 399*bcb5dc79SHONG Yifan grep -sq -- "$pattern" $TEST_log && return 0 400*bcb5dc79SHONG Yifan 401*bcb5dc79SHONG Yifan fail "$message" 402*bcb5dc79SHONG Yifan return 1 403*bcb5dc79SHONG Yifan} 404*bcb5dc79SHONG Yifan 405*bcb5dc79SHONG Yifan# Usage: expect_log_warn <regexp> [error-message] 406*bcb5dc79SHONG Yifan# Warns if $TEST_log does not match regexp. Prints the contents of 407*bcb5dc79SHONG Yifan# $TEST_log and the specified (optional) error message on mismatch. 408*bcb5dc79SHONG Yifanfunction expect_log_warn() { 409*bcb5dc79SHONG Yifan local pattern=$1 410*bcb5dc79SHONG Yifan local message=${2:-Expected regexp "$pattern" not found} 411*bcb5dc79SHONG Yifan grep -sq -- "$pattern" $TEST_log && return 0 412*bcb5dc79SHONG Yifan 413*bcb5dc79SHONG Yifan warn "$message" 414*bcb5dc79SHONG Yifan return 1 415*bcb5dc79SHONG Yifan} 416*bcb5dc79SHONG Yifan 417*bcb5dc79SHONG Yifan# Usage: expect_log_once <regexp> [error-message] 418*bcb5dc79SHONG Yifan# Asserts that $TEST_log contains one line matching <regexp>. 419*bcb5dc79SHONG Yifan# Prints the contents of $TEST_log and the specified (optional) 420*bcb5dc79SHONG Yifan# error message otherwise, and returns non-zero. 421*bcb5dc79SHONG Yifanfunction expect_log_once() { 422*bcb5dc79SHONG Yifan local pattern=$1 423*bcb5dc79SHONG Yifan local message=${2:-Expected regexp "$pattern" not found exactly once} 424*bcb5dc79SHONG Yifan expect_log_n "$pattern" 1 "$message" 425*bcb5dc79SHONG Yifan} 426*bcb5dc79SHONG Yifan 427*bcb5dc79SHONG Yifan# Usage: expect_log_n <regexp> <count> [error-message] 428*bcb5dc79SHONG Yifan# Asserts that $TEST_log contains <count> lines matching <regexp>. 429*bcb5dc79SHONG Yifan# Prints the contents of $TEST_log and the specified (optional) 430*bcb5dc79SHONG Yifan# error message otherwise, and returns non-zero. 431*bcb5dc79SHONG Yifanfunction expect_log_n() { 432*bcb5dc79SHONG Yifan local pattern=$1 433*bcb5dc79SHONG Yifan local expectednum=${2:-1} 434*bcb5dc79SHONG Yifan local message=${3:-Expected regexp "$pattern" not found exactly $expectednum times} 435*bcb5dc79SHONG Yifan local count=$(grep -sc -- "$pattern" $TEST_log) 436*bcb5dc79SHONG Yifan [[ $count = $expectednum ]] && return 0 437*bcb5dc79SHONG Yifan fail "$message" 438*bcb5dc79SHONG Yifan return 1 439*bcb5dc79SHONG Yifan} 440*bcb5dc79SHONG Yifan 441*bcb5dc79SHONG Yifan# Usage: expect_not_log <regexp> [error-message] 442*bcb5dc79SHONG Yifan# Asserts that $TEST_log does not match regexp. Prints the contents 443*bcb5dc79SHONG Yifan# of $TEST_log and the specified (optional) error message otherwise, and 444*bcb5dc79SHONG Yifan# returns non-zero. 445*bcb5dc79SHONG Yifanfunction expect_not_log() { 446*bcb5dc79SHONG Yifan local pattern=$1 447*bcb5dc79SHONG Yifan local message=${2:-Unexpected regexp "$pattern" found} 448*bcb5dc79SHONG Yifan grep -sq -- "$pattern" $TEST_log || return 0 449*bcb5dc79SHONG Yifan 450*bcb5dc79SHONG Yifan fail "$message" 451*bcb5dc79SHONG Yifan return 1 452*bcb5dc79SHONG Yifan} 453*bcb5dc79SHONG Yifan 454*bcb5dc79SHONG Yifan# Usage: expect_log_with_timeout <regexp> <timeout> [error-message] 455*bcb5dc79SHONG Yifan# Waits for the given regexp in the $TEST_log for up to timeout seconds. 456*bcb5dc79SHONG Yifan# Prints the contents of $TEST_log and the specified (optional) 457*bcb5dc79SHONG Yifan# error message otherwise, and returns non-zero. 458*bcb5dc79SHONG Yifanfunction expect_log_with_timeout() { 459*bcb5dc79SHONG Yifan local pattern=$1 460*bcb5dc79SHONG Yifan local timeout=$2 461*bcb5dc79SHONG Yifan local message=${3:-Regexp "$pattern" not found in "$timeout" seconds} 462*bcb5dc79SHONG Yifan local count=0 463*bcb5dc79SHONG Yifan while [ $count -lt $timeout ]; do 464*bcb5dc79SHONG Yifan grep -sq -- "$pattern" $TEST_log && return 0 465*bcb5dc79SHONG Yifan let count=count+1 466*bcb5dc79SHONG Yifan sleep 1 467*bcb5dc79SHONG Yifan done 468*bcb5dc79SHONG Yifan 469*bcb5dc79SHONG Yifan grep -sq -- "$pattern" $TEST_log && return 0 470*bcb5dc79SHONG Yifan fail "$message" 471*bcb5dc79SHONG Yifan return 1 472*bcb5dc79SHONG Yifan} 473*bcb5dc79SHONG Yifan 474*bcb5dc79SHONG Yifan# Usage: expect_cmd_with_timeout <expected> <cmd> [timeout] 475*bcb5dc79SHONG Yifan# Repeats the command once a second for up to timeout seconds (10s by default), 476*bcb5dc79SHONG Yifan# until the output matches the expected value. Fails and returns 1 if 477*bcb5dc79SHONG Yifan# the command does not return the expected value in the end. 478*bcb5dc79SHONG Yifanfunction expect_cmd_with_timeout() { 479*bcb5dc79SHONG Yifan local expected="$1" 480*bcb5dc79SHONG Yifan local cmd="$2" 481*bcb5dc79SHONG Yifan local timeout=${3:-10} 482*bcb5dc79SHONG Yifan local count=0 483*bcb5dc79SHONG Yifan while [ $count -lt $timeout ]; do 484*bcb5dc79SHONG Yifan local actual="$($cmd)" 485*bcb5dc79SHONG Yifan [ "$expected" = "$actual" ] && return 0 486*bcb5dc79SHONG Yifan let count=count+1 487*bcb5dc79SHONG Yifan sleep 1 488*bcb5dc79SHONG Yifan done 489*bcb5dc79SHONG Yifan 490*bcb5dc79SHONG Yifan [ "$expected" = "$actual" ] && return 0 491*bcb5dc79SHONG Yifan fail "Expected '$expected' within ${timeout}s, was '$actual'" 492*bcb5dc79SHONG Yifan return 1 493*bcb5dc79SHONG Yifan} 494*bcb5dc79SHONG Yifan 495*bcb5dc79SHONG Yifan# Usage: assert_one_of <expected_list>... <actual> 496*bcb5dc79SHONG Yifan# Asserts that actual is one of the items in expected_list 497*bcb5dc79SHONG Yifan# Example: assert_one_of ( "foo", "bar", "baz" ) actualval 498*bcb5dc79SHONG Yifanfunction assert_one_of() { 499*bcb5dc79SHONG Yifan local args=("$@") 500*bcb5dc79SHONG Yifan local last_arg_index=$((${#args[@]} - 1)) 501*bcb5dc79SHONG Yifan local actual=${args[last_arg_index]} 502*bcb5dc79SHONG Yifan unset args[last_arg_index] 503*bcb5dc79SHONG Yifan for expected_item in "${args[@]}"; do 504*bcb5dc79SHONG Yifan [ "$expected_item" = "$actual" ] && return 0 505*bcb5dc79SHONG Yifan done; 506*bcb5dc79SHONG Yifan 507*bcb5dc79SHONG Yifan fail "Expected one of '${args[@]}', was '$actual'" 508*bcb5dc79SHONG Yifan return 1 509*bcb5dc79SHONG Yifan} 510*bcb5dc79SHONG Yifan 511*bcb5dc79SHONG Yifan# Usage: assert_equals <expected> <actual> 512*bcb5dc79SHONG Yifan# Asserts [ expected = actual ]. 513*bcb5dc79SHONG Yifanfunction assert_equals() { 514*bcb5dc79SHONG Yifan local expected=$1 actual=$2 515*bcb5dc79SHONG Yifan [ "$expected" = "$actual" ] && return 0 516*bcb5dc79SHONG Yifan 517*bcb5dc79SHONG Yifan fail "Expected '$expected', was '$actual'" 518*bcb5dc79SHONG Yifan return 1 519*bcb5dc79SHONG Yifan} 520*bcb5dc79SHONG Yifan 521*bcb5dc79SHONG Yifan# Usage: assert_not_equals <unexpected> <actual> 522*bcb5dc79SHONG Yifan# Asserts [ unexpected != actual ]. 523*bcb5dc79SHONG Yifanfunction assert_not_equals() { 524*bcb5dc79SHONG Yifan local unexpected=$1 actual=$2 525*bcb5dc79SHONG Yifan [ "$unexpected" != "$actual" ] && return 0; 526*bcb5dc79SHONG Yifan 527*bcb5dc79SHONG Yifan fail "Expected not '$unexpected', was '$actual'" 528*bcb5dc79SHONG Yifan return 1 529*bcb5dc79SHONG Yifan} 530*bcb5dc79SHONG Yifan 531*bcb5dc79SHONG Yifan# Usage: assert_contains <regexp> <file> [error-message] 532*bcb5dc79SHONG Yifan# Asserts that file matches regexp. Prints the contents of 533*bcb5dc79SHONG Yifan# file and the specified (optional) error message otherwise, and 534*bcb5dc79SHONG Yifan# returns non-zero. 535*bcb5dc79SHONG Yifanfunction assert_contains() { 536*bcb5dc79SHONG Yifan local pattern=$1 537*bcb5dc79SHONG Yifan local file=$2 538*bcb5dc79SHONG Yifan local message=${3:-Expected regexp "$pattern" not found in "$file"} 539*bcb5dc79SHONG Yifan grep -sq -- "$pattern" "$file" && return 0 540*bcb5dc79SHONG Yifan 541*bcb5dc79SHONG Yifan cat "$file" >&2 542*bcb5dc79SHONG Yifan fail "$message" 543*bcb5dc79SHONG Yifan return 1 544*bcb5dc79SHONG Yifan} 545*bcb5dc79SHONG Yifan 546*bcb5dc79SHONG Yifan# Usage: assert_not_contains <regexp> <file> [error-message] 547*bcb5dc79SHONG Yifan# Asserts that file does not match regexp. Prints the contents of 548*bcb5dc79SHONG Yifan# file and the specified (optional) error message otherwise, and 549*bcb5dc79SHONG Yifan# returns non-zero. 550*bcb5dc79SHONG Yifanfunction assert_not_contains() { 551*bcb5dc79SHONG Yifan local pattern=$1 552*bcb5dc79SHONG Yifan local file=$2 553*bcb5dc79SHONG Yifan local message=${3:-Expected regexp "$pattern" found in "$file"} 554*bcb5dc79SHONG Yifan grep -sq -- "$pattern" "$file" || return 0 555*bcb5dc79SHONG Yifan 556*bcb5dc79SHONG Yifan cat "$file" >&2 557*bcb5dc79SHONG Yifan fail "$message" 558*bcb5dc79SHONG Yifan return 1 559*bcb5dc79SHONG Yifan} 560*bcb5dc79SHONG Yifan 561*bcb5dc79SHONG Yifan# Updates the global variables TESTS if 562*bcb5dc79SHONG Yifan# sharding is enabled, i.e. ($TEST_TOTAL_SHARDS > 0). 563*bcb5dc79SHONG Yifanfunction __update_shards() { 564*bcb5dc79SHONG Yifan [ -z "${TEST_TOTAL_SHARDS-}" ] && return 0 565*bcb5dc79SHONG Yifan 566*bcb5dc79SHONG Yifan [ "$TEST_TOTAL_SHARDS" -gt 0 ] || 567*bcb5dc79SHONG Yifan { echo "Invalid total shards $TEST_TOTAL_SHARDS" >&2; exit 1; } 568*bcb5dc79SHONG Yifan 569*bcb5dc79SHONG Yifan [ "$TEST_SHARD_INDEX" -lt 0 -o "$TEST_SHARD_INDEX" -ge "$TEST_TOTAL_SHARDS" ] && 570*bcb5dc79SHONG Yifan { echo "Invalid shard $shard_index" >&2; exit 1; } 571*bcb5dc79SHONG Yifan 572*bcb5dc79SHONG Yifan TESTS=$(for test in "${TESTS[@]}"; do echo "$test"; done | 573*bcb5dc79SHONG Yifan awk "NR % $TEST_TOTAL_SHARDS == $TEST_SHARD_INDEX") 574*bcb5dc79SHONG Yifan 575*bcb5dc79SHONG Yifan [ -z "${TEST_SHARD_STATUS_FILE-}" ] || touch "$TEST_SHARD_STATUS_FILE" 576*bcb5dc79SHONG Yifan} 577*bcb5dc79SHONG Yifan 578*bcb5dc79SHONG Yifan# Usage: __test_terminated <signal-number> 579*bcb5dc79SHONG Yifan# Handler that is called when the test terminated unexpectedly 580*bcb5dc79SHONG Yifanfunction __test_terminated() { 581*bcb5dc79SHONG Yifan __show_log >&2 582*bcb5dc79SHONG Yifan echo "$TEST_name FAILED: terminated by signal $1." >&2 583*bcb5dc79SHONG Yifan TEST_passed="false" 584*bcb5dc79SHONG Yifan __show_stack 585*bcb5dc79SHONG Yifan timeout 586*bcb5dc79SHONG Yifan exit 1 587*bcb5dc79SHONG Yifan} 588*bcb5dc79SHONG Yifan 589*bcb5dc79SHONG Yifan# Usage: __test_terminated_err 590*bcb5dc79SHONG Yifan# Handler that is called when the test terminated unexpectedly due to "errexit". 591*bcb5dc79SHONG Yifanfunction __test_terminated_err() { 592*bcb5dc79SHONG Yifan # When a subshell exits due to signal ERR, its parent shell also exits, 593*bcb5dc79SHONG Yifan # thus the signal handler is called recursively and we print out the 594*bcb5dc79SHONG Yifan # error message and stack trace multiple times. We're only interested 595*bcb5dc79SHONG Yifan # in the first one though, as it contains the most information, so ignore 596*bcb5dc79SHONG Yifan # all following. 597*bcb5dc79SHONG Yifan if [[ -f $TEST_TMPDIR/__err_handled ]]; then 598*bcb5dc79SHONG Yifan exit 1 599*bcb5dc79SHONG Yifan fi 600*bcb5dc79SHONG Yifan __show_log >&2 601*bcb5dc79SHONG Yifan if [[ ! -z "$TEST_name" ]]; then 602*bcb5dc79SHONG Yifan echo -n "$TEST_name " 603*bcb5dc79SHONG Yifan fi 604*bcb5dc79SHONG Yifan echo "FAILED: terminated because this command returned a non-zero status:" >&2 605*bcb5dc79SHONG Yifan touch $TEST_TMPDIR/__err_handled 606*bcb5dc79SHONG Yifan TEST_passed="false" 607*bcb5dc79SHONG Yifan __show_stack 608*bcb5dc79SHONG Yifan # If $TEST_name is still empty, the test suite failed before we even started 609*bcb5dc79SHONG Yifan # to run tests, so we shouldn't call tear_down. 610*bcb5dc79SHONG Yifan if [[ ! -z "$TEST_name" ]]; then 611*bcb5dc79SHONG Yifan tear_down 612*bcb5dc79SHONG Yifan fi 613*bcb5dc79SHONG Yifan exit 1 614*bcb5dc79SHONG Yifan} 615*bcb5dc79SHONG Yifan 616*bcb5dc79SHONG Yifan# Usage: __trap_with_arg <handler> <signals ...> 617*bcb5dc79SHONG Yifan# Helper to install a trap handler for several signals preserving the signal 618*bcb5dc79SHONG Yifan# number, so that the signal number is available to the trap handler. 619*bcb5dc79SHONG Yifanfunction __trap_with_arg() { 620*bcb5dc79SHONG Yifan func="$1" ; shift 621*bcb5dc79SHONG Yifan for sig ; do 622*bcb5dc79SHONG Yifan trap "$func $sig" "$sig" 623*bcb5dc79SHONG Yifan done 624*bcb5dc79SHONG Yifan} 625*bcb5dc79SHONG Yifan 626*bcb5dc79SHONG Yifan# Usage: <node> <block> 627*bcb5dc79SHONG Yifan# Adds the block to the given node in the report file. Quotes in the in 628*bcb5dc79SHONG Yifan# arguments need to be escaped. 629*bcb5dc79SHONG Yifanfunction __log_to_test_report() { 630*bcb5dc79SHONG Yifan local node="$1" 631*bcb5dc79SHONG Yifan local block="$2" 632*bcb5dc79SHONG Yifan if [[ ! -e "$XML_OUTPUT_FILE" ]]; then 633*bcb5dc79SHONG Yifan local xml_header='<?xml version="1.0" encoding="UTF-8"?>' 634*bcb5dc79SHONG Yifan echo "$xml_header<testsuites></testsuites>" > $XML_OUTPUT_FILE 635*bcb5dc79SHONG Yifan fi 636*bcb5dc79SHONG Yifan 637*bcb5dc79SHONG Yifan # replace match on node with block and match 638*bcb5dc79SHONG Yifan # replacement expression only needs escaping for quotes 639*bcb5dc79SHONG Yifan perl -e "\ 640*bcb5dc79SHONG Yifan\$input = @ARGV[0]; \ 641*bcb5dc79SHONG Yifan\$/=undef; \ 642*bcb5dc79SHONG Yifanopen FILE, '+<$XML_OUTPUT_FILE'; \ 643*bcb5dc79SHONG Yifan\$content = <FILE>; \ 644*bcb5dc79SHONG Yifanif (\$content =~ /($node.*)\$/) { \ 645*bcb5dc79SHONG Yifan seek FILE, 0, 0; \ 646*bcb5dc79SHONG Yifan print FILE \$\` . \$input . \$1; \ 647*bcb5dc79SHONG Yifan}; \ 648*bcb5dc79SHONG Yifanclose FILE" "$block" 649*bcb5dc79SHONG Yifan} 650*bcb5dc79SHONG Yifan 651*bcb5dc79SHONG Yifan# Usage: <total> <passed> 652*bcb5dc79SHONG Yifan# Adds the test summaries to the xml nodes. 653*bcb5dc79SHONG Yifanfunction __finish_test_report() { 654*bcb5dc79SHONG Yifan local total=$1 655*bcb5dc79SHONG Yifan local passed=$2 656*bcb5dc79SHONG Yifan local failed=$((total - passed)) 657*bcb5dc79SHONG Yifan 658*bcb5dc79SHONG Yifan cat $XML_OUTPUT_FILE | \ 659*bcb5dc79SHONG Yifan sed \ 660*bcb5dc79SHONG Yifan "s/<testsuites>/<testsuites tests=\"$total\" failures=\"0\" errors=\"$failed\">/" | \ 661*bcb5dc79SHONG Yifan sed \ 662*bcb5dc79SHONG Yifan "s/<testsuite>/<testsuite tests=\"$total\" failures=\"0\" errors=\"$failed\">/" \ 663*bcb5dc79SHONG Yifan > $XML_OUTPUT_FILE.bak 664*bcb5dc79SHONG Yifan 665*bcb5dc79SHONG Yifan rm -f $XML_OUTPUT_FILE 666*bcb5dc79SHONG Yifan mv $XML_OUTPUT_FILE.bak $XML_OUTPUT_FILE 667*bcb5dc79SHONG Yifan} 668*bcb5dc79SHONG Yifan 669*bcb5dc79SHONG Yifan# Multi-platform timestamp function 670*bcb5dc79SHONG YifanUNAME=$(uname -s | tr 'A-Z' 'a-z') 671*bcb5dc79SHONG Yifanif [ "$UNAME" = "linux" ] || [[ "$UNAME" =~ msys_nt* ]]; then 672*bcb5dc79SHONG Yifan function timestamp() { 673*bcb5dc79SHONG Yifan echo $(($(date +%s%N)/1000000)) 674*bcb5dc79SHONG Yifan } 675*bcb5dc79SHONG Yifanelse 676*bcb5dc79SHONG Yifan function timestamp() { 677*bcb5dc79SHONG Yifan # macOS and BSDs do not have %N, so Python is the best we can do. 678*bcb5dc79SHONG Yifan # LC_ALL=C works around python 3.8 and 3.9 crash on macOS when the 679*bcb5dc79SHONG Yifan # filesystem encoding is unspecified (e.g. when LANG=en_US). 680*bcb5dc79SHONG Yifan local PYTHON=python 681*bcb5dc79SHONG Yifan command -v python3 &> /dev/null && PYTHON=python3 682*bcb5dc79SHONG Yifan LC_ALL=C "${PYTHON}" -c 'import time; print(int(round(time.time() * 1000)))' 683*bcb5dc79SHONG Yifan } 684*bcb5dc79SHONG Yifanfi 685*bcb5dc79SHONG Yifan 686*bcb5dc79SHONG Yifanfunction get_run_time() { 687*bcb5dc79SHONG Yifan local ts_start=$1 688*bcb5dc79SHONG Yifan local ts_end=$2 689*bcb5dc79SHONG Yifan run_time_ms=$((${ts_end}-${ts_start})) 690*bcb5dc79SHONG Yifan echo $(($run_time_ms/1000)).${run_time_ms: -3} 691*bcb5dc79SHONG Yifan} 692*bcb5dc79SHONG Yifan 693*bcb5dc79SHONG Yifan# Usage: run_tests <suite-comment> 694*bcb5dc79SHONG Yifan# Must be called from the end of the user's test suite. 695*bcb5dc79SHONG Yifan# Calls exit with zero on success, non-zero otherwise. 696*bcb5dc79SHONG Yifanfunction run_suite() { 697*bcb5dc79SHONG Yifan echo >&2 698*bcb5dc79SHONG Yifan echo "$1" >&2 699*bcb5dc79SHONG Yifan echo >&2 700*bcb5dc79SHONG Yifan 701*bcb5dc79SHONG Yifan __log_to_test_report "<\/testsuites>" "<testsuite></testsuite>" 702*bcb5dc79SHONG Yifan 703*bcb5dc79SHONG Yifan local total=0 704*bcb5dc79SHONG Yifan local passed=0 705*bcb5dc79SHONG Yifan 706*bcb5dc79SHONG Yifan atexit "cleanup" 707*bcb5dc79SHONG Yifan 708*bcb5dc79SHONG Yifan # If the user didn't specify an explicit list of tests (e.g. a 709*bcb5dc79SHONG Yifan # working set), use them all. 710*bcb5dc79SHONG Yifan if [ ${#TESTS[@]} = 0 ]; then 711*bcb5dc79SHONG Yifan TESTS=$(declare -F | awk '{print $3}' | grep ^test_) 712*bcb5dc79SHONG Yifan elif [ -n "${TEST_WARNINGS_OUTPUT_FILE:-}" ]; then 713*bcb5dc79SHONG Yifan if grep -q "TESTS=" "$TEST_script" ; then 714*bcb5dc79SHONG Yifan echo "TESTS variable overridden in Bazel sh_test. Please remove before submitting" \ 715*bcb5dc79SHONG Yifan >> "$TEST_WARNINGS_OUTPUT_FILE" 716*bcb5dc79SHONG Yifan fi 717*bcb5dc79SHONG Yifan fi 718*bcb5dc79SHONG Yifan 719*bcb5dc79SHONG Yifan __update_shards 720*bcb5dc79SHONG Yifan 721*bcb5dc79SHONG Yifan for TEST_name in ${TESTS[@]}; do 722*bcb5dc79SHONG Yifan >$TEST_log # Reset the log. 723*bcb5dc79SHONG Yifan TEST_passed="true" 724*bcb5dc79SHONG Yifan 725*bcb5dc79SHONG Yifan total=$(($total + 1)) 726*bcb5dc79SHONG Yifan if [[ "$TEST_verbose" == "true" ]]; then 727*bcb5dc79SHONG Yifan __pad $TEST_name '*' >&2 728*bcb5dc79SHONG Yifan fi 729*bcb5dc79SHONG Yifan 730*bcb5dc79SHONG Yifan local run_time="0.0" 731*bcb5dc79SHONG Yifan rm -f $TEST_TMPDIR/{__ts_start,__ts_end} 732*bcb5dc79SHONG Yifan 733*bcb5dc79SHONG Yifan if [ "$(type -t $TEST_name)" = function ]; then 734*bcb5dc79SHONG Yifan # Save exit handlers eventually set. 735*bcb5dc79SHONG Yifan local SAVED_ATEXIT="$ATEXIT"; 736*bcb5dc79SHONG Yifan ATEXIT= 737*bcb5dc79SHONG Yifan 738*bcb5dc79SHONG Yifan # Run test in a subshell. 739*bcb5dc79SHONG Yifan rm -f $TEST_TMPDIR/__err_handled 740*bcb5dc79SHONG Yifan __trap_with_arg __test_terminated INT KILL PIPE TERM ABRT FPE ILL QUIT SEGV 741*bcb5dc79SHONG Yifan ( 742*bcb5dc79SHONG Yifan timestamp >$TEST_TMPDIR/__ts_start 743*bcb5dc79SHONG Yifan set_up 744*bcb5dc79SHONG Yifan eval $TEST_name 745*bcb5dc79SHONG Yifan tear_down 746*bcb5dc79SHONG Yifan timestamp >$TEST_TMPDIR/__ts_end 747*bcb5dc79SHONG Yifan test $TEST_passed == "true" 748*bcb5dc79SHONG Yifan ) 2>&1 | tee $TEST_TMPDIR/__log 749*bcb5dc79SHONG Yifan # Note that tee will prevent the control flow continuing if the test 750*bcb5dc79SHONG Yifan # spawned any processes which are still running and have not closed 751*bcb5dc79SHONG Yifan # their stdout. 752*bcb5dc79SHONG Yifan 753*bcb5dc79SHONG Yifan test_subshell_status=${PIPESTATUS[0]} 754*bcb5dc79SHONG Yifan if [ "$test_subshell_status" != 0 ]; then 755*bcb5dc79SHONG Yifan TEST_passed="false" 756*bcb5dc79SHONG Yifan # Ensure that an end time is recorded in case the test subshell 757*bcb5dc79SHONG Yifan # terminated prematurely. 758*bcb5dc79SHONG Yifan [ -f $TEST_TMPDIR/__ts_end ] || timestamp >$TEST_TMPDIR/__ts_end 759*bcb5dc79SHONG Yifan fi 760*bcb5dc79SHONG Yifan 761*bcb5dc79SHONG Yifan # Calculate run time for the testcase. 762*bcb5dc79SHONG Yifan local ts_start=$(cat $TEST_TMPDIR/__ts_start) 763*bcb5dc79SHONG Yifan local ts_end=$(cat $TEST_TMPDIR/__ts_end) 764*bcb5dc79SHONG Yifan run_time=$(get_run_time $ts_start $ts_end) 765*bcb5dc79SHONG Yifan 766*bcb5dc79SHONG Yifan # Eventually restore exit handlers. 767*bcb5dc79SHONG Yifan if [ -n "$SAVED_ATEXIT" ]; then 768*bcb5dc79SHONG Yifan ATEXIT="$SAVED_ATEXIT" 769*bcb5dc79SHONG Yifan trap "$ATEXIT" EXIT 770*bcb5dc79SHONG Yifan fi 771*bcb5dc79SHONG Yifan else # Bad test explicitly specified in $TESTS. 772*bcb5dc79SHONG Yifan fail "Not a function: '$TEST_name'" 773*bcb5dc79SHONG Yifan fi 774*bcb5dc79SHONG Yifan 775*bcb5dc79SHONG Yifan local testcase_tag="" 776*bcb5dc79SHONG Yifan 777*bcb5dc79SHONG Yifan if [[ "$TEST_passed" == "true" ]]; then 778*bcb5dc79SHONG Yifan if [[ "$TEST_verbose" == "true" ]]; then 779*bcb5dc79SHONG Yifan echo "PASSED: $TEST_name" >&2 780*bcb5dc79SHONG Yifan fi 781*bcb5dc79SHONG Yifan passed=$(($passed + 1)) 782*bcb5dc79SHONG Yifan testcase_tag="<testcase name=\"$TEST_name\" status=\"run\" time=\"$run_time\" classname=\"\"></testcase>" 783*bcb5dc79SHONG Yifan else 784*bcb5dc79SHONG Yifan echo "FAILED: $TEST_name" >&2 785*bcb5dc79SHONG Yifan # end marker in CDATA cannot be escaped, we need to split the CDATA sections 786*bcb5dc79SHONG Yifan log=$(cat $TEST_TMPDIR/__log | sed 's/]]>/]]>]]><![CDATA[/g') 787*bcb5dc79SHONG Yifan fail_msg=$(cat $TEST_TMPDIR/__fail 2> /dev/null || echo "No failure message") 788*bcb5dc79SHONG Yifan testcase_tag="<testcase name=\"$TEST_name\" status=\"run\" time=\"$run_time\" classname=\"\"><error message=\"$fail_msg\"><![CDATA[$log]]></error></testcase>" 789*bcb5dc79SHONG Yifan fi 790*bcb5dc79SHONG Yifan 791*bcb5dc79SHONG Yifan if [[ "$TEST_verbose" == "true" ]]; then 792*bcb5dc79SHONG Yifan echo >&2 793*bcb5dc79SHONG Yifan fi 794*bcb5dc79SHONG Yifan __log_to_test_report "<\/testsuite>" "$testcase_tag" 795*bcb5dc79SHONG Yifan done 796*bcb5dc79SHONG Yifan 797*bcb5dc79SHONG Yifan __finish_test_report $total $passed 798*bcb5dc79SHONG Yifan __pad "$passed / $total tests passed." '*' >&2 799*bcb5dc79SHONG Yifan [ $total = $passed ] || { 800*bcb5dc79SHONG Yifan __pad "There were errors." '*' 801*bcb5dc79SHONG Yifan exit 1 802*bcb5dc79SHONG Yifan } >&2 803*bcb5dc79SHONG Yifan 804*bcb5dc79SHONG Yifan exit 0 805*bcb5dc79SHONG Yifan} 806