shared_test_functions.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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).
  41. USE_VALGRIND=${USE_VALGRIND:-0}
  42. # A non-zero value unlikely to be used as an exit code by the programs being
  43. # tested.
  44. valgrind_exit_code=108
  45. # Set ${bindir} to $1 if given, else use "." for in-tree builds.
  46. bindir=$(CDPATH= cd -- "$(dirname -- "${1-.}")" && pwd -P)
  47. ## prepare_directories():
  48. # Delete any old directories, and create new ones as necessary. Must be run
  49. # after check_optional_valgrind().
  50. prepare_directories() {
  51. # Clean up previous directories.
  52. if [ -d "${out}" ]; then
  53. rm -rf ${out}
  54. fi
  55. if [ -d "${out_valgrind}" ]; then
  56. rm -rf ${out_valgrind}
  57. fi
  58. # Make new directories.
  59. mkdir ${out}
  60. if [ "$USE_VALGRIND" -gt 0 ]; then
  61. mkdir ${out_valgrind}
  62. fi
  63. }
  64. ## find_system (cmd, args...):
  65. # Look for ${cmd} in the $PATH, and ensure that it supports ${args}.
  66. find_system() {
  67. cmd=$1
  68. cmd_with_args=$@
  69. # Look for ${cmd}; the "|| true" and -} make this work with set -e.
  70. system_binary=`command -v ${cmd}` || true
  71. if [ -z "${system_binary-}" ]; then
  72. system_binary=""
  73. printf "System ${cmd} not found.\n" 1>&2
  74. # If the command exists, check it ensures the ${args}.
  75. elif ${cmd_with_args} 2>&1 >/dev/null | \
  76. grep -qE "(invalid|illegal) option"; then
  77. system_binary=""
  78. printf "Cannot use system ${cmd}; does not" 1>&2
  79. printf " support necessary arguments.\n" 1>&2
  80. fi
  81. echo "${system_binary}"
  82. }
  83. ## has_pid (cmd):
  84. # Look for ${cmd} in ps; return 0 if ${cmd} exists.
  85. has_pid() {
  86. cmd=$1
  87. pid=`ps -Aopid,args | grep -F "${cmd}" | grep -v "grep"` || true
  88. if [ -n "${pid}" ]; then
  89. return 0
  90. fi
  91. return 1
  92. }
  93. ## check_optional_valgrind ():
  94. # Return a $USE_VALGRIND variable defined; if it was previously defined and
  95. # was greater than 0, then check that valgrind is available in the $PATH.
  96. check_optional_valgrind() {
  97. if [ "$USE_VALGRIND" -gt 0 ]; then
  98. # Look for valgrind in $PATH.
  99. if ! command -v valgrind >/dev/null 2>&1; then
  100. printf "valgrind not found\n" 1>&2
  101. exit 1
  102. fi
  103. fi
  104. }
  105. ## ensure_valgrind_suppresssion (potential_memleaks_binary):
  106. # Run the ${potential_memleaks_binary} through valgrind, keeping
  107. # track of any apparent memory leak in order to suppress reporting
  108. # those leaks when testing other binaries.
  109. ensure_valgrind_suppression() {
  110. potential_memleaks_binary=$1
  111. # Quit if we're not using valgrind.
  112. if [ ! "$USE_VALGRIND" -gt 0 ]; then
  113. return
  114. fi;
  115. printf "Generating valgrind suppressions... "
  116. valgrind_suppressions="${out_valgrind}/suppressions"
  117. valgrind_suppressions_log="${out_valgrind}/suppressions.pre"
  118. # Start off with an empty suppression file
  119. touch ${valgrind_suppressions}
  120. # Get list of tests
  121. ${potential_memleaks_binary} | while read testname; do
  122. this_valgrind_supp="${valgrind_suppressions_log}-${testname}"
  123. # Run valgrind on the binary, sending it a "\n" so that
  124. # a test which uses STDIN will not wait for user input.
  125. printf "\n" | (valgrind \
  126. --leak-check=full --show-leak-kinds=all \
  127. --gen-suppressions=all \
  128. --suppressions=${valgrind_suppressions} \
  129. --log-file=${this_valgrind_supp} \
  130. ${potential_memleaks_binary} \
  131. ${testname})
  132. # Append name to suppressions file
  133. printf "# ${testname}\n" >> ${valgrind_suppressions}
  134. # Strip out useless parts from the log file, as well as
  135. # removing references to the main and "pl_*" ("potential loss")
  136. # functions so that the suppressions can apply to other
  137. # binaries. Append to suppressions file.
  138. (grep -v "^==" ${this_valgrind_supp} \
  139. | grep -v " fun:pl_" - \
  140. | grep -v " fun:main" - \
  141. >> ${valgrind_suppressions} ) || true
  142. done
  143. # Clean up
  144. rm -f ${valgrind_suppressions_log}
  145. printf "done.\n"
  146. }
  147. ## setup_check_variables ():
  148. # Set up the "check" variables ${c_exitfile} and ${c_valgrind_cmd}, the
  149. # latter depending on the previously-defined ${c_valgrind_min}.
  150. # Advances the number of checks ${s_count} so that the next call to this
  151. # function will set up new filenames.
  152. setup_check_variables() {
  153. # Set up the "exit" file.
  154. c_exitfile="${s_basename}-`printf %02d ${s_count}`.exit"
  155. # Set up the valgrind command if $USE_VALGRIND is greater
  156. # than or equal to ${valgrind_min}; otherwise, produce an
  157. # empty string. Using --error-exitcode means that if
  158. # there is a serious problem (such that scrypt calls
  159. # exit(1)) *and* a memory leak, the test suite reports an
  160. # exit value of ${valgrind_exit_code}. However, if there
  161. # is a serious problem but no memory leak, we still
  162. # receive a non-zero exit code. The most important thing
  163. # is that we only receive an exit code of 0 if both the
  164. # program and valgrind are happy.
  165. if [ "$USE_VALGRIND" -ge "${c_valgrind_min}" ]; then
  166. val_logfilename=${s_val_basename}-`printf %02d ${s_count}`.log
  167. c_valgrind_cmd="valgrind \
  168. --log-file=${val_logfilename} \
  169. --leak-check=full --show-leak-kinds=all \
  170. --errors-for-leak-kinds=all \
  171. --suppressions=${valgrind_suppressions} \
  172. --error-exitcode=${valgrind_exit_code} "
  173. else
  174. c_valgrind_cmd=""
  175. fi
  176. # Advances the number of checks.
  177. s_count=$((s_count + 1))
  178. }
  179. ## get_val_logfile (val_basename, exitfile):
  180. # Return the valgrind logfile corresponding to ${exitfile}.
  181. get_val_logfile() {
  182. val_basename=$1
  183. exitfile=$2
  184. num=`echo "${exitfile}" | rev | cut -c 1-7 | rev | cut -c 1-2 `
  185. echo "${val_basename}-${num}.log"
  186. }
  187. ## expected_exitcode (expected, exitcode):
  188. # If ${exitcode} matches the ${expected} value, return 0. If the exitcode is
  189. # ${valgrind_exit_code}, return that. Otherwise, return 1 to indicate
  190. # failure.
  191. expected_exitcode() {
  192. expected=$1
  193. exitcode=$2
  194. if [ "${exitcode}" -eq "${expected}" ]; then
  195. echo "0"
  196. elif [ "${exitcode}" -eq "${valgrind_exit_code}" ]; then
  197. echo "${valgrind_exit_code}"
  198. else
  199. echo "1"
  200. fi
  201. }
  202. ## notify_success_or_fail (log_basename, val_log_basename):
  203. # Examine all "exit code" files beginning with ${log_basename} and
  204. # print "SUCCESS!" or "FAILED!" as appropriate. If the test failed
  205. # with the code ${valgrind_exit_code}, output the appropriate
  206. # valgrind logfile to stdout.
  207. notify_success_or_fail() {
  208. log_basename=$1
  209. val_log_basename=$2
  210. # Bail if there's no exitfiles.
  211. exitfiles=`ls ${log_basename}-*.exit` || true
  212. if [ -z "$exitfiles" ]; then
  213. echo "FAILED"
  214. s_retval=1
  215. return
  216. fi
  217. # Count results
  218. total_exitfiles=0
  219. skip_exitfiles=0
  220. # Check each exitfile.
  221. for exitfile in `echo $exitfiles | sort`; do
  222. ret=`cat ${exitfile}`
  223. total_exitfiles=$(( total_exitfiles + 1 ))
  224. if [ "${ret}" -lt 0 ]; then
  225. skip_exitfiles=$(( skip_exitfiles + 1 ))
  226. fi
  227. if [ "${ret}" -gt 0 ]; then
  228. echo "FAILED!"
  229. retval=${ret}
  230. if [ ${VERBOSE} -ne 0 ]; then
  231. printf "File ${exitfile} contains exit" 1>&2
  232. printf " code ${ret}.\n" 1>&2
  233. fi
  234. if [ "${ret}" -eq "${valgrind_exit_code}" ]; then
  235. val_logfilename=$( get_val_logfile \
  236. ${val_log_basename} ${exitfile} )
  237. cat ${val_logfilename}
  238. fi
  239. s_retval=${ret}
  240. return
  241. fi
  242. done
  243. if [ ${skip_exitfiles} -gt 0 ]; then
  244. if [ ${skip_exitfiles} -eq ${total_exitfiles} ]; then
  245. echo "SKIP!"
  246. else
  247. echo "PARTIAL SUCCESS / SKIP!"
  248. fi
  249. else
  250. echo "SUCCESS!"
  251. fi
  252. }
  253. ## scenario_runner (scenario_filename):
  254. # Run a test scenario from ${scenario_filename}.
  255. scenario_runner() {
  256. scenario_filename=$1
  257. basename=`basename ${scenario_filename} .sh`
  258. printf " ${basename}... " 1>&2
  259. # Initialize "scenario" and "check" variables.
  260. s_basename=${out}/${basename}
  261. s_val_basename=${out_valgrind}/${basename}
  262. s_count=0
  263. c_exitfile=/dev/null
  264. c_valgrind_min=9
  265. c_valgrind_cmd=""
  266. # Load scenario_cmd() from the scenario file.
  267. unset scenario_cmd
  268. . ${scenario_filename}
  269. if ! command -v scenario_cmd 1>/dev/null ; then
  270. printf "ERROR: scenario_cmd() is not defined in\n"
  271. printf " ${scenario_filename}\n"
  272. exit 1
  273. fi
  274. # Run the scenario command.
  275. scenario_cmd
  276. # Print PASS or FAIL, and return result.
  277. s_retval=0
  278. notify_success_or_fail ${s_basename} ${s_val_basename}
  279. return "${s_retval}"
  280. }
  281. ## run_scenarios (scenario_filenames):
  282. # Run all scenarios matching ${scenario_filenames}.
  283. run_scenarios() {
  284. # Check for optional valgrind.
  285. check_optional_valgrind
  286. # Clean up previous directories, and create new ones.
  287. prepare_directories
  288. # Generate valgrind suppression file if it is required. Must be
  289. # done after preparing directories.
  290. ensure_valgrind_suppression ${bindir}/tests/valgrind/potential-memleaks
  291. printf -- "Running tests\n"
  292. printf -- "-------------\n"
  293. scenario_filenames=$@
  294. for scenario in ${scenario_filenames}; do
  295. # We can't call this function with $( ... ) because we
  296. # want to allow it to echo values to stdout.
  297. scenario_runner ${scenario}
  298. retval=$?
  299. if [ ${retval} -gt 0 ]; then
  300. exit ${retval}
  301. fi
  302. done
  303. }