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