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# <https://www.eyrie.org/~eagle/software/c-tap-harness/>.
11#
12# Written by Russ Allbery <eagle@eyrie.org>
13# Copyright 2009-2012, 2016 Russ Allbery <eagle@eyrie.org>
14# Copyright 2006-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# SPDX-License-Identifier: MIT
36
37# Print out the number of test cases we expect to run.
38plan () {
39    count=1
40    planned="$1"
41    failed=0
42    echo "1..$1"
43    trap finish 0
44}
45
46# Prepare for lazy planning.
47plan_lazy () {
48    count=1
49    planned=0
50    failed=0
51    trap finish 0
52}
53
54# Report the test status on exit.
55finish () {
56    tap_highest=`expr "$count" - 1`
57    if [ "$planned" = 0 ] ; then
58        echo "1..$tap_highest"
59        planned="$tap_highest"
60    fi
61    tap_looks='# Looks like you'
62    if [ "$planned" -gt 0 ] ; then
63        if [ "$planned" -gt "$tap_highest" ] ; then
64            if [ "$planned" -gt 1 ] ; then
65                echo "$tap_looks planned $planned tests but only ran" \
66                    "$tap_highest"
67            else
68                echo "$tap_looks planned $planned test but only ran" \
69                    "$tap_highest"
70            fi
71        elif [ "$planned" -lt "$tap_highest" ] ; then
72            tap_extra=`expr "$tap_highest" - "$planned"`
73            if [ "$planned" -gt 1 ] ; then
74                echo "$tap_looks planned $planned tests but ran" \
75                    "$tap_extra extra"
76            else
77                echo "$tap_looks planned $planned test but ran" \
78                    "$tap_extra extra"
79            fi
80        elif [ "$failed" -gt 0 ] ; then
81            if [ "$failed" -gt 1 ] ; then
82                echo "$tap_looks failed $failed tests of $planned"
83            else
84                echo "$tap_looks failed $failed test of $planned"
85            fi
86        elif [ "$planned" -gt 1 ] ; then
87            echo "# All $planned tests successful or skipped"
88        else
89            echo "# $planned test successful or skipped"
90        fi
91    fi
92}
93
94# Skip the entire test suite.  Should be run instead of plan.
95skip_all () {
96    tap_desc="$1"
97    if [ -n "$tap_desc" ] ; then
98        echo "1..0 # skip $tap_desc"
99    else
100        echo "1..0 # skip"
101    fi
102    exit 0
103}
104
105# ok takes a test description and a command to run and prints success if that
106# command is successful, false otherwise.  The count starts at 1 and is
107# updated each time ok is printed.
108ok () {
109    tap_desc="$1"
110    if [ -n "$tap_desc" ] ; then
111        tap_desc=" - $tap_desc"
112    fi
113    shift
114    if "$@" ; then
115        echo ok "$count$tap_desc"
116    else
117        echo not ok "$count$tap_desc"
118        failed=`expr $failed + 1`
119    fi
120    count=`expr $count + 1`
121}
122
123# Skip the next test.  Takes the reason why the test is skipped.
124skip () {
125    echo "ok $count # skip $*"
126    count=`expr $count + 1`
127}
128
129# Report the same status on a whole set of tests.  Takes the count of tests,
130# the description, and then the command to run to determine the status.
131ok_block () {
132    tap_i=$count
133    tap_end=`expr $count + $1`
134    shift
135    while [ "$tap_i" -lt "$tap_end" ] ; do
136        ok "$@"
137        tap_i=`expr $tap_i + 1`
138    done
139}
140
141# Skip a whole set of tests.  Takes the count and then the reason for skipping
142# the test.
143skip_block () {
144    tap_i=$count
145    tap_end=`expr $count + $1`
146    shift
147    while [ "$tap_i" -lt "$tap_end" ] ; do
148        skip "$@"
149        tap_i=`expr $tap_i + 1`
150    done
151}
152
153# Portable variant of printf '%s\n' "$*".  In the majority of cases, this
154# function is slower than printf, because the latter is often implemented
155# as a builtin command.  The value of the variable IFS is ignored.
156#
157# This macro must not be called via backticks inside double quotes, since this
158# will result in bizarre escaping behavior and lots of extra backslashes on
159# Solaris.
160puts () {
161    cat << EOH
162$@
163EOH
164}
165
166# Run a program expected to succeed, and print ok if it does and produces the
167# correct output.  Takes the description, expected exit status, the expected
168# output, the command to run, and then any arguments for that command.
169# Standard output and standard error are combined when analyzing the output of
170# the command.
171#
172# If the command may contain system-specific error messages in its output,
173# add strip_colon_error before the command to post-process its output.
174ok_program () {
175    tap_desc="$1"
176    shift
177    tap_w_status="$1"
178    shift
179    tap_w_output="$1"
180    shift
181    tap_output=`"$@" 2>&1`
182    tap_status=$?
183    if [ $tap_status = $tap_w_status ] \
184        && [ x"$tap_output" = x"$tap_w_output" ] ; then
185        ok "$tap_desc" true
186    else
187        echo "#  saw: ($tap_status) $tap_output"
188        echo "#  not: ($tap_w_status) $tap_w_output"
189        ok "$tap_desc" false
190    fi
191}
192
193# Strip a colon and everything after it off the output of a command, as long
194# as that colon comes after at least one whitespace character.  (This is done
195# to avoid stripping the name of the program from the start of an error
196# message.)  This is used to remove system-specific error messages (coming
197# from strerror, for example).
198strip_colon_error() {
199    tap_output=`"$@" 2>&1`
200    tap_status=$?
201    tap_output=`puts "$tap_output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
202    puts "$tap_output"
203    return $tap_status
204}
205
206# Bail out with an error message.
207bail () {
208    echo 'Bail out!' "$@"
209    exit 255
210}
211
212# Output a diagnostic on standard error, preceded by the required # mark.
213diag () {
214    echo '#' "$@"
215}
216
217# Search for the given file first in $C_TAP_BUILD and then in $C_TAP_SOURCE
218# and echo the path where the file was found, or the empty string if the file
219# wasn't found.
220#
221# This macro uses puts, so don't run it using backticks inside double quotes
222# or bizarre quoting behavior will happen with Solaris sh.
223test_file_path () {
224    if [ -n "$C_TAP_BUILD" ] && [ -f "$C_TAP_BUILD/$1" ] ; then
225        puts "$C_TAP_BUILD/$1"
226    elif [ -n "$C_TAP_SOURCE" ] && [ -f "$C_TAP_SOURCE/$1" ] ; then
227        puts "$C_TAP_SOURCE/$1"
228    else
229        echo ''
230    fi
231}
232
233# Create $C_TAP_BUILD/tmp for use by tests for storing temporary files and
234# return the path (via standard output).
235#
236# This macro uses puts, so don't run it using backticks inside double quotes
237# or bizarre quoting behavior will happen with Solaris sh.
238test_tmpdir () {
239    if [ -z "$C_TAP_BUILD" ] ; then
240        tap_tmpdir="./tmp"
241    else
242        tap_tmpdir="$C_TAP_BUILD"/tmp
243    fi
244    if [ ! -d "$tap_tmpdir" ] ; then
245        mkdir "$tap_tmpdir" || bail "Error creating $tap_tmpdir"
246    fi
247    puts "$tap_tmpdir"
248}
249