123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- set ::global_overrides {}
- set ::tags {}
- set ::valgrind_errors {}
- proc start_server_error {config_file error} {
- set err {}
- append err "Can't start the Redis server\n"
- append err "CONFIGURATION:"
- append err [exec cat $config_file]
- append err "\nERROR:"
- append err [string trim $error]
- send_data_packet $::test_server_fd err $err
- }
- proc check_valgrind_errors stderr {
- set fd [open $stderr]
- set buf [read $fd]
- close $fd
- # look for stack trace and other errors, or the absense of a leak free summary
- if {[regexp -- { at 0x} $buf] ||
- [regexp -- {Warning} $buf] ||
- [regexp -- {Invalid} $buf] ||
- [regexp -- {Mismatched} $buf] ||
- [regexp -- {uninitialized} $buf] ||
- [regexp -- {has a fishy} $buf] ||
- [regexp -- {overlap} $buf] ||
- (![regexp -- {definitely lost: 0 bytes} $buf] &&
- ![regexp -- {no leaks are possible} $buf])} {
- send_data_packet $::test_server_fd err "Valgrind error: $buf\n"
- }
- }
- proc kill_server config {
- # nothing to kill when running against external server
- if {$::external} return
- # nevermind if its already dead
- if {![is_alive $config]} {
- # Check valgrind errors if needed
- if {$::valgrind} {
- check_valgrind_errors [dict get $config stderr]
- }
- return
- }
- set pid [dict get $config pid]
- # check for leaks
- if {![dict exists $config "skipleaks"]} {
- catch {
- if {[string match {*Darwin*} [exec uname -a]]} {
- tags {"leaks"} {
- test "Check for memory leaks (pid $pid)" {
- set output {0 leaks}
- catch {exec leaks $pid} output
- if {[string match {*process does not exist*} $output] ||
- [string match {*cannot examine*} $output]} {
- # In a few tests we kill the server process.
- set output "0 leaks"
- }
- set output
- } {*0 leaks*}
- }
- }
- }
- }
- # kill server and wait for the process to be totally exited
- send_data_packet $::test_server_fd server-killing $pid
- catch {exec kill $pid}
- if {$::valgrind} {
- set max_wait 60000
- } else {
- set max_wait 10000
- }
- while {[is_alive $config]} {
- incr wait 10
- if {$wait >= $max_wait} {
- puts "Forcing process $pid to exit..."
- catch {exec kill -KILL $pid}
- } elseif {$wait % 1000 == 0} {
- puts "Waiting for process $pid to exit..."
- }
- after 10
- }
- # Check valgrind errors if needed
- if {$::valgrind} {
- check_valgrind_errors [dict get $config stderr]
- }
- # Remove this pid from the set of active pids in the test server.
- send_data_packet $::test_server_fd server-killed $pid
- }
- proc is_alive config {
- set pid [dict get $config pid]
- if {[catch {exec ps -p $pid} err]} {
- return 0
- } else {
- return 1
- }
- }
- proc ping_server {host port} {
- set retval 0
- if {[catch {
- if {$::tls} {
- set fd [::tls::socket $host $port]
- } else {
- set fd [socket $host $port]
- }
- fconfigure $fd -translation binary
- puts $fd "PING\r\n"
- flush $fd
- set reply [gets $fd]
- if {[string range $reply 0 0] eq {+} ||
- [string range $reply 0 0] eq {-}} {
- set retval 1
- }
- close $fd
- } e]} {
- if {$::verbose} {
- puts -nonewline "."
- }
- } else {
- if {$::verbose} {
- puts -nonewline "ok"
- }
- }
- return $retval
- }
- # Return 1 if the server at the specified addr is reachable by PING, otherwise
- # returns 0. Performs a try every 50 milliseconds for the specified number
- # of retries.
- proc server_is_up {host port retrynum} {
- after 10 ;# Use a small delay to make likely a first-try success.
- set retval 0
- while {[incr retrynum -1]} {
- if {[catch {ping_server $host $port} ping]} {
- set ping 0
- }
- if {$ping} {return 1}
- after 50
- }
- return 0
- }
- # doesn't really belong here, but highly coupled to code in start_server
- proc tags {tags code} {
- set ::tags [concat $::tags $tags]
- uplevel 1 $code
- set ::tags [lrange $::tags 0 end-[llength $tags]]
- }
- # Write the configuration in the dictionary 'config' in the specified
- # file name.
- proc create_server_config_file {filename config} {
- set fp [open $filename w+]
- foreach directive [dict keys $config] {
- puts -nonewline $fp "$directive "
- puts $fp [dict get $config $directive]
- }
- close $fp
- }
- proc spawn_server {config_file stdout stderr} {
- if {$::valgrind} {
- set pid [exec valgrind --track-origins=yes --trace-children=yes --suppressions=[pwd]/src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file >> $stdout 2>> $stderr &]
- } elseif ($::stack_logging) {
- set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/redis-server $config_file >> $stdout 2>> $stderr &]
- } else {
- set pid [exec src/redis-server $config_file >> $stdout 2>> $stderr &]
- }
- if {$::wait_server} {
- set msg "server started PID: $pid. press any key to continue..."
- puts $msg
- read stdin 1
- }
- # Tell the test server about this new instance.
- send_data_packet $::test_server_fd server-spawned $pid
- return $pid
- }
- # Wait for actual startup, return 1 if port is busy, 0 otherwise
- proc wait_server_started {config_file stdout pid} {
- set checkperiod 100; # Milliseconds
- set maxiter [expr {120*1000/$checkperiod}] ; # Wait up to 2 minutes.
- set port_busy 0
- while 1 {
- if {[regexp -- " PID: $pid" [exec cat $stdout]]} {
- break
- }
- after $checkperiod
- incr maxiter -1
- if {$maxiter == 0} {
- start_server_error $config_file "No PID detected in log $stdout"
- puts "--- LOG CONTENT ---"
- puts [exec cat $stdout]
- puts "-------------------"
- break
- }
- # Check if the port is actually busy and the server failed
- # for this reason.
- if {[regexp {Could not create server TCP} [exec cat $stdout]]} {
- set port_busy 1
- break
- }
- }
- return $port_busy
- }
- proc start_server {options {code undefined}} {
- # If we are running against an external server, we just push the
- # host/port pair in the stack the first time
- if {$::external} {
- if {[llength $::servers] == 0} {
- set srv {}
- dict set srv "host" $::host
- dict set srv "port" $::port
- set client [redis $::host $::port 0 $::tls]
- dict set srv "client" $client
- $client select 9
- # append the server to the stack
- lappend ::servers $srv
- }
- uplevel 1 $code
- return
- }
- # setup defaults
- set baseconfig "default.conf"
- set overrides {}
- set tags {}
- # parse options
- foreach {option value} $options {
- switch $option {
- "config" {
- set baseconfig $value }
- "overrides" {
- set overrides $value }
- "tags" {
- set tags $value
- set ::tags [concat $::tags $value] }
- default {
- error "Unknown option $option" }
- }
- }
- set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
- set config {}
- if {$::tls} {
- dict set config "tls-cert-file" [format "%s/tests/tls/redis.crt" [pwd]]
- dict set config "tls-key-file" [format "%s/tests/tls/redis.key" [pwd]]
- dict set config "tls-dh-params-file" [format "%s/tests/tls/redis.dh" [pwd]]
- dict set config "tls-ca-cert-file" [format "%s/tests/tls/ca.crt" [pwd]]
- dict set config "loglevel" "debug"
- }
- foreach line $data {
- if {[string length $line] > 0 && [string index $line 0] ne "#"} {
- set elements [split $line " "]
- set directive [lrange $elements 0 0]
- set arguments [lrange $elements 1 end]
- dict set config $directive $arguments
- }
- }
- # use a different directory every time a server is started
- dict set config dir [tmpdir server]
- # start every server on a different port
- set port [find_available_port $::baseport $::portcount]
- if {$::tls} {
- dict set config "port" 0
- dict set config "tls-port" $port
- dict set config "tls-cluster" "yes"
- dict set config "tls-replication" "yes"
- } else {
- dict set config port $port
- }
- set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]]
- dict set config "unixsocket" $unixsocket
- # apply overrides from global space and arguments
- foreach {directive arguments} [concat $::global_overrides $overrides] {
- dict set config $directive $arguments
- }
- # write new configuration to temporary file
- set config_file [tmpfile redis.conf]
- create_server_config_file $config_file $config
- set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
- set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
- # We need a loop here to retry with different ports.
- set server_started 0
- while {$server_started == 0} {
- if {$::verbose} {
- puts -nonewline "=== ($tags) Starting server ${::host}:${port} "
- }
- send_data_packet $::test_server_fd "server-spawning" "port $port"
- set pid [spawn_server $config_file $stdout $stderr]
- # check that the server actually started
- set port_busy [wait_server_started $config_file $stdout $pid]
- # Sometimes we have to try a different port, even if we checked
- # for availability. Other test clients may grab the port before we
- # are able to do it for example.
- if {$port_busy} {
- puts "Port $port was already busy, trying another port..."
- set port [find_available_port $::baseport $::portcount]
- if {$::tls} {
- dict set config "tls-port" $port
- } else {
- dict set config port $port
- }
- create_server_config_file $config_file $config
- continue; # Try again
- }
- if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
- if {$code ne "undefined"} {
- set serverisup [server_is_up $::host $port $retrynum]
- } else {
- set serverisup 1
- }
- if {$::verbose} {
- puts ""
- }
- if {!$serverisup} {
- set err {}
- append err [exec cat $stdout] "\n" [exec cat $stderr]
- start_server_error $config_file $err
- return
- }
- set server_started 1
- }
- # setup properties to be able to initialize a client object
- set port_param [expr $::tls ? {"tls-port"} : {"port"}]
- set host $::host
- if {[dict exists $config bind]} { set host [dict get $config bind] }
- if {[dict exists $config $port_param]} { set port [dict get $config $port_param] }
- # setup config dict
- dict set srv "config_file" $config_file
- dict set srv "config" $config
- dict set srv "pid" $pid
- dict set srv "host" $host
- dict set srv "port" $port
- dict set srv "stdout" $stdout
- dict set srv "stderr" $stderr
- dict set srv "unixsocket" $unixsocket
- # if a block of code is supplied, we wait for the server to become
- # available, create a client object and kill the server afterwards
- if {$code ne "undefined"} {
- set line [exec head -n1 $stdout]
- if {[string match {*already in use*} $line]} {
- error_and_quit $config_file $line
- }
- while 1 {
- # check that the server actually started and is ready for connections
- if {[exec grep -i "Ready to accept" | wc -l < $stdout] > 0} {
- break
- }
- after 10
- }
- # append the server to the stack
- lappend ::servers $srv
- # connect client (after server dict is put on the stack)
- reconnect
- # execute provided block
- set num_tests $::num_tests
- if {[catch { uplevel 1 $code } error]} {
- set backtrace $::errorInfo
- # fetch srv back from the server list, in case it was restarted by restart_server (new PID)
- set srv [lindex $::servers end]
- # pop the server object
- set ::servers [lrange $::servers 0 end-1]
- # Kill the server without checking for leaks
- dict set srv "skipleaks" 1
- kill_server $srv
- # Print warnings from log
- puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]]
- set warnings [warnings_from_file [dict get $srv "stdout"]]
- if {[string length $warnings] > 0} {
- puts "$warnings"
- } else {
- puts "(none)"
- }
- puts ""
- error $error $backtrace
- }
- # fetch srv back from the server list, in case it was restarted by restart_server (new PID)
- set srv [lindex $::servers end]
- # Don't do the leak check when no tests were run
- if {$num_tests == $::num_tests} {
- dict set srv "skipleaks" 1
- }
- # pop the server object
- set ::servers [lrange $::servers 0 end-1]
- set ::tags [lrange $::tags 0 end-[llength $tags]]
- kill_server $srv
- } else {
- set ::tags [lrange $::tags 0 end-[llength $tags]]
- set _ $srv
- }
- }
- proc restart_server {level wait_ready} {
- set srv [lindex $::servers end+$level]
- kill_server $srv
- set stdout [dict get $srv "stdout"]
- set stderr [dict get $srv "stderr"]
- set config_file [dict get $srv "config_file"]
- set prev_ready_count [exec grep -i "Ready to accept" | wc -l < $stdout]
- set pid [spawn_server $config_file $stdout $stderr]
- # check that the server actually started
- wait_server_started $config_file $stdout $pid
- # update the pid in the servers list
- dict set srv "pid" $pid
- # re-set $srv in the servers list
- lset ::servers end+$level $srv
- if {$wait_ready} {
- while 1 {
- # check that the server actually started and is ready for connections
- if {[exec grep -i "Ready to accept" | wc -l < $stdout] > $prev_ready_count + 1} {
- break
- }
- after 10
- }
- }
- reconnect $level
- }
|