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