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