shared_test_functions.sh 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #!/bin/sh
  2. ### Definitions
  3. #
  4. # This test suite uses the following terminology:
  5. # - scenario: a series of commands to test. Each must be in a
  6. # separate file, and must be completely self-contained
  7. # (other than the variables listed below).
  8. # - check: a series of commands that produces an exit code which
  9. # the test suite should check. A scenario may contain any
  10. # number of checks.
  11. #
  12. ### Design
  13. #
  14. # The main function is scenario_runner(scenario_filename), which
  15. # takes a scenario file as the argument, and runs a
  16. # scenario_cmd()
  17. # function which was defined in that file.
  18. #
  19. ### Variables
  20. #
  21. # Wherever possible, this suite uses local variables and
  22. # explicitly-passed arguments, with the following exceptions:
  23. # - s_basename: this is the basename for the scenario's temporary
  24. # and log files.
  25. # - s_val_basename: this is the basename for the scenario's
  26. # valgrind log files.
  27. # - s_count: this is the count of the scenario's checks (so that
  28. # each check can have distinct files).
  29. # - s_retval: this is the overall exit code of the scenario.
  30. # - c_exitfile: this contains the exit code of each check.
  31. # - c_valgrind_min: this is the minimum value of USE_VALGRIND
  32. # which will enable valgrind checking for this check.
  33. # - c_valgrind_cmd: this is the valgrind command (including
  34. # appropriate log file) if necessary, or is "" otherwise.
  35. set -o noclobber -o nounset
  36. # Keep the user-specified "print info about test failures", or initialize to 0
  37. # (don't print extra info).
  38. VERBOSE=${VERBOSE:-0}
  39. # Keep the user-specified ${USE_VALGRIND}, or initialize to 0 (don't do memory
  40. # tests). If ${USE_VALGRIND_NO_REGEN} is non-zero, re-use the previous
  41. # suppressions files instead of generating new ones.
  42. USE_VALGRIND=${USE_VALGRIND:-0}
  43. USE_VALGRIND_NO_REGEN=${USE_VALGRIND_NO_REGEN:-0}
  44. # Load valgrind-related functions. These functions will bail on a per-check
  45. # basis if the ${USE_VALGRIND} value does not indicate that we should run a
  46. # valgrind for that check.
  47. . ${scriptdir}/shared_valgrind_functions.sh
  48. # Set ${bindir} to $1 if given, else use "." for in-tree builds.
  49. bindir=$(CDPATH='' cd -- "$(dirname -- "${1-.}")" && pwd -P)
  50. # Default value (should be set by tests).
  51. NO_EXITFILE=/dev/null
  52. ## prepare_directory():
  53. # Delete the previous test output directory, and create a new one.
  54. prepare_directory() {
  55. if [ -d "${out}" ]; then
  56. rm -rf ${out}
  57. fi
  58. mkdir ${out}
  59. }
  60. ## find_system (cmd, args):
  61. # Look for ${cmd} in the $PATH, and ensure that it supports ${args}.
  62. find_system() {
  63. cmd=$1
  64. cmd_with_args="$1 ${2:-}"
  65. # Sanity check.
  66. if [ "$#" -gt "2" ]; then
  67. printf "Programmer error: find_system: too many args\n" 1>&2
  68. exit 1
  69. fi
  70. # Look for ${cmd}; the "|| true" and -} make this work with set -e.
  71. system_binary=$(command -v ${cmd}) || true
  72. if [ -z "${system_binary-}" ]; then
  73. system_binary=""
  74. printf "System ${cmd} not found.\n" 1>&2
  75. # If the command exists, check it ensures the ${args}.
  76. elif ${cmd_with_args} 2>&1 >/dev/null | \
  77. grep -qE "(invalid|illegal) option"; then
  78. system_binary=""
  79. printf "Cannot use system ${cmd}; does not" 1>&2
  80. printf " support necessary arguments.\n" 1>&2
  81. fi
  82. echo "${system_binary}"
  83. }
  84. ## has_pid (cmd):
  85. # Look for ${cmd} in ps; return 0 if ${cmd} exists.
  86. has_pid() {
  87. cmd=$1
  88. pid=$(ps -Aopid,args | grep -F "${cmd}" | grep -v "grep") || true
  89. if [ -n "${pid}" ]; then
  90. return 0
  91. fi
  92. return 1
  93. }
  94. ## wait_for_file (filename):
  95. # Waits until ${filename} exists.
  96. wait_for_file() {
  97. filename=$1
  98. while [ ! -e ${filename} ]; do
  99. if [ ${VERBOSE} -ne 0 ]; then
  100. echo "Waiting for ${filename}" 1>&2
  101. fi
  102. sleep 1
  103. done
  104. }
  105. ## setup_check_variables (description, check_prev=1):
  106. # Set up the "check" variables ${c_exitfile} and ${c_valgrind_cmd}, the
  107. # latter depending on the previously-defined ${c_valgrind_min}.
  108. # Advances the number of checks ${s_count} so that the next call to this
  109. # function will set up new filenames. Write ${description} into a
  110. # file. If ${check_prev} is non-zero, check that the previous
  111. # ${c_exitfile} exists.
  112. setup_check_variables() {
  113. description=$1
  114. check_prev=${2:-1}
  115. # Should we check for the previous exitfile?
  116. if [ "${c_exitfile}" != "${NO_EXITFILE}" ] && \
  117. [ "${check_prev}" -gt 0 ] ; then
  118. # Check for the file.
  119. if [ ! -f "${c_exitfile}" ] ; then
  120. # We should have written the result of the
  121. # previous test to this file.
  122. echo "PROGRAMMING FAILURE" 1>&2
  123. echo "We should already have ${c_exitfile}" 1>&2
  124. exit 1
  125. fi
  126. fi
  127. # Set up the "exit" file.
  128. count_str=$(printf "%02d" "${s_count}")
  129. c_exitfile="${s_basename}-${count_str}.exit"
  130. # Write the "description" file.
  131. printf "${description}\n" > \
  132. "${s_basename}-${count_str}.desc"
  133. # Set up the valgrind command (or an empty string).
  134. c_valgrind_cmd="$(valgrind_setup_cmd)"
  135. # Advances the number of checks.
  136. s_count=$((s_count + 1))
  137. }
  138. ## expected_exitcode (expected, exitcode):
  139. # If ${exitcode} matches the ${expected} value, return 0. If the exitcode is
  140. # ${valgrind_exit_code}, return that. Otherwise, return 1 to indicate
  141. # failure.
  142. expected_exitcode() {
  143. expected=$1
  144. exitcode=$2
  145. if [ "${exitcode}" -eq "${expected}" ]; then
  146. echo "0"
  147. elif [ "${exitcode}" -eq "${valgrind_exit_code}" ]; then
  148. echo "${valgrind_exit_code}"
  149. else
  150. echo "1"
  151. fi
  152. }
  153. ## notify_success_or_fail (log_basename, val_log_basename):
  154. # Examine all "exit code" files beginning with ${log_basename} and
  155. # print "SUCCESS!", "FAILED!", "SKIP!", or "PARTIAL SUCCESS / SKIP!"
  156. # as appropriate. Check any valgrind log files associated with the
  157. # test and print "FAILED!" if appropriate, along with the valgrind
  158. # logfile. If the test failed and ${VERBOSE} is non-zero, print
  159. # the description to stderr.
  160. notify_success_or_fail() {
  161. log_basename=$1
  162. val_log_basename=$2
  163. # Bail if there's no exitfiles.
  164. exitfiles=$(ls ${log_basename}-*.exit) || true
  165. if [ -z "$exitfiles" ]; then
  166. echo "FAILED" 1>&2
  167. s_retval=1
  168. return
  169. fi
  170. # Count results
  171. total_exitfiles=0
  172. skip_exitfiles=0
  173. # Check each exitfile.
  174. for exitfile in $(echo $exitfiles | sort); do
  175. ret=$(cat ${exitfile})
  176. total_exitfiles=$(( total_exitfiles + 1 ))
  177. if [ "${ret}" -lt 0 ]; then
  178. skip_exitfiles=$(( skip_exitfiles + 1 ))
  179. fi
  180. # Check for test failure.
  181. if [ "${ret}" -gt 0 ]; then
  182. echo "FAILED!" 1>&2
  183. if [ ${VERBOSE} -ne 0 ]; then
  184. printf "File ${exitfile} contains exit" 1>&2
  185. printf " code ${ret}.\n" 1>&2
  186. descfile=$(echo ${exitfile} | \
  187. sed 's/\.exit/\.desc/g')
  188. printf "Test description: " 1>&2
  189. cat ${descfile} 1>&2
  190. fi
  191. s_retval=${ret}
  192. return
  193. fi
  194. # Check valgrind logfile(s).
  195. val_failed="$(valgrind_check_basenames "${exitfile}")"
  196. if [ -n "${val_failed}" ]; then
  197. echo "FAILED!" 1>&2
  198. s_retval="${valgrind_exit_code}"
  199. cat "${val_failed}" 1>&2
  200. return
  201. fi
  202. done
  203. # Notify about skip or success.
  204. if [ ${skip_exitfiles} -gt 0 ]; then
  205. if [ ${skip_exitfiles} -eq ${total_exitfiles} ]; then
  206. echo "SKIP!" 1>&2
  207. else
  208. echo "PARTIAL SUCCESS / SKIP!" 1>&2
  209. fi
  210. else
  211. echo "SUCCESS!" 1>&2
  212. fi
  213. }
  214. ## scenario_runner (scenario_filename):
  215. # Run a test scenario from ${scenario_filename}.
  216. scenario_runner() {
  217. scenario_filename=$1
  218. basename=$(basename ${scenario_filename} .sh)
  219. printf " ${basename}... " 1>&2
  220. # Initialize "scenario" and "check" variables.
  221. s_basename=${out}/${basename}
  222. s_val_basename=${out_valgrind}/${basename}
  223. s_count=0
  224. c_exitfile="${NO_EXITFILE}"
  225. c_valgrind_min=9
  226. c_valgrind_cmd=""
  227. # Load scenario_cmd() from the scenario file.
  228. unset scenario_cmd
  229. . ${scenario_filename}
  230. if ! command -v scenario_cmd 1>/dev/null ; then
  231. printf "ERROR: scenario_cmd() is not defined in\n" 1>&2
  232. printf " ${scenario_filename}\n" 1>&2
  233. exit 1
  234. fi
  235. # Run the scenario command.
  236. scenario_cmd
  237. # Print PASS or FAIL, and return result.
  238. s_retval=0
  239. notify_success_or_fail ${s_basename} ${s_val_basename}
  240. return "${s_retval}"
  241. }
  242. ## run_scenarios (scenario_filenames):
  243. # Run all scenarios matching ${scenario_filenames}.
  244. run_scenarios() {
  245. # Get the test number(s) to run.
  246. if [ "${N:-0}" -gt "0" ]; then
  247. test_scenarios="$(printf "${scriptdir}/%02d-*.sh" "${N}")"
  248. else
  249. test_scenarios="${scriptdir}/??-*.sh"
  250. fi
  251. # Clean up any previous directory, and create a new one.
  252. prepare_directory
  253. # Clean up any previous valgrind directory, and prepare for new
  254. # valgrind tests (if applicable).
  255. valgrind_init
  256. printf -- "Running tests\n" 1>&2
  257. printf -- "-------------\n" 1>&2
  258. for scenario in ${test_scenarios}; do
  259. # We can't call this function with $( ... ) because we
  260. # want to allow it to echo values to stdout.
  261. scenario_runner ${scenario}
  262. retval=$?
  263. if [ ${retval} -gt 0 ]; then
  264. exit ${retval}
  265. fi
  266. done
  267. }