]> git.saurik.com Git - redis.git/blob - tests/test_helper.tcl
Merge remote-tracking branch 'origin/unstable' into unstable
[redis.git] / tests / test_helper.tcl
1 # Redis test suite. Copyright (C) 2009 Salvatore Sanfilippo antirez@gmail.com
2 # This softare is released under the BSD License. See the COPYING file for
3 # more information.
4
5 set tcl_precision 17
6 source tests/support/redis.tcl
7 source tests/support/server.tcl
8 source tests/support/tmpfile.tcl
9 source tests/support/test.tcl
10 source tests/support/util.tcl
11
12 set ::all_tests {
13 unit/printver
14 unit/auth
15 unit/protocol
16 unit/basic
17 unit/type/list
18 unit/type/list-2
19 unit/type/list-3
20 unit/type/set
21 unit/type/zset
22 unit/type/hash
23 unit/sort
24 unit/expire
25 unit/other
26 unit/cas
27 unit/quit
28 unit/aofrw
29 integration/replication
30 integration/replication-2
31 integration/replication-3
32 integration/aof
33 unit/pubsub
34 unit/slowlog
35 unit/scripting
36 unit/maxmemory
37 }
38 # Index to the next test to run in the ::all_tests list.
39 set ::next_test 0
40
41 set ::host 127.0.0.1
42 set ::port 21111
43 set ::traceleaks 0
44 set ::valgrind 0
45 set ::verbose 0
46 set ::quiet 0
47 set ::denytags {}
48 set ::allowtags {}
49 set ::external 0; # If "1" this means, we are running against external instance
50 set ::file ""; # If set, runs only the tests in this comma separated list
51 set ::curfile ""; # Hold the filename of the current suite
52 set ::accurate 0; # If true runs fuzz tests with more iterations
53 set ::force_failure 0
54
55 # Set to 1 when we are running in client mode. The Redis test uses a
56 # server-client model to run tests simultaneously. The server instance
57 # runs the specified number of client instances that will actually run tests.
58 # The server is responsible of showing the result to the user, and exit with
59 # the appropriate exit code depending on the test outcome.
60 set ::client 0
61 set ::numclients 16
62
63 proc execute_tests name {
64 set path "tests/$name.tcl"
65 set ::curfile $path
66 source $path
67 send_data_packet $::test_server_fd done "$name"
68 }
69
70 # Setup a list to hold a stack of server configs. When calls to start_server
71 # are nested, use "srv 0 pid" to get the pid of the inner server. To access
72 # outer servers, use "srv -1 pid" etcetera.
73 set ::servers {}
74 proc srv {args} {
75 set level 0
76 if {[string is integer [lindex $args 0]]} {
77 set level [lindex $args 0]
78 set property [lindex $args 1]
79 } else {
80 set property [lindex $args 0]
81 }
82 set srv [lindex $::servers end+$level]
83 dict get $srv $property
84 }
85
86 # Provide easy access to the client for the inner server. It's possible to
87 # prepend the argument list with a negative level to access clients for
88 # servers running in outer blocks.
89 proc r {args} {
90 set level 0
91 if {[string is integer [lindex $args 0]]} {
92 set level [lindex $args 0]
93 set args [lrange $args 1 end]
94 }
95 [srv $level "client"] {*}$args
96 }
97
98 proc reconnect {args} {
99 set level [lindex $args 0]
100 if {[string length $level] == 0 || ![string is integer $level]} {
101 set level 0
102 }
103
104 set srv [lindex $::servers end+$level]
105 set host [dict get $srv "host"]
106 set port [dict get $srv "port"]
107 set config [dict get $srv "config"]
108 set client [redis $host $port]
109 dict set srv "client" $client
110
111 # select the right db when we don't have to authenticate
112 if {![dict exists $config "requirepass"]} {
113 $client select 9
114 }
115
116 # re-set $srv in the servers list
117 set ::servers [lreplace $::servers end+$level 1 $srv]
118 }
119
120 proc redis_deferring_client {args} {
121 set level 0
122 if {[llength $args] > 0 && [string is integer [lindex $args 0]]} {
123 set level [lindex $args 0]
124 set args [lrange $args 1 end]
125 }
126
127 # create client that defers reading reply
128 set client [redis [srv $level "host"] [srv $level "port"] 1]
129
130 # select the right db and read the response (OK)
131 $client select 9
132 $client read
133 return $client
134 }
135
136 # Provide easy access to INFO properties. Same semantic as "proc r".
137 proc s {args} {
138 set level 0
139 if {[string is integer [lindex $args 0]]} {
140 set level [lindex $args 0]
141 set args [lrange $args 1 end]
142 }
143 status [srv $level "client"] [lindex $args 0]
144 }
145
146 proc cleanup {} {
147 if {!$::quiet} {puts -nonewline "Cleanup: may take some time... "}
148 flush stdout
149 catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
150 catch {exec rm -rf {*}[glob tests/tmp/server.*]}
151 if {!$::quiet} {puts "OK"}
152 }
153
154 proc find_available_port start {
155 for {set j $start} {$j < $start+1024} {incr j} {
156 if {[catch {
157 set fd [socket 127.0.0.1 $j]
158 }]} {
159 return $j
160 } else {
161 close $fd
162 }
163 }
164 if {$j == $start+1024} {
165 error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
166 }
167 }
168
169 proc test_server_main {} {
170 cleanup
171 # Open a listening socket, trying different ports in order to find a
172 # non busy one.
173 set port [find_available_port 11111]
174 if {!$::quiet} {
175 puts "Starting test server at port $port"
176 }
177 socket -server accept_test_clients $port
178
179 # Start the client instances
180 set ::clients_pids {}
181 set start_port [expr {$::port+100}]
182 for {set j 0} {$j < $::numclients} {incr j} {
183 set start_port [find_available_port $start_port]
184 set p [exec tclsh8.5 [info script] {*}$::argv \
185 --client $port --port $start_port &]
186 lappend ::clients_pids $p
187 incr start_port 10
188 }
189
190 # Setup global state for the test server
191 set ::idle_clients {}
192 set ::active_clients {}
193 array set ::clients_start_time {}
194 set ::clients_time_history {}
195 set ::failed_tests {}
196
197 # Enter the event loop to handle clients I/O
198 after 100 test_server_cron
199 vwait forever
200 }
201
202 # This function gets called 10 times per second, for now does nothing but
203 # may be used in the future in order to detect test clients taking too much
204 # time to execute the task.
205 proc test_server_cron {} {
206 }
207
208 proc accept_test_clients {fd addr port} {
209 fileevent $fd readable [list read_from_test_client $fd]
210 }
211
212 # This is the readable handler of our test server. Clients send us messages
213 # in the form of a status code such and additional data. Supported
214 # status types are:
215 #
216 # ready: the client is ready to execute the command. Only sent at client
217 # startup. The server will queue the client FD in the list of idle
218 # clients.
219 # testing: just used to signal that a given test started.
220 # ok: a test was executed with success.
221 # err: a test was executed with an error.
222 # exception: there was a runtime exception while executing the test.
223 # done: all the specified test file was processed, this test client is
224 # ready to accept a new task.
225 proc read_from_test_client fd {
226 set bytes [gets $fd]
227 set payload [read $fd $bytes]
228 foreach {status data} $payload break
229 if {$status eq {ready}} {
230 if {!$::quiet} {
231 puts "\[$status\]: $data"
232 }
233 signal_idle_client $fd
234 } elseif {$status eq {done}} {
235 set elapsed [expr {[clock seconds]-$::clients_start_time($fd)}]
236 set all_tests_count [llength $::all_tests]
237 set running_tests_count [expr {[llength $::active_clients]-1}]
238 set completed_tests_count [expr {$::next_test-$running_tests_count}]
239 puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)"
240 lappend ::clients_time_history $elapsed $data
241 signal_idle_client $fd
242 } elseif {$status eq {ok}} {
243 if {!$::quiet} {
244 puts "\[[colorstr green $status]\]: $data"
245 }
246 } elseif {$status eq {err}} {
247 set err "\[[colorstr red $status]\]: $data"
248 puts $err
249 lappend ::failed_tests $err
250 } elseif {$status eq {exception}} {
251 puts "\[[colorstr red $status]\]: $data"
252 foreach p $::clients_pids {
253 catch {exec kill -9 $p}
254 }
255 exit 1
256 } elseif {$status eq {testing}} {
257 # No op
258 } else {
259 if {!$::quiet} {
260 puts "\[$status\]: $data"
261 }
262 }
263 }
264
265 # A new client is idle. Remove it from the list of active clients and
266 # if there are still test units to run, launch them.
267 proc signal_idle_client fd {
268 # Remove this fd from the list of active clients.
269 set ::active_clients \
270 [lsearch -all -inline -not -exact $::active_clients $fd]
271 # New unit to process?
272 if {$::next_test != [llength $::all_tests]} {
273 if {!$::quiet} {
274 puts [colorstr bold-white "Testing [lindex $::all_tests $::next_test]"]
275 }
276 set ::clients_start_time($fd) [clock seconds]
277 send_data_packet $fd run [lindex $::all_tests $::next_test]
278 lappend ::active_clients $fd
279 incr ::next_test
280 } else {
281 lappend ::idle_clients $fd
282 if {[llength $::active_clients] == 0} {
283 the_end
284 }
285 }
286 }
287
288 # The the_end funciton gets called when all the test units were already
289 # executed, so the test finished.
290 proc the_end {} {
291 # TODO: print the status, exit with the rigth exit code.
292 puts "\n The End\n"
293 puts "Execution time of different units:"
294 foreach {time name} $::clients_time_history {
295 puts " $time seconds - $name"
296 }
297 if {[llength $::failed_tests]} {
298 puts "\n[colorstr bold-red {!!! WARNING}] The following tests failed:\n"
299 foreach failed $::failed_tests {
300 puts "*** $failed"
301 }
302 cleanup
303 exit 1
304 } else {
305 puts "\n[colorstr bold-white {\o/}] [colorstr bold-green {All tests passed without errors!}]\n"
306 cleanup
307 exit 0
308 }
309 }
310
311 # The client is not even driven (the test server is instead) as we just need
312 # to read the command, execute, reply... all this in a loop.
313 proc test_client_main server_port {
314 set ::test_server_fd [socket localhost $server_port]
315 send_data_packet $::test_server_fd ready [pid]
316 while 1 {
317 set bytes [gets $::test_server_fd]
318 set payload [read $::test_server_fd $bytes]
319 foreach {cmd data} $payload break
320 if {$cmd eq {run}} {
321 execute_tests $data
322 } else {
323 error "Unknown test client command: $cmd"
324 }
325 }
326 }
327
328 proc send_data_packet {fd status data} {
329 set payload [list $status $data]
330 puts $fd [string length $payload]
331 puts -nonewline $fd $payload
332 flush $fd
333 }
334
335 proc print_help_screen {} {
336 puts [join {
337 "--valgrind Run the test over valgrind."
338 "--accurate Run slow randomized tests for more iterations."
339 "--quiet Don't show individual tests."
340 "--single <unit> Just execute the specified unit (see next option)."
341 "--list-tests List all the available test units."
342 "--force-failure Force the execution of a test that always fails."
343 "--help Print this help screen."
344 } "\n"]
345 }
346
347 # parse arguments
348 for {set j 0} {$j < [llength $argv]} {incr j} {
349 set opt [lindex $argv $j]
350 set arg [lindex $argv [expr $j+1]]
351 if {$opt eq {--tags}} {
352 foreach tag $arg {
353 if {[string index $tag 0] eq "-"} {
354 lappend ::denytags [string range $tag 1 end]
355 } else {
356 lappend ::allowtags $tag
357 }
358 }
359 incr j
360 } elseif {$opt eq {--valgrind}} {
361 set ::valgrind 1
362 } elseif {$opt eq {--quiet}} {
363 set ::quiet 1
364 } elseif {$opt eq {--host}} {
365 set ::external 1
366 set ::host $arg
367 incr j
368 } elseif {$opt eq {--port}} {
369 set ::port $arg
370 incr j
371 } elseif {$opt eq {--accurate}} {
372 set ::accurate 1
373 } elseif {$opt eq {--force-failure}} {
374 set ::force_failure 1
375 } elseif {$opt eq {--single}} {
376 set ::all_tests $arg
377 incr j
378 } elseif {$opt eq {--list-tests}} {
379 foreach t $::all_tests {
380 puts $t
381 }
382 exit 0
383 } elseif {$opt eq {--client}} {
384 set ::client 1
385 set ::test_server_port $arg
386 incr j
387 } elseif {$opt eq {--help}} {
388 print_help_screen
389 exit 0
390 } else {
391 puts "Wrong argument: $opt"
392 exit 1
393 }
394 }
395
396 if {$::client} {
397 if {[catch { test_client_main $::test_server_port } err]} {
398 set estr "Executing test client: $err.\n$::errorInfo"
399 if {[catch {send_data_packet $::test_server_fd exception $estr}]} {
400 puts $estr
401 }
402 exit 1
403 }
404 } else {
405 if {[catch { test_server_main } err]} {
406 if {[string length $err] > 0} {
407 # only display error when not generated by the test suite
408 if {$err ne "exception"} {
409 puts $::errorInfo
410 }
411 exit 1
412 }
413 }
414 }