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