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