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