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