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