2014-02-17 20:37:56 +04:00
# Sentinel test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
# This softare is released under the BSD License. See the COPYING file for
# more information.
package require Tcl 8.5
set tcl_precision 17
source tests/ support/ redis.tcl
source tests/ support/ util.tcl
source tests/ support/ server.tcl
source tests/ support/ test.tcl
set : : verbose 0
2014-02-23 20:57:53 +04:00
set : : pause_on_error 0
2014-02-17 20:37:56 +04:00
set : : sentinel_instances { }
set : : redis_instances { }
set : : sentinel_base_port 20000
set : : redis_base_port 30000
set : : instances_count 5 ; # How many Sentinels / Instances we use at max
set : : pids { } ; # We kill everything at exit
set : : dirs { } ; # We remove all the temp dirs at exit
2014-02-20 19:28:38 +04:00
set : : run_matching { } ; # If non empty, only tests matching pattern are run.
2014-02-17 20:37:56 +04:00
if { [ catch { cd tests/ sentinel-tmp} ] } {
puts " t e s t s / s e n t i n e l - t m p d i r e c t o r y n o t f o u n d . "
puts " P l e a s e r u n t h i s t e s t f r o m t h e R e d i s s o u r c e r o o t . "
exit 1
}
# Spawn a redis or sentinel instance, depending on 'type'.
proc spawn_instance { type base_port count} {
for { set j 0 } { $j < $count } { incr j} {
set port [ find_available_port $base_port ]
incr base_port
puts " S t a r t i n g $ t y p e # $ j a t p o r t $ p o r t "
# Create a directory for this Sentinel.
set dirname " $ { t y p e } _ $ { j } "
lappend : : dirs $dirname
catch { exec rm - rf $dirname }
file mkdir $dirname
# Write the Sentinel config file.
set cfgfile [ file join $dirname $type.conf ]
set cfg [ open $cfgfile w]
puts $cfg " p o r t $ p o r t "
puts $cfg " d i r . / $ d i r n a m e "
puts $cfg " l o g f i l e l o g . t x t "
close $cfg
# Finally exec it and remember the pid for later cleanup.
2014-02-18 14:38:49 +04:00
if { $type eq " r e d i s " } {
set prgname redis-server
} else {
set prgname redis-sentinel
}
2014-02-22 20:26:30 +04:00
set pid [ exec ../ ../ src/ $ { prgname } $cfgfile & ]
lappend : : pids $pid
2014-02-17 20:37:56 +04:00
# Check availability
if { [ server_is_up 127.0 .0.1 $port 100 ] == 0 } {
abort_sentinel_test " P r o b l e m s s t a r t i n g $ t y p e # $ j : p i n g t i m e o u t "
}
# Push the instance into the right list
2014-02-18 14:04:01 +04:00
lappend : : $ { type } _instances [ list \
2014-02-22 20:26:30 +04:00
pid $pid \
2014-02-17 20:37:56 +04:00
host 127.0 .0.1 \
port $port \
2014-02-18 14:04:01 +04:00
link [ redis 127.0 .0.1 $port ] \
2014-02-17 20:37:56 +04:00
]
}
}
proc cleanup { } {
puts " C l e a n i n g u p . . . "
foreach pid $::pids {
catch { exec kill - 9 $pid }
}
foreach dir $::dirs {
catch { exec rm - rf $dir }
}
}
proc abort_sentinel_test msg {
puts " W A R N I N G : A b o r t i n g t h e t e s t . "
puts " > > > > > > > > $ m s g "
cleanup
exit 1
}
2014-02-20 19:28:38 +04:00
proc parse_options { } {
for { set j 0 } { $j < [ llength $::argv ] } { incr j} {
set opt [ lindex $::argv $j ]
set val [ lindex $::argv [ expr $j + 1 ] ]
if { $opt eq " - - s i n g l e " } {
incr j
set : : run_matching " * $ { v a l } * "
2014-02-23 20:57:53 +04:00
} elseif { $opt eq " - - p a u s e - o n - e r r o r " } {
set : : pause_on_error 1
2014-02-20 19:28:38 +04:00
} elseif { $opt eq " - - h e l p " } {
puts " H e l l o , I ' m s e n t i n e l . t c l a n d I r u n S e n t i n e l u n i t t e s t s . "
puts " \n O p t i o n s : "
puts " - - s i n g l e < p a t t e r n > O n l y r u n s t e s t s s p e c i f i e d b y p a t t e r n . "
2014-02-23 20:57:53 +04:00
puts " - - p a u s e - o n - e r r o r P a u s e f o r m a n u a l i n s p e c t i o n o n e r r o r . "
2014-02-20 19:28:38 +04:00
puts " - - h e l p S h o w s t h i s h e l p . "
exit 0
} else {
puts " U n k n o w n o p t i o n $ o p t "
exit 1
}
}
}
2014-02-17 20:37:56 +04:00
proc main { } {
2014-02-20 19:28:38 +04:00
parse_options
2014-02-17 20:37:56 +04:00
spawn_instance sentinel $::sentinel_base_port $::instances_count
spawn_instance redis $::redis_base_port $::instances_count
run_tests
cleanup
}
2014-02-23 20:57:53 +04:00
# If --pause-on-error option was passed at startup this function is called
# on error in order to give the developer a chance to understand more about
# the error condition while the instances are still running.
proc pause_on_error { } {
2014-02-23 21:01:30 +04:00
puts " "
2014-02-23 20:57:53 +04:00
puts [ colorstr yellow " * * * P l e a s e i n s p e c t t h e e r r o r n o w * * * " ]
2014-02-23 21:01:30 +04:00
puts " \n T y p e \" c o n t i n u e \" t o r e s u m e t h e t e s t . \n "
while 1 {
puts - nonewline " > "
2014-02-23 20:57:53 +04:00
flush stdout
2014-02-23 21:01:30 +04:00
if { [ gets stdin] eq { continue } } break
2014-02-23 20:57:53 +04:00
}
}
2014-02-17 20:37:56 +04:00
# We redefine 'test' as for Sentinel we don't use the server-client
# architecture for the test, everything is sequential.
proc test { descr code} {
puts - nonewline " > $ d e s c r : "
flush stdout
if { [ catch { set retval [ uplevel 1 $code ] } error] } {
if { [ string match " a s s e r t i o n : * " $error ] } {
set msg [ string range $error 10 end]
puts [ colorstr red $msg ]
2014-02-23 20:57:53 +04:00
if { $::pause_on_error } pause_on_error
2014-02-17 20:37:56 +04:00
} else {
# Re-raise, let handler up the stack take care of this.
error $error $::errorInfo
}
} else {
puts [ colorstr green OK]
}
}
proc run_tests { } {
set tests [ lsort [ glob ../ sentinel-tests/ * ] ]
foreach test $tests {
2014-02-20 19:28:38 +04:00
if { $::run_matching ne { } && [ string match $::run_matching $test ] == 0 } {
continue
}
2014-02-20 19:57:51 +04:00
if { [ file isdirectory $test ] } continue
2014-02-18 19:31:52 +04:00
puts [ colorstr yellow " T e s t i n g u n i t : [ l i n d e x [ f i l e s p l i t $ t e s t ] e n d ] " ]
2014-02-17 20:37:56 +04:00
source $test
}
}
2014-02-18 14:04:01 +04:00
# The "S" command is used to interact with the N-th Sentinel.
# The general form is:
#
# S <sentinel-id> command arg arg arg ...
#
# Example to ping the Sentinel 0 (first instance): S 0 PING
proc S { n args} {
set s [ lindex $::sentinel_instances $n ]
[ dict get $s link] { * } $args
}
# Like R but to chat with Redis instances.
proc R { n args} {
set r [ lindex $::redis_instances $n ]
[ dict get $r link] { * } $args
}
2014-02-18 14:38:49 +04:00
proc get_info_field { info field} {
set fl [ string length $field ]
append field :
foreach line [ split $info " \n " ] {
set line [ string trim $line " \r \n " ]
if { [ string range $line 0 $fl ] eq $field } {
return [ string range $line [ expr { $fl + 1 } ] end]
}
}
return { }
}
proc SI { n field} {
get_info_field [ S $n info] $field
}
proc RI { n field} {
get_info_field [ R $n info] $field
}
2014-02-18 14:04:01 +04:00
# Iterate over IDs of sentinel or redis instances.
2014-02-18 19:31:52 +04:00
proc foreach_instance_id { instances idvar code} {
2014-02-18 14:04:01 +04:00
upvar 1 $idvar id
2014-02-18 19:31:52 +04:00
for { set id 0 } { $id < [ llength $instances ] } { incr id} {
set errcode [ catch { uplevel 1 $code } result]
if { $errcode == 1 } {
error $result $::errorInfo $::errorCode
} elseif { $errcode != 0 } {
return - code $errcode $result
}
2014-02-18 14:04:01 +04:00
}
}
2014-02-18 19:31:52 +04:00
proc foreach_sentinel_id { idvar code} {
set errcode [ catch { uplevel 1 [ list foreach_instance_id $::sentinel_instances $idvar $code ] } result]
return - code $errcode $result
}
2014-02-18 14:04:01 +04:00
proc foreach_redis_id { idvar code} {
2014-02-18 19:31:52 +04:00
set errcode [ catch { uplevel 1 [ list foreach_instance_id $::redis_instances $idvar $code ] } result]
return - code $errcode $result
2014-02-18 14:04:01 +04:00
}
2014-02-18 14:38:49 +04:00
# Get the specific attribute of the specified instance type, id.
proc get_instance_attrib { type id attrib} {
dict get [ lindex [ set : : $ { type } _instances] $id ] $attrib
}
2014-02-22 20:26:30 +04:00
# Set the specific attribute of the specified instance type, id.
proc set_instance_attrib { type id attrib newval} {
set d [ lindex [ set : : $ { type } _instances] $id ]
dict set d $attrib $newval
lset : : $ { type } _instances $id $d
}
2014-02-18 14:38:49 +04:00
# Create a master-slave cluster of the given number of total instances.
# The first instance "0" is the master, all others are configured as
# slaves.
proc create_redis_master_slave_cluster n {
foreach_redis_id id {
if { $id == 0 } {
# Our master.
R $id slaveof no one
2014-02-22 20:26:30 +04:00
R $id flushall
2014-02-18 14:38:49 +04:00
} elseif { $id < $n } {
R $id slaveof [ get_instance_attrib redis 0 host] \
[ get_instance_attrib redis 0 port]
} else {
# Instances not part of the cluster.
R $id slaveof no one
}
}
# Wait for all the slaves to sync.
wait_for_condition 100 50 {
[ RI 0 connected_slaves] == ( $n-1 )
} else {
fail " U n a b l e t o c r e a t e a m a s t e r - s l a v e s c l u s t e r . "
}
}
2014-02-18 19:31:52 +04:00
proc get_instance_id_by_port { type port} {
foreach_ $ { type } _id id {
if { [ get_instance_attrib $type $id port] == $port } {
return $id
}
}
fail " I n s t a n c e $ t y p e p o r t $ p o r t n o t f o u n d . "
}
2014-02-22 20:26:30 +04:00
# Kill an instance of the specified type/id with SIGKILL.
# This function will mark the instance PID as -1 to remember that this instance
# is no longer running and will remove its PID from the list of pids that
# we kill at cleanup.
#
# The instance can be restarted with restart-instance.
proc kill_instance { type id} {
set pid [ get_instance_attrib $type $id pid]
exec kill - 9 $pid
set_instance_attrib $type $id pid - 1
set_instance_attrib $type $id link you_tried_to_talk_with_killed_instance
# Remove the PID from the list of pids to kill at exit.
set : : pids [ lsearch - all - inline - not - exact $::pids $pid ]
}
# Restart an instance previously killed by kill_instance
proc restart_instance { type id} {
set dirname " $ { t y p e } _ $ { i d } "
set cfgfile [ file join $dirname $type.conf ]
set port [ get_instance_attrib $type $id port]
# Execute the instance with its old setup and append the new pid
# file for cleanup.
if { $type eq " r e d i s " } {
set prgname redis-server
} else {
set prgname redis-sentinel
}
set pid [ exec ../ ../ src/ $ { prgname } $cfgfile & ]
lappend : : pids $pid
# Check that the instance is running
if { [ server_is_up 127.0 .0.1 $port 100 ] == 0 } {
abort_sentinel_test " P r o b l e m s s t a r t i n g $ t y p e # $ j : p i n g t i m e o u t "
}
# Connect with it with a fresh link
set_instance_attrib $type $id link [ redis 127.0 .0.1 $port ]
}
2014-02-17 20:37:56 +04:00
if { [ catch main e] } {
puts $::errorInfo
cleanup
}