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 exec src/redis-server $config_file > $stdout 2> $stderr & 181 } 182 183 # check that the server actually started 184 # ugly but tries to be as fast as possible... 185 set retrynum 20 186 set serverisup 0 187 188 if {$::verbose} { 189 puts -nonewline "=== ($tags) Starting server ${::host}:${::port} " 190 } 191 192 after 10 193 if {$code ne "undefined"} { 194 while {[incr retrynum -1]} { 195 catch { 196 if {[ping_server $::host $::port]} { 197 set serverisup 1 198 } 199 } 200 if {$serverisup} break 201 after 50 202 } 203 } else { 204 set serverisup 1 205 } 206 207 if {$::verbose} { 208 puts "" 209 } 210 211 if {!$serverisup} { 212 error_and_quit $config_file [exec cat $stderr] 213 } 214 215 # find out the pid 216 while {![info exists pid]} { 217 regexp {^\[(\d+)\]} [exec head -n1 $stdout] _ pid 218 after 100 219 } 220 221 # setup properties to be able to initialize a client object 222 set host $::host 223 set port $::port 224 if {[dict exists $config bind]} { set host [dict get $config bind] } 225 if {[dict exists $config port]} { set port [dict get $config port] } 226 227 # setup config dict 228 dict set srv "config_file" $config_file 229 dict set srv "config" $config 230 dict set srv "pid" $pid 231 dict set srv "host" $host 232 dict set srv "port" $port 233 dict set srv "stdout" $stdout 234 dict set srv "stderr" $stderr 235 236 # if a block of code is supplied, we wait for the server to become 237 # available, create a client object and kill the server afterwards 238 if {$code ne "undefined"} { 239 set line [exec head -n1 $stdout] 240 if {[string match {*already in use*} $line]} { 241 error_and_quit $config_file $line 242 } 243 244 while 1 { 245 # check that the server actually started and is ready for connections 246 if {[exec cat $stdout | grep "ready to accept" | wc -l] > 0} { 247 break 248 } 249 after 10 250 } 251 252 # append the server to the stack 253 lappend ::servers $srv 254 255 # connect client (after server dict is put on the stack) 256 reconnect 257 258 # execute provided block 259 set num_tests $::num_tests 260 if {[catch { uplevel 1 $code } error]} { 261 set backtrace $::errorInfo 262 263 # Kill the server without checking for leaks 264 dict set srv "skipleaks" 1 265 kill_server $srv 266 267 # Print warnings from log 268 puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]] 269 set warnings [warnings_from_file [dict get $srv "stdout"]] 270 if {[string length $warnings] > 0} { 271 puts "$warnings" 272 } else { 273 puts "(none)" 274 } 275 puts "" 276 277 error $error $backtrace 278 } 279 280 # Don't do the leak check when no tests were run 281 if {$num_tests == $::num_tests} { 282 dict set srv "skipleaks" 1 283 } 284 285 # pop the server object 286 set ::servers [lrange $::servers 0 end-1] 287 288 set ::tags [lrange $::tags 0 end-[llength $tags]] 289 kill_server $srv 290 } else { 291 set ::tags [lrange $::tags 0 end-[llength $tags]] 292 set _ $srv 293 } 294} 295