util.tcl 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. proc randstring {min max {type binary}} {
  2. set len [expr {$min+int(rand()*($max-$min+1))}]
  3. set output {}
  4. if {$type eq {binary}} {
  5. set minval 0
  6. set maxval 255
  7. } elseif {$type eq {alpha}} {
  8. set minval 48
  9. set maxval 122
  10. } elseif {$type eq {compr}} {
  11. set minval 48
  12. set maxval 52
  13. }
  14. while {$len} {
  15. set rr [expr {$minval+int(rand()*($maxval-$minval+1))}]
  16. if {$type eq {alpha} && $rr eq 92} {
  17. set rr 90; # avoid putting '\' char in the string, it can mess up TCL processing
  18. }
  19. append output [format "%c" $rr]
  20. incr len -1
  21. }
  22. return $output
  23. }
  24. # Useful for some test
  25. proc zlistAlikeSort {a b} {
  26. if {[lindex $a 0] > [lindex $b 0]} {return 1}
  27. if {[lindex $a 0] < [lindex $b 0]} {return -1}
  28. string compare [lindex $a 1] [lindex $b 1]
  29. }
  30. # Return all log lines starting with the first line that contains a warning.
  31. # Generally, this will be an assertion error with a stack trace.
  32. proc crashlog_from_file {filename} {
  33. set lines [split [exec cat $filename] "\n"]
  34. set matched 0
  35. set logall 0
  36. set result {}
  37. foreach line $lines {
  38. if {[string match {*REDIS BUG REPORT START*} $line]} {
  39. set logall 1
  40. }
  41. if {[regexp {^\[\d+\]\s+\d+\s+\w+\s+\d{2}:\d{2}:\d{2} \#} $line]} {
  42. set matched 1
  43. }
  44. if {$logall || $matched} {
  45. lappend result $line
  46. }
  47. }
  48. join $result "\n"
  49. }
  50. proc getInfoProperty {infostr property} {
  51. if {[regexp "\r\n$property:(.*?)\r\n" $infostr _ value]} {
  52. set _ $value
  53. }
  54. }
  55. # Return value for INFO property
  56. proc status {r property} {
  57. set _ [getInfoProperty [{*}$r info] $property]
  58. }
  59. proc waitForBgsave r {
  60. while 1 {
  61. if {[status r rdb_bgsave_in_progress] eq 1} {
  62. if {$::verbose} {
  63. puts -nonewline "\nWaiting for background save to finish... "
  64. flush stdout
  65. }
  66. after 1000
  67. } else {
  68. break
  69. }
  70. }
  71. }
  72. proc waitForBgrewriteaof r {
  73. while 1 {
  74. if {[status r aof_rewrite_in_progress] eq 1} {
  75. if {$::verbose} {
  76. puts -nonewline "\nWaiting for background AOF rewrite to finish... "
  77. flush stdout
  78. }
  79. after 1000
  80. } else {
  81. break
  82. }
  83. }
  84. }
  85. proc wait_for_sync r {
  86. wait_for_condition 50 100 {
  87. [status $r master_link_status] eq "up"
  88. } else {
  89. fail "replica didn't sync in time"
  90. }
  91. }
  92. proc wait_for_ofs_sync {r1 r2} {
  93. wait_for_condition 50 100 {
  94. [status $r1 master_repl_offset] eq [status $r2 master_repl_offset]
  95. } else {
  96. fail "replica didn't sync in time"
  97. }
  98. }
  99. proc wait_done_loading r {
  100. wait_for_condition 50 100 {
  101. [catch {$r ping} e] == 0
  102. } else {
  103. fail "Loading DB is taking too much time."
  104. }
  105. }
  106. # count current log lines in server's stdout
  107. proc count_log_lines {srv_idx} {
  108. set _ [string trim [exec wc -l < [srv $srv_idx stdout]]]
  109. }
  110. # returns the number of times a line with that pattern appears in a file
  111. proc count_message_lines {file pattern} {
  112. set res 0
  113. # exec fails when grep exists with status other than 0 (when the patter wasn't found)
  114. catch {
  115. set res [string trim [exec grep $pattern $file 2> /dev/null | wc -l]]
  116. }
  117. return $res
  118. }
  119. # returns the number of times a line with that pattern appears in the log
  120. proc count_log_message {srv_idx pattern} {
  121. set stdout [srv $srv_idx stdout]
  122. return [count_message_lines $stdout $pattern]
  123. }
  124. # verify pattern exists in server's sdtout after a certain line number
  125. proc verify_log_message {srv_idx pattern from_line} {
  126. incr from_line
  127. set result [exec tail -n +$from_line < [srv $srv_idx stdout]]
  128. if {![string match $pattern $result]} {
  129. error "assertion:expected message not found in log file: $pattern"
  130. }
  131. }
  132. # wait for pattern to be found in server's stdout after certain line number
  133. # return value is a list containing the line that matched the pattern and the line number
  134. proc wait_for_log_messages {srv_idx patterns from_line maxtries delay} {
  135. set retry $maxtries
  136. set next_line [expr $from_line + 1] ;# searching form the line after
  137. set stdout [srv $srv_idx stdout]
  138. while {$retry} {
  139. # re-read the last line (unless it's before to our first), last time we read it, it might have been incomplete
  140. set next_line [expr $next_line - 1 > $from_line + 1 ? $next_line - 1 : $from_line + 1]
  141. set result [exec tail -n +$next_line < $stdout]
  142. set result [split $result "\n"]
  143. foreach line $result {
  144. foreach pattern $patterns {
  145. if {[string match $pattern $line]} {
  146. return [list $line $next_line]
  147. }
  148. }
  149. incr next_line
  150. }
  151. incr retry -1
  152. after $delay
  153. }
  154. if {$retry == 0} {
  155. if {$::verbose} {
  156. puts "content of $stdout from line: $from_line:"
  157. puts [exec tail -n +$from_line < $stdout]
  158. }
  159. fail "log message of '$patterns' not found in $stdout after line: $from_line till line: [expr $next_line -1]"
  160. }
  161. }
  162. # write line to server log file
  163. proc write_log_line {srv_idx msg} {
  164. set logfile [srv $srv_idx stdout]
  165. set fd [open $logfile "a+"]
  166. puts $fd "### $msg"
  167. close $fd
  168. }
  169. # Random integer between 0 and max (excluded).
  170. proc randomInt {max} {
  171. expr {int(rand()*$max)}
  172. }
  173. # Random signed integer between -max and max (both extremes excluded).
  174. proc randomSignedInt {max} {
  175. set i [randomInt $max]
  176. if {rand() > 0.5} {
  177. set i -$i
  178. }
  179. return $i
  180. }
  181. proc randpath args {
  182. set path [expr {int(rand()*[llength $args])}]
  183. uplevel 1 [lindex $args $path]
  184. }
  185. proc randomValue {} {
  186. randpath {
  187. # Small enough to likely collide
  188. randomSignedInt 1000
  189. } {
  190. # 32 bit compressible signed/unsigned
  191. randpath {randomSignedInt 2000000000} {randomSignedInt 4000000000}
  192. } {
  193. # 64 bit
  194. randpath {randomSignedInt 1000000000000}
  195. } {
  196. # Random string
  197. randpath {randstring 0 256 alpha} \
  198. {randstring 0 256 compr} \
  199. {randstring 0 256 binary}
  200. }
  201. }
  202. proc randomKey {} {
  203. randpath {
  204. # Small enough to likely collide
  205. randomInt 1000
  206. } {
  207. # 32 bit compressible signed/unsigned
  208. randpath {randomInt 2000000000} {randomInt 4000000000}
  209. } {
  210. # 64 bit
  211. randpath {randomInt 1000000000000}
  212. } {
  213. # Random string
  214. randpath {randstring 1 256 alpha} \
  215. {randstring 1 256 compr}
  216. }
  217. }
  218. proc findKeyWithType {r type} {
  219. for {set j 0} {$j < 20} {incr j} {
  220. set k [{*}$r randomkey]
  221. if {$k eq {}} {
  222. return {}
  223. }
  224. if {[{*}$r type $k] eq $type} {
  225. return $k
  226. }
  227. }
  228. return {}
  229. }
  230. proc createComplexDataset {r ops {opt {}}} {
  231. set useexpire [expr {[lsearch -exact $opt useexpire] != -1}]
  232. if {[lsearch -exact $opt usetag] != -1} {
  233. set tag "{t}"
  234. } else {
  235. set tag ""
  236. }
  237. for {set j 0} {$j < $ops} {incr j} {
  238. set k [randomKey]$tag
  239. set k2 [randomKey]$tag
  240. set f [randomValue]
  241. set v [randomValue]
  242. if {$useexpire} {
  243. if {rand() < 0.1} {
  244. {*}$r expire [randomKey] [randomInt 2]
  245. }
  246. }
  247. randpath {
  248. set d [expr {rand()}]
  249. } {
  250. set d [expr {rand()}]
  251. } {
  252. set d [expr {rand()}]
  253. } {
  254. set d [expr {rand()}]
  255. } {
  256. set d [expr {rand()}]
  257. } {
  258. randpath {set d +inf} {set d -inf}
  259. }
  260. set t [{*}$r type $k]
  261. if {$t eq {none}} {
  262. randpath {
  263. {*}$r set $k $v
  264. } {
  265. {*}$r lpush $k $v
  266. } {
  267. {*}$r sadd $k $v
  268. } {
  269. {*}$r zadd $k $d $v
  270. } {
  271. {*}$r hset $k $f $v
  272. } {
  273. {*}$r del $k
  274. }
  275. set t [{*}$r type $k]
  276. }
  277. switch $t {
  278. {string} {
  279. # Nothing to do
  280. }
  281. {list} {
  282. randpath {{*}$r lpush $k $v} \
  283. {{*}$r rpush $k $v} \
  284. {{*}$r lrem $k 0 $v} \
  285. {{*}$r rpop $k} \
  286. {{*}$r lpop $k}
  287. }
  288. {set} {
  289. randpath {{*}$r sadd $k $v} \
  290. {{*}$r srem $k $v} \
  291. {
  292. set otherset [findKeyWithType {*}$r set]
  293. if {$otherset ne {}} {
  294. randpath {
  295. {*}$r sunionstore $k2 $k $otherset
  296. } {
  297. {*}$r sinterstore $k2 $k $otherset
  298. } {
  299. {*}$r sdiffstore $k2 $k $otherset
  300. }
  301. }
  302. }
  303. }
  304. {zset} {
  305. randpath {{*}$r zadd $k $d $v} \
  306. {{*}$r zrem $k $v} \
  307. {
  308. set otherzset [findKeyWithType {*}$r zset]
  309. if {$otherzset ne {}} {
  310. randpath {
  311. {*}$r zunionstore $k2 2 $k $otherzset
  312. } {
  313. {*}$r zinterstore $k2 2 $k $otherzset
  314. }
  315. }
  316. }
  317. }
  318. {hash} {
  319. randpath {{*}$r hset $k $f $v} \
  320. {{*}$r hdel $k $f}
  321. }
  322. }
  323. }
  324. }
  325. proc formatCommand {args} {
  326. set cmd "*[llength $args]\r\n"
  327. foreach a $args {
  328. append cmd "$[string length $a]\r\n$a\r\n"
  329. }
  330. set _ $cmd
  331. }
  332. proc csvdump r {
  333. set o {}
  334. if {$::singledb} {
  335. set maxdb 1
  336. } else {
  337. set maxdb 16
  338. }
  339. for {set db 0} {$db < $maxdb} {incr db} {
  340. if {!$::singledb} {
  341. {*}$r select $db
  342. }
  343. foreach k [lsort [{*}$r keys *]] {
  344. set type [{*}$r type $k]
  345. append o [csvstring $db] , [csvstring $k] , [csvstring $type] ,
  346. switch $type {
  347. string {
  348. append o [csvstring [{*}$r get $k]] "\n"
  349. }
  350. list {
  351. foreach e [{*}$r lrange $k 0 -1] {
  352. append o [csvstring $e] ,
  353. }
  354. append o "\n"
  355. }
  356. set {
  357. foreach e [lsort [{*}$r smembers $k]] {
  358. append o [csvstring $e] ,
  359. }
  360. append o "\n"
  361. }
  362. zset {
  363. foreach e [{*}$r zrange $k 0 -1 withscores] {
  364. append o [csvstring $e] ,
  365. }
  366. append o "\n"
  367. }
  368. hash {
  369. set fields [{*}$r hgetall $k]
  370. set newfields {}
  371. foreach {k v} $fields {
  372. lappend newfields [list $k $v]
  373. }
  374. set fields [lsort -index 0 $newfields]
  375. foreach kv $fields {
  376. append o [csvstring [lindex $kv 0]] ,
  377. append o [csvstring [lindex $kv 1]] ,
  378. }
  379. append o "\n"
  380. }
  381. }
  382. }
  383. }
  384. if {!$::singledb} {
  385. {*}$r select 9
  386. }
  387. return $o
  388. }
  389. proc csvstring s {
  390. return "\"$s\""
  391. }
  392. proc roundFloat f {
  393. format "%.10g" $f
  394. }
  395. set ::last_port_attempted 0
  396. proc find_available_port {start count} {
  397. set port [expr $::last_port_attempted + 1]
  398. for {set attempts 0} {$attempts < $count} {incr attempts} {
  399. if {$port < $start || $port >= $start+$count} {
  400. set port $start
  401. }
  402. if {[catch {set fd1 [socket 127.0.0.1 $port]}] &&
  403. [catch {set fd2 [socket 127.0.0.1 [expr $port+10000]]}]} {
  404. set ::last_port_attempted $port
  405. return $port
  406. } else {
  407. catch {
  408. close $fd1
  409. close $fd2
  410. }
  411. }
  412. incr port
  413. }
  414. error "Can't find a non busy port in the $start-[expr {$start+$count-1}] range."
  415. }
  416. # Test if TERM looks like to support colors
  417. proc color_term {} {
  418. expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
  419. }
  420. proc colorstr {color str} {
  421. if {[color_term]} {
  422. set b 0
  423. if {[string range $color 0 4] eq {bold-}} {
  424. set b 1
  425. set color [string range $color 5 end]
  426. }
  427. switch $color {
  428. red {set colorcode {31}}
  429. green {set colorcode {32}}
  430. yellow {set colorcode {33}}
  431. blue {set colorcode {34}}
  432. magenta {set colorcode {35}}
  433. cyan {set colorcode {36}}
  434. white {set colorcode {37}}
  435. default {set colorcode {37}}
  436. }
  437. if {$colorcode ne {}} {
  438. return "\033\[$b;${colorcode};49m$str\033\[0m"
  439. }
  440. } else {
  441. return $str
  442. }
  443. }
  444. proc find_valgrind_errors {stderr on_termination} {
  445. set fd [open $stderr]
  446. set buf [read $fd]
  447. close $fd
  448. # Look for stack trace (" at 0x") and other errors (Invalid, Mismatched, etc).
  449. # Look for "Warnings", but not the "set address range perms". These don't indicate any real concern.
  450. # corrupt-dump unit, not sure why but it seems they don't indicate any real concern.
  451. if {[regexp -- { at 0x} $buf] ||
  452. [regexp -- {^(?=.*Warning)(?:(?!set address range perms).)*$} $buf] ||
  453. [regexp -- {Invalid} $buf] ||
  454. [regexp -- {Mismatched} $buf] ||
  455. [regexp -- {uninitialized} $buf] ||
  456. [regexp -- {has a fishy} $buf] ||
  457. [regexp -- {overlap} $buf]} {
  458. return $buf
  459. }
  460. # If the process didn't terminate yet, we can't look for the summary report
  461. if {!$on_termination} {
  462. return ""
  463. }
  464. # Look for the absence of a leak free summary (happens when redis isn't terminated properly).
  465. if {(![regexp -- {definitely lost: 0 bytes} $buf] &&
  466. ![regexp -- {no leaks are possible} $buf])} {
  467. return $buf
  468. }
  469. return ""
  470. }
  471. # Execute a background process writing random data for the specified number
  472. # of seconds to the specified Redis instance.
  473. proc start_write_load {host port seconds} {
  474. set tclsh [info nameofexecutable]
  475. exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds $::tls &
  476. }
  477. # Stop a process generating write load executed with start_write_load.
  478. proc stop_write_load {handle} {
  479. catch {exec /bin/kill -9 $handle}
  480. }
  481. proc wait_load_handlers_disconnected {{level 0}} {
  482. wait_for_condition 50 100 {
  483. ![string match {*name=LOAD_HANDLER*} [r $level client list]]
  484. } else {
  485. fail "load_handler(s) still connected after too long time."
  486. }
  487. }
  488. proc K { x y } { set x }
  489. # Shuffle a list with Fisher-Yates algorithm.
  490. proc lshuffle {list} {
  491. set n [llength $list]
  492. while {$n>1} {
  493. set j [expr {int(rand()*$n)}]
  494. incr n -1
  495. if {$n==$j} continue
  496. set v [lindex $list $j]
  497. lset list $j [lindex $list $n]
  498. lset list $n $v
  499. }
  500. return $list
  501. }
  502. # Execute a background process writing complex data for the specified number
  503. # of ops to the specified Redis instance.
  504. proc start_bg_complex_data {host port db ops} {
  505. set tclsh [info nameofexecutable]
  506. exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops $::tls &
  507. }
  508. # Stop a process generating write load executed with start_bg_complex_data.
  509. proc stop_bg_complex_data {handle} {
  510. catch {exec /bin/kill -9 $handle}
  511. }
  512. proc populate {num {prefix key:} {size 3}} {
  513. set rd [redis_deferring_client]
  514. for {set j 0} {$j < $num} {incr j} {
  515. $rd set $prefix$j [string repeat A $size]
  516. }
  517. for {set j 0} {$j < $num} {incr j} {
  518. $rd read
  519. }
  520. $rd close
  521. }
  522. proc get_child_pid {idx} {
  523. set pid [srv $idx pid]
  524. if {[file exists "/usr/bin/pgrep"]} {
  525. set fd [open "|pgrep -P $pid" "r"]
  526. set child_pid [string trim [lindex [split [read $fd] \n] 0]]
  527. } else {
  528. set fd [open "|ps --ppid $pid -o pid" "r"]
  529. set child_pid [string trim [lindex [split [read $fd] \n] 1]]
  530. }
  531. close $fd
  532. return $child_pid
  533. }
  534. proc cmdrstat {cmd r} {
  535. if {[regexp "\r\ncmdstat_$cmd:(.*?)\r\n" [$r info commandstats] _ value]} {
  536. set _ $value
  537. }
  538. }
  539. proc errorrstat {cmd r} {
  540. if {[regexp "\r\nerrorstat_$cmd:(.*?)\r\n" [$r info errorstats] _ value]} {
  541. set _ $value
  542. }
  543. }
  544. proc generate_fuzzy_traffic_on_key {key duration} {
  545. # Commands per type, blocking commands removed
  546. # TODO: extract these from help.h or elsewhere, and improve to include other types
  547. set string_commands {APPEND BITCOUNT BITFIELD BITOP BITPOS DECR DECRBY GET GETBIT GETRANGE GETSET INCR INCRBY INCRBYFLOAT MGET MSET MSETNX PSETEX SET SETBIT SETEX SETNX SETRANGE STRALGO STRLEN}
  548. set hash_commands {HDEL HEXISTS HGET HGETALL HINCRBY HINCRBYFLOAT HKEYS HLEN HMGET HMSET HSCAN HSET HSETNX HSTRLEN HVALS HRANDFIELD}
  549. set zset_commands {ZADD ZCARD ZCOUNT ZINCRBY ZINTERSTORE ZLEXCOUNT ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYLEX ZRANGEBYSCORE ZRANK ZREM ZREMRANGEBYLEX ZREMRANGEBYRANK ZREMRANGEBYSCORE ZREVRANGE ZREVRANGEBYLEX ZREVRANGEBYSCORE ZREVRANK ZSCAN ZSCORE ZUNIONSTORE ZRANDMEMBER}
  550. set list_commands {LINDEX LINSERT LLEN LPOP LPOS LPUSH LPUSHX LRANGE LREM LSET LTRIM RPOP RPOPLPUSH RPUSH RPUSHX}
  551. set set_commands {SADD SCARD SDIFF SDIFFSTORE SINTER SINTERSTORE SISMEMBER SMEMBERS SMOVE SPOP SRANDMEMBER SREM SSCAN SUNION SUNIONSTORE}
  552. set stream_commands {XACK XADD XCLAIM XDEL XGROUP XINFO XLEN XPENDING XRANGE XREAD XREADGROUP XREVRANGE XTRIM}
  553. set commands [dict create string $string_commands hash $hash_commands zset $zset_commands list $list_commands set $set_commands stream $stream_commands]
  554. set type [r type $key]
  555. set cmds [dict get $commands $type]
  556. set start_time [clock seconds]
  557. set sent {}
  558. set succeeded 0
  559. while {([clock seconds]-$start_time) < $duration} {
  560. # find a random command for our key type
  561. set cmd_idx [expr {int(rand()*[llength $cmds])}]
  562. set cmd [lindex $cmds $cmd_idx]
  563. # get the command details from redis
  564. if { [ catch {
  565. set cmd_info [lindex [r command info $cmd] 0]
  566. } err ] } {
  567. # if we failed, it means redis crashed after the previous command
  568. return $sent
  569. }
  570. # try to build a valid command argument
  571. set arity [lindex $cmd_info 1]
  572. set arity [expr $arity < 0 ? - $arity: $arity]
  573. set firstkey [lindex $cmd_info 3]
  574. set lastkey [lindex $cmd_info 4]
  575. set i 1
  576. if {$cmd == "XINFO"} {
  577. lappend cmd "STREAM"
  578. lappend cmd $key
  579. lappend cmd "FULL"
  580. incr i 3
  581. }
  582. if {$cmd == "XREAD"} {
  583. lappend cmd "STREAMS"
  584. lappend cmd $key
  585. randpath {
  586. lappend cmd \$
  587. } {
  588. lappend cmd [randomValue]
  589. }
  590. incr i 3
  591. }
  592. if {$cmd == "XADD"} {
  593. lappend cmd $key
  594. randpath {
  595. lappend cmd "*"
  596. } {
  597. lappend cmd [randomValue]
  598. }
  599. lappend cmd [randomValue]
  600. lappend cmd [randomValue]
  601. incr i 4
  602. }
  603. for {} {$i < $arity} {incr i} {
  604. if {$i == $firstkey || $i == $lastkey} {
  605. lappend cmd $key
  606. } else {
  607. lappend cmd [randomValue]
  608. }
  609. }
  610. # execute the command, we expect commands to fail on syntax errors
  611. lappend sent $cmd
  612. if { ! [ catch {
  613. r {*}$cmd
  614. } err ] } {
  615. incr succeeded
  616. } else {
  617. set err [format "%s" $err] ;# convert to string for pattern matching
  618. if {[string match "*SIGTERM*" $err]} {
  619. puts "command caused test to hang? $cmd"
  620. exit 1
  621. }
  622. }
  623. }
  624. # print stats so that we know if we managed to generate commands that actually made senes
  625. #if {$::verbose} {
  626. # set count [llength $sent]
  627. # puts "Fuzzy traffic sent: $count, succeeded: $succeeded"
  628. #}
  629. # return the list of commands we sent
  630. return $sent
  631. }
  632. # write line to server log file
  633. proc write_log_line {srv_idx msg} {
  634. set logfile [srv $srv_idx stdout]
  635. set fd [open $logfile "a+"]
  636. puts $fd "### $msg"
  637. close $fd
  638. }
  639. proc string2printable s {
  640. set res {}
  641. set has_special_chars false
  642. foreach i [split $s {}] {
  643. scan $i %c int
  644. # non printable characters, including space and excluding: " \ $ { }
  645. if {$int < 32 || $int > 122 || $int == 34 || $int == 36 || $int == 92} {
  646. set has_special_chars true
  647. }
  648. # TCL8.5 has issues mixing \x notation and normal chars in the same
  649. # source code string, so we'll convert the entire string.
  650. append res \\x[format %02X $int]
  651. }
  652. if {!$has_special_chars} {
  653. return $s
  654. }
  655. set res "\"$res\""
  656. return $res
  657. }
  658. # Calculation value of Chi-Square Distribution. By this value
  659. # we can verify the random distribution sample confidence.
  660. # Based on the following wiki:
  661. # https://en.wikipedia.org/wiki/Chi-square_distribution
  662. #
  663. # param res Random sample list
  664. # return Value of Chi-Square Distribution
  665. #
  666. # x2_value: return of chi_square_value function
  667. # df: Degrees of freedom, Number of independent values minus 1
  668. #
  669. # By using x2_value and df to back check the cardinality table,
  670. # we can know the confidence of the random sample.
  671. proc chi_square_value {res} {
  672. unset -nocomplain mydict
  673. foreach key $res {
  674. dict incr mydict $key 1
  675. }
  676. set x2_value 0
  677. set p [expr [llength $res] / [dict size $mydict]]
  678. foreach key [dict keys $mydict] {
  679. set value [dict get $mydict $key]
  680. # Aggregate the chi-square value of each element
  681. set v [expr {pow($value - $p, 2) / $p}]
  682. set x2_value [expr {$x2_value + $v}]
  683. }
  684. return $x2_value
  685. }
  686. #subscribe to Pub/Sub channels
  687. proc consume_subscribe_messages {client type channels} {
  688. set numsub -1
  689. set counts {}
  690. for {set i [llength $channels]} {$i > 0} {incr i -1} {
  691. set msg [$client read]
  692. assert_equal $type [lindex $msg 0]
  693. # when receiving subscribe messages the channels names
  694. # are ordered. when receiving unsubscribe messages
  695. # they are unordered
  696. set idx [lsearch -exact $channels [lindex $msg 1]]
  697. if {[string match "*unsubscribe" $type]} {
  698. assert {$idx >= 0}
  699. } else {
  700. assert {$idx == 0}
  701. }
  702. set channels [lreplace $channels $idx $idx]
  703. # aggregate the subscription count to return to the caller
  704. lappend counts [lindex $msg 2]
  705. }
  706. # we should have received messages for channels
  707. assert {[llength $channels] == 0}
  708. return $counts
  709. }
  710. proc subscribe {client channels} {
  711. $client subscribe {*}$channels
  712. consume_subscribe_messages $client subscribe $channels
  713. }
  714. proc unsubscribe {client {channels {}}} {
  715. $client unsubscribe {*}$channels
  716. consume_subscribe_messages $client unsubscribe $channels
  717. }
  718. proc psubscribe {client channels} {
  719. $client psubscribe {*}$channels
  720. consume_subscribe_messages $client psubscribe $channels
  721. }
  722. proc punsubscribe {client {channels {}}} {
  723. $client punsubscribe {*}$channels
  724. consume_subscribe_messages $client punsubscribe $channels
  725. }
  726. proc debug_digest_value {key} {
  727. if {!$::ignoredigest} {
  728. r debug digest-value $key
  729. } else {
  730. return "dummy-digest-value"
  731. }
  732. }
  733. proc wait_for_blocked_client {} {
  734. wait_for_condition 50 100 {
  735. [s blocked_clients] ne 0
  736. } else {
  737. fail "no blocked clients"
  738. }
  739. }
  740. proc wait_for_blocked_clients_count {count {maxtries 100} {delay 10}} {
  741. wait_for_condition $maxtries $delay {
  742. [s blocked_clients] == $count
  743. } else {
  744. fail "Timeout waiting for blocked clients"
  745. }
  746. }
  747. proc read_from_aof {fp} {
  748. # Input fp is a blocking binary file descriptor of an opened AOF file.
  749. if {[gets $fp count] == -1} return ""
  750. set count [string range $count 1 end]
  751. # Return a list of arguments for the command.
  752. set res {}
  753. for {set j 0} {$j < $count} {incr j} {
  754. read $fp 1
  755. set arg [::redis::redis_bulk_read $fp]
  756. if {$j == 0} {set arg [string tolower $arg]}
  757. lappend res $arg
  758. }
  759. return $res
  760. }
  761. proc assert_aof_content {aof_path patterns} {
  762. set fp [open $aof_path r]
  763. fconfigure $fp -translation binary
  764. fconfigure $fp -blocking 1
  765. for {set j 0} {$j < [llength $patterns]} {incr j} {
  766. assert_match [lindex $patterns $j] [read_from_aof $fp]
  767. }
  768. }
  769. proc config_set {param value {options {}}} {
  770. set mayfail 0
  771. foreach option $options {
  772. switch $option {
  773. "mayfail" {
  774. set mayfail 1
  775. }
  776. default {
  777. error "Unknown option $option"
  778. }
  779. }
  780. }
  781. if {[catch {r config set $param $value} err]} {
  782. if {!$mayfail} {
  783. error $err
  784. } else {
  785. if {$::verbose} {
  786. puts "Ignoring CONFIG SET $param $value failure: $err"
  787. }
  788. }
  789. }
  790. }
  791. proc delete_lines_with_pattern {filename tmpfilename pattern} {
  792. set fh_in [open $filename r]
  793. set fh_out [open $tmpfilename w]
  794. while {[gets $fh_in line] != -1} {
  795. if {![regexp $pattern $line]} {
  796. puts $fh_out $line
  797. }
  798. }
  799. close $fh_in
  800. close $fh_out
  801. file rename -force $tmpfilename $filename
  802. }