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