xref: /aosp_15_r20/external/bazel-skylib/tests/unittest.bash (revision bcb5dc7965af6ee42bf2f21341a2ec00233a8c8a)
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/]]>/]]>]]&gt;<![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