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