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 
   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
 
  29     integration
/replication
 
  30     integration
/replication-2
 
  31     integration
/replication-3
 
  32     integration
/replication-4
 
  35     integration
/convert-zipmap-hash-on-load
 
  46 # Index to the next test to run in the ::all_tests list. 
  57 set ::external 0; # If "1" this means, we are running against external instance 
  58 set ::file ""; # If set, runs only the tests in this comma separated list 
  59 set ::curfile ""; # Hold the filename of the current suite 
  60 set ::accurate 0; # If true runs fuzz tests with more iterations 
  63 # Set to 1 when we are running in client mode. The Redis test uses a 
  64 # server-client model to run tests simultaneously. The server instance 
  65 # runs the specified number of client instances that will actually run tests. 
  66 # The server is responsible of showing the result to the user, and exit with 
  67 # the appropriate exit code depending on the test outcome. 
  71 proc execute_tests name 
{ 
  72     set path 
"tests/$name.tcl" 
  75     send_data_packet 
$::test_server_fd done 
"$name" 
  78 # Setup a list to hold a stack of server configs. When calls to start_server 
  79 # are nested, use "srv 0 pid" to get the pid of the inner server. To access 
  80 # outer servers, use "srv -1 pid" etcetera. 
  84     if {[string is integer 
[lindex $args 0]]} { 
  85         set level 
[lindex $args 0] 
  86         set property 
[lindex $args 1] 
  88         set property 
[lindex $args 0] 
  90     set srv 
[lindex $::servers end
+$level] 
  91     dict get 
$srv $property 
  94 # Provide easy access to the client for the inner server. It's possible to 
  95 # prepend the argument list with a negative level to access clients for 
  96 # servers running in outer blocks. 
  99     if {[string is integer 
[lindex $args 0]]} { 
 100         set level 
[lindex $args 0] 
 101         set args 
[lrange $args 1 end
] 
 103     [srv 
$level "client"] {*}$args 
 106 proc reconnect 
{args
} { 
 107     set level 
[lindex $args 0] 
 108     if {[string length 
$level] == 0 || 
![string is integer 
$level]} { 
 112     set srv 
[lindex $::servers end
+$level] 
 113     set host 
[dict get 
$srv "host"] 
 114     set port 
[dict get 
$srv "port"] 
 115     set config 
[dict get 
$srv "config"] 
 116     set client 
[redis 
$host $port] 
 117     dict 
set srv 
"client" $client 
 119     # select the right db when we don't have to authenticate 
 120     if {![dict exists 
$config "requirepass"]} { 
 124     # re-set $srv in the servers list 
 125     lset ::servers end
+$level $srv 
 128 proc redis_deferring_client 
{args
} { 
 130     if {[llength $args] > 0 && [string is integer 
[lindex $args 0]]} { 
 131         set level 
[lindex $args 0] 
 132         set args 
[lrange $args 1 end
] 
 135     # create client that defers reading reply 
 136     set client 
[redis 
[srv 
$level "host"] [srv 
$level "port"] 1] 
 138     # select the right db and read the response (OK) 
 144 # Provide easy access to INFO properties. Same semantic as "proc r". 
 147     if {[string is integer 
[lindex $args 0]]} { 
 148         set level 
[lindex $args 0] 
 149         set args 
[lrange $args 1 end
] 
 151     status 
[srv 
$level "client"] [lindex $args 0] 
 155     if {!$::quiet} {puts -nonewline "Cleanup: may take some time... "} 
 157     catch {exec rm 
-rf {*}[glob tests
/tmp
/redis.conf.
*]} 
 158     catch {exec rm 
-rf {*}[glob tests
/tmp
/server.
*]} 
 159     if {!$::quiet} {puts "OK"} 
 162 proc find_available_port start 
{ 
 163     for {set j 
$start} {$j < $start+1024} {incr j
} { 
 165             set fd 
[socket 127.0.0.1 $j] 
 172     if {$j == $start+1024} { 
 173         error "Can't find a non busy port in the $start-[expr {$start+1023}] range." 
 177 proc test_server_main 
{} { 
 179     # Open a listening socket, trying different ports in order to find a 
 181     set port 
[find_available_port 
11111] 
 183         puts "Starting test server at port $port" 
 185     socket -server accept_test_clients 
$port 
 187     # Start the client instances 
 188     set ::clients_pids {} 
 189     set start_port 
[expr {$::port+100}] 
 190     for {set j 
0} {$j < $::numclients} {incr j
} { 
 191         set start_port 
[find_available_port 
$start_port] 
 192         set p 
[exec tclsh8.5 
[info script
] {*}$::argv \ 
 193             --client $port --port $start_port &] 
 194         lappend ::clients_pids $p 
 198     # Setup global state for the test server 
 199     set ::idle_clients {} 
 200     set ::active_clients {} 
 201     array set ::clients_start_time {} 
 202     set ::clients_time_history {} 
 203     set ::failed_tests {} 
 205     # Enter the event loop to handle clients I/O 
 206     after 100 test_server_cron
 
 210 # This function gets called 10 times per second, for now does nothing but 
 211 # may be used in the future in order to detect test clients taking too much 
 212 # time to execute the task. 
 213 proc test_server_cron 
{} { 
 216 proc accept_test_clients 
{fd addr port
} { 
 217     fileevent $fd readable 
[list read_from_test_client 
$fd] 
 220 # This is the readable handler of our test server. Clients send us messages 
 221 # in the form of a status code such and additional data. Supported 
 224 # ready: the client is ready to execute the command. Only sent at client 
 225 #        startup. The server will queue the client FD in the list of idle 
 227 # testing: just used to signal that a given test started. 
 228 # ok: a test was executed with success. 
 229 # err: a test was executed with an error. 
 230 # exception: there was a runtime exception while executing the test. 
 231 # done: all the specified test file was processed, this test client is 
 232 #       ready to accept a new task. 
 233 proc read_from_test_client fd 
{ 
 235     set payload 
[read $fd $bytes] 
 236     foreach {status data
} $payload break 
 237     if {$status eq 
{ready
}} { 
 239             puts "\[$status\]: $data" 
 241         signal_idle_client 
$fd 
 242     } elseif 
{$status eq 
{done
}} { 
 243         set elapsed 
[expr {[clock seconds
]-$::clients_start_time($fd)}] 
 244         set all_tests_count 
[llength $::all_tests] 
 245         set running_tests_count 
[expr {[llength $::active_clients]-1}] 
 246         set completed_tests_count 
[expr {$::next_test-$running_tests_count}] 
 247         puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)" 
 248         lappend ::clients_time_history $elapsed $data 
 249         signal_idle_client 
$fd 
 250     } elseif 
{$status eq 
{ok
}} { 
 252             puts "\[[colorstr green $status]\]: $data" 
 254     } elseif 
{$status eq 
{err
}} { 
 255         set err 
"\[[colorstr red $status]\]: $data" 
 257         lappend ::failed_tests $err 
 258     } elseif 
{$status eq 
{exception
}} { 
 259         puts "\[[colorstr red $status]\]: $data" 
 260         foreach p 
$::clients_pids { 
 261             catch {exec kill 
-9 $p} 
 264     } elseif 
{$status eq 
{testing
}} { 
 268             puts "\[$status\]: $data" 
 273 # A new client is idle. Remove it from the list of active clients and 
 274 # if there are still test units to run, launch them. 
 275 proc signal_idle_client fd 
{ 
 276     # Remove this fd from the list of active clients. 
 277     set ::active_clients \ 
 278         [lsearch -all -inline -not -exact $::active_clients $fd] 
 279     # New unit to process? 
 280     if {$::next_test != [llength $::all_tests]} { 
 282             puts [colorstr bold-white 
"Testing [lindex $::all_tests $::next_test]"] 
 284         set ::clients_start_time($fd) [clock seconds
] 
 285         send_data_packet 
$fd run 
[lindex $::all_tests $::next_test] 
 286         lappend ::active_clients $fd 
 289         lappend ::idle_clients $fd 
 290         if {[llength $::active_clients] == 0} { 
 296 # The the_end funciton gets called when all the test units were already 
 297 # executed, so the test finished. 
 299     # TODO: print the status, exit with the rigth exit code. 
 301     puts "Execution time of different units:" 
 302     foreach {time name
} $::clients_time_history { 
 303         puts "  $time seconds - $name" 
 305     if {[llength $::failed_tests]} { 
 306         puts "\n[colorstr bold-red {!!! WARNING}] The following tests failed:\n" 
 307         foreach failed 
$::failed_tests { 
 313         puts "\n[colorstr bold-white {\o/}] [colorstr bold-green {All tests passed without errors!}]\n" 
 319 # The client is not even driven (the test server is instead) as we just need 
 320 # to read the command, execute, reply... all this in a loop. 
 321 proc test_client_main server_port 
{ 
 322     set ::test_server_fd [socket localhost 
$server_port] 
 323     send_data_packet 
$::test_server_fd ready 
[pid] 
 325         set bytes 
[gets $::test_server_fd] 
 326         set payload 
[read $::test_server_fd $bytes] 
 327         foreach {cmd data
} $payload break 
 331             error "Unknown test client command: $cmd" 
 336 proc send_data_packet 
{fd status data
} { 
 337     set payload 
[list $status $data] 
 338     puts $fd [string length 
$payload] 
 339     puts -nonewline $fd $payload 
 343 proc print_help_screen 
{} { 
 345         "--valgrind         Run the test over valgrind." 
 346         "--accurate         Run slow randomized tests for more iterations." 
 347         "--quiet            Don't show individual tests." 
 348         "--single <unit>    Just execute the specified unit (see next option)." 
 349         "--list-tests       List all the available test units." 
 350         "--clients <num>    Number of test clients (16)." 
 351         "--force-failure    Force the execution of a test that always fails." 
 352         "--help             Print this help screen." 
 357 for {set j 
0} {$j < [llength $argv]} {incr j
} { 
 358     set opt 
[lindex $argv $j] 
 359     set arg 
[lindex $argv [expr $j+1]] 
 360     if {$opt eq 
{--tags}} { 
 362             if {[string index 
$tag 0] eq 
"-"} { 
 363                 lappend ::denytags [string range 
$tag 1 end
] 
 365                 lappend ::allowtags $tag 
 369     } elseif 
{$opt eq 
{--valgrind}} { 
 371     } elseif 
{$opt eq 
{--quiet}} { 
 373     } elseif 
{$opt eq 
{--host}} { 
 377     } elseif 
{$opt eq 
{--port}} { 
 380     } elseif 
{$opt eq 
{--accurate}} { 
 382     } elseif 
{$opt eq 
{--force-failure
}} { 
 383         set ::force_failure 1 
 384     } elseif 
{$opt eq 
{--single}} { 
 387     } elseif 
{$opt eq 
{--list-tests
}} { 
 388         foreach t 
$::all_tests { 
 392     } elseif 
{$opt eq 
{--client}} { 
 394         set ::test_server_port $arg 
 396     } elseif 
{$opt eq 
{--clients}} { 
 397         set ::numclients $arg 
 399     } elseif 
{$opt eq 
{--help}} { 
 403         puts "Wrong argument: $opt" 
 408 # With the parallel test running multiple Redis instances at the same time 
 409 # we need a fast enough computer, otherwise a lot of tests may generate 
 411 # If the computer is too slow we revert the sequetial test without any 
 412 # parallelism, that is, clients == 1. 
 413 proc is_a_slow_computer 
{} { 
 414     set start 
[clock milliseconds
] 
 415     for {set j 
0} {$j < 1000000} {incr j
} {} 
 416     set elapsed 
[expr [clock milliseconds
]-$start] 
 417     expr {$elapsed > 200} 
 421     if {[catch { test_client_main 
$::test_server_port } err
]} { 
 422         set estr 
"Executing test client: $err.\n$::errorInfo" 
 423         if {[catch {send_data_packet 
$::test_server_fd exception 
$estr}]} { 
 429     if {[is_a_slow_computer
]} { 
 430         puts "** SLOW COMPUTER ** Using a single client to avoid false positives." 
 434     if {[catch { test_server_main 
} err
]} { 
 435         if {[string length 
$err] > 0} { 
 436             # only display error when not generated by the test suite 
 437             if {$err ne 
"exception"} {