]> git.saurik.com Git - redis.git/blame_incremental - tests/test_helper.tcl
list-2 added to the list of tests
[redis.git] / tests / test_helper.tcl
... / ...
CommitLineData
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
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
11
12set ::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/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
28 integration/replication-2
29 integration/replication-3
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
37set ::host 127.0.0.1
38set ::port 16379
39set ::traceleaks 0
40set ::valgrind 0
41set ::verbose 0
42set ::denytags {}
43set ::allowtags {}
44set ::external 0; # If "1" this means, we are running against external instance
45set ::file ""; # If set, runs only the tests in this comma separated list
46set ::curfile ""; # Hold the filename of the current suite
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
55
56proc execute_tests name {
57 set path "tests/$name.tcl"
58 set ::curfile $path
59 source $path
60 send_data_packet $::test_server_fd done "$name"
61}
62
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 {}
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 }
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.
82proc r {args} {
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
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
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
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]
137}
138
139proc cleanup {} {
140 puts -nonewline "Cleanup: warning may take some time... "
141 flush stdout
142 catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
143 catch {exec rm -rf {*}[glob tests/tmp/server.*]}
144 puts "OK"
145}
146
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 }
169 }
170
171 # Start the client instances
172 set ::clients_pids {}
173 for {set j 0} {$j < $::numclients} {incr j} {
174 set p [exec tclsh8.5 [info script] {*}$::argv \
175 --client $port --port [expr {$::port+($j*10)}] &]
176 lappend ::clients_pids $p
177 }
178
179 # Setup global state for the test server
180 set ::idle_clients {}
181 set ::active_clients {}
182 array set ::clients_start_time {}
183 set ::clients_time_history {}
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
217 if {$status eq {ready}} {
218 puts "\[$status\]: $data"
219 signal_idle_client $fd
220 } elseif {$status eq {done}} {
221 set elapsed [expr {[clock seconds]-$::clients_start_time($fd)}]
222 puts "\[[colorstr yellow $status]\]: $data ($elapsed seconds)"
223 puts "+++ [expr {[llength $::active_clients]-1}] units still in execution."
224 lappend ::clients_time_history $elapsed $data
225 signal_idle_client $fd
226 } elseif {$status eq {ok}} {
227 puts "\[[colorstr green $status]\]: $data"
228 } elseif {$status eq {err}} {
229 puts "\[[colorstr red $status]\]: $data"
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
236 } elseif {$status eq {testing}} {
237 # No op
238 } else {
239 puts "\[$status\]: $data"
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]} {
251 puts [colorstr bold-white "Testing [lindex $::all_tests $::next_test]"]
252 set ::clients_start_time($fd) [clock seconds]
253 send_data_packet $fd run [lindex $::all_tests $::next_test]
254 lappend ::active_clients $fd
255 incr ::next_test
256 } else {
257 lappend ::idle_clients $fd
258 if {[llength $::active_clients] == 0} {
259 the_end
260 }
261 }
262}
263
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.
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 }
273 exit 1
274}
275
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"
289 }
290 }
291}
292
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
298}
299
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
313 } elseif {$opt eq {--valgrind}} {
314 set ::valgrind 1
315 } elseif {$opt eq {--file}} {
316 set ::file $arg
317 incr j
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
325 } elseif {$opt eq {--verbose}} {
326 set ::verbose 1
327 } elseif {$opt eq {--client}} {
328 set ::client 1
329 set ::test_server_port $arg
330 incr j
331 } else {
332 puts "Wrong argument: $opt"
333 exit 1
334 }
335}
336
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
342 }
343 exit 1
344 }
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 }
355}