|
@@ -0,0 +1,389 @@
|
|
1
|
+Tests
|
|
2
|
+=====
|
|
3
|
+
|
|
4
|
+Running tests is handled by tfkit/runtests:
|
|
5
|
+
|
|
6
|
+ $ tfkit/runtest [filter]
|
|
7
|
+
|
|
8
|
+*filter* is a regular expression to be applied to sub-test name, running
|
|
9
|
+only the matching ones. See below for details.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+Writing tests
|
|
13
|
+-------------
|
|
14
|
+
|
|
15
|
+Tests can be written in any scripting language, although the built-in
|
|
16
|
+framework, written in Bash, provides some useful features for writing
|
|
17
|
+certain kind of relatively simple tests.
|
|
18
|
+
|
|
19
|
+The harness, though, assumes that:
|
|
20
|
+
|
|
21
|
+ * Any direct sub-directory of `$TF_SUITE` directory ("tests" by default)
|
|
22
|
+ that contains at least *TF_RUN* executable becomes a test,
|
|
23
|
+
|
|
24
|
+ * basename of this directory becomes the name of the test,
|
|
25
|
+
|
|
26
|
+ * and return code from running the executable is reported
|
|
27
|
+ as result of the test, according to "Exit status" section below.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+Naming
|
|
31
|
+------
|
|
32
|
+
|
|
33
|
+Test name should start with name of the module that is tested and
|
|
34
|
+underscore. If module name contains dots, they should be replaced with
|
|
35
|
+underscores as well.
|
|
36
|
+
|
|
37
|
+ core_sanity
|
|
38
|
+ mod_submod_function
|
|
39
|
+ ini_iniread
|
|
40
|
+
|
|
41
|
+are valid test names.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+Data
|
|
45
|
+----
|
|
46
|
+
|
|
47
|
+Should the test need any data, just leave it around in the test directory
|
|
48
|
+along with *TF_RUN*.
|
|
49
|
+
|
|
50
|
+Note that before running, the whole test directory is automatically
|
|
51
|
+copied to a temporary location (one per test), and should the test fail,
|
|
52
|
+copied back as a debugging artifact. For this reason, *do not store
|
|
53
|
+huge amounts of data here*. If you really need huge data, consider
|
|
54
|
+obtaining it (and throwing it away) within runtime of *TF_RUN*.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+Exit status
|
|
58
|
+-----------
|
|
59
|
+
|
|
60
|
+We try hard to follow this semantic:
|
|
61
|
+
|
|
62
|
+ * *Zero* means *OK* -- test has been run and passed.
|
|
63
|
+
|
|
64
|
+ * *One* means *Failure* -- test has been run but failed (e.g. found
|
|
65
|
+ a bug).
|
|
66
|
+
|
|
67
|
+ * *Two* means *Bailout* -- test has decided not to run at all.
|
|
68
|
+
|
|
69
|
+ * *Three* means *Error* -- there was error detected during execution,
|
|
70
|
+ but script was able to clean up properly.
|
|
71
|
+
|
|
72
|
+ * *Four* means *Panic* -- there was other error but script *was not*
|
|
73
|
+ able to clean up properly.
|
|
74
|
+
|
|
75
|
+ * Anything else should indicate other uncaught errors, including those
|
|
76
|
+ outside control of the program such as segfaults in the test code
|
|
77
|
+ or test being SIGKILLed.
|
|
78
|
+
|
|
79
|
+Notice that the higher the value is, the worse situation it indicates.
|
|
80
|
+Thus, if a test is composed of several sub-tests, you need to make sure
|
|
81
|
+to always **exit with the highest value** (subtest.sh does take care
|
|
82
|
+of this).
|
|
83
|
+
|
|
84
|
+See *common.sh* for functions and variables to help with handling exit
|
|
85
|
+statuses with this semantic.
|
|
86
|
+
|
|
87
|
+Also see Notes section for more details on exit statuses, including
|
|
88
|
+cheat sheet and dscussuion.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+Framework
|
|
92
|
+---------
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+### harness.sh ###
|
|
96
|
+
|
|
97
|
+This part is not intended to be used in tests, but rather contains
|
|
98
|
+functions that help govern test discovery, preparation and execution as
|
|
99
|
+is described in previous sections. Feel free to poke around, of course.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+### subtest.sh ###
|
|
103
|
+
|
|
104
|
+As name suggests, this file defines few functions to handle subtests
|
|
105
|
+in *TF_RUN*.
|
|
106
|
+
|
|
107
|
+In order to make use of the subtests functionality, you will need to
|
|
108
|
+define two functions yourself: `tf_enum_subtests` to enumerate names of
|
|
109
|
+tests you want to run, and `tf_do_subtest` with actual test
|
|
110
|
+implementation.
|
|
111
|
+
|
|
112
|
+The minimal *TF_RUN* with two subtests could look like this:
|
|
113
|
+
|
|
114
|
+ #!/bin/bash
|
|
115
|
+
|
|
116
|
+ . $TF_DIR/include/subtest.sh
|
|
117
|
+
|
|
118
|
+ tf_enum_subtests() {
|
|
119
|
+ echo test1
|
|
120
|
+ echo test2
|
|
121
|
+ something && echo test3
|
|
122
|
+ }
|
|
123
|
+
|
|
124
|
+ tf_do_subtest() {
|
|
125
|
+ case $1 in
|
|
126
|
+ test1) myprog foo ;;
|
|
127
|
+ test2) myprog bar ;;
|
|
128
|
+ test3) myprog baz ;;
|
|
129
|
+ esac
|
|
130
|
+ }
|
|
131
|
+
|
|
132
|
+ tf_do_subtests
|
|
133
|
+
|
|
134
|
+At the end, `tf_do_subtests` acts as a launcher of the actual test.
|
|
135
|
+In short, it will
|
|
136
|
+
|
|
137
|
+ 1. run `tf_enum_subtests`, taking each line as name of a subtest;
|
|
138
|
+ for each subtest:
|
|
139
|
+
|
|
140
|
+ 1. source *TF_SETUP*, if such file is found,
|
|
141
|
+ 2. launch the `tf_do_subtest()` function with subtest name as
|
|
142
|
+ the only argument,
|
|
143
|
+ 3. source *TF_CLEANUP*, if such file is found,
|
|
144
|
+
|
|
145
|
+ 2. and finally, report "worst" exit status encountered.
|
|
146
|
+
|
|
147
|
+Note that subtest names need to be single words (`[a-zA-Z0-9_]`).
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+### tools.sh ###
|
|
151
|
+
|
|
152
|
+This file contains various tools and utilities to help with testing.
|
|
153
|
+
|
|
154
|
+Curently there is only one function, `tf_testflt` designed to help write
|
|
155
|
+tests for simple unix filters.
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+#### tf_testflt ####
|
|
159
|
+
|
|
160
|
+The idea is that tester specifies
|
|
161
|
+
|
|
162
|
+ * test name,
|
|
163
|
+ * command to launch the system under test,
|
|
164
|
+ * a data stream to use as STDIN,
|
|
165
|
+ * and expected STDOUT, STDERR, and exit status.
|
|
166
|
+
|
|
167
|
+and tf_testflt launches the command, collects tha data and evaluates
|
|
168
|
+and reports the result using unified diff.
|
|
169
|
+
|
|
170
|
+In its simplest form:
|
|
171
|
+
|
|
172
|
+ tf_testflt -n foo my_command arg
|
|
173
|
+
|
|
174
|
+the function will run `my_command arg` (not piping anything to it),
|
|
175
|
+and will expect it to finish with exit status 0 and empty both STDERR
|
|
176
|
+and STDOUT.
|
|
177
|
+
|
|
178
|
+Example of full form,
|
|
179
|
+
|
|
180
|
+ tf_testflt -n foo -i foo.in -O foo.stdout -E foo.stderr -S 2 myprog
|
|
181
|
+
|
|
182
|
+will pipe foo.in into `myprog`, expecting exit status of 2, and STDOUT and
|
|
183
|
+STDERR as above. Notice that parameters specifying expected values are
|
|
184
|
+uppercase, and those specifying input values are lowercase.
|
|
185
|
+
|
|
186
|
+Specifying name is mandatory, because it's used in reporting messages,
|
|
187
|
+and as a basis for naming temporary result files: these are saved in
|
|
188
|
+*results* subdirectory and kept for further reference.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+### common.sh ###
|
|
192
|
+
|
|
193
|
+This includes simple functions and variables shared between both mentioned
|
|
194
|
+libraries.
|
|
195
|
+
|
|
196
|
+First group is designed to help support the exit status semantic:
|
|
197
|
+
|
|
198
|
+ * The functions are `tf_exit_pass`, `tf_exit_fail`, `tf_exit_bailout`,
|
|
199
|
+ `tf_exit_error` and `tf_exit_panic` and each take any number of
|
|
200
|
+ parameters that are printed on stderr.
|
|
201
|
+
|
|
202
|
+ * The variables are `TF_ES_OK`, `TF_ES_FAIL`, `TF_ES_BAILOUT`,
|
|
203
|
+ `TF_ES_ERROR` and `TF_ES_PANIC` and are supposed to be used with
|
|
204
|
+ `return` builtin, e.g. to return from `tf_exit_error`.
|
|
205
|
+
|
|
206
|
+Second group is useful to better control output: functions `tf_warn`,
|
|
207
|
+`tf_debug` and `tf_think` are used to print stuff on STDERR. Use of
|
|
208
|
+`tf_warn` is apparent, just as `tf_debug`, the latter being muted if
|
|
209
|
+`TF_DEBUG` is set to `false` (set it to `true` to turn on debugging).
|
|
210
|
+
|
|
211
|
+`tf_think` is used for progress info, and is muted unless `TF_VERBOSE`
|
|
212
|
+is set to `true`, which is by default.
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+### Setup and cleanup ###
|
|
216
|
+
|
|
217
|
+Special files *TF_SETUP* and *TF_CLEANUP* (one of them or both) can be
|
|
218
|
+added along with *TF_RUN*. These will be sourced before (*TF_SETUP*)
|
|
219
|
+and after every subtest (*TF_CLEANUP*).
|
|
220
|
+
|
|
221
|
+First, if any of these files are missing, it is considered as if the
|
|
222
|
+respective phase succeeded. Second, if setup phase fails, test will
|
|
223
|
+be skipped and subtest exit status will be *TF_ES_BAILOUT*. Last,
|
|
224
|
+if cleanup fails (no matter result of setup), subtests aborts with
|
|
225
|
+*TF_ES_PANIC* returned. Be aware that in this case the actual test
|
|
226
|
+status, albeit useful, is lost.
|
|
227
|
+
|
|
228
|
+When coming from other test frameworks, this may feel harsh, but note
|
|
229
|
+that this has been designed with the idea that if a cleanup fails,
|
|
230
|
+it may render all further tests are automatically unsafe, because the
|
|
231
|
+environment is not as expected.
|
|
232
|
+
|
|
233
|
+To cope with this behavior, try to bear in mind following advice:
|
|
234
|
+
|
|
235
|
+ 1. Make sure you write setup/cleanup procedures with extreme care and
|
|
236
|
+ test them well.
|
|
237
|
+
|
|
238
|
+ 2. Do not do complicated and risky things in the setup/cleanup phases.
|
|
239
|
+
|
|
240
|
+ 3. If you need to do such things, consider doing them in the *TF_RUN*
|
|
241
|
+ instead of doing them for all subtests.
|
|
242
|
+
|
|
243
|
+ 4. You don't need to clean up everything, the contents of the testing dir
|
|
244
|
+ will be moved out from the test system.
|
|
245
|
+
|
|
246
|
+ 5. If there are scenarios you can safely fix or ignore, handle them in
|
|
247
|
+ a robust manner.
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+Notes
|
|
251
|
+-----
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+### bailout vs. `tf_enum_subtests` ###
|
|
255
|
+
|
|
256
|
+One more note to claify relation of bailout and `tf_enum_subtests`.
|
|
257
|
+As you may have noticed, there are two ways how to skip a test:
|
|
258
|
+return prematurely with `TF_ES_BAILOUT`, or suppress enumeration in
|
|
259
|
+`tf_enum_subtests`. The problem is that the latter does not do anything
|
|
260
|
+to inform upper in the stack that a test has been skipped, which seems to
|
|
261
|
+break the principle described in previous sections.
|
|
262
|
+
|
|
263
|
+Don't confuse these mechanisms, though. Each is supposed to be used
|
|
264
|
+for distinct purpose. Compare: by using the `tf_enum_subtests` you are
|
|
265
|
+saying that you actually **did not even want** to run the test in the
|
|
266
|
+first place. By using `TF_ES_BAILOUT`, you are saying that you **wanted**
|
|
267
|
+to run the test but could not.
|
|
268
|
+
|
|
269
|
+A few common cases if that helps you:
|
|
270
|
+
|
|
271
|
+ * If during the test you find out that for some reason it can't be
|
|
272
|
+ carried out (e.g. an external resource is not available, or
|
|
273
|
+ something outside the SUT is broken), use `TF_ES_BAILOUT`.
|
|
274
|
+
|
|
275
|
+ tf_enum_subtests() {
|
|
276
|
+ echo test1
|
|
277
|
+ echo test2
|
|
278
|
+ echo test3
|
|
279
|
+ }
|
|
280
|
+
|
|
281
|
+ tf_do_subtest() {
|
|
282
|
+ case $1 in
|
|
283
|
+ test1) do_stuff ;;
|
|
284
|
+ test2) do_other_stuff ;;
|
|
285
|
+ test3) curl -s http://www.example.com/ >file \
|
|
286
|
+ || return $TF_ES_BAILOUT
|
|
287
|
+ do_stuff_with file ;;
|
|
288
|
+ esac
|
|
289
|
+ }
|
|
290
|
+
|
|
291
|
+ * If you want to filter out some sub-tests for some platforms, e.g. a
|
|
292
|
+ test for only 64-bit architectures, or a test only for Mac OS (IOW,
|
|
293
|
+ you can safely say that running this sub-test would be totally
|
|
294
|
+ pointless on this box), use `tf_enum_subtests`--just omit this test
|
|
295
|
+ from enumeration.
|
|
296
|
+
|
|
297
|
+ tf_enum_subtests() {
|
|
298
|
+ echo test1
|
|
299
|
+ echo test2
|
|
300
|
+ if this_is_macos_x; then
|
|
301
|
+ echo test3
|
|
302
|
+ fi
|
|
303
|
+ }
|
|
304
|
+
|
|
305
|
+ * If you want to disable (comment out test) that you might not have
|
|
306
|
+ implemented yet or is broken (and for some reason you still want
|
|
307
|
+ it to haunt the test code) or something else outside SUT is broken
|
|
308
|
+ and prevents you from running the test, use `tf_enum_subtests` and
|
|
309
|
+ properly comment the reasons in code.
|
|
310
|
+
|
|
311
|
+ tf_enum_subtests() {
|
|
312
|
+ echo test1
|
|
313
|
+ echo test2
|
|
314
|
+ # echo test3 #FIXME: implement after bz1234
|
|
315
|
+ }
|
|
316
|
+
|
|
317
|
+ * If in doubt, use `TF_ES_BAILOUT`.
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+### On exit statuses: three and above ###
|
|
321
|
+
|
|
322
|
+The difference in *error*, *panic* and higher values is subtle but
|
|
323
|
+important. Follow me as I try to explain:
|
|
324
|
+
|
|
325
|
+ 1. If script has changed something on the system outside the working
|
|
326
|
+ directory, it is apparently expected to revert that change.
|
|
327
|
+
|
|
328
|
+ 2. Now if an error occurs, but the code responsible for cleaning up is
|
|
329
|
+ safely run, you can say there was *error but we have recovered*.
|
|
330
|
+
|
|
331
|
+ 3. But if the change can't be reverted safely, we know that we have
|
|
332
|
+ broken something and latter code may lead to weird results (including
|
|
333
|
+ masking bugs(!)), it's time to *panic* (in the code, not in real
|
|
334
|
+ life ;))
|
|
335
|
+
|
|
336
|
+ 4. And then there are corner cases like a bug in the script, OOM kill
|
|
337
|
+ or timeout when the status will be different and not really controlled
|
|
338
|
+ by the script. Such cases will have to be treated the same way as
|
|
339
|
+ the "panic" case, but...
|
|
340
|
+
|
|
341
|
+ 5. the use of *panic* adds hint that the status has been set consciously
|
|
342
|
+ by the script, albeit exiting "in a hurry"--without proper clean up.
|
|
343
|
+
|
|
344
|
+Unfortunately there will be cases like above but with the error code less
|
|
345
|
+than four. Example is a Bash script syntax error, which returns 2, or
|
|
346
|
+Python exception which returns 1. Yes, in such cases the information
|
|
347
|
+conveyed by the exit status is wrong and you should do everything to
|
|
348
|
+avoid it.
|
|
349
|
+
|
|
350
|
+Possibilities like "test has passed but then something blew up" exist,
|
|
351
|
+but conveying this information is responsibility of the test output.
|
|
352
|
+
|
|
353
|
+Following table can be used as a cheat-sheet:
|
|
354
|
+
|
|
355
|
+ .---------------------------------------------------------------.
|
|
356
|
+ | e | state of | |
|
|
357
|
+ | s |---------------------| script says |
|
|
358
|
+ | | SUT | environment | |
|
|
359
|
+ |---|-------|-------------|-------------------------------------|
|
|
360
|
+ | 0 | OK | safe | test passed, everything worked fine |
|
|
361
|
+ | 1 | buggy | safe | test failed, everything worked fine |
|
|
362
|
+ | 2 | ??? | safe | I decided not to run the test |
|
|
363
|
+ | 3 | ??? | safe | Something blew up but I managed to |
|
|
364
|
+ | | | | clean up (I promise!) |
|
|
365
|
+ | 4 | ??? | broken | Something blew up and I rushed out |
|
|
366
|
+ | | | | in panic |
|
|
367
|
+ | * | ??? | broken | ...nothing (is dead) |
|
|
368
|
+ '---------------------------------------------------------------'
|
|
369
|
+
|
|
370
|
+As you can see, following this semantic allows us to see both the state
|
|
371
|
+of the system under test (SUT) *and* the environment.
|
|
372
|
+
|
|
373
|
+Following table illustrates how different statuses map to different
|
|
374
|
+scenarios with regard to test result as well as state of the environment:
|
|
375
|
+
|
|
376
|
+ .--------------------------------------------------.
|
|
377
|
+ | environment | test result | test result |
|
|
378
|
+ | | pass fail unkn | pass fail unkn |
|
|
379
|
+ |-------------|----------------|-------------------|
|
|
380
|
+ | clean(ed) | 0 1 3 | OK FAIL ERROR |
|
|
381
|
+ | untouched | ~ ~ 2 | ~ ~ BAILOUT |
|
|
382
|
+ | mess | ~ ~ 4 | ~ ~ PANIC |
|
|
383
|
+ | ?! (trap) | ~ ~ 5 | ~ ~ ~ |
|
|
384
|
+ | ?! (sig 9) | ~ ~ 137 | ~ ~ ~ |
|
|
385
|
+ | ?! (aliens) | ~ ~ ? | ~ ~ ~ |
|
|
386
|
+ '-------------|----------------|-------------------|
|
|
387
|
+ | exit status | human-readable |
|
|
388
|
+ | | name (TF_ES_*) |
|
|
389
|
+ '------------------------------------'
|