1set ::global_overrides {} 2set ::tags {} 3set ::valgrind_errors {} 4 5proc start_server_error {config_file error} { 6 set err {} 7 append err "Cant' start the Redis server\n" 8 append err "CONFIGURATION:" 9 append err [exec cat $config_file] 10 append err "\nERROR:" 11 append err [string trim $error] 12 send_data_packet $::test_server_fd err $err 13} 14 15proc check_valgrind_errors stderr { 16 set fd [open $stderr] 17 set buf [read $fd] 18 close $fd 19 20 if {[regexp -- { at 0x} $buf] || 21 (![regexp -- {definitely lost: 0 bytes} $buf] && 22 ![regexp -- {no leaks are possible} $buf])} { 23 send_data_packet $::test_server_fd err "Valgrind error: $buf\n" 24 } 25} 26 27proc kill_server config { 28 # nothing to kill when running against external server 29 if {$::external} return 30 31 # nevermind if its already dead 32 if {![is_alive $config]} { return } 33 set pid [dict get $config pid] 34 35 # check for leaks 36 if {![dict exists $config "skipleaks"]} { 37 catch { 38 if {[string match {*Darwin*} [exec uname -a]]} { 39 tags {"leaks"} { 40 test "Check for memory leaks (pid $pid)" { 41 set output {0 leaks} 42 catch {exec leaks $pid} output 43 if {[string match {*process does not exist*} $output]} { 44 # In a few tests we kill the server process. 45 set output "0 leaks" 46 } 47 set output 48 } {*0 leaks*} 49 } 50 } 51 } 52 } 53 54 # kill server and wait for the process to be totally exited 55 catch {exec kill $pid} 56 while {[is_alive $config]} { 57 incr wait 10 58 59 if {$wait >= 5000} { 60 puts "Forcing process $pid to exit..." 61 catch {exec kill -KILL $pid} 62 } elseif {$wait % 1000 == 0} { 63 puts "Waiting for process $pid to exit..." 64 } 65 after 10 66 } 67 68 # Check valgrind errors if needed 69 if {$::valgrind} { 70 check_valgrind_errors [dict get $config stderr] 71 } 72} 73 74proc is_alive config { 75 set pid [dict get $config pid] 76 if {[catch {exec ps -p $pid} err]} { 77 return 0 78 } else { 79 return 1 80 } 81} 82 83proc ping_server {host port} { 84 set retval 0 85 if {[catch { 86 set fd [socket $host $port] 87 fconfigure $fd -translation binary 88 puts $fd "PING\r\n" 89 flush $fd 90 set reply [gets $fd] 91 if {[string range $reply 0 0] eq {+} || 92 [string range $reply 0 0] eq {-}} { 93 set retval 1 94 } 95 close $fd 96 } e]} { 97 if {$::verbose} { 98 puts -nonewline "." 99 } 100 } else { 101 if {$::verbose} { 102 puts -nonewline "ok" 103 } 104 } 105 return $retval 106} 107 108# Return 1 if the server at the specified addr is reachable by PING, otherwise 109# returns 0. Performs a try every 50 milliseconds for the specified number 110# of retries. 111proc server_is_up {host port retrynum} { 112 after 10 ;# Use a small delay to make likely a first-try success. 113 set retval 0 114 while {[incr retrynum -1]} { 115 if {[catch {ping_server $host $port} ping]} { 116 set ping 0 117 } 118 if {$ping} {return 1} 119 after 50 120 } 121 return 0 122} 123 124# doesn't really belong here, but highly coupled to code in start_server 125proc tags {tags code} { 126 set ::tags [concat $::tags $tags] 127 uplevel 1 $code 128 set ::tags [lrange $::tags 0 end-[llength $tags]] 129} 130 131proc start_server {options {code undefined}} { 132 # If we are running against an external server, we just push the 133 # host/port pair in the stack the first time 134 if {$::external} { 135 if {[llength $::servers] == 0} { 136 set srv {} 137 dict set srv "host" $::host 138 dict set srv "port" $::port 139 set client [redis $::host $::port] 140 dict set srv "client" $client 141 $client select 9 142 143 # append the server to the stack 144 lappend ::servers $srv 145 } 146 uplevel 1 $code 147 return 148 } 149 150 # setup defaults 151 set baseconfig "default.conf" 152 set overrides {} 153 set tags {} 154 155 # parse options 156 foreach {option value} $options { 157 switch $option { 158 "config" { 159 set baseconfig $value } 160 "overrides" { 161 set overrides $value } 162 "tags" { 163 set tags $value 164 set ::tags [concat $::tags $value] } 165 default { 166 error "Unknown option $option" } 167 } 168 } 169 170 set data [split [exec cat "tests/assets/$baseconfig"] "\n"] 171 set config {} 172 foreach line $data { 173 if {[string length $line] > 0 && [string index $line 0] ne "#"} { 174 set elements [split $line " "] 175 set directive [lrange $elements 0 0] 176 set arguments [lrange $elements 1 end] 177 dict set config $directive $arguments 178 } 179 } 180 181 # use a different directory every time a server is started 182 dict set config dir [tmpdir server] 183 184 # start every server on a different port 185 set ::port [find_available_port [expr {$::port+1}]] 186 dict set config port $::port 187 188 # apply overrides from global space and arguments 189 foreach {directive arguments} [concat $::global_overrides $overrides] { 190 dict set config $directive $arguments 191 } 192 193 # write new configuration to temporary file 194 set config_file [tmpfile redis.conf] 195 set fp [open $config_file w+] 196 foreach directive [dict keys $config] { 197 puts -nonewline $fp "$directive " 198 puts $fp [dict get $config $directive] 199 } 200 close $fp 201 202 set stdout [format "%s/%s" [dict get $config "dir"] "stdout"] 203 set stderr [format "%s/%s" [dict get $config "dir"] "stderr"] 204 205 if {$::valgrind} { 206 exec valgrind --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr & 207 } else { 208 exec src/redis-server $config_file > $stdout 2> $stderr & 209 } 210 211 # check that the server actually started 212 # ugly but tries to be as fast as possible... 213 if {$::valgrind} {set retrynum 1000} else {set retrynum 100} 214 215 if {$::verbose} { 216 puts -nonewline "=== ($tags) Starting server ${::host}:${::port} " 217 } 218 219 if {$code ne "undefined"} { 220 set serverisup [server_is_up $::host $::port $retrynum] 221 } else { 222 set serverisup 1 223 } 224 225 if {$::verbose} { 226 puts "" 227 } 228 229 if {!$serverisup} { 230 set err {} 231 append err [exec cat $stdout] "\n" [exec cat $stderr] 232 start_server_error $config_file $err 233 return 234 } 235 236 # find out the pid 237 while {![info exists pid]} { 238 regexp {\[(\d+)\]} [exec cat $stdout] _ pid 239 after 100 240 } 241 242 # setup properties to be able to initialize a client object 243 set host $::host 244 set port $::port 245 if {[dict exists $config bind]} { set host [dict get $config bind] } 246 if {[dict exists $config port]} { set port [dict get $config port] } 247 248 # setup config dict 249 dict set srv "config_file" $config_file 250 dict set srv "config" $config 251 dict set srv "pid" $pid 252 dict set srv "host" $host 253 dict set srv "port" $port 254 dict set srv "stdout" $stdout 255 dict set srv "stderr" $stderr 256 257 # if a block of code is supplied, we wait for the server to become 258 # available, create a client object and kill the server afterwards 259 if {$code ne "undefined"} { 260 set line [exec head -n1 $stdout] 261 if {[string match {*already in use*} $line]} { 262 error_and_quit $config_file $line 263 } 264 265 while 1 { 266 # check that the server actually started and is ready for connections 267 if {[exec grep "ready to accept" | wc -l < $stdout] > 0} { 268 break 269 } 270 after 10 271 } 272 273 # append the server to the stack 274 lappend ::servers $srv 275 276 # connect client (after server dict is put on the stack) 277 reconnect 278 279 # execute provided block 280 set num_tests $::num_tests 281 if {[catch { uplevel 1 $code } error]} { 282 set backtrace $::errorInfo 283 284 # Kill the server without checking for leaks 285 dict set srv "skipleaks" 1 286 kill_server $srv 287 288 # Print warnings from log 289 puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]] 290 set warnings [warnings_from_file [dict get $srv "stdout"]] 291 if {[string length $warnings] > 0} { 292 puts "$warnings" 293 } else { 294 puts "(none)" 295 } 296 puts "" 297 298 error $error $backtrace 299 } 300 301 # Don't do the leak check when no tests were run 302 if {$num_tests == $::num_tests} { 303 dict set srv "skipleaks" 1 304 } 305 306 # pop the server object 307 set ::servers [lrange $::servers 0 end-1] 308 309 set ::tags [lrange $::tags 0 end-[llength $tags]] 310 kill_server $srv 311 } else { 312 set ::tags [lrange $::tags 0 end-[llength $tags]] 313 set _ $srv 314 } 315} 316