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 foreach k [lsort [{*}$r keys *]] { 266 set type [{*}$r type $k] 267 append o [csvstring $k] , [csvstring $type] , 268 switch $type { 269 string { 270 append o [csvstring [{*}$r get $k]] "\n" 271 } 272 list { 273 foreach e [{*}$r lrange $k 0 -1] { 274 append o [csvstring $e] , 275 } 276 append o "\n" 277 } 278 set { 279 foreach e [lsort [{*}$r smembers $k]] { 280 append o [csvstring $e] , 281 } 282 append o "\n" 283 } 284 zset { 285 foreach e [{*}$r zrange $k 0 -1 withscores] { 286 append o [csvstring $e] , 287 } 288 append o "\n" 289 } 290 hash { 291 set fields [{*}$r hgetall $k] 292 set newfields {} 293 foreach {k v} $fields { 294 lappend newfields [list $k $v] 295 } 296 set fields [lsort -index 0 $newfields] 297 foreach kv $fields { 298 append o [csvstring [lindex $kv 0]] , 299 append o [csvstring [lindex $kv 1]] , 300 } 301 append o "\n" 302 } 303 } 304 } 305 return $o 306} 307 308proc csvstring s { 309 return "\"$s\"" 310} 311 312proc roundFloat f { 313 format "%.10g" $f 314} 315 316proc find_available_port start { 317 for {set j $start} {$j < $start+1024} {incr j} { 318 if {[catch {set fd1 [socket 127.0.0.1 $j]}] && 319 [catch {set fd2 [socket 127.0.0.1 [expr $j+10000]]}]} { 320 return $j 321 } else { 322 catch { 323 close $fd1 324 close $fd2 325 } 326 } 327 } 328 if {$j == $start+1024} { 329 error "Can't find a non busy port in the $start-[expr {$start+1023}] range." 330 } 331} 332 333# Test if TERM looks like to support colors 334proc color_term {} { 335 expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]} 336} 337 338proc colorstr {color str} { 339 if {[color_term]} { 340 set b 0 341 if {[string range $color 0 4] eq {bold-}} { 342 set b 1 343 set color [string range $color 5 end] 344 } 345 switch $color { 346 red {set colorcode {31}} 347 green {set colorcode {32}} 348 yellow {set colorcode {33}} 349 blue {set colorcode {34}} 350 magenta {set colorcode {35}} 351 cyan {set colorcode {36}} 352 white {set colorcode {37}} 353 default {set colorcode {37}} 354 } 355 if {$colorcode ne {}} { 356 return "\033\[$b;${colorcode};40m$str\033\[0m" 357 } 358 } else { 359 return $str 360 } 361} 362 363# Execute a background process writing random data for the specified number 364# of seconds to the specified Redis instance. 365proc start_write_load {host port seconds} { 366 set tclsh [info nameofexecutable] 367 exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds & 368} 369 370# Stop a process generating write load executed with start_write_load. 371proc stop_write_load {handle} { 372 catch {exec /bin/kill -9 $handle} 373} 374