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