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
2dba1e39
Unverified
Commit
2dba1e39
authored
Feb 01, 2021
by
Oran Agra
Committed by
GitHub
Feb 01, 2021
Browse files
Merge 6.2 RC3
parents
ec2d1807
95338f9c
Changes
91
Hide whitespace changes
Inline
Side-by-side
tests/unit/pause.tcl
View file @
2dba1e39
start_server
{
tags
{
"pause"
}}
{
start_server
{
tags
{
"pause
network
"
}}
{
test
"Test read commands are not blocked by client pause"
{
test
"Test read commands are not blocked by client pause"
{
r client PAUSE 100000000 WRITE
r client PAUSE 100000000 WRITE
set rd
[
redis_deferring_client
]
set rd
[
redis_deferring_client
]
...
...
tests/unit/protocol.tcl
View file @
2dba1e39
start_server
{
tags
{
"protocol"
}}
{
start_server
{
tags
{
"protocol
network
"
}}
{
test
"Handle an empty query"
{
test
"Handle an empty query"
{
reconnect
reconnect
r write
"
\r\n
"
r write
"
\r\n
"
...
...
tests/unit/pubsub.tcl
View file @
2dba1e39
start_server
{
tags
{
"pubsub"
}}
{
start_server
{
tags
{
"pubsub
network
"
}}
{
proc __consume_subscribe_messages
{
client type channels
}
{
proc __consume_subscribe_messages
{
client type channels
}
{
set numsub -1
set numsub -1
set counts
{}
set counts
{}
...
...
tests/unit/scan.tcl
View file @
2dba1e39
start_server
{
tags
{
"scan"
}}
{
start_server
{
tags
{
"scan
network
"
}}
{
test
"SCAN basic"
{
test
"SCAN basic"
{
r flushdb
r flushdb
r debug populate 1000
r debug populate 1000
...
...
tests/unit/scripting.tcl
View file @
2dba1e39
...
@@ -330,6 +330,15 @@ start_server {tags {"scripting"}} {
...
@@ -330,6 +330,15 @@ start_server {tags {"scripting"}} {
set e
set e
}
{
NOSCRIPT*
}
}
{
NOSCRIPT*
}
test
{
SCRIPTING FLUSH ASYNC
}
{
for
{
set j 0
}
{
$j
< 100
}
{
incr j
}
{
r script load
"return
$j
"
}
assert
{
[
string match
"*number_of_cached_scripts:100*"
[
r info Memory
]]
}
r script flush async
assert
{
[
string match
"*number_of_cached_scripts:0*"
[
r info Memory
]]
}
}
test
{
SCRIPT EXISTS - can detect already defined scripts?
}
{
test
{
SCRIPT EXISTS - can detect already defined scripts?
}
{
r eval
"return 1+1"
0
r eval
"return 1+1"
0
r script exists a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9 a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bda
r script exists a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9 a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bda
...
...
tests/unit/tracking.tcl
View file @
2dba1e39
start_server
{
tags
{
"tracking"
}}
{
start_server
{
tags
{
"tracking
network
"
}}
{
# Create a deferred client we'll use to redirect invalidation
# Create a deferred client we'll use to redirect invalidation
# messages to.
# messages to.
set rd_redirection
[
redis_deferring_client
]
set rd_redirection
[
redis_deferring_client
]
...
...
tests/unit/type/hash.tcl
View file @
2dba1e39
...
@@ -18,6 +18,181 @@ start_server {tags {"hash"}} {
...
@@ -18,6 +18,181 @@ start_server {tags {"hash"}} {
assert_encoding ziplist smallhash
assert_encoding ziplist smallhash
}
}
proc create_hash
{
key entries
}
{
r del $key
foreach entry $entries
{
r hset $key
[
lindex $entry 0
]
[
lindex $entry 1
]
}
}
proc get_keys
{
l
}
{
set res
{}
foreach entry $l
{
set key
[
lindex $entry 0
]
lappend res $key
}
return $res
}
foreach
{
type contents
}
"ziplist {{a 1} {b 2} {c 3}} hashtable {{a 1} {b 2} {
[
randstring 70 90 alpha
]
3}}"
{
set original_max_value
[
lindex
[
r config get hash-max-ziplist-value
]
1
]
r config set hash-max-ziplist-value 10
create_hash myhash $contents
assert_encoding $type myhash
test
"HRANDFIELD -
$type
"
{
unset -nocomplain myhash
array set myhash
{}
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
set key
[
r hrandfield myhash
]
set myhash
(
$key
)
1
}
assert_equal
[
lsort
[
get_keys $contents
]]
[
lsort
[
array names myhash
]]
}
r config set hash-max-ziplist-value $original_max_value
}
test
"HRANDFIELD with RESP3"
{
r hello 3
set res
[
r hrandfield myhash 3 withvalues
]
assert_equal
[
llength $res
]
3
assert_equal
[
llength
[
lindex $res 1
]]
2
set res
[
r hrandfield myhash 3
]
assert_equal
[
llength $res
]
3
assert_equal
[
llength
[
lindex $res 1
]]
1
}
r hello 2
test
"HRANDFIELD count of 0 is handled correctly"
{
r hrandfield myhash 0
}
{}
test
"HRANDFIELD with <count> against non existing key"
{
r hrandfield nonexisting_key 100
}
{}
foreach
{
type contents
}
"
hashtable {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {
[
randstring 70 90 alpha
]
10}}
ziplist {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {10 j}} "
{
test
"HRANDFIELD with <count> -
$type
"
{
set original_max_value
[
lindex
[
r config get hash-max-ziplist-value
]
1
]
r config set hash-max-ziplist-value 10
create_hash myhash $contents
assert_encoding $type myhash
# create a dict for easy lookup
unset -nocomplain mydict
foreach
{
k v
}
[
r hgetall myhash
]
{
dict append mydict $k $v
}
# We'll stress different parts of the code, see the implementation
# of HRANDFIELD for more information, but basically there are
# four different code paths.
# PATH 1: Use negative count.
# 1) Check that it returns repeated elements with and without values.
set res
[
r hrandfield myhash -20
]
assert_equal
[
llength $res
]
20
# again with WITHVALUES
set res
[
r hrandfield myhash -20 withvalues
]
assert_equal
[
llength $res
]
40
# 2) Check that all the elements actually belong to the original hash.
foreach
{
key val
}
$res
{
assert
{[
dict exists $mydict $key
]}
}
# 3) Check that eventually all the elements are returned.
# Use both WITHVALUES and without
unset -nocomplain auxset
set iterations 1000
while
{
$iterations
!= 0
}
{
incr iterations -1
if
{[
expr
{
$iterations
% 2
}]
== 0
}
{
set res
[
r hrandfield myhash -3 withvalues
]
foreach
{
key val
}
$res
{
dict append auxset $key $val
}
}
else
{
set res
[
r hrandfield myhash -3
]
foreach key $res
{
dict append auxset $key $val
}
}
if
{[
lsort
[
dict keys $mydict
]]
eq
[
lsort
[
dict keys $auxset
]]}
{
break
;
}
}
assert
{
$iterations
!= 0
}
# PATH 2: positive count
(
unique behavior
)
with requested size
# equal or greater than set size.
foreach size
{
10 20
}
{
set res
[
r hrandfield myhash $size
]
assert_equal
[
llength $res
]
10
assert_equal
[
lsort $res
]
[
lsort
[
dict keys $mydict
]]
# again with WITHVALUES
set res
[
r hrandfield myhash $size withvalues
]
assert_equal
[
llength $res
]
20
assert_equal
[
lsort $res
]
[
lsort $mydict
]
}
# PATH 3: Ask almost as elements as there are in the set.
# In this case the implementation will duplicate the original
# set and will remove random elements up to the requested size.
#
# PATH 4: Ask a number of elements definitely smaller than
# the set size.
#
# We can test both the code paths just changing the size but
# using the same code.
foreach size
{
8 2
}
{
set res
[
r hrandfield myhash $size
]
assert_equal
[
llength $res
]
$size
# again with WITHVALUES
set res
[
r hrandfield myhash $size withvalues
]
assert_equal
[
llength $res
]
[
expr
{
$size
* 2
}]
# 1) Check that all the elements actually belong to the
# original set.
foreach ele
[
dict keys $res
]
{
assert
{[
dict exists $mydict $ele
]}
}
# 2) Check that eventually all the elements are returned.
# Use both WITHVALUES and without
unset -nocomplain auxset
set iterations 1000
while
{
$iterations
!= 0
}
{
incr iterations -1
if
{[
expr
{
$iterations
% 2
}]
== 0
}
{
set res
[
r hrandfield myhash $size withvalues
]
foreach
{
key value
}
$res
{
dict append auxset $key $value
}
}
else
{
set res
[
r hrandfield myhash $size
]
foreach key $res
{
dict append auxset $key
}
}
if
{[
lsort
[
dict keys $mydict
]]
eq
[
lsort
[
dict keys $auxset
]]}
{
break
;
}
}
assert
{
$iterations
!= 0
}
}
}
r config set hash-max-ziplist-value $original_max_value
}
test
{
HSET/HLEN - Big hash creation
}
{
test
{
HSET/HLEN - Big hash creation
}
{
array set bighash
{}
array set bighash
{}
for
{
set i 0
}
{
$i
< 1024
}
{
incr i
}
{
for
{
set i 0
}
{
$i
< 1024
}
{
incr i
}
{
...
...
tests/unit/type/set.tcl
View file @
2dba1e39
...
@@ -501,7 +501,7 @@ start_server {
...
@@ -501,7 +501,7 @@ start_server {
set iterations 1000
set iterations 1000
while
{
$iterations
!= 0
}
{
while
{
$iterations
!= 0
}
{
incr iterations -1
incr iterations -1
set res
[
r srandmember myset
-10
]
set res
[
r srandmember myset
$size
]
foreach ele $res
{
foreach ele $res
{
set auxset
(
$ele
)
1
set auxset
(
$ele
)
1
}
}
...
...
tests/unit/type/string.tcl
View file @
2dba1e39
...
@@ -102,6 +102,91 @@ start_server {tags {"string"}} {
...
@@ -102,6 +102,91 @@ start_server {tags {"string"}} {
assert_equal 20
[
r get x
]
assert_equal 20
[
r get x
]
}
}
test
"GETEX EX option"
{
r del foo
r set foo bar
r getex foo ex 10
assert_range
[
r ttl foo
]
5 10
}
test
"GETEX PX option"
{
r del foo
r set foo bar
r getex foo px 10000
assert_range
[
r pttl foo
]
5000 10000
}
test
"GETEX EXAT option"
{
r del foo
r set foo bar
r getex foo exat
[
expr
[
clock seconds
]
+ 10
]
assert_range
[
r ttl foo
]
5 10
}
test
"GETEX PXAT option"
{
r del foo
r set foo bar
r getex foo pxat
[
expr
[
clock milliseconds
]
+ 10000
]
assert_range
[
r pttl foo
]
5000 10000
}
test
"GETEX PERSIST option"
{
r del foo
r set foo bar ex 10
assert_range
[
r ttl foo
]
5 10
r getex foo persist
assert_equal -1
[
r ttl foo
]
}
test
"GETEX no option"
{
r del foo
r set foo bar
r getex foo
assert_equal bar
[
r getex foo
]
}
test
"GETEX syntax errors"
{
set ex
{}
catch
{
r getex foo non-existent-option
}
ex
set ex
}
{
*syntax*
}
test
"GETEX no arguments"
{
set ex
{}
catch
{
r getex
}
ex
set ex
}
{
*wrong number of arguments*
}
test
"GETDEL command"
{
r del foo
r set foo bar
assert_equal bar
[
r getdel foo
]
assert_equal
{}
[
r getdel foo
]
}
test
{
GETDEL propagate as DEL command to replica
}
{
set repl
[
attach_to_replication_stream
]
r set foo bar
r getdel foo
assert_replication_stream $repl
{
{
select *
}
{
set foo bar
}
{
del foo
}
}
}
test
{
GETEX without argument does not propagate to replica
}
{
set repl
[
attach_to_replication_stream
]
r set foo bar
r getex foo
r del foo
assert_replication_stream $repl
{
{
select *
}
{
set foo bar
}
{
del foo
}
}
}
test
{
MGET
}
{
test
{
MGET
}
{
r flushdb
r flushdb
r set foo BAR
r set foo BAR
...
@@ -437,6 +522,17 @@ start_server {tags {"string"}} {
...
@@ -437,6 +522,17 @@ start_server {tags {"string"}} {
assert
{
$ttl
<= 10 && $ttl > 5
}
assert
{
$ttl
<= 10 && $ttl > 5
}
}
}
test
"Extended SET EXAT option"
{
r del foo
r set foo bar exat
[
expr
[
clock seconds
]
+ 10
]
assert_range
[
r ttl foo
]
5 10
}
test
"Extended SET PXAT option"
{
r del foo
r set foo bar pxat
[
expr
[
clock milliseconds
]
+ 10000
]
assert_range
[
r ttl foo
]
5 10
}
test
{
Extended SET using multiple options at once
}
{
test
{
Extended SET using multiple options at once
}
{
r set foo val
r set foo val
assert
{[
r set foo bar xx px 10000
]
eq
{
OK
}}
assert
{[
r set foo bar xx px 10000
]
eq
{
OK
}}
...
...
tests/unit/type/zset.tcl
View file @
2dba1e39
...
@@ -7,6 +7,8 @@ start_server {tags {"zset"}} {
...
@@ -7,6 +7,8 @@ start_server {tags {"zset"}} {
}
}
proc basics
{
encoding
}
{
proc basics
{
encoding
}
{
set original_max_entries
[
lindex
[
r config get zset-max-ziplist-entries
]
1
]
set original_max_value
[
lindex
[
r config get zset-max-ziplist-value
]
1
]
if
{
$encoding
==
"ziplist"
}
{
if
{
$encoding
==
"ziplist"
}
{
r config set zset-max-ziplist-entries 128
r config set zset-max-ziplist-entries 128
r config set zset-max-ziplist-value 64
r config set zset-max-ziplist-value 64
...
@@ -713,6 +715,12 @@ start_server {tags {"zset"}} {
...
@@ -713,6 +715,12 @@ start_server {tags {"zset"}} {
assert_equal
{
b 3 c 5
}
[
r zinter 2 zseta zsetb withscores
]
assert_equal
{
b 3 c 5
}
[
r zinter 2 zseta zsetb withscores
]
}
}
test
"ZINTER RESP3 -
$encoding
"
{
r hello 3
assert_equal
{{
b 3.0
}
{
c 5.0
}}
[
r zinter 2 zseta zsetb withscores
]
}
r hello 2
test
"ZINTERSTORE with weights -
$encoding
"
{
test
"ZINTERSTORE with weights -
$encoding
"
{
assert_equal 2
[
r zinterstore zsetc 2 zseta zsetb weights 2 3
]
assert_equal 2
[
r zinterstore zsetc 2 zseta zsetb weights 2 3
]
assert_equal
{
b 7 c 12
}
[
r zrange zsetc 0 -1 withscores
]
assert_equal
{
b 7 c 12
}
[
r zrange zsetc 0 -1 withscores
]
...
@@ -919,6 +927,9 @@ start_server {tags {"zset"}} {
...
@@ -919,6 +927,9 @@ start_server {tags {"zset"}} {
assert_equal 0
[
r zcard z1
]
assert_equal 0
[
r zcard z1
]
assert_equal 1
[
r zcard z2
]
assert_equal 1
[
r zcard z2
]
}
}
r config set zset-max-ziplist-entries $original_max_entries
r config set zset-max-ziplist-value $original_max_value
}
}
basics ziplist
basics ziplist
...
@@ -1016,6 +1027,8 @@ start_server {tags {"zset"}} {
...
@@ -1016,6 +1027,8 @@ start_server {tags {"zset"}} {
}
}
proc stressers
{
encoding
}
{
proc stressers
{
encoding
}
{
set original_max_entries
[
lindex
[
r config get zset-max-ziplist-entries
]
1
]
set original_max_value
[
lindex
[
r config get zset-max-ziplist-value
]
1
]
if
{
$encoding
==
"ziplist"
}
{
if
{
$encoding
==
"ziplist"
}
{
# Little extra to allow proper fuzzing in the sorting stresser
# Little extra to allow proper fuzzing in the sorting stresser
r config set zset-max-ziplist-entries 256
r config set zset-max-ziplist-entries 256
...
@@ -1440,6 +1453,8 @@ start_server {tags {"zset"}} {
...
@@ -1440,6 +1453,8 @@ start_server {tags {"zset"}} {
r zadd zset 0 foo
r zadd zset 0 foo
assert_equal
{
zset foo 0
}
[
$rd
read
]
assert_equal
{
zset foo 0
}
[
$rd
read
]
}
}
r config set zset-max-ziplist-entries $original_max_entries
r config set zset-max-ziplist-value $original_max_value
}
}
tags
{
"slow"
}
{
tags
{
"slow"
}
{
...
@@ -1481,6 +1496,12 @@ start_server {tags {"zset"}} {
...
@@ -1481,6 +1496,12 @@ start_server {tags {"zset"}} {
r zrange z2 0 -1 withscores
r zrange z2 0 -1 withscores
}
{
a 1 b 2 c 3 d 4
}
}
{
a 1 b 2 c 3 d 4
}
test
{
ZRANGESTORE RESP3
}
{
r hello 3
r zrange z2 0 -1 withscores
}
{{
a 1.0
}
{
b 2.0
}
{
c 3.0
}
{
d 4.0
}}
r hello 2
test
{
ZRANGESTORE range
}
{
test
{
ZRANGESTORE range
}
{
set res
[
r zrangestore z2 z1 1 2
]
set res
[
r zrangestore z2 z1 1 2
]
assert_equal $res 2
assert_equal $res 2
...
@@ -1554,4 +1575,171 @@ start_server {tags {"zset"}} {
...
@@ -1554,4 +1575,171 @@ start_server {tags {"zset"}} {
catch
{
r zrangebyscore z1 0 -1 REV
}
err
catch
{
r zrangebyscore z1 0 -1 REV
}
err
assert_match
"*syntax*"
$err
assert_match
"*syntax*"
$err
}
}
proc get_keys
{
l
}
{
set res
{}
foreach
{
score key
}
$l
{
lappend res $key
}
return $res
}
foreach
{
type contents
}
"ziplist {1 a 2 b 3 c} skiplist {1 a 2 b 3
[
randstring 70 90 alpha
]
}"
{
set original_max_value
[
lindex
[
r config get zset-max-ziplist-value
]
1
]
r config set zset-max-ziplist-value 10
create_zset myzset $contents
assert_encoding $type myzset
test
"ZRANDMEMBER -
$type
"
{
unset -nocomplain myzset
array set myzset
{}
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
set key
[
r zrandmember myzset
]
set myzset
(
$key
)
1
}
assert_equal
[
lsort
[
get_keys $contents
]]
[
lsort
[
array names myzset
]]
}
r config set zset-max-ziplist-value $original_max_value
}
test
"ZRANDMEMBER with RESP3"
{
r hello 3
set res
[
r zrandmember myzset 3 withscores
]
assert_equal
[
llength $res
]
3
assert_equal
[
llength
[
lindex $res 1
]]
2
set res
[
r zrandmember myzset 3
]
assert_equal
[
llength $res
]
3
assert_equal
[
llength
[
lindex $res 1
]]
1
}
r hello 2
test
"ZRANDMEMBER count of 0 is handled correctly"
{
r zrandmember myzset 0
}
{}
test
"ZRANDMEMBER with <count> against non existing key"
{
r zrandmember nonexisting_key 100
}
{}
foreach
{
type contents
}
"
skiplist {1 a 2 b 3 c 4 d 5 e 6 f 7 g 7 h 9 i 10
[
randstring 70 90 alpha
]
}
ziplist {1 a 2 b 3 c 4 d 5 e 6 f 7 g 7 h 9 i 10 j} "
{
test
"ZRANDMEMBER with <count> -
$type
"
{
set original_max_value
[
lindex
[
r config get zset-max-ziplist-value
]
1
]
r config set zset-max-ziplist-value 10
create_zset myzset $contents
assert_encoding $type myzset
# create a dict for easy lookup
unset -nocomplain mydict
foreach
{
k v
}
[
r zrange myzset 0 -1 withscores
]
{
dict append mydict $k $v
}
# We'll stress different parts of the code, see the implementation
# of ZRANDMEMBER for more information, but basically there are
# four different code paths.
# PATH 1: Use negative count.
# 1) Check that it returns repeated elements with and without values.
set res
[
r zrandmember myzset -20
]
assert_equal
[
llength $res
]
20
# again with WITHSCORES
set res
[
r zrandmember myzset -20 withscores
]
assert_equal
[
llength $res
]
40
# 2) Check that all the elements actually belong to the original zset.
foreach
{
key val
}
$res
{
assert
{[
dict exists $mydict $key
]}
}
# 3) Check that eventually all the elements are returned.
# Use both WITHSCORES and without
unset -nocomplain auxset
set iterations 1000
while
{
$iterations
!= 0
}
{
incr iterations -1
if
{[
expr
{
$iterations
% 2
}]
== 0
}
{
set res
[
r zrandmember myzset -3 withscores
]
foreach
{
key val
}
$res
{
dict append auxset $key $val
}
}
else
{
set res
[
r zrandmember myzset -3
]
foreach key $res
{
dict append auxset $key $val
}
}
if
{[
lsort
[
dict keys $mydict
]]
eq
[
lsort
[
dict keys $auxset
]]}
{
break
;
}
}
assert
{
$iterations
!= 0
}
# PATH 2: positive count
(
unique behavior
)
with requested size
# equal or greater than set size.
foreach size
{
10 20
}
{
set res
[
r zrandmember myzset $size
]
assert_equal
[
llength $res
]
10
assert_equal
[
lsort $res
]
[
lsort
[
dict keys $mydict
]]
# again with WITHSCORES
set res
[
r zrandmember myzset $size withscores
]
assert_equal
[
llength $res
]
20
assert_equal
[
lsort $res
]
[
lsort $mydict
]
}
# PATH 3: Ask almost as elements as there are in the set.
# In this case the implementation will duplicate the original
# set and will remove random elements up to the requested size.
#
# PATH 4: Ask a number of elements definitely smaller than
# the set size.
#
# We can test both the code paths just changing the size but
# using the same code.
foreach size
{
8 2
}
{
set res
[
r zrandmember myzset $size
]
assert_equal
[
llength $res
]
$size
# again with WITHSCORES
set res
[
r zrandmember myzset $size withscores
]
assert_equal
[
llength $res
]
[
expr
{
$size
* 2
}]
# 1) Check that all the elements actually belong to the
# original set.
foreach ele
[
dict keys $res
]
{
assert
{[
dict exists $mydict $ele
]}
}
# 2) Check that eventually all the elements are returned.
# Use both WITHSCORES and without
unset -nocomplain auxset
set iterations 1000
while
{
$iterations
!= 0
}
{
incr iterations -1
if
{[
expr
{
$iterations
% 2
}]
== 0
}
{
set res
[
r zrandmember myzset $size withscores
]
foreach
{
key value
}
$res
{
dict append auxset $key $value
}
}
else
{
set res
[
r zrandmember myzset $size
]
foreach key $res
{
dict append auxset $key
}
}
if
{[
lsort
[
dict keys $mydict
]]
eq
[
lsort
[
dict keys $auxset
]]}
{
break
;
}
}
assert
{
$iterations
!= 0
}
}
}
r config set zset-max-ziplist-value $original_max_value
}
}
}
tests/unit/wait.tcl
View file @
2dba1e39
source tests/support/cli.tcl
source tests/support/cli.tcl
start_server
{
tags
{
"wait"
}}
{
start_server
{
tags
{
"wait
network
"
}}
{
start_server
{}
{
start_server
{}
{
set slave
[
srv 0 client
]
set slave
[
srv 0 client
]
set slave_host
[
srv 0 host
]
set slave_host
[
srv 0 host
]
...
...
Prev
1
2
3
4
5
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