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