xref: /redis-3.2.3/tests/test_helper.tcl (revision f1c237cb)
1# Redis test suite. Copyright (C) 2009 Salvatore Sanfilippo [email protected]
2# This software is released under the BSD License. See the COPYING file for
3# more information.
4
5package require Tcl 8.5
6
7set tcl_precision 17
8source tests/support/redis.tcl
9source tests/support/server.tcl
10source tests/support/tmpfile.tcl
11source tests/support/test.tcl
12source tests/support/util.tcl
13
14set ::all_tests {
15    unit/printver
16    unit/dump
17    unit/auth
18    unit/protocol
19    unit/keyspace
20    unit/scan
21    unit/type/string
22    unit/type/incr
23    unit/type/list
24    unit/type/list-2
25    unit/type/list-3
26    unit/type/set
27    unit/type/zset
28    unit/type/hash
29    unit/sort
30    unit/expire
31    unit/other
32    unit/multi
33    unit/quit
34    unit/aofrw
35    integration/replication
36    integration/replication-2
37    integration/replication-3
38    integration/replication-4
39    integration/replication-psync
40    integration/aof
41    integration/rdb
42    integration/convert-zipmap-hash-on-load
43    integration/logging
44    unit/pubsub
45    unit/slowlog
46    unit/scripting
47    unit/maxmemory
48    unit/introspection
49    unit/introspection-2
50    unit/limits
51    unit/obuf-limits
52    unit/bitops
53    unit/bitfield
54    unit/geo
55    unit/memefficiency
56    unit/hyperloglog
57}
58# Index to the next test to run in the ::all_tests list.
59set ::next_test 0
60
61set ::host 127.0.0.1
62set ::port 21111
63set ::traceleaks 0
64set ::valgrind 0
65set ::verbose 0
66set ::quiet 0
67set ::denytags {}
68set ::allowtags {}
69set ::external 0; # If "1" this means, we are running against external instance
70set ::file ""; # If set, runs only the tests in this comma separated list
71set ::curfile ""; # Hold the filename of the current suite
72set ::accurate 0; # If true runs fuzz tests with more iterations
73set ::force_failure 0
74set ::timeout 600; # 10 minutes without progresses will quit the test.
75set ::last_progress [clock seconds]
76set ::active_servers {} ; # Pids of active Redis instances.
77
78# Set to 1 when we are running in client mode. The Redis test uses a
79# server-client model to run tests simultaneously. The server instance
80# runs the specified number of client instances that will actually run tests.
81# The server is responsible of showing the result to the user, and exit with
82# the appropriate exit code depending on the test outcome.
83set ::client 0
84set ::numclients 16
85
86proc execute_tests name {
87    set path "tests/$name.tcl"
88    set ::curfile $path
89    source $path
90    send_data_packet $::test_server_fd done "$name"
91}
92
93# Setup a list to hold a stack of server configs. When calls to start_server
94# are nested, use "srv 0 pid" to get the pid of the inner server. To access
95# outer servers, use "srv -1 pid" etcetera.
96set ::servers {}
97proc srv {args} {
98    set level 0
99    if {[string is integer [lindex $args 0]]} {
100        set level [lindex $args 0]
101        set property [lindex $args 1]
102    } else {
103        set property [lindex $args 0]
104    }
105    set srv [lindex $::servers end+$level]
106    dict get $srv $property
107}
108
109# Provide easy access to the client for the inner server. It's possible to
110# prepend the argument list with a negative level to access clients for
111# servers running in outer blocks.
112proc r {args} {
113    set level 0
114    if {[string is integer [lindex $args 0]]} {
115        set level [lindex $args 0]
116        set args [lrange $args 1 end]
117    }
118    [srv $level "client"] {*}$args
119}
120
121proc reconnect {args} {
122    set level [lindex $args 0]
123    if {[string length $level] == 0 || ![string is integer $level]} {
124        set level 0
125    }
126
127    set srv [lindex $::servers end+$level]
128    set host [dict get $srv "host"]
129    set port [dict get $srv "port"]
130    set config [dict get $srv "config"]
131    set client [redis $host $port]
132    dict set srv "client" $client
133
134    # select the right db when we don't have to authenticate
135    if {![dict exists $config "requirepass"]} {
136        $client select 9
137    }
138
139    # re-set $srv in the servers list
140    lset ::servers end+$level $srv
141}
142
143proc redis_deferring_client {args} {
144    set level 0
145    if {[llength $args] > 0 && [string is integer [lindex $args 0]]} {
146        set level [lindex $args 0]
147        set args [lrange $args 1 end]
148    }
149
150    # create client that defers reading reply
151    set client [redis [srv $level "host"] [srv $level "port"] 1]
152
153    # select the right db and read the response (OK)
154    $client select 9
155    $client read
156    return $client
157}
158
159# Provide easy access to INFO properties. Same semantic as "proc r".
160proc s {args} {
161    set level 0
162    if {[string is integer [lindex $args 0]]} {
163        set level [lindex $args 0]
164        set args [lrange $args 1 end]
165    }
166    status [srv $level "client"] [lindex $args 0]
167}
168
169proc cleanup {} {
170    if {!$::quiet} {puts -nonewline "Cleanup: may take some time... "}
171    flush stdout
172    catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
173    catch {exec rm -rf {*}[glob tests/tmp/server.*]}
174    if {!$::quiet} {puts "OK"}
175}
176
177proc test_server_main {} {
178    cleanup
179    set tclsh [info nameofexecutable]
180    # Open a listening socket, trying different ports in order to find a
181    # non busy one.
182    set port [find_available_port 11111]
183    if {!$::quiet} {
184        puts "Starting test server at port $port"
185    }
186    socket -server accept_test_clients -myaddr 127.0.0.1 $port
187
188    # Start the client instances
189    set ::clients_pids {}
190    set start_port [expr {$::port+100}]
191    for {set j 0} {$j < $::numclients} {incr j} {
192        set start_port [find_available_port $start_port]
193        set p [exec $tclsh [info script] {*}$::argv \
194            --client $port --port $start_port &]
195        lappend ::clients_pids $p
196        incr start_port 10
197    }
198
199    # Setup global state for the test server
200    set ::idle_clients {}
201    set ::active_clients {}
202    array set ::active_clients_task {}
203    array set ::clients_start_time {}
204    set ::clients_time_history {}
205    set ::failed_tests {}
206
207    # Enter the event loop to handle clients I/O
208    after 100 test_server_cron
209    vwait forever
210}
211
212# This function gets called 10 times per second.
213proc test_server_cron {} {
214    set elapsed [expr {[clock seconds]-$::last_progress}]
215
216    if {$elapsed > $::timeout} {
217        set err "\[[colorstr red TIMEOUT]\]: clients state report follows."
218        puts $err
219        show_clients_state
220        kill_clients
221        force_kill_all_servers
222        the_end
223    }
224
225    after 100 test_server_cron
226}
227
228proc accept_test_clients {fd addr port} {
229    fconfigure $fd -encoding binary
230    fileevent $fd readable [list read_from_test_client $fd]
231}
232
233# This is the readable handler of our test server. Clients send us messages
234# in the form of a status code such and additional data. Supported
235# status types are:
236#
237# ready: the client is ready to execute the command. Only sent at client
238#        startup. The server will queue the client FD in the list of idle
239#        clients.
240# testing: just used to signal that a given test started.
241# ok: a test was executed with success.
242# err: a test was executed with an error.
243# exception: there was a runtime exception while executing the test.
244# done: all the specified test file was processed, this test client is
245#       ready to accept a new task.
246proc read_from_test_client fd {
247    set bytes [gets $fd]
248    set payload [read $fd $bytes]
249    foreach {status data} $payload break
250    set ::last_progress [clock seconds]
251
252    if {$status eq {ready}} {
253        if {!$::quiet} {
254            puts "\[$status\]: $data"
255        }
256        signal_idle_client $fd
257    } elseif {$status eq {done}} {
258        set elapsed [expr {[clock seconds]-$::clients_start_time($fd)}]
259        set all_tests_count [llength $::all_tests]
260        set running_tests_count [expr {[llength $::active_clients]-1}]
261        set completed_tests_count [expr {$::next_test-$running_tests_count}]
262        puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)"
263        lappend ::clients_time_history $elapsed $data
264        signal_idle_client $fd
265        set ::active_clients_task($fd) DONE
266    } elseif {$status eq {ok}} {
267        if {!$::quiet} {
268            puts "\[[colorstr green $status]\]: $data"
269        }
270        set ::active_clients_task($fd) "(OK) $data"
271    } elseif {$status eq {err}} {
272        set err "\[[colorstr red $status]\]: $data"
273        puts $err
274        lappend ::failed_tests $err
275        set ::active_clients_task($fd) "(ERR) $data"
276    } elseif {$status eq {exception}} {
277        puts "\[[colorstr red $status]\]: $data"
278        kill_clients
279        force_kill_all_servers
280        exit 1
281    } elseif {$status eq {testing}} {
282        set ::active_clients_task($fd) "(IN PROGRESS) $data"
283    } elseif {$status eq {server-spawned}} {
284        lappend ::active_servers $data
285    } elseif {$status eq {server-killed}} {
286        set ::active_servers [lsearch -all -inline -not -exact $::active_servers $data]
287    } else {
288        if {!$::quiet} {
289            puts "\[$status\]: $data"
290        }
291    }
292}
293
294proc show_clients_state {} {
295    # The following loop is only useful for debugging tests that may
296    # enter an infinite loop. Commented out normally.
297    foreach x $::active_clients {
298        if {[info exist ::active_clients_task($x)]} {
299            puts "$x => $::active_clients_task($x)"
300        } else {
301            puts "$x => ???"
302        }
303    }
304}
305
306proc kill_clients {} {
307    foreach p $::clients_pids {
308        catch {exec kill $p}
309    }
310}
311
312proc force_kill_all_servers {} {
313    foreach p $::active_servers {
314        puts "Killing still running Redis server $p"
315        catch {exec kill -9 $p}
316    }
317}
318
319# A new client is idle. Remove it from the list of active clients and
320# if there are still test units to run, launch them.
321proc signal_idle_client fd {
322    # Remove this fd from the list of active clients.
323    set ::active_clients \
324        [lsearch -all -inline -not -exact $::active_clients $fd]
325
326    if 0 {show_clients_state}
327
328    # New unit to process?
329    if {$::next_test != [llength $::all_tests]} {
330        if {!$::quiet} {
331            puts [colorstr bold-white "Testing [lindex $::all_tests $::next_test]"]
332            set ::active_clients_task($fd) "ASSIGNED: $fd ([lindex $::all_tests $::next_test])"
333        }
334        set ::clients_start_time($fd) [clock seconds]
335        send_data_packet $fd run [lindex $::all_tests $::next_test]
336        lappend ::active_clients $fd
337        incr ::next_test
338    } else {
339        lappend ::idle_clients $fd
340        if {[llength $::active_clients] == 0} {
341            the_end
342        }
343    }
344}
345
346# The the_end function gets called when all the test units were already
347# executed, so the test finished.
348proc the_end {} {
349    # TODO: print the status, exit with the rigth exit code.
350    puts "\n                   The End\n"
351    puts "Execution time of different units:"
352    foreach {time name} $::clients_time_history {
353        puts "  $time seconds - $name"
354    }
355    if {[llength $::failed_tests]} {
356        puts "\n[colorstr bold-red {!!! WARNING}] The following tests failed:\n"
357        foreach failed $::failed_tests {
358            puts "*** $failed"
359        }
360        cleanup
361        exit 1
362    } else {
363        puts "\n[colorstr bold-white {\o/}] [colorstr bold-green {All tests passed without errors!}]\n"
364        cleanup
365        exit 0
366    }
367}
368
369# The client is not even driven (the test server is instead) as we just need
370# to read the command, execute, reply... all this in a loop.
371proc test_client_main server_port {
372    set ::test_server_fd [socket localhost $server_port]
373    fconfigure $::test_server_fd -encoding binary
374    send_data_packet $::test_server_fd ready [pid]
375    while 1 {
376        set bytes [gets $::test_server_fd]
377        set payload [read $::test_server_fd $bytes]
378        foreach {cmd data} $payload break
379        if {$cmd eq {run}} {
380            execute_tests $data
381        } else {
382            error "Unknown test client command: $cmd"
383        }
384    }
385}
386
387proc send_data_packet {fd status data} {
388    set payload [list $status $data]
389    puts $fd [string length $payload]
390    puts -nonewline $fd $payload
391    flush $fd
392}
393
394proc print_help_screen {} {
395    puts [join {
396        "--valgrind         Run the test over valgrind."
397        "--accurate         Run slow randomized tests for more iterations."
398        "--quiet            Don't show individual tests."
399        "--single <unit>    Just execute the specified unit (see next option)."
400        "--list-tests       List all the available test units."
401        "--clients <num>    Number of test clients (default 16)."
402        "--timeout <sec>    Test timeout in seconds (default 10 min)."
403        "--force-failure    Force the execution of a test that always fails."
404        "--help             Print this help screen."
405    } "\n"]
406}
407
408# parse arguments
409for {set j 0} {$j < [llength $argv]} {incr j} {
410    set opt [lindex $argv $j]
411    set arg [lindex $argv [expr $j+1]]
412    if {$opt eq {--tags}} {
413        foreach tag $arg {
414            if {[string index $tag 0] eq "-"} {
415                lappend ::denytags [string range $tag 1 end]
416            } else {
417                lappend ::allowtags $tag
418            }
419        }
420        incr j
421    } elseif {$opt eq {--valgrind}} {
422        set ::valgrind 1
423    } elseif {$opt eq {--quiet}} {
424        set ::quiet 1
425    } elseif {$opt eq {--host}} {
426        set ::external 1
427        set ::host $arg
428        incr j
429    } elseif {$opt eq {--port}} {
430        set ::port $arg
431        incr j
432    } elseif {$opt eq {--accurate}} {
433        set ::accurate 1
434    } elseif {$opt eq {--force-failure}} {
435        set ::force_failure 1
436    } elseif {$opt eq {--single}} {
437        set ::all_tests $arg
438        incr j
439    } elseif {$opt eq {--list-tests}} {
440        foreach t $::all_tests {
441            puts $t
442        }
443        exit 0
444    } elseif {$opt eq {--client}} {
445        set ::client 1
446        set ::test_server_port $arg
447        incr j
448    } elseif {$opt eq {--clients}} {
449        set ::numclients $arg
450        incr j
451    } elseif {$opt eq {--timeout}} {
452        set ::timeout $arg
453        incr j
454    } elseif {$opt eq {--help}} {
455        print_help_screen
456        exit 0
457    } else {
458        puts "Wrong argument: $opt"
459        exit 1
460    }
461}
462
463proc attach_to_replication_stream {} {
464    set s [socket [srv 0 "host"] [srv 0 "port"]]
465    fconfigure $s -translation binary
466    puts -nonewline $s "SYNC\r\n"
467    flush $s
468
469    # Get the count
470    while 1 {
471        set count [gets $s]
472        set prefix [string range $count 0 0]
473        if {$prefix ne {}} break; # Newlines are allowed as PINGs.
474    }
475    if {$prefix ne {$}} {
476        error "attach_to_replication_stream error. Received '$count' as count."
477    }
478    set count [string range $count 1 end]
479
480    # Consume the bulk payload
481    while {$count} {
482        set buf [read $s $count]
483        set count [expr {$count-[string length $buf]}]
484    }
485    return $s
486}
487
488proc read_from_replication_stream {s} {
489    fconfigure $s -blocking 0
490    set attempt 0
491    while {[gets $s count] == -1} {
492        if {[incr attempt] == 10} return ""
493        after 100
494    }
495    fconfigure $s -blocking 1
496    set count [string range $count 1 end]
497
498    # Return a list of arguments for the command.
499    set res {}
500    for {set j 0} {$j < $count} {incr j} {
501        read $s 1
502        set arg [::redis::redis_bulk_read $s]
503        if {$j == 0} {set arg [string tolower $arg]}
504        lappend res $arg
505    }
506    return $res
507}
508
509proc assert_replication_stream {s patterns} {
510    for {set j 0} {$j < [llength $patterns]} {incr j} {
511        assert_match [lindex $patterns $j] [read_from_replication_stream $s]
512    }
513}
514
515proc close_replication_stream {s} {
516    close $s
517}
518
519# With the parallel test running multiple Redis instances at the same time
520# we need a fast enough computer, otherwise a lot of tests may generate
521# false positives.
522# If the computer is too slow we revert the sequential test without any
523# parallelism, that is, clients == 1.
524proc is_a_slow_computer {} {
525    set start [clock milliseconds]
526    for {set j 0} {$j < 1000000} {incr j} {}
527    set elapsed [expr [clock milliseconds]-$start]
528    expr {$elapsed > 200}
529}
530
531if {$::client} {
532    if {[catch { test_client_main $::test_server_port } err]} {
533        set estr "Executing test client: $err.\n$::errorInfo"
534        if {[catch {send_data_packet $::test_server_fd exception $estr}]} {
535            puts $estr
536        }
537        exit 1
538    }
539} else {
540    if {[is_a_slow_computer]} {
541        puts "** SLOW COMPUTER ** Using a single client to avoid false positives."
542        set ::numclients 1
543    }
544
545    if {[catch { test_server_main } err]} {
546        if {[string length $err] > 0} {
547            # only display error when not generated by the test suite
548            if {$err ne "exception"} {
549                puts $::errorInfo
550            }
551            exit 1
552        }
553    }
554}
555