Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
2968d8e3
Unverified
Commit
2968d8e3
authored
Dec 12, 2019
by
Salvatore Sanfilippo
Committed by
GitHub
Dec 12, 2019
Browse files
Merge branch 'unstable' into ModuleSecurity
parents
ff682d79
c6fb9d09
Changes
170
Show whitespace changes
Inline
Side-by-side
tests/support/cluster.tcl
View file @
2968d8e3
...
@@ -62,7 +62,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
...
@@ -62,7 +62,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
lassign
[
split $ip_port :
]
start_host start_port
lassign
[
split $ip_port :
]
start_host start_port
if
{[
catch
{
if
{[
catch
{
set r
{}
set r
{}
set r
[
redis $start_host $start_port
]
set r
[
redis $start_host $start_port
0 $::tls
]
set nodes_descr
[
$r
cluster nodes
]
set nodes_descr
[
$r
cluster nodes
]
$r close
$r close
}
e
]}
{
}
e
]}
{
...
@@ -107,7 +107,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
...
@@ -107,7 +107,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
# Connect to the node
# Connect to the node
set link
{}
set link
{}
catch
{
set link
[
redis $host $port
]}
catch
{
set link
[
redis $host $port
0 $::tls
]}
# Build this node description as an hash.
# Build this node description as an hash.
set node
[
dict create
\
set node
[
dict create
\
...
...
tests/support/redis.tcl
View file @
2968d8e3
...
@@ -39,8 +39,18 @@ array set ::redis::callback {}
...
@@ -39,8 +39,18 @@ array set ::redis::callback {}
array set ::redis::state
{}
;
# State in non-blocking reply reading
array set ::redis::state
{}
;
# State in non-blocking reply reading
array set ::redis::statestack
{}
;
# Stack of states, for nested mbulks
array set ::redis::statestack
{}
;
# Stack of states, for nested mbulks
proc redis
{{
server 127.0.0.1
}
{
port 6379
}
{
defer 0
}}
{
proc redis
{{
server 127.0.0.1
}
{
port 6379
}
{
defer 0
}
{
tls 0
}
{
tlsoptions
{}}}
{
if
{
$tls
}
{
package require tls
::tls::init
\
-cafile
"
$::tlsdir
/ca.crt"
\
-certfile
"
$::tlsdir
/redis.crt"
\
-keyfile
"
$::tlsdir
/redis.key"
\
{*}
$tlsoptions
set fd
[
::tls::socket $server $port
]
}
else
{
set fd
[
socket $server $port
]
set fd
[
socket $server $port
]
}
fconfigure $fd -translation binary
fconfigure $fd -translation binary
set id
[
incr ::redis::id
]
set id
[
incr ::redis::id
]
set ::redis::fd
(
$id
)
$fd
set ::redis::fd
(
$id
)
$fd
...
@@ -48,6 +58,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
...
@@ -48,6 +58,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
set ::redis::blocking
(
$id
)
1
set ::redis::blocking
(
$id
)
1
set ::redis::deferred
(
$id
)
$defer
set ::redis::deferred
(
$id
)
$defer
set ::redis::reconnect
(
$id
)
0
set ::redis::reconnect
(
$id
)
0
set ::redis::tls $tls
::redis::redis_reset_state $id
::redis::redis_reset_state $id
interp alias
{}
::redis::redisHandle$id
{}
::redis::__dispatch__ $id
interp alias
{}
::redis::redisHandle$id
{}
::redis::__dispatch__ $id
}
}
...
@@ -72,7 +83,11 @@ proc ::redis::__dispatch__raw__ {id method argv} {
...
@@ -72,7 +83,11 @@ proc ::redis::__dispatch__raw__ {id method argv} {
# Reconnect the link if needed.
# Reconnect the link if needed.
if
{
$fd
eq
{}}
{
if
{
$fd
eq
{}}
{
lassign $::redis::addr
(
$id
)
host port
lassign $::redis::addr
(
$id
)
host port
if
{
$::redis::tls
}
{
set ::redis::fd
(
$id
)
[
::tls::socket $host $port
]
}
else
{
set ::redis::fd
(
$id
)
[
socket $host $port
]
set ::redis::fd
(
$id
)
[
socket $host $port
]
}
fconfigure $::redis::fd
(
$id
)
-translation binary
fconfigure $::redis::fd
(
$id
)
-translation binary
set fd $::redis::fd
(
$id
)
set fd $::redis::fd
(
$id
)
}
}
...
...
tests/support/server.tcl
View file @
2968d8e3
...
@@ -92,7 +92,11 @@ proc is_alive config {
...
@@ -92,7 +92,11 @@ proc is_alive config {
proc ping_server
{
host port
}
{
proc ping_server
{
host port
}
{
set retval 0
set retval 0
if
{[
catch
{
if
{[
catch
{
if
{
$::tls
}
{
set fd
[
::tls::socket $host $port
]
}
else
{
set fd
[
socket $host $port
]
set fd
[
socket $host $port
]
}
fconfigure $fd -translation binary
fconfigure $fd -translation binary
puts $fd
"PING
\r\n
"
puts $fd
"PING
\r\n
"
flush $fd
flush $fd
...
@@ -136,7 +140,6 @@ proc tags {tags code} {
...
@@ -136,7 +140,6 @@ proc tags {tags code} {
uplevel 1 $code
uplevel 1 $code
set ::tags
[
lrange $::tags 0 end-
[
llength $tags
]]
set ::tags
[
lrange $::tags 0 end-
[
llength $tags
]]
}
}
proc start_server
{
options
{
code undefined
}}
{
proc start_server
{
options
{
code undefined
}}
{
# If we are running against an external server, we just push the
# If we are running against an external server, we just push the
# host/port pair in the stack the first time
# host/port pair in the stack the first time
...
@@ -145,7 +148,7 @@ proc start_server {options {code undefined}} {
...
@@ -145,7 +148,7 @@ proc start_server {options {code undefined}} {
set srv
{}
set srv
{}
dict set srv
"host"
$::host
dict set srv
"host"
$::host
dict set srv
"port"
$::port
dict set srv
"port"
$::port
set client
[
redis $::host $::port
]
set client
[
redis $::host $::port
0 $::tls
]
dict set srv
"client"
$client
dict set srv
"client"
$client
$client select 9
$client select 9
...
@@ -178,6 +181,13 @@ proc start_server {options {code undefined}} {
...
@@ -178,6 +181,13 @@ proc start_server {options {code undefined}} {
set data
[
split
[
exec cat
"tests/assets/
$baseconfig
"
]
"
\n
"
]
set data
[
split
[
exec cat
"tests/assets/
$baseconfig
"
]
"
\n
"
]
set config
{}
set config
{}
if
{
$::tls
}
{
dict set config
"tls-cert-file"
[
format
"%s/tests/tls/redis.crt"
[
pwd
]]
dict set config
"tls-key-file"
[
format
"%s/tests/tls/redis.key"
[
pwd
]]
dict set config
"tls-dh-params-file"
[
format
"%s/tests/tls/redis.dh"
[
pwd
]]
dict set config
"tls-ca-cert-file"
[
format
"%s/tests/tls/ca.crt"
[
pwd
]]
dict set config
"loglevel"
"debug"
}
foreach line $data
{
foreach line $data
{
if
{[
string length $line
]
> 0 &&
[
string index $line 0
]
ne
"#"
}
{
if
{[
string length $line
]
> 0 &&
[
string index $line 0
]
ne
"#"
}
{
set elements
[
split $line
" "
]
set elements
[
split $line
" "
]
...
@@ -192,7 +202,17 @@ proc start_server {options {code undefined}} {
...
@@ -192,7 +202,17 @@ proc start_server {options {code undefined}} {
# start every server on a different port
# start every server on a different port
set ::port
[
find_available_port
[
expr
{
$::port
+1
}]]
set ::port
[
find_available_port
[
expr
{
$::port
+1
}]]
if
{
$::tls
}
{
dict set config
"port"
0
dict set config
"tls-port"
$::port
dict set config
"tls-cluster"
"yes"
dict set config
"tls-replication"
"yes"
}
else
{
dict set config port $::port
dict set config port $::port
}
set unixsocket
[
file normalize
[
format
"%s/%s"
[
dict get $config
"dir"
]
"socket"
]]
dict set config
"unixsocket"
$unixsocket
# apply overrides from global space and arguments
# apply overrides from global space and arguments
foreach
{
directive arguments
}
[
concat $::global_overrides $overrides
]
{
foreach
{
directive arguments
}
[
concat $::global_overrides $overrides
]
{
...
@@ -254,10 +274,11 @@ proc start_server {options {code undefined}} {
...
@@ -254,10 +274,11 @@ proc start_server {options {code undefined}} {
}
}
# setup properties to be able to initialize a client object
# setup properties to be able to initialize a client object
set port_param
[
expr $::tls ?
{
"tls-port"
}
:
{
"port"
}]
set host $::host
set host $::host
set port $::port
set port $::port
if
{[
dict exists $config bind
]}
{
set host
[
dict get $config bind
]
}
if
{[
dict exists $config bind
]}
{
set host
[
dict get $config bind
]
}
if
{[
dict exists $config port
]}
{
set port
[
dict get $config port
]
}
if
{[
dict exists $config
$
port
_param
]}
{
set port
[
dict get $config
$
port
_param
]
}
# setup config dict
# setup config dict
dict set srv
"config_file"
$config_file
dict set srv
"config_file"
$config_file
...
@@ -267,6 +288,7 @@ proc start_server {options {code undefined}} {
...
@@ -267,6 +288,7 @@ proc start_server {options {code undefined}} {
dict set srv
"port"
$port
dict set srv
"port"
$port
dict set srv
"stdout"
$stdout
dict set srv
"stdout"
$stdout
dict set srv
"stderr"
$stderr
dict set srv
"stderr"
$stderr
dict set srv
"unixsocket"
$unixsocket
# if a block of code is supplied, we wait for the server to become
# if a block of code is supplied, we wait for the server to become
# available, create a client object and kill the server afterwards
# available, create a client object and kill the server afterwards
...
...
tests/support/test.tcl
View file @
2968d8e3
...
@@ -11,22 +11,55 @@ proc fail {msg} {
...
@@ -11,22 +11,55 @@ proc fail {msg} {
proc assert
{
condition
}
{
proc assert
{
condition
}
{
if
{
!
[
uplevel 1
[
list expr $condition
]]}
{
if
{
!
[
uplevel 1
[
list expr $condition
]]}
{
error
"assertion:Expected condition '
$condition
' to be true (
[
uplevel 1
[
list subst -nocommands $condition
]]
)"
set context
"(context:
[
info frame -1
]
)"
error
"assertion:Expected
[
uplevel 1
[
list subst -nocommands $condition
]]
$context
"
}
}
proc assert_no_match
{
pattern value
}
{
if
{[
string match $pattern $value
]}
{
set context
"(context:
[
info frame -1
]
)"
error
"assertion:Expected '
$value
' to not match '
$pattern
'
$context
"
}
}
}
}
proc assert_match
{
pattern value
}
{
proc assert_match
{
pattern value
}
{
if
{
!
[
string match $pattern $value
]}
{
if
{
!
[
string match $pattern $value
]}
{
error
"assertion:Expected '
$value
' to match '
$pattern
'"
set context
"(context:
[
info frame -1
]
)"
error
"assertion:Expected '
$value
' to match '
$pattern
'
$context
"
}
}
}
}
proc assert_equal
{
expected
value
{
detail
""
}}
{
proc assert_equal
{
value
expected
{
detail
""
}}
{
if
{
$expected
ne $value
}
{
if
{
$expected
ne $value
}
{
if
{
$detail
ne
""
}
{
if
{
$detail
ne
""
}
{
set detail
" (detail:
$detail
)"
set detail
"(detail:
$detail
)"
}
else
{
set detail
"(context:
[
info frame -1
]
)"
}
error
"assertion:Expected '
$value
' to be equal to '
$expected
'
$detail
"
}
}
proc assert_lessthan
{
value expected
{
detail
""
}}
{
if
{
!
(
$value
< $expected
)}
{
if
{
$detail
ne
""
}
{
set detail
"(detail:
$detail
)"
}
else
{
set detail
"(context:
[
info frame -1
]
)"
}
error
"assertion:Expected '
$value
' to be lessthan to '
$expected
'
$detail
"
}
}
proc assert_range
{
value min max
{
detail
""
}}
{
if
{
!
(
$value
<= $max && $value >= $min
)}
{
if
{
$detail
ne
""
}
{
set detail
"(detail:
$detail
)"
}
else
{
set detail
"(context:
[
info frame -1
]
)"
}
}
error
"assertion:Expected '
$value
' to be
equal to '
$expected
'
$detail
"
error
"assertion:Expected '
$value
' to be
between to '
$min
' and '
$max
'
$detail
"
}
}
}
}
...
...
tests/support/util.tcl
View file @
2968d8e3
...
@@ -395,7 +395,7 @@ proc colorstr {color str} {
...
@@ -395,7 +395,7 @@ proc colorstr {color str} {
# of seconds to the specified Redis instance.
# of seconds to the specified Redis instance.
proc start_write_load
{
host port seconds
}
{
proc start_write_load
{
host port seconds
}
{
set tclsh
[
info nameofexecutable
]
set tclsh
[
info nameofexecutable
]
exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds &
exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds
$::tls
&
}
}
# Stop a process generating write load executed with start_write_load.
# Stop a process generating write load executed with start_write_load.
...
@@ -423,7 +423,7 @@ proc lshuffle {list} {
...
@@ -423,7 +423,7 @@ proc lshuffle {list} {
# of ops to the specified Redis instance.
# of ops to the specified Redis instance.
proc start_bg_complex_data
{
host port db ops
}
{
proc start_bg_complex_data
{
host port db ops
}
{
set tclsh
[
info nameofexecutable
]
set tclsh
[
info nameofexecutable
]
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops
$::tls
&
}
}
# Stop a process generating write load executed with start_bg_complex_data.
# Stop a process generating write load executed with start_bg_complex_data.
...
...
tests/test_helper.tcl
View file @
2968d8e3
...
@@ -63,6 +63,7 @@ set ::all_tests {
...
@@ -63,6 +63,7 @@ set ::all_tests {
unit/lazyfree
unit/lazyfree
unit/wait
unit/wait
unit/pendingquerybuf
unit/pendingquerybuf
unit/tls
}
}
# Index to the next test to run in the ::all_tests list.
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
set ::next_test 0
...
@@ -71,6 +72,7 @@ set ::host 127.0.0.1
...
@@ -71,6 +72,7 @@ set ::host 127.0.0.1
set ::port 21111
set ::port 21111
set ::traceleaks 0
set ::traceleaks 0
set ::valgrind 0
set ::valgrind 0
set ::tls 0
set ::stack_logging 0
set ::stack_logging 0
set ::verbose 0
set ::verbose 0
set ::quiet 0
set ::quiet 0
...
@@ -92,6 +94,7 @@ set ::dont_clean 0
...
@@ -92,6 +94,7 @@ set ::dont_clean 0
set ::wait_server 0
set ::wait_server 0
set ::stop_on_failure 0
set ::stop_on_failure 0
set ::loop 0
set ::loop 0
set ::tlsdir
"tests/tls"
# Set to 1 when we are running in client mode. The Redis test uses a
# Set to 1 when we are running in client mode. The Redis test uses a
# server-client model to run tests simultaneously. The server instance
# server-client model to run tests simultaneously. The server instance
...
@@ -146,7 +149,7 @@ proc reconnect {args} {
...
@@ -146,7 +149,7 @@ proc reconnect {args} {
set host
[
dict get $srv
"host"
]
set host
[
dict get $srv
"host"
]
set port
[
dict get $srv
"port"
]
set port
[
dict get $srv
"port"
]
set config
[
dict get $srv
"config"
]
set config
[
dict get $srv
"config"
]
set client
[
redis $host $port
]
set client
[
redis $host $port
0 $::tls
]
dict set srv
"client"
$client
dict set srv
"client"
$client
# select the right db when we don't have to authenticate
# select the right db when we don't have to authenticate
...
@@ -166,7 +169,7 @@ proc redis_deferring_client {args} {
...
@@ -166,7 +169,7 @@ proc redis_deferring_client {args} {
}
}
# create client that defers reading reply
# create client that defers reading reply
set client
[
redis
[
srv $level
"host"
]
[
srv $level
"port"
]
1
]
set client
[
redis
[
srv $level
"host"
]
[
srv $level
"port"
]
1
$::tls
]
# select the right db and read the response
(
OK
)
# select the right db and read the response
(
OK
)
$client select 9
$client select 9
...
@@ -450,6 +453,7 @@ proc print_help_screen {} {
...
@@ -450,6 +453,7 @@ proc print_help_screen {} {
"--stop Blocks once the first test fails."
"--stop Blocks once the first test fails."
"--loop Execute the specified set of tests forever."
"--loop Execute the specified set of tests forever."
"--wait-server Wait after server is started (so that you can attach a debugger)."
"--wait-server Wait after server is started (so that you can attach a debugger)."
"--tls Run tests in TLS mode."
"--help Print this help screen."
"--help Print this help screen."
}
"
\n
"
]
}
"
\n
"
]
}
}
...
@@ -486,6 +490,13 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
...
@@ -486,6 +490,13 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
}
}
}
elseif
{
$opt
eq
{
--quiet
}}
{
}
elseif
{
$opt
eq
{
--quiet
}}
{
set ::quiet 1
set ::quiet 1
}
elseif
{
$opt
eq
{
--tls
}}
{
package require tls 1.6
set ::tls 1
::tls::init
\
-cafile
"
$::tlsdir
/ca.crt"
\
-certfile
"
$::tlsdir
/redis.crt"
\
-keyfile
"
$::tlsdir
/redis.key"
}
elseif
{
$opt
eq
{
--host
}}
{
}
elseif
{
$opt
eq
{
--host
}}
{
set ::external 1
set ::external 1
set ::host $arg
set ::host $arg
...
@@ -565,7 +576,11 @@ if {[llength $::single_tests] > 0} {
...
@@ -565,7 +576,11 @@ if {[llength $::single_tests] > 0} {
}
}
proc attach_to_replication_stream
{}
{
proc attach_to_replication_stream
{}
{
if
{
$::tls
}
{
set s
[
::tls::socket
[
srv 0
"host"
]
[
srv 0
"port"
]]
}
else
{
set s
[
socket
[
srv 0
"host"
]
[
srv 0
"port"
]]
set s
[
socket
[
srv 0
"host"
]
[
srv 0
"port"
]]
}
fconfigure $s -translation binary
fconfigure $s -translation binary
puts -nonewline $s
"SYNC
\r\n
"
puts -nonewline $s
"SYNC
\r\n
"
flush $s
flush $s
...
...
tests/unit/acl.tcl
View file @
2968d8e3
...
@@ -35,6 +35,32 @@ start_server {tags {"acl"}} {
...
@@ -35,6 +35,32 @@ start_server {tags {"acl"}} {
set e
set e
}
{
*WRONGPASS*
}
}
{
*WRONGPASS*
}
test
{
Test password hashes can be added
}
{
r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6
catch
{
r AUTH newuser passwd4
}
e
assert
{
$e
eq
"OK"
}
}
test
{
Test password hashes validate input
}
{
# Validate Length
catch
{
r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e
}
e
# Validate character outside set
catch
{
r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4eq
}
e
set e
}
{
*Error in ACL SETUSER modifier*
}
test
{
ACL GETUSER returns the password hash instead of the actual password
}
{
set passstr
[
dict get
[
r ACL getuser newuser
]
passwords
]
assert_match
{
*34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6*
}
$passstr
assert_no_match
{
*passwd4*
}
$passstr
}
test
{
Test hashed passwords removal
}
{
r ACL setuser newuser !34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6
set passstr
[
dict get
[
r ACL getuser newuser
]
passwords
]
assert_no_match
{
*34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6*
}
$passstr
}
test
{
By default users are not able to access any command
}
{
test
{
By default users are not able to access any command
}
{
catch
{
r SET foo bar
}
e
catch
{
r SET foo bar
}
e
set e
set e
...
@@ -67,7 +93,7 @@ start_server {tags {"acl"}} {
...
@@ -67,7 +93,7 @@ start_server {tags {"acl"}} {
set e
set e
}
{
*NOPERM*
}
}
{
*NOPERM*
}
test
{
ACLs can include or exclu
s
e whole classes of commands
}
{
test
{
ACLs can include or exclu
d
e whole classes of commands
}
{
r ACL setuser newuser -@all +@set +acl
r ACL setuser newuser -@all +@set +acl
r SADD myset a b c
;
# Should not raise an error
r SADD myset a b c
;
# Should not raise an error
r ACL setuser newuser +@all -@string
r ACL setuser newuser +@all -@string
...
...
tests/unit/geo.tcl
View file @
2968d8e3
...
@@ -129,7 +129,7 @@ start_server {tags {"geo"}} {
...
@@ -129,7 +129,7 @@ start_server {tags {"geo"}} {
r del points
r del points
r geoadd points -5.6 42.6 test
r geoadd points -5.6 42.6 test
lindex
[
r geohash points test
]
0
lindex
[
r geohash points test
]
0
}
{
ezs42e44yx
0
}
}
{
ezs42e44yx
}
test
{
GEOPOS simple
}
{
test
{
GEOPOS simple
}
{
r del points
r del points
...
...
tests/unit/introspection.tcl
View file @
2968d8e3
...
@@ -57,4 +57,69 @@ start_server {tags {"introspection"}} {
...
@@ -57,4 +57,69 @@ start_server {tags {"introspection"}} {
fail
"Client still listed in CLIENT LIST after SETNAME."
fail
"Client still listed in CLIENT LIST after SETNAME."
}
}
}
}
test
{
CONFIG sanity
}
{
# Do CONFIG GET, CONFIG SET and then CONFIG GET again
# Skip immutable configs, one with no get, and other complicated configs
set skip_configs
{
rdbchecksum
daemonize
io-threads-do-reads
lua-replicate-commands
always-show-logo
syslog-enabled
cluster-enabled
aclfile
unixsocket
pidfile
syslog-ident
appendfilename
supervised
syslog-facility
databases
port
io-threads
tls-port
tls-prefer-server-ciphers
tls-cert-file
tls-key-file
tls-dh-params-file
tls-ca-cert-file
tls-ca-cert-dir
tls-protocols
tls-ciphers
tls-ciphersuites
logfile
unixsocketperm
slaveof
bind
requirepass
}
set configs
{}
foreach
{
k v
}
[
r config get *
]
{
if
{[
lsearch $skip_configs $k
]
!= -1
}
{
continue
}
dict set configs $k $v
# try to set the config to the same value it already has
r config set $k $v
}
set newconfigs
{}
foreach
{
k v
}
[
r config get *
]
{
if
{[
lsearch $skip_configs $k
]
!= -1
}
{
continue
}
dict set newconfigs $k $v
}
dict for
{
k v
}
$configs
{
set vv
[
dict get $newconfigs $k
]
if
{
$v
!= $vv
}
{
fail
"config
$k
mismatch, expecting
$v
but got
$vv
"
}
}
}
}
}
tests/unit/limits.tcl
View file @
2968d8e3
start_server
{
tags
{
"limits"
}
overrides
{
maxclients 10
}}
{
start_server
{
tags
{
"limits"
}
overrides
{
maxclients 10
}}
{
if
{
$::tls
}
{
set expected_code
"*I/O error*"
}
else
{
set expected_code
"*ERR max*reached*"
}
test
{
Check if maxclients works refusing connections
}
{
test
{
Check if maxclients works refusing connections
}
{
set c 0
set c 0
catch
{
catch
{
...
@@ -12,5 +17,5 @@ start_server {tags {"limits"} overrides {maxclients 10}} {
...
@@ -12,5 +17,5 @@ start_server {tags {"limits"} overrides {maxclients 10}} {
}
e
}
e
assert
{
$c
> 8 && $c <= 10
}
assert
{
$c
> 8 && $c <= 10
}
set e
set e
}
{
*ERR max*reached*
}
}
$expected_code
}
}
tests/unit/moduleapi/blockonkeys.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/blockonkeys.so
]
start_server
{
tags
{
"modules"
}}
{
r module load $testmodule
test
{
Module client blocked on keys
(
no metadata
)
: No block
}
{
r del k
r fsl.push k 33
r fsl.push k 34
r fsl.bpop2 k 0
}
{
34 33
}
test
{
Module client blocked on keys
(
no metadata
)
: Timeout
}
{
r del k
set rd
[
redis_deferring_client
]
r fsl.push k 33
$rd fsl.bpop2 k 1
assert_equal
{
Request timedout
}
[
$rd
read
]
}
test
{
Module client blocked on keys
(
no metadata
)
: Blocked, case 1
}
{
r del k
set rd
[
redis_deferring_client
]
r fsl.push k 33
$rd fsl.bpop2 k 0
r fsl.push k 34
assert_equal
{
34 33
}
[
$rd
read
]
}
test
{
Module client blocked on keys
(
no metadata
)
: Blocked, case 2
}
{
r del k
set rd
[
redis_deferring_client
]
r fsl.push k 33
r fsl.push k 34
$rd fsl.bpop2 k 0
assert_equal
{
34 33
}
[
$rd
read
]
}
test
{
Module client blocked on keys
(
with metadata
)
: No block
}
{
r del k
r fsl.push k 34
r fsl.bpopgt k 30 0
}
{
34
}
test
{
Module client blocked on keys
(
with metadata
)
: Timeout
}
{
r del k
set rd
[
redis_deferring_client
]
r fsl.push k 33
$rd fsl.bpopgt k 35 1
assert_equal
{
Request timedout
}
[
$rd
read
]
}
test
{
Module client blocked on keys
(
with metadata
)
: Blocked, case 1
}
{
r del k
set rd
[
redis_deferring_client
]
r fsl.push k 33
$rd fsl.bpopgt k 33 0
r fsl.push k 34
assert_equal
{
34
}
[
$rd
read
]
}
test
{
Module client blocked on keys
(
with metadata
)
: Blocked, case 2
}
{
r del k
set rd
[
redis_deferring_client
]
$rd fsl.bpopgt k 35 0
r fsl.push k 33
r fsl.push k 34
r fsl.push k 35
r fsl.push k 36
assert_equal
{
36
}
[
$rd
read
]
}
test
{
Module client blocked on keys does not wake up on wrong type
}
{
r del k
set rd
[
redis_deferring_client
]
$rd fsl.bpop2 k 0
r lpush k 12
r lpush k 13
r lpush k 14
r del k
r fsl.push k 33
r fsl.push k 34
assert_equal
{
34 33
}
[
$rd
read
]
}
}
tests/unit/moduleapi/datatype.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/datatype.so
]
start_server
{
tags
{
"modules"
}}
{
r module load $testmodule
test
{
DataType: Test module is sane, GET/SET work.
}
{
r datatype.set dtkey 100 stringval
assert
{[
r datatype.get dtkey
]
eq
{
100 stringval
}}
}
test
{
DataType: RM_SaveDataTypeToString
()
, RM_LoadDataTypeFromString
()
work
}
{
r datatype.set dtkey -1111 MyString
set encoded
[
r datatype.dump dtkey
]
r datatype.restore dtkeycopy $encoded
assert
{[
r datatype.get dtkeycopy
]
eq
{
-1111 MyString
}}
}
test
{
DataType: Handle truncated RM_LoadDataTypeFromString
()}
{
r datatype.set dtkey -1111 MyString
set encoded
[
r datatype.dump dtkey
]
set truncated
[
string range $encoded 0 end-1
]
catch
{
r datatype.restore dtkeycopy $truncated
}
e
set e
}
{
*Invalid*
}
}
tests/unit/moduleapi/fork.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/fork.so
]
proc count_log_message
{
pattern
}
{
set result
[
exec grep -c $pattern <
[
srv 0 stdout
]]
}
start_server
{
tags
{
"modules"
}}
{
r module load $testmodule
test
{
Module fork
}
{
# the argument to fork.create is the exitcode on termination
r fork.create 3
wait_for_condition 20 100
{
[
r fork.exitcode
]
!= -1
}
else
{
fail
"fork didn't terminate"
}
r fork.exitcode
}
{
3
}
test
{
Module fork kill
}
{
r fork.create 3
after 20
r fork.kill
after 100
assert
{[
count_log_message
"fork child started"
]
eq
"2"
}
assert
{[
count_log_message
"Received SIGUSR1 in child"
]
eq
"1"
}
assert
{[
count_log_message
"fork child exiting"
]
eq
"1"
}
}
}
tests/unit/moduleapi/hooks.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/hooks.so
]
tags
"modules"
{
start_server
[
list overrides
[
list loadmodule
"
$testmodule
"
appendonly yes
]]
{
test
{
Test clients connection / disconnection hooks
}
{
for
{
set j 0
}
{
$j
< 2
}
{
incr j
}
{
set rd1
[
redis_deferring_client
]
$rd1 close
}
assert
{[
r hooks.event_count client-connected
]
> 1
}
assert
{[
r hooks.event_count client-disconnected
]
> 1
}
}
test
{
Test module cron hook
}
{
after 100
assert
{[
r hooks.event_count cron-loop
]
> 0
}
set hz
[
r hooks.event_last cron-loop
]
assert_equal $hz 10
}
test
{
Test module loaded / unloaded hooks
}
{
set othermodule
[
file normalize tests/modules/infotest.so
]
r module load $othermodule
r module unload infotest
assert_equal
[
r hooks.event_last module-loaded
]
"infotest"
assert_equal
[
r hooks.event_last module-unloaded
]
"infotest"
}
test
{
Test module aofrw hook
}
{
r debug populate 1000 foo 10000
;
# 10mb worth of data
r config set rdbcompression no
;
# rdb progress is only checked once in 2mb
r BGREWRITEAOF
waitForBgrewriteaof r
assert_equal
[
string match
{
*module-event-persistence-aof-start*
}
[
exec tail -20 <
[
srv 0 stdout
]]]
1
assert_equal
[
string match
{
*module-event-persistence-end*
}
[
exec tail -20 <
[
srv 0 stdout
]]]
1
}
test
{
Test module aof load and rdb/aof progress hooks
}
{
# create some aof tail
(
progress is checked only once in 1000 commands
)
for
{
set j 0
}
{
$j
< 4000
}
{
incr j
}
{
r set
"bar
$j
"
x
}
# set some configs that will cause many loading progress events during aof loading
r config set key-load-delay 500
r config set dynamic-hz no
r config set hz 500
r DEBUG LOADAOF
assert_equal
[
r hooks.event_last loading-aof-start
]
0
assert_equal
[
r hooks.event_last loading-end
]
0
assert
{[
r hooks.event_count loading-rdb-start
]
== 0
}
assert_lessthan 2
[
r hooks.event_count loading-progress-rdb
]
;
# comes from the preamble section
assert_lessthan 2
[
r hooks.event_count loading-progress-aof
]
if
{
$::verbose
}
{
puts
"rdb progress events
[
r hooks.event_count loading-progress-rdb
]
"
puts
"aof progress events
[
r hooks.event_count loading-progress-aof
]
"
}
}
# undo configs before next test
r config set dynamic-hz yes
r config set key-load-delay 0
test
{
Test module rdb save hook
}
{
# debug reload does: save, flush, load:
assert
{[
r hooks.event_count persistence-syncrdb-start
]
== 0
}
assert
{[
r hooks.event_count loading-rdb-start
]
== 0
}
r debug reload
assert
{[
r hooks.event_count persistence-syncrdb-start
]
== 1
}
assert
{[
r hooks.event_count loading-rdb-start
]
== 1
}
}
test
{
Test flushdb hooks
}
{
r flushdb
assert_equal
[
r hooks.event_last flush-start
]
9
assert_equal
[
r hooks.event_last flush-end
]
9
r flushall
assert_equal
[
r hooks.event_last flush-start
]
-1
assert_equal
[
r hooks.event_last flush-end
]
-1
}
# replication related tests
set master
[
srv 0 client
]
set master_host
[
srv 0 host
]
set master_port
[
srv 0 port
]
start_server
{}
{
r module load $testmodule
set replica
[
srv 0 client
]
set replica_host
[
srv 0 host
]
set replica_port
[
srv 0 port
]
$replica replicaof $master_host $master_port
wait_for_condition 50 100
{
[
string match
{
*master_link_status:up*
}
[
r info replication
]]
}
else
{
fail
"Can't turn the instance into a replica"
}
test
{
Test master link up hook
}
{
assert_equal
[
r hooks.event_count masterlink-up
]
1
assert_equal
[
r hooks.event_count masterlink-down
]
0
}
test
{
Test role-replica hook
}
{
assert_equal
[
r hooks.event_count role-replica
]
1
assert_equal
[
r hooks.event_count role-master
]
0
assert_equal
[
r hooks.event_last role-replica
]
[
s 0 master_host
]
}
test
{
Test replica-online hook
}
{
assert_equal
[
r -1 hooks.event_count replica-online
]
1
assert_equal
[
r -1 hooks.event_count replica-offline
]
0
}
test
{
Test master link down hook
}
{
r client kill type master
assert_equal
[
r hooks.event_count masterlink-down
]
1
}
$replica replicaof no one
test
{
Test role-master hook
}
{
assert_equal
[
r hooks.event_count role-replica
]
1
assert_equal
[
r hooks.event_count role-master
]
1
assert_equal
[
r hooks.event_last role-master
]
{}
}
test
{
Test replica-offline hook
}
{
assert_equal
[
r -1 hooks.event_count replica-online
]
1
assert_equal
[
r -1 hooks.event_count replica-offline
]
1
}
# get the replica stdout, to be used by the next test
set replica_stdout
[
srv 0 stdout
]
}
# look into the log file of the server that just exited
test
{
Test shutdown hook
}
{
assert_equal
[
string match
{
*module-event-shutdown*
}
[
exec tail -5 < $replica_stdout
]]
1
}
}
}
tests/unit/moduleapi/infotest.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/infotest.so
]
# Return value for INFO property
proc field
{
info property
}
{
if
{[
regexp
"
\r\n
$property:
(.*?)
\r\n
"
$info _ value
]}
{
set _ $value
}
}
start_server
{
tags
{
"modules"
}}
{
r module load $testmodule log-key 0
test
{
module reading info
}
{
# check string, integer and float fields
assert_equal
[
r info.gets replication role
]
"master"
assert_equal
[
r info.getc replication role
]
"master"
assert_equal
[
r info.geti stats expired_keys
]
0
assert_equal
[
r info.getd stats expired_stale_perc
]
0
# check signed and unsigned
assert_equal
[
r info.geti infotest infotest_global
]
-2
assert_equal
[
r info.getu infotest infotest_uglobal
]
-2
# the above are always 0, try module info that is non-zero
assert_equal
[
r info.geti infotest_italian infotest_due
]
2
set tre
[
r info.getd infotest_italian infotest_tre
]
assert
{
$tre
> 3.2 && $tre < 3.4
}
# search using the wrong section
catch
{
[
r info.gets badname redis_version
]
}
e
assert_match
{
*not found*
}
$e
# check that section filter works
assert
{
[
string match
"*usec_per_call*"
[
r info.gets all cmdstat_info.gets
]
]
}
catch
{
[
r info.gets default cmdstat_info.gets
]
]
}
e
assert_match
{
*not found*
}
$e
}
test
{
module info all
}
{
set info
[
r info all
]
# info all does not contain modules
assert
{
!
[
string match
"*Spanish*"
$info
]
}
assert
{
!
[
string match
"*infotest_*"
$info
]
}
assert
{
[
string match
"*used_memory*"
$info
]
}
}
test
{
module info everything
}
{
set info
[
r info everything
]
# info everything contains all default sections, but not ones for crash report
assert
{
[
string match
"*infotest_global*"
$info
]
}
assert
{
[
string match
"*Spanish*"
$info
]
}
assert
{
[
string match
"*Italian*"
$info
]
}
assert
{
[
string match
"*used_memory*"
$info
]
}
assert
{
!
[
string match
"*Klingon*"
$info
]
}
field $info infotest_dos
}
{
2
}
test
{
module info modules
}
{
set info
[
r info modules
]
# info all does not contain modules
assert
{
[
string match
"*Spanish*"
$info
]
}
assert
{
[
string match
"*infotest_global*"
$info
]
}
assert
{
!
[
string match
"*used_memory*"
$info
]
}
}
test
{
module info one module
}
{
set info
[
r info INFOTEST
]
# info all does not contain modules
assert
{
[
string match
"*Spanish*"
$info
]
}
assert
{
!
[
string match
"*used_memory*"
$info
]
}
field $info infotest_global
}
{
-2
}
test
{
module info one section
}
{
set info
[
r info INFOTEST_SPANISH
]
assert
{
!
[
string match
"*used_memory*"
$info
]
}
assert
{
!
[
string match
"*Italian*"
$info
]
}
assert
{
!
[
string match
"*infotest_global*"
$info
]
}
field $info infotest_uno
}
{
one
}
test
{
module info dict
}
{
set info
[
r info infotest_keyspace
]
set keyspace
[
field $info infotest_db0
]
set keys
[
scan
[
regexp -inline
{
keys
\=
([
\d
]
*
)}
$keyspace
]
keys=%d
]
}
{
3
}
# TODO: test crash report.
}
tests/unit/moduleapi/misc.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/misc.so
]
start_server
{
tags
{
"modules"
}}
{
r module load $testmodule
test
{
test RM_Call
}
{
set info
[
r test.call_info commandstats
]
# cmdstat is not in a default section, so we also test an argument was passed
assert
{
[
string match
"*cmdstat_module*"
$info
]
}
}
test
{
test RM_Call args array
}
{
set info
[
r test.call_generic info commandstats
]
# cmdstat is not in a default section, so we also test an argument was passed
assert
{
[
string match
"*cmdstat_module*"
$info
]
}
}
test
{
test long double conversions
}
{
set ld
[
r test.ld_conversion
]
assert
{[
string match $ld
"0.00000000000000001"
]}
}
test
{
test module db commands
}
{
r set x foo
set key
[
r test.randomkey
]
assert_equal $key
"x"
assert_equal
[
r test.dbsize
]
1
r test.flushall
assert_equal
[
r test.dbsize
]
0
}
test
{
test modle lru api
}
{
r config set maxmemory-policy allkeys-lru
r set x foo
set lru
[
r test.getlru x
]
assert
{
$lru <= 1000
}
set was_set
[
r test.setlru x 100000
]
assert
{
$was_set == 1
}
set idle
[
r object idletime x
]
assert
{
$idle >= 100
}
set lru
[
r test.getlru x
]
assert
{
$lru >= 100000
}
r config set maxmemory-policy allkeys-lfu
set lru
[
r test.getlru x
]
assert
{
$lru == -1
}
set was_set
[
r test.setlru x 100000
]
assert
{
$was_set == 0
}
}
r config set maxmemory-policy allkeys-lru
test
{
test modle lfu api
}
{
r config set maxmemory-policy allkeys-lfu
r set x foo
set lfu
[
r test.getlfu x
]
assert
{
$lfu >= 1
}
set was_set
[
r test.setlfu x 100
]
assert
{
$was_set == 1
}
set freq
[
r object freq x
]
assert
{
$freq <= 100
}
set lfu
[
r test.getlfu x
]
assert
{
$lfu <= 100
}
r config set maxmemory-policy allkeys-lru
set lfu
[
r test.getlfu x
]
assert
{
$lfu == -1
}
set was_set
[
r test.setlfu x 100
]
assert
{
$was_set == 0
}
}
}
tests/unit/moduleapi/propagate.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/propagate.so
]
tags
"modules"
{
test
{
Modules can propagate in async and threaded contexts
}
{
start_server
{}
{
set replica
[
srv 0 client
]
set replica_host
[
srv 0 host
]
set replica_port
[
srv 0 port
]
start_server
[
list overrides
[
list loadmodule
"
$testmodule
"
]]
{
set master
[
srv 0 client
]
set master_host
[
srv 0 host
]
set master_port
[
srv 0 port
]
# Start the replication process...
$replica replicaof $master_host $master_port
wait_for_sync $replica
after 1000
$master propagate-test
wait_for_condition 5000 10
{
([
$replica
get timer
]
eq
"10"
)
&&
\
([
$replica
get thread
]
eq
"10"
)
}
else
{
fail
"The two counters don't match the expected value."
}
}
}
}
}
tests/unit/moduleapi/scan.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/scan.so
]
start_server
{
tags
{
"modules"
}}
{
r module load $testmodule
test
{
Module scan keyspace
}
{
# the module create a scan command with filtering which also return values
r set x 1
r set y 2
r set z 3
r hset h f v
lsort
[
r scan.scan_strings
]
}
{{
x 1
}
{
y 2
}
{
z 3
}}
test
{
Module scan hash ziplist
}
{
r hmset hh f1 v1 f2 v2
lsort
[
r scan.scan_key hh
]
}
{{
f1 v1
}
{
f2 v2
}}
test
{
Module scan hash dict
}
{
r config set hash-max-ziplist-entries 2
r hmset hh f3 v3
lsort
[
r scan.scan_key hh
]
}
{{
f1 v1
}
{
f2 v2
}
{
f3 v3
}}
test
{
Module scan zset ziplist
}
{
r zadd zz 1 f1 2 f2
lsort
[
r scan.scan_key zz
]
}
{{
f1 1
}
{
f2 2
}}
test
{
Module scan zset dict
}
{
r config set zset-max-ziplist-entries 2
r zadd zz 3 f3
lsort
[
r scan.scan_key zz
]
}
{{
f1 1
}
{
f2 2
}
{
f3 3
}}
test
{
Module scan set intset
}
{
r sadd ss 1 2
lsort
[
r scan.scan_key ss
]
}
{{
1
{}}
{
2
{}}}
test
{
Module scan set dict
}
{
r config set set-max-intset-entries 2
r sadd ss 3
lsort
[
r scan.scan_key ss
]
}
{{
1
{}}
{
2
{}}
{
3
{}}}
}
tests/unit/moduleapi/testrdb.tcl
0 → 100644
View file @
2968d8e3
set testmodule
[
file normalize tests/modules/testrdb.so
]
tags
"modules"
{
test
{
modules are able to persist types
}
{
start_server
[
list overrides
[
list loadmodule
"
$testmodule
"
]]
{
r testrdb.set.key key1 value1
assert_equal
"value1"
[
r testrdb.get.key key1
]
r debug reload
assert_equal
"value1"
[
r testrdb.get.key key1
]
}
}
test
{
modules global are lost without aux
}
{
set server_path
[
tmpdir
"server.module-testrdb"
]
start_server
[
list overrides
[
list loadmodule
"
$testmodule
"
"dir"
$server_path
]]
{
r testrdb.set.before global1
assert_equal
"global1"
[
r testrdb.get.before
]
}
start_server
[
list overrides
[
list loadmodule
"
$testmodule
"
"dir"
$server_path
]]
{
assert_equal
""
[
r testrdb.get.before
]
}
}
test
{
modules are able to persist globals before and after
}
{
set server_path
[
tmpdir
"server.module-testrdb"
]
start_server
[
list overrides
[
list loadmodule
"
$testmodule
2"
"dir"
$server_path
]]
{
r testrdb.set.before global1
r testrdb.set.after global2
assert_equal
"global1"
[
r testrdb.get.before
]
assert_equal
"global2"
[
r testrdb.get.after
]
}
start_server
[
list overrides
[
list loadmodule
"
$testmodule
2"
"dir"
$server_path
]]
{
assert_equal
"global1"
[
r testrdb.get.before
]
assert_equal
"global2"
[
r testrdb.get.after
]
}
}
test
{
modules are able to persist globals just after
}
{
set server_path
[
tmpdir
"server.module-testrdb"
]
start_server
[
list overrides
[
list loadmodule
"
$testmodule
1"
"dir"
$server_path
]]
{
r testrdb.set.after global2
assert_equal
"global2"
[
r testrdb.get.after
]
}
start_server
[
list overrides
[
list loadmodule
"
$testmodule
1"
"dir"
$server_path
]]
{
assert_equal
"global2"
[
r testrdb.get.after
]
}
}
tags
{
repl
}
{
test
{
diskless loading short read with module
}
{
start_server
[
list overrides
[
list loadmodule
"
$testmodule
"
]]
{
set replica
[
srv 0 client
]
set replica_host
[
srv 0 host
]
set replica_port
[
srv 0 port
]
start_server
[
list overrides
[
list loadmodule
"
$testmodule
"
]]
{
set master
[
srv 0 client
]
set master_host
[
srv 0 host
]
set master_port
[
srv 0 port
]
# Set master and replica to use diskless replication
$master config set repl-diskless-sync yes
$master config set rdbcompression no
$replica config set repl-diskless-load swapdb
for
{
set k 0
}
{
$k
< 30
}
{
incr k
}
{
r testrdb.set.key key$k
[
string repeat A
[
expr
{
int
(
rand
()
*1000000
)}]]
}
# Start the replication process...
$master config set repl-diskless-sync-delay 0
$replica replicaof $master_host $master_port
# kill the replication at various points
set attempts 3
if
{
$::accurate
}
{
set attempts 10
}
for
{
set i 0
}
{
$i
< $attempts
}
{
incr i
}
{
# wait for the replica to start reading the rdb
# using the log file since the replica only responds to INFO once in 2mb
wait_for_log_message -1
"*Loading DB in memory*"
5 2000 1
# add some additional random sleep so that we kill the master on a different place each time
after
[
expr
{
int
(
rand
()
*100
)}]
# kill the replica connection on the master
set killed
[
$master
client kill type replica
]
if
{[
catch
{
set res
[
wait_for_log_message -1
"*Internal error in RDB*"
5 100 10
]
if
{
$::verbose
}
{
puts $res
}
}]}
{
puts
"failed triggering short read"
# force the replica to try another full sync
$master client kill type replica
$master set asdf asdf
# the side effect of resizing the backlog is that it is flushed
(
16k is the min size
)
$master config set repl-backlog-size
[
expr
{
16384 + $i
}]
}
# wait for loading to stop
(
fail
)
wait_for_condition 100 10
{
[
s -1 loading
]
eq 0
}
else
{
fail
"Replica didn't disconnect"
}
}
# enable fast shutdown
$master config set rdb-key-save-delay 0
}
}
}
}
}
tests/unit/multi.tcl
View file @
2968d8e3
...
@@ -306,4 +306,18 @@ start_server {tags {"multi"}} {
...
@@ -306,4 +306,18 @@ start_server {tags {"multi"}} {
}
}
close_replication_stream $repl
close_replication_stream $repl
}
}
test
{
DISCARD should not fail during OOM
}
{
set rd
[
redis_deferring_client
]
$rd config set maxmemory 1
assert
{[
$rd
read
]
eq
{
OK
}}
r multi
catch
{
r set x 1
}
e
assert_match
{
OOM*
}
$e
r discard
$rd config set maxmemory 0
assert
{[
$rd
read
]
eq
{
OK
}}
$rd close
r ping
}
{
PONG
}
}
}
Prev
1
…
4
5
6
7
8
9
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment