1proc 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 append output [format "%c" [expr {$minval+int(rand()*($maxval-$minval+1))}]] 16 incr len -1 17 } 18 return $output 19} 20 21# Useful for some test 22proc zlistAlikeSort {a b} { 23 if {[lindex $a 0] > [lindex $b 0]} {return 1} 24 if {[lindex $a 0] < [lindex $b 0]} {return -1} 25 string compare [lindex $a 1] [lindex $b 1] 26} 27 28# Return all log lines starting with the first line that contains a warning. 29# Generally, this will be an assertion error with a stack trace. 30proc warnings_from_file {filename} { 31 set lines [split [exec cat $filename] "\n"] 32 set matched 0 33 set logall 0 34 set result {} 35 foreach line $lines { 36 if {[string match {*REDIS BUG REPORT START*} $line]} { 37 set logall 1 38 } 39 if {[regexp {^\[\d+\]\s+\d+\s+\w+\s+\d{2}:\d{2}:\d{2} \#} $line]} { 40 set matched 1 41 } 42 if {$logall || $matched} { 43 lappend result $line 44 } 45 } 46 join $result "\n" 47} 48 49# Return value for INFO property 50proc status {r property} { 51 if {[regexp "\r\n$property:(.*?)\r\n" [{*}$r info] _ value]} { 52 set _ $value 53 } 54} 55 56proc waitForBgsave r { 57 while 1 { 58 if {[status r rdb_bgsave_in_progress] eq 1} { 59 if {$::verbose} { 60 puts -nonewline "\nWaiting for background save to finish... " 61 flush stdout 62 } 63 after 1000 64 } else { 65 break 66 } 67 } 68} 69 70proc waitForBgrewriteaof r { 71 while 1 { 72 if {[status r aof_rewrite_in_progress] eq 1} { 73 if {$::verbose} { 74 puts -nonewline "\nWaiting for background AOF rewrite to finish... " 75 flush stdout 76 } 77 after 1000 78 } else { 79 break 80 } 81 } 82} 83 84proc wait_for_sync r { 85 while 1 { 86 if {[status $r master_link_status] eq "down"} { 87 after 10 88 } else { 89 break 90 } 91 } 92} 93 94# Random integer between 0 and max (excluded). 95proc randomInt {max} { 96 expr {int(rand()*$max)} 97} 98 99# Random signed integer between -max and max (both extremes excluded). 100proc randomSignedInt {max} { 101 set i [randomInt $max] 102 if {rand() > 0.5} { 103 set i -$i 104 } 105 return $i 106} 107 108proc randpath args { 109 set path [expr {int(rand()*[llength $args])}] 110 uplevel 1 [lindex $args $path] 111} 112 113proc randomValue {} { 114 randpath { 115 # Small enough to likely collide 116 randomSignedInt 1000 117 } { 118 # 32 bit compressible signed/unsigned 119 randpath {randomSignedInt 2000000000} {randomSignedInt 4000000000} 120 } { 121 # 64 bit 122 randpath {randomSignedInt 1000000000000} 123 } { 124 # Random string 125 randpath {randstring 0 256 alpha} \ 126 {randstring 0 256 compr} \ 127 {randstring 0 256 binary} 128 } 129} 130 131proc randomKey {} { 132 randpath { 133 # Small enough to likely collide 134 randomInt 1000 135 } { 136 # 32 bit compressible signed/unsigned 137 randpath {randomInt 2000000000} {randomInt 4000000000} 138 } { 139 # 64 bit 140 randpath {randomInt 1000000000000} 141 } { 142 # Random string 143 randpath {randstring 1 256 alpha} \ 144 {randstring 1 256 compr} 145 } 146} 147 148proc findKeyWithType {r type} { 149 for {set j 0} {$j < 20} {incr j} { 150 set k [{*}$r randomkey] 151 if {$k eq {}} { 152 return {} 153 } 154 if {[{*}$r type $k] eq $type} { 155 return $k 156 } 157 } 158 return {} 159} 160 161proc createComplexDataset {r ops {opt {}}} { 162 for {set j 0} {$j < $ops} {incr j} { 163 set k [randomKey] 164 set k2 [randomKey] 165 set f [randomValue] 166 set v [randomValue] 167 168 if {[lsearch -exact $opt useexpire] != -1} { 169 if {rand() < 0.1} { 170 {*}$r expire [randomKey] [randomInt 2] 171 } 172 } 173 174 randpath { 175 set d [expr {rand()}] 176 } { 177 set d [expr {rand()}] 178 } { 179 set d [expr {rand()}] 180 } { 181 set d [expr {rand()}] 182 } { 183 set d [expr {rand()}] 184 } { 185 randpath {set d +inf} {set d -inf} 186 } 187 set t [{*}$r type $k] 188 189 if {$t eq {none}} { 190 randpath { 191 {*}$r set $k $v 192 } { 193 {*}$r lpush $k $v 194 } { 195 {*}$r sadd $k $v 196 } { 197 {*}$r zadd $k $d $v 198 } { 199 {*}$r hset $k $f $v 200 } { 201 {*}$r del $k 202 } 203 set t [{*}$r type $k] 204 } 205 206 switch $t { 207 {string} { 208 # Nothing to do 209 } 210 {list} { 211 randpath {{*}$r lpush $k $v} \ 212 {{*}$r rpush $k $v} \ 213 {{*}$r lrem $k 0 $v} \ 214 {{*}$r rpop $k} \ 215 {{*}$r lpop $k} 216 } 217 {set} { 218 randpath {{*}$r sadd $k $v} \ 219 {{*}$r srem $k $v} \ 220 { 221 set otherset [findKeyWithType {*}$r set] 222 if {$otherset ne {}} { 223 randpath { 224 {*}$r sunionstore $k2 $k $otherset 225 } { 226 {*}$r sinterstore $k2 $k $otherset 227 } { 228 {*}$r sdiffstore $k2 $k $otherset 229 } 230 } 231 } 232 } 233 {zset} { 234 randpath {{*}$r zadd $k $d $v} \ 235 {{*}$r zrem $k $v} \ 236 { 237 set otherzset [findKeyWithType {*}$r zset] 238 if {$otherzset ne {}} { 239 randpath { 240 {*}$r zunionstore $k2 2 $k $otherzset 241 } { 242 {*}$r zinterstore $k2 2 $k $otherzset 243 } 244 } 245 } 246 } 247 {hash} { 248 randpath {{*}$r hset $k $f $v} \ 249 {{*}$r hdel $k $f} 250 } 251 } 252 } 253} 254 255proc formatCommand {args} { 256 set cmd "*[llength $args]\r\n" 257 foreach a $args { 258 append cmd "$[string length $a]\r\n$a\r\n" 259 } 260 set _ $cmd 261} 262 263proc csvdump r { 264 set o {} 265 for {set db 0} {$db < 16} {incr db} { 266 {*}$r select $db 267 foreach k [lsort [{*}$r keys *]] { 268 set type [{*}$r type $k] 269 append o [csvstring $db] , [csvstring $k] , [csvstring $type] , 270 switch $type { 271 string { 272 append o [csvstring [{*}$r get $k]] "\n" 273 } 274 list { 275 foreach e [{*}$r lrange $k 0 -1] { 276 append o [csvstring $e] , 277 } 278 append o "\n" 279 } 280 set { 281 foreach e [lsort [{*}$r smembers $k]] { 282 append o [csvstring $e] , 283 } 284 append o "\n" 285 } 286 zset { 287 foreach e [{*}$r zrange $k 0 -1 withscores] { 288 append o [csvstring $e] , 289 } 290 append o "\n" 291 } 292 hash { 293 set fields [{*}$r hgetall $k] 294 set newfields {} 295 foreach {k v} $fields { 296 lappend newfields [list $k $v] 297 } 298 set fields [lsort -index 0 $newfields] 299 foreach kv $fields { 300 append o [csvstring [lindex $kv 0]] , 301 append o [csvstring [lindex $kv 1]] , 302 } 303 append o "\n" 304 } 305 } 306 } 307 } 308 {*}$r select 9 309 return $o 310} 311 312proc csvstring s { 313 return "\"$s\"" 314} 315 316proc roundFloat f { 317 format "%.10g" $f 318} 319 320proc find_available_port start { 321 for {set j $start} {$j < $start+1024} {incr j} { 322 if {[catch {set fd1 [socket 127.0.0.1 $j]}] && 323 [catch {set fd2 [socket 127.0.0.1 [expr $j+10000]]}]} { 324 return $j 325 } else { 326 catch { 327 close $fd1 328 close $fd2 329 } 330 } 331 } 332 if {$j == $start+1024} { 333 error "Can't find a non busy port in the $start-[expr {$start+1023}] range." 334 } 335} 336 337# Test if TERM looks like to support colors 338proc color_term {} { 339 expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]} 340} 341 342proc colorstr {color str} { 343 if {[color_term]} { 344 set b 0 345 if {[string range $color 0 4] eq {bold-}} { 346 set b 1 347 set color [string range $color 5 end] 348 } 349 switch $color { 350 red {set colorcode {31}} 351 green {set colorcode {32}} 352 yellow {set colorcode {33}} 353 blue {set colorcode {34}} 354 magenta {set colorcode {35}} 355 cyan {set colorcode {36}} 356 white {set colorcode {37}} 357 default {set colorcode {37}} 358 } 359 if {$colorcode ne {}} { 360 return "\033\[$b;${colorcode};49m$str\033\[0m" 361 } 362 } else { 363 return $str 364 } 365} 366 367# Execute a background process writing random data for the specified number 368# of seconds to the specified Redis instance. 369proc start_write_load {host port seconds} { 370 set tclsh [info nameofexecutable] 371 exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds & 372} 373 374# Stop a process generating write load executed with start_write_load. 375proc stop_write_load {handle} { 376 catch {exec /bin/kill -9 $handle} 377} 378