util.tcl 26 KB

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