1# Shell function library for test cases.
2#
3# Note that while many of the functions in this library could benefit from
4# using "local" to avoid possibly hammering global variables, Solaris /bin/sh
5# doesn't support local and this library aspires to be portable to Solaris
6# Bourne shell.  Instead, all private variables are prefixed with "tap_".
7#
8# This file provides a TAP-compatible shell function library useful for
9# writing test cases.  It is part of C TAP Harness, which can be found at
10# <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
11#
12# Written by Russ Allbery <eagle@eyrie.org>
13# Copyright 2009, 2010, 2011, 2012 Russ Allbery <eagle@eyrie.org>
14# Copyright 2006, 2007, 2008, 2013
15#     The Board of Trustees of the Leland Stanford Junior University
16#
17# Permission is hereby granted, free of charge, to any person obtaining a copy
18# of this software and associated documentation files (the "Software"), to
19# deal in the Software without restriction, including without limitation the
20# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
21# sell copies of the Software, and to permit persons to whom the Software is
22# furnished to do so, subject to the following conditions:
23#
24# The above copyright notice and this permission notice shall be included in
25# all copies or substantial portions of the Software.
26#
27# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
30# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
32# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
33# IN THE SOFTWARE.
34
35# Print out the number of test cases we expect to run.
36plan () {
37    count=1
38    planned="$1"
39    failed=0
40    echo "1..$1"
41    trap finish 0
42}
43
44# Prepare for lazy planning.
45plan_lazy () {
46    count=1
47    planned=0
48    failed=0
49    trap finish 0
50}
51
52# Report the test status on exit.
53finish () {
54    tap_highest=`expr "$count" - 1`
55    if [ "$planned" = 0 ] ; then
56        echo "1..$tap_highest"
57        planned="$tap_highest"
58    fi
59    tap_looks='# Looks like you'
60    if [ "$planned" -gt 0 ] ; then
61        if [ "$planned" -gt "$tap_highest" ] ; then
62            if [ "$planned" -gt 1 ] ; then
63                echo "$tap_looks planned $planned tests but only ran" \
64                    "$tap_highest"
65            else
66                echo "$tap_looks planned $planned test but only ran" \
67                    "$tap_highest"
68            fi
69        elif [ "$planned" -lt "$tap_highest" ] ; then
70            tap_extra=`expr "$tap_highest" - "$planned"`
71            if [ "$planned" -gt 1 ] ; then
72                echo "$tap_looks planned $planned tests but ran" \
73                    "$tap_extra extra"
74            else
75                echo "$tap_looks planned $planned test but ran" \
76                    "$tap_extra extra"
77            fi
78        elif [ "$failed" -gt 0 ] ; then
79            if [ "$failed" -gt 1 ] ; then
80                echo "$tap_looks failed $failed tests of $planned"
81            else
82                echo "$tap_looks failed $failed test of $planned"
83            fi
84        elif [ "$planned" -gt 1 ] ; then
85            echo "# All $planned tests successful or skipped"
86        else
87            echo "# $planned test successful or skipped"
88        fi
89    fi
90}
91
92# Skip the entire test suite.  Should be run instead of plan.
93skip_all () {
94    tap_desc="$1"
95    if [ -n "$tap_desc" ] ; then
96        echo "1..0 # skip $tap_desc"
97    else
98        echo "1..0 # skip"
99    fi
100    exit 0
101}
102
103# ok takes a test description and a command to run and prints success if that
104# command is successful, false otherwise.  The count starts at 1 and is
105# updated each time ok is printed.
106ok () {
107    tap_desc="$1"
108    if [ -n "$tap_desc" ] ; then
109        tap_desc=" - $tap_desc"
110    fi
111    shift
112    if "$@" ; then
113        echo ok "$count$tap_desc"
114    else
115        echo not ok "$count$tap_desc"
116        failed=`expr $failed + 1`
117    fi
118    count=`expr $count + 1`
119}
120
121# Skip the next test.  Takes the reason why the test is skipped.
122skip () {
123    echo "ok $count # skip $*"
124    count=`expr $count + 1`
125}
126
127# Report the same status on a whole set of tests.  Takes the count of tests,
128# the description, and then the command to run to determine the status.
129ok_block () {
130    tap_i=$count
131    tap_end=`expr $count + $1`
132    shift
133    while [ "$tap_i" -lt "$tap_end" ] ; do
134        ok "$@"
135        tap_i=`expr $tap_i + 1`
136    done
137}
138
139# Skip a whole set of tests.  Takes the count and then the reason for skipping
140# the test.
141skip_block () {
142    tap_i=$count
143    tap_end=`expr $count + $1`
144    shift
145    while [ "$tap_i" -lt "$tap_end" ] ; do
146        skip "$@"
147        tap_i=`expr $tap_i + 1`
148    done
149}
150
151# Portable variant of printf '%s\n' "$*".  In the majority of cases, this
152# function is slower than printf, because the latter is often implemented
153# as a builtin command.  The value of the variable IFS is ignored.
154#
155# This macro must not be called via backticks inside double quotes, since this
156# will result in bizarre escaping behavior and lots of extra backslashes on
157# Solaris.
158puts () {
159    cat << EOH
160$@
161EOH
162}
163
164# Run a program expected to succeed, and print ok if it does and produces the
165# correct output.  Takes the description, expected exit status, the expected
166# output, the command to run, and then any arguments for that command.
167# Standard output and standard error are combined when analyzing the output of
168# the command.
169#
170# If the command may contain system-specific error messages in its output,
171# add strip_colon_error before the command to post-process its output.
172ok_program () {
173    tap_desc="$1"
174    shift
175    tap_w_status="$1"
176    shift
177    tap_w_output="$1"
178    shift
179    tap_output=`"$@" 2>&1`
180    tap_status=$?
181    if [ $tap_status = $tap_w_status ] \
182        && [ x"$tap_output" = x"$tap_w_output" ] ; then
183        ok "$tap_desc" true
184    else
185        echo "#  saw: ($tap_status) $tap_output"
186        echo "#  not: ($tap_w_status) $tap_w_output"
187        ok "$tap_desc" false
188    fi
189}
190
191# Strip a colon and everything after it off the output of a command, as long
192# as that colon comes after at least one whitespace character.  (This is done
193# to avoid stripping the name of the program from the start of an error
194# message.)  This is used to remove system-specific error messages (coming
195# from strerror, for example).
196strip_colon_error() {
197    tap_output=`"$@" 2>&1`
198    tap_status=$?
199    tap_output=`puts "$tap_output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
200    puts "$tap_output"
201    return $tap_status
202}
203
204# Bail out with an error message.
205bail () {
206    echo 'Bail out!' "$@"
207    exit 255
208}
209
210# Output a diagnostic on standard error, preceded by the required # mark.
211diag () {
212    echo '#' "$@"
213}
214
215# Search for the given file first in $BUILD and then in $SOURCE and echo the
216# path where the file was found, or the empty string if the file wasn't
217# found.
218#
219# This macro uses puts, so don't run it using backticks inside double quotes
220# or bizarre quoting behavior will happen with Solaris sh.
221test_file_path () {
222    if [ -n "$BUILD" ] && [ -f "$BUILD/$1" ] ; then
223        puts "$BUILD/$1"
224    elif [ -n "$SOURCE" ] && [ -f "$SOURCE/$1" ] ; then
225        puts "$SOURCE/$1"
226    else
227        echo ''
228    fi
229}
230
231# Create $BUILD/tmp for use by tests for storing temporary files and return
232# the path (via standard output).
233#
234# This macro uses puts, so don't run it using backticks inside double quotes
235# or bizarre quoting behavior will happen with Solaris sh.
236test_tmpdir () {
237    if [ -z "$BUILD" ] ; then
238        tap_tmpdir="./tmp"
239    else
240        tap_tmpdir="$BUILD"/tmp
241    fi
242    if [ ! -d "$tap_tmpdir" ] ; then
243        mkdir "$tap_tmpdir" || bail "Error creating $tap_tmpdir"
244    fi
245    puts "$tap_tmpdir"
246}
247