X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/0f3010ab0d076d01b31019d902bfc94ca1e310d3..bad7d097e95ec0735c41f85b50ace65bec6134ae:/tests/support/server.tcl diff --git a/tests/support/server.tcl b/tests/support/server.tcl index 40f21925..24fef467 100644 --- a/tests/support/server.tcl +++ b/tests/support/server.tcl @@ -1,3 +1,6 @@ +set ::global_overrides {} +set ::tags {} + proc error_and_quit {config_file error} { puts "!!COULD NOT START REDIS-SERVER\n" puts "CONFIGURATION:" @@ -7,31 +10,131 @@ proc error_and_quit {config_file error} { exit 1 } +proc check_valgrind_errors stderr { + set fd [open $stderr] + set buf [read $fd] + close $fd + + if {![regexp -- {ERROR SUMMARY: 0 errors} $buf] || + ![regexp -- {definitely lost: 0 bytes} $buf]} { + puts "*** VALGRIND ERRORS ***" + puts $buf + puts "--- press enter to continue ---" + gets stdin + } +} + proc kill_server config { + # nothing to kill when running against external server + if {$::external} return + + # nevermind if its already dead + if {![is_alive $config]} { return } set pid [dict get $config pid] # check for leaks - catch { - if {[string match {*Darwin*} [exec uname -a]]} { - test "Check for memory leaks (pid $pid)" { - exec leaks $pid - } {*0 leaks*} + if {![dict exists $config "skipleaks"]} { + catch { + if {[string match {*Darwin*} [exec uname -a]]} { + tags {"leaks"} { + test "Check for memory leaks (pid $pid)" { + exec leaks $pid + } {*0 leaks*} + } + } } } # kill server and wait for the process to be totally exited - exec kill $pid - while 1 { - # with a non-zero exit status, the process is gone - if {[catch {exec ps -p $pid | grep redis-server} result]} { - break + while {[is_alive $config]} { + if {[incr wait 10] % 1000 == 0} { + puts "Waiting for process $pid to exit..." } + catch {exec kill $pid} after 10 } + + # Check valgrind errors if needed + if {$::valgrind} { + check_valgrind_errors [dict get $config stderr] + } } -proc start_server {filename overrides {code undefined}} { - set data [split [exec cat "tests/assets/$filename"] "\n"] +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 { + 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 4] eq {+PONG} || + [string range $reply 0 3] eq {-ERR}} { + set retval 1 + } + close $fd + } e]} { + puts "Can't PING server at $host:$port... $e" + } + return $retval +} + +# 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]] +} + +proc start_server {options {code undefined}} { + # If we are runnign 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] + 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 {} foreach line $data { if {[string length $line] > 0 && [string index $line 0] ne "#"} { @@ -48,10 +151,8 @@ proc start_server {filename overrides {code undefined}} { # start every server on a different port dict set config port [incr ::port] - # apply overrides from arguments - foreach override $overrides { - set directive [lrange $override 0 0] - set arguments [lrange $override 1 end] + # apply overrides from global space and arguments + foreach {directive arguments} [concat $::global_overrides $overrides] { dict set config $directive $arguments } @@ -66,41 +167,31 @@ proc start_server {filename overrides {code undefined}} { set stdout [format "%s/%s" [dict get $config "dir"] "stdout"] set stderr [format "%s/%s" [dict get $config "dir"] "stderr"] - exec ./redis-server $config_file > $stdout 2> $stderr & - after 500 + + if {$::valgrind} { + exec valgrind src/redis-server $config_file > $stdout 2> $stderr & + after 2000 + } else { + exec src/redis-server $config_file > $stdout 2> $stderr & + after 500 + } # check that the server actually started - if {[file size $stderr] > 0} { + if {$code ne "undefined" && ![ping_server $::host $::port]} { error_and_quit $config_file [exec cat $stderr] } - 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 cat $stdout | grep "ready to accept" | wc -l] > 0} { - break - } - after 10 - } - # find out the pid - regexp {^\[(\d+)\]} [exec head -n1 $stdout] _ pid + while {![info exists pid]} { + regexp {^\[(\d+)\]} [exec head -n1 $stdout] _ pid + after 100 + } - # create the client object + # setup properties to be able to initialize a client object set host $::host set port $::port if {[dict exists $config bind]} { set host [dict get $config bind] } if {[dict exists $config port]} { set port [dict get $config port] } - set client [redis $host $port] - - # select the right db when we don't have to authenticate - if {![dict exists $config requirepass]} { - $client select 9 - } # setup config dict dict set srv "config" $config_file @@ -109,26 +200,75 @@ proc start_server {filename overrides {code undefined}} { dict set srv "port" $port dict set srv "stdout" $stdout dict set srv "stderr" $stderr - dict set srv "client" $client + # 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 cat $stdout | grep "ready to accept" | wc -l] > 0} { + break + } + after 10 + } + + set client [redis $host $port] + dict set srv "client" $client + + # select the right db when we don't have to authenticate + if {![dict exists $config requirepass]} { + $client select 9 + } + # append the server to the stack lappend ::servers $srv # execute provided block - catch { uplevel 1 $code } err + set curnum $::testnum + if {![catch { uplevel 1 $code } err]} { + # zero exit status is good + unset err + } + + if {$curnum == $::testnum} { + # don't check for leaks when no tests were executed + dict set srv "skipleaks" 1 + } # pop the server object set ::servers [lrange $::servers 0 end-1] - kill_server $srv - - if {[string length $err] > 0} { - puts "Error executing the suite, aborting..." - puts $err - exit 1 + # allow an exception to bubble up the call chain but still kill this + # server, because we want to reuse the ports when the tests are re-run + if {[info exists err]} { + if {$err eq "exception"} { + puts [format "Logged 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)" + } + # kill this server without checking for leaks + dict set srv "skipleaks" 1 + kill_server $srv + error "exception" + } elseif {[string length $err] > 0} { + puts "Error executing the suite, aborting..." + puts $err + exit 1 + } } + + set ::tags [lrange $::tags 0 end-[llength $tags]] + kill_server $srv } else { + set ::tags [lrange $::tags 0 end-[llength $tags]] set _ $srv } }