]>
Commit | Line | Data |
---|---|---|
0c1f3509 MG |
1 | #!/bin/sh |
2 | ||
3 | ### Definitions | |
4 | # | |
5 | # This test suite uses the following terminology: | |
6 | # - scenario: a series of commands to test. Each must be in a | |
7 | # separate file, and must be completely self-contained | |
8 | # (other than the variables listed below). | |
9 | # - check: a series of commands that produces an exit code which | |
10 | # the test suite should check. A scenario may contain any | |
11 | # number of checks. | |
12 | # | |
13 | ### Design | |
14 | # | |
15 | # The main function is scenario_runner(scenario_filename), which | |
16 | # takes a scenario file as the argument, and runs a | |
17 | # scenario_cmd() | |
18 | # function which was defined in that file. | |
19 | # | |
20 | ### Variables | |
21 | # | |
22 | # Wherever possible, this suite uses local variables and | |
23 | # explicitly-passed arguments, with the following exceptions: | |
24 | # - s_basename: this is the basename for the scenario's temporary | |
25 | # and log files. | |
26 | # - s_val_basename: this is the basename for the scenario's | |
27 | # valgrind log files. | |
28 | # - s_count: this is the count of the scenario's checks (so that | |
29 | # each check can have distinct files). | |
30 | # - s_retval: this is the overall exit code of the scenario. | |
31 | # - c_exitfile: this contains the exit code of each check. | |
32 | # - c_valgrind_min: this is the minimum value of USE_VALGRIND | |
33 | # which will enable valgrind checking for this check. | |
34 | # - c_valgrind_cmd: this is the valgrind command (including | |
35 | # appropriate log file) if necessary, or is "" otherwise. | |
36 | ||
37 | set -o nounset | |
38 | ||
39 | ### Constants | |
40 | out="tests-output" | |
41 | out_valgrind="tests-valgrind" | |
42 | valgrind_suppressions="${out_valgrind}/suppressions" | |
43 | valgrind_suppressions_log="${out_valgrind}/suppressions.pre" | |
44 | ||
45 | # Keep the user-specified ${USE_VALGRIND}, or initialize to 0. | |
46 | USE_VALGRIND=${USE_VALGRIND:-0} | |
47 | ||
48 | # A non-zero value unlikely to be used as an exit code by the programs being | |
49 | # tested. | |
50 | valgrind_exit_code=108 | |
51 | ||
52 | ## prepare_directories(): | |
53 | # Delete any old directories, and create new ones as necessary. Must be run | |
54 | # after check_optional_valgrind(). | |
55 | prepare_directories() { | |
56 | # Clean up previous directories. | |
57 | if [ -d "${out}" ]; then | |
58 | rm -rf ${out} | |
59 | fi | |
60 | if [ -d "${out_valgrind}" ]; then | |
61 | rm -rf ${out_valgrind} | |
62 | fi | |
63 | ||
64 | # Make new directories. | |
65 | mkdir ${out} | |
66 | if [ "$USE_VALGRIND" -gt 0 ]; then | |
67 | mkdir ${out_valgrind} | |
68 | fi | |
69 | } | |
70 | ||
71 | ## find_system (cmd, args...): | |
72 | # Looks for ${cmd} in the $PATH, and ensure that it supports ${args}. | |
73 | find_system() { | |
74 | cmd=$1 | |
75 | cmd_with_args=$@ | |
76 | # Look for ${cmd}. | |
77 | system_binary=`command -v ${cmd}` | |
78 | if [ -z "${system_binary}" ]; then | |
79 | system_binary="" | |
80 | printf "System ${cmd} not found.\n" 1>&2 | |
81 | # If the command exists, check it ensures the ${args}. | |
82 | elif ${cmd_with_args} 2>&1 >/dev/null | \ | |
83 | grep -qE "(invalid|illegal) option"; then | |
84 | system_binary="" | |
85 | printf "Cannot use system ${cmd}; does not" 1>&2 | |
86 | printf " support necessary arguments.\n" 1>&2 | |
87 | fi | |
88 | echo "${system_binary}" | |
89 | } | |
90 | ||
91 | ## check_optional_valgrind (): | |
92 | # Return a $USE_VALGRIND variable defined; if it was previously defined and | |
93 | # was greater than 0, then check that valgrind is available in the $PATH. | |
94 | check_optional_valgrind() { | |
95 | if [ "$USE_VALGRIND" -gt 0 ]; then | |
96 | # Look for valgrind in $PATH. | |
97 | if ! command -v valgrind >/dev/null 2>&1; then | |
98 | printf "valgrind not found\n" 1>&2 | |
99 | exit 1 | |
100 | fi | |
101 | fi | |
102 | } | |
103 | ||
104 | ## ensure_valgrind_suppresssion (potential_memleaks_binary): | |
105 | # Runs the ${potential_memleaks_binary} through valgrind, keeping | |
106 | # track of any apparent memory leak in order to suppress reporting | |
107 | # those leaks when testing other binaries. | |
108 | ensure_valgrind_suppression() { | |
109 | potential_memleaks_binary=$1 | |
110 | ||
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 | ||
117 | # Run valgrind on the binary, sending it a "\n" so that | |
118 | # a test which uses STDIN will not wait for user input. | |
119 | printf "\n" | (valgrind --leak-check=full --show-leak-kinds=all \ | |
120 | --gen-suppressions=all \ | |
121 | --log-file=${valgrind_suppressions_log} \ | |
122 | ${potential_memleaks_binary}) | |
123 | ||
124 | # Strip out useless parts from the log file, as well as | |
125 | # removing references to the main and "pl_*" ("potential | |
126 | # loss") functions so that the suppressions can apply to | |
127 | # other binaries. | |
128 | (grep -v "^==" ${valgrind_suppressions_log} \ | |
129 | | grep -v " fun:pl_" - \ | |
130 | | grep -v " fun:main" - \ | |
131 | > ${valgrind_suppressions} ) | |
132 | ||
133 | # Clean up | |
134 | rm -f ${valgrind_suppressions_log} | |
135 | printf "done.\n" | |
136 | } | |
137 | ||
138 | ## setup_check_variables (): | |
139 | # Set up the "check" variables ${c_exitfile} and ${c_valgrind_cmd}, the | |
140 | # latter depending on the previously-defined ${c_valgrind_min}. | |
141 | # Advances the number of checks ${s_count} so that the next call to this | |
142 | # function will set up new filenames. | |
143 | setup_check_variables() { | |
144 | # Set up the "exit" file. | |
145 | c_exitfile="${s_basename}-`printf %02d ${s_count}`.exit" | |
146 | ||
147 | # If we don't have a suppressions file, don't try to use it. | |
148 | if [ ! -e ${valgrind_suppressions} ]; then | |
149 | valgrind_suppressions=/dev/null | |
150 | fi | |
151 | ||
152 | # Set up the valgrind command if $USE_VALGRIND is greater | |
153 | # than or equal to ${valgrind_min}; otherwise, produce an | |
154 | # empty string. Using --error-exitcode means that if | |
155 | # there is a serious problem (such that scrypt calls | |
156 | # exit(1)) *and* a memory leak, the test suite reports an | |
157 | # exit value of ${valgrind_exit_code}. However, if there | |
158 | # is a serious problem but no memory leak, we still | |
159 | # receive a non-zero exit code. The most important thing | |
160 | # is that we only receive an exit code of 0 if both the | |
161 | # program and valgrind are happy. | |
162 | if [ "$USE_VALGRIND" -ge "${c_valgrind_min}" ]; then | |
163 | val_logfilename=${s_val_basename}-`printf %02d ${s_count}`.log | |
164 | c_valgrind_cmd="valgrind \ | |
165 | --log-file=${val_logfilename} \ | |
166 | --leak-check=full --show-leak-kinds=all \ | |
167 | --errors-for-leak-kinds=all \ | |
168 | --suppressions=${valgrind_suppressions} \ | |
169 | --error-exitcode=${valgrind_exit_code} " | |
170 | else | |
171 | c_valgrind_cmd="" | |
172 | fi | |
173 | ||
174 | # Advances the number of checks. | |
175 | s_count=$((s_count + 1)) | |
176 | } | |
177 | ||
178 | ## get_val_logfile (val_basename, exitfile): | |
179 | # Return the valgrind logfile corresponding to ${exitfile}. | |
180 | get_val_logfile() { | |
181 | val_basename=$1 | |
182 | exitfile=$2 | |
183 | num=`echo "${exitfile}" | rev | cut -c 1-7 | rev | cut -c 1-2 ` | |
184 | echo "${val_basename}-${num}.log" | |
185 | } | |
186 | ||
187 | ## notify_success_or_fail (log_basename, val_log_basename): | |
188 | # Examine all "exit code" files beginning with ${log_basename} and | |
189 | # print "SUCCESS!" or "FAILED!" as appropriate. If the test failed | |
190 | # with the code ${valgrind_exit_code}, output the appropriate | |
191 | # valgrind logfile to stdout. | |
192 | notify_success_or_fail() { | |
193 | log_basename=$1 | |
194 | val_log_basename=$2 | |
195 | ||
196 | # Check each exitfile. | |
197 | for exitfile in `ls ${log_basename}-*.exit | sort`; do | |
198 | ret=`cat ${exitfile}` | |
199 | if [ "${ret}" -lt 0 ]; then | |
200 | echo "SKIP!" | |
201 | return | |
202 | fi | |
203 | if [ "${ret}" -gt 0 ]; then | |
204 | echo "FAILED!" | |
205 | retval=${ret} | |
206 | if [ "${ret}" -eq "${valgrind_exit_code}" ]; then | |
207 | val_logfilename=$( get_val_logfile \ | |
208 | ${val_log_basename} ${exitfile} ) | |
209 | cat ${val_logfilename} | |
210 | fi | |
211 | s_retval=${ret} | |
212 | return | |
213 | fi | |
214 | done | |
215 | ||
216 | echo "SUCCESS!" | |
217 | } | |
218 | ||
219 | ## scenario_runner (scenario_filename): | |
220 | # Runs a test scenario from ${scenario_filename}. | |
221 | scenario_runner() { | |
222 | scenario_filename=$1 | |
223 | basename=`basename ${scenario_filename} .sh` | |
224 | printf " ${basename}... " 1>&2 | |
225 | ||
226 | # Initialize "scenario" and "check" variables. | |
227 | s_basename=${out}/${basename} | |
228 | s_val_basename=${out_valgrind}/${basename} | |
229 | s_count=0 | |
230 | c_exitfile=/dev/null | |
231 | c_valgrind_min=9 | |
232 | c_valgrind_cmd="" | |
233 | ||
234 | # Load scenario_cmd() from the scenario file. | |
235 | unset scenario_cmd | |
236 | . ${scenario_filename} | |
237 | if ! command -v scenario_cmd 1>/dev/null ; then | |
238 | printf "ERROR: scenario_cmd() is not defined in\n" | |
239 | printf " ${scenario_filename}\n" | |
240 | exit 1 | |
241 | fi | |
242 | ||
243 | # Run the scenario command. | |
244 | scenario_cmd | |
245 | ||
246 | # Print PASS or FAIL, and return result. | |
247 | s_retval=0 | |
248 | notify_success_or_fail ${s_basename} ${s_val_basename} | |
249 | ||
250 | return "${s_retval}" | |
251 | } | |
252 | ||
253 | ## run_scenarios (scenario_filenames): | |
254 | # Runs all scenarios matching ${scenario_filenames}. | |
255 | run_scenarios() { | |
256 | printf -- "Running tests\n" | |
257 | printf -- "-------------\n" | |
258 | scenario_filenames=$@ | |
259 | for scenario in ${scenario_filenames}; do | |
260 | # We can't call this function with $( ... ) because we | |
261 | # want to allow it to echo values to stdout. | |
262 | scenario_runner ${scenario} | |
263 | retval=$? | |
264 | if [ ${retval} -gt 0 ]; then | |
265 | exit ${retval} | |
266 | fi | |
267 | done | |
268 | } |