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 <rra@stanford.edu> 13# Copyright 2009, 2010, 2011, 2012 Russ Allbery <rra@stanford.edu> 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