server.tcl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. set ::global_overrides {}
  2. set ::tags {}
  3. set ::valgrind_errors {}
  4. proc start_server_error {config_file error} {
  5. set err {}
  6. append err "Can't start the Redis server\n"
  7. append err "CONFIGURATION:"
  8. append err [exec cat $config_file]
  9. append err "\nERROR:"
  10. append err [string trim $error]
  11. send_data_packet $::test_server_fd err $err
  12. }
  13. proc check_valgrind_errors stderr {
  14. set fd [open $stderr]
  15. set buf [read $fd]
  16. close $fd
  17. # look for stack trace and other errors, or the absense of a leak free summary
  18. if {[regexp -- { at 0x} $buf] ||
  19. [regexp -- {Warning} $buf] ||
  20. [regexp -- {Invalid} $buf] ||
  21. [regexp -- {Mismatched} $buf] ||
  22. [regexp -- {uninitialized} $buf] ||
  23. [regexp -- {has a fishy} $buf] ||
  24. [regexp -- {overlap} $buf] ||
  25. (![regexp -- {definitely lost: 0 bytes} $buf] &&
  26. ![regexp -- {no leaks are possible} $buf])} {
  27. send_data_packet $::test_server_fd err "Valgrind error: $buf\n"
  28. }
  29. }
  30. proc kill_server config {
  31. # nothing to kill when running against external server
  32. if {$::external} return
  33. # nevermind if its already dead
  34. if {![is_alive $config]} {
  35. # Check valgrind errors if needed
  36. if {$::valgrind} {
  37. check_valgrind_errors [dict get $config stderr]
  38. }
  39. return
  40. }
  41. set pid [dict get $config pid]
  42. # check for leaks
  43. if {![dict exists $config "skipleaks"]} {
  44. catch {
  45. if {[string match {*Darwin*} [exec uname -a]]} {
  46. tags {"leaks"} {
  47. test "Check for memory leaks (pid $pid)" {
  48. set output {0 leaks}
  49. catch {exec leaks $pid} output
  50. if {[string match {*process does not exist*} $output] ||
  51. [string match {*cannot examine*} $output]} {
  52. # In a few tests we kill the server process.
  53. set output "0 leaks"
  54. }
  55. set output
  56. } {*0 leaks*}
  57. }
  58. }
  59. }
  60. }
  61. # kill server and wait for the process to be totally exited
  62. send_data_packet $::test_server_fd server-killing $pid
  63. catch {exec kill $pid}
  64. if {$::valgrind} {
  65. set max_wait 60000
  66. } else {
  67. set max_wait 10000
  68. }
  69. while {[is_alive $config]} {
  70. incr wait 10
  71. if {$wait >= $max_wait} {
  72. puts "Forcing process $pid to exit..."
  73. catch {exec kill -KILL $pid}
  74. } elseif {$wait % 1000 == 0} {
  75. puts "Waiting for process $pid to exit..."
  76. }
  77. after 10
  78. }
  79. # Check valgrind errors if needed
  80. if {$::valgrind} {
  81. check_valgrind_errors [dict get $config stderr]
  82. }
  83. # Remove this pid from the set of active pids in the test server.
  84. send_data_packet $::test_server_fd server-killed $pid
  85. }
  86. proc is_alive config {
  87. set pid [dict get $config pid]
  88. if {[catch {exec ps -p $pid} err]} {
  89. return 0
  90. } else {
  91. return 1
  92. }
  93. }
  94. proc ping_server {host port} {
  95. set retval 0
  96. if {[catch {
  97. if {$::tls} {
  98. set fd [::tls::socket $host $port]
  99. } else {
  100. set fd [socket $host $port]
  101. }
  102. fconfigure $fd -translation binary
  103. puts $fd "PING\r\n"
  104. flush $fd
  105. set reply [gets $fd]
  106. if {[string range $reply 0 0] eq {+} ||
  107. [string range $reply 0 0] eq {-}} {
  108. set retval 1
  109. }
  110. close $fd
  111. } e]} {
  112. if {$::verbose} {
  113. puts -nonewline "."
  114. }
  115. } else {
  116. if {$::verbose} {
  117. puts -nonewline "ok"
  118. }
  119. }
  120. return $retval
  121. }
  122. # Return 1 if the server at the specified addr is reachable by PING, otherwise
  123. # returns 0. Performs a try every 50 milliseconds for the specified number
  124. # of retries.
  125. proc server_is_up {host port retrynum} {
  126. after 10 ;# Use a small delay to make likely a first-try success.
  127. set retval 0
  128. while {[incr retrynum -1]} {
  129. if {[catch {ping_server $host $port} ping]} {
  130. set ping 0
  131. }
  132. if {$ping} {return 1}
  133. after 50
  134. }
  135. return 0
  136. }
  137. # doesn't really belong here, but highly coupled to code in start_server
  138. proc tags {tags code} {
  139. set ::tags [concat $::tags $tags]
  140. uplevel 1 $code
  141. set ::tags [lrange $::tags 0 end-[llength $tags]]
  142. }
  143. # Write the configuration in the dictionary 'config' in the specified
  144. # file name.
  145. proc create_server_config_file {filename config} {
  146. set fp [open $filename w+]
  147. foreach directive [dict keys $config] {
  148. puts -nonewline $fp "$directive "
  149. puts $fp [dict get $config $directive]
  150. }
  151. close $fp
  152. }
  153. proc spawn_server {config_file stdout stderr} {
  154. if {$::valgrind} {
  155. set pid [exec valgrind --track-origins=yes --trace-children=yes --suppressions=[pwd]/src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file >> $stdout 2>> $stderr &]
  156. } elseif ($::stack_logging) {
  157. set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/redis-server $config_file >> $stdout 2>> $stderr &]
  158. } else {
  159. set pid [exec src/redis-server $config_file >> $stdout 2>> $stderr &]
  160. }
  161. if {$::wait_server} {
  162. set msg "server started PID: $pid. press any key to continue..."
  163. puts $msg
  164. read stdin 1
  165. }
  166. # Tell the test server about this new instance.
  167. send_data_packet $::test_server_fd server-spawned $pid
  168. return $pid
  169. }
  170. # Wait for actual startup, return 1 if port is busy, 0 otherwise
  171. proc wait_server_started {config_file stdout pid} {
  172. set checkperiod 100; # Milliseconds
  173. set maxiter [expr {120*1000/$checkperiod}] ; # Wait up to 2 minutes.
  174. set port_busy 0
  175. while 1 {
  176. if {[regexp -- " PID: $pid" [exec cat $stdout]]} {
  177. break
  178. }
  179. after $checkperiod
  180. incr maxiter -1
  181. if {$maxiter == 0} {
  182. start_server_error $config_file "No PID detected in log $stdout"
  183. puts "--- LOG CONTENT ---"
  184. puts [exec cat $stdout]
  185. puts "-------------------"
  186. break
  187. }
  188. # Check if the port is actually busy and the server failed
  189. # for this reason.
  190. if {[regexp {Could not create server TCP} [exec cat $stdout]]} {
  191. set port_busy 1
  192. break
  193. }
  194. }
  195. return $port_busy
  196. }
  197. proc start_server {options {code undefined}} {
  198. # If we are running against an external server, we just push the
  199. # host/port pair in the stack the first time
  200. if {$::external} {
  201. if {[llength $::servers] == 0} {
  202. set srv {}
  203. dict set srv "host" $::host
  204. dict set srv "port" $::port
  205. set client [redis $::host $::port 0 $::tls]
  206. dict set srv "client" $client
  207. $client select 9
  208. # append the server to the stack
  209. lappend ::servers $srv
  210. }
  211. uplevel 1 $code
  212. return
  213. }
  214. # setup defaults
  215. set baseconfig "default.conf"
  216. set overrides {}
  217. set tags {}
  218. # parse options
  219. foreach {option value} $options {
  220. switch $option {
  221. "config" {
  222. set baseconfig $value }
  223. "overrides" {
  224. set overrides $value }
  225. "tags" {
  226. set tags $value
  227. set ::tags [concat $::tags $value] }
  228. default {
  229. error "Unknown option $option" }
  230. }
  231. }
  232. set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
  233. set config {}
  234. if {$::tls} {
  235. dict set config "tls-cert-file" [format "%s/tests/tls/redis.crt" [pwd]]
  236. dict set config "tls-key-file" [format "%s/tests/tls/redis.key" [pwd]]
  237. dict set config "tls-dh-params-file" [format "%s/tests/tls/redis.dh" [pwd]]
  238. dict set config "tls-ca-cert-file" [format "%s/tests/tls/ca.crt" [pwd]]
  239. dict set config "loglevel" "debug"
  240. }
  241. foreach line $data {
  242. if {[string length $line] > 0 && [string index $line 0] ne "#"} {
  243. set elements [split $line " "]
  244. set directive [lrange $elements 0 0]
  245. set arguments [lrange $elements 1 end]
  246. dict set config $directive $arguments
  247. }
  248. }
  249. # use a different directory every time a server is started
  250. dict set config dir [tmpdir server]
  251. # start every server on a different port
  252. set port [find_available_port $::baseport $::portcount]
  253. if {$::tls} {
  254. dict set config "port" 0
  255. dict set config "tls-port" $port
  256. dict set config "tls-cluster" "yes"
  257. dict set config "tls-replication" "yes"
  258. } else {
  259. dict set config port $port
  260. }
  261. set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]]
  262. dict set config "unixsocket" $unixsocket
  263. # apply overrides from global space and arguments
  264. foreach {directive arguments} [concat $::global_overrides $overrides] {
  265. dict set config $directive $arguments
  266. }
  267. # write new configuration to temporary file
  268. set config_file [tmpfile redis.conf]
  269. create_server_config_file $config_file $config
  270. set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
  271. set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
  272. # We need a loop here to retry with different ports.
  273. set server_started 0
  274. while {$server_started == 0} {
  275. if {$::verbose} {
  276. puts -nonewline "=== ($tags) Starting server ${::host}:${port} "
  277. }
  278. send_data_packet $::test_server_fd "server-spawning" "port $port"
  279. set pid [spawn_server $config_file $stdout $stderr]
  280. # check that the server actually started
  281. set port_busy [wait_server_started $config_file $stdout $pid]
  282. # Sometimes we have to try a different port, even if we checked
  283. # for availability. Other test clients may grab the port before we
  284. # are able to do it for example.
  285. if {$port_busy} {
  286. puts "Port $port was already busy, trying another port..."
  287. set port [find_available_port $::baseport $::portcount]
  288. if {$::tls} {
  289. dict set config "tls-port" $port
  290. } else {
  291. dict set config port $port
  292. }
  293. create_server_config_file $config_file $config
  294. continue; # Try again
  295. }
  296. if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
  297. if {$code ne "undefined"} {
  298. set serverisup [server_is_up $::host $port $retrynum]
  299. } else {
  300. set serverisup 1
  301. }
  302. if {$::verbose} {
  303. puts ""
  304. }
  305. if {!$serverisup} {
  306. set err {}
  307. append err [exec cat $stdout] "\n" [exec cat $stderr]
  308. start_server_error $config_file $err
  309. return
  310. }
  311. set server_started 1
  312. }
  313. # setup properties to be able to initialize a client object
  314. set port_param [expr $::tls ? {"tls-port"} : {"port"}]
  315. set host $::host
  316. if {[dict exists $config bind]} { set host [dict get $config bind] }
  317. if {[dict exists $config $port_param]} { set port [dict get $config $port_param] }
  318. # setup config dict
  319. dict set srv "config_file" $config_file
  320. dict set srv "config" $config
  321. dict set srv "pid" $pid
  322. dict set srv "host" $host
  323. dict set srv "port" $port
  324. dict set srv "stdout" $stdout
  325. dict set srv "stderr" $stderr
  326. dict set srv "unixsocket" $unixsocket
  327. # if a block of code is supplied, we wait for the server to become
  328. # available, create a client object and kill the server afterwards
  329. if {$code ne "undefined"} {
  330. set line [exec head -n1 $stdout]
  331. if {[string match {*already in use*} $line]} {
  332. error_and_quit $config_file $line
  333. }
  334. while 1 {
  335. # check that the server actually started and is ready for connections
  336. if {[exec grep -i "Ready to accept" | wc -l < $stdout] > 0} {
  337. break
  338. }
  339. after 10
  340. }
  341. # append the server to the stack
  342. lappend ::servers $srv
  343. # connect client (after server dict is put on the stack)
  344. reconnect
  345. # execute provided block
  346. set num_tests $::num_tests
  347. if {[catch { uplevel 1 $code } error]} {
  348. set backtrace $::errorInfo
  349. # fetch srv back from the server list, in case it was restarted by restart_server (new PID)
  350. set srv [lindex $::servers end]
  351. # pop the server object
  352. set ::servers [lrange $::servers 0 end-1]
  353. # Kill the server without checking for leaks
  354. dict set srv "skipleaks" 1
  355. kill_server $srv
  356. # Print warnings from log
  357. puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]]
  358. set warnings [warnings_from_file [dict get $srv "stdout"]]
  359. if {[string length $warnings] > 0} {
  360. puts "$warnings"
  361. } else {
  362. puts "(none)"
  363. }
  364. puts ""
  365. error $error $backtrace
  366. }
  367. # fetch srv back from the server list, in case it was restarted by restart_server (new PID)
  368. set srv [lindex $::servers end]
  369. # Don't do the leak check when no tests were run
  370. if {$num_tests == $::num_tests} {
  371. dict set srv "skipleaks" 1
  372. }
  373. # pop the server object
  374. set ::servers [lrange $::servers 0 end-1]
  375. set ::tags [lrange $::tags 0 end-[llength $tags]]
  376. kill_server $srv
  377. } else {
  378. set ::tags [lrange $::tags 0 end-[llength $tags]]
  379. set _ $srv
  380. }
  381. }
  382. proc restart_server {level wait_ready} {
  383. set srv [lindex $::servers end+$level]
  384. kill_server $srv
  385. set stdout [dict get $srv "stdout"]
  386. set stderr [dict get $srv "stderr"]
  387. set config_file [dict get $srv "config_file"]
  388. set prev_ready_count [exec grep -i "Ready to accept" | wc -l < $stdout]
  389. set pid [spawn_server $config_file $stdout $stderr]
  390. # check that the server actually started
  391. wait_server_started $config_file $stdout $pid
  392. # update the pid in the servers list
  393. dict set srv "pid" $pid
  394. # re-set $srv in the servers list
  395. lset ::servers end+$level $srv
  396. if {$wait_ready} {
  397. while 1 {
  398. # check that the server actually started and is ready for connections
  399. if {[exec grep -i "Ready to accept" | wc -l < $stdout] > $prev_ready_count + 1} {
  400. break
  401. }
  402. after 10
  403. }
  404. }
  405. reconnect $level
  406. }