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