summaryrefslogtreecommitdiffstats
path: root/tests/unit/expire.tcl
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:40:54 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:40:54 +0000
commit317c0644ccf108aa23ef3fd8358bd66c2840bfc0 (patch)
treec417b3d25c86b775989cb5ac042f37611b626c8a /tests/unit/expire.tcl
parentInitial commit. (diff)
downloadredis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.tar.xz
redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.zip
Adding upstream version 5:7.2.4.upstream/5%7.2.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/unit/expire.tcl')
-rw-r--r--tests/unit/expire.tcl835
1 files changed, 835 insertions, 0 deletions
diff --git a/tests/unit/expire.tcl b/tests/unit/expire.tcl
new file mode 100644
index 0000000..fc0ef61
--- /dev/null
+++ b/tests/unit/expire.tcl
@@ -0,0 +1,835 @@
+start_server {tags {"expire"}} {
+ test {EXPIRE - set timeouts multiple times} {
+ r set x foobar
+ set v1 [r expire x 5]
+ set v2 [r ttl x]
+ set v3 [r expire x 10]
+ set v4 [r ttl x]
+ r expire x 2
+ list $v1 $v2 $v3 $v4
+ } {1 [45] 1 10}
+
+ test {EXPIRE - It should be still possible to read 'x'} {
+ r get x
+ } {foobar}
+
+ tags {"slow"} {
+ test {EXPIRE - After 2.1 seconds the key should no longer be here} {
+ after 2100
+ list [r get x] [r exists x]
+ } {{} 0}
+ }
+
+ test {EXPIRE - write on expire should work} {
+ r del x
+ r lpush x foo
+ r expire x 1000
+ r lpush x bar
+ r lrange x 0 -1
+ } {bar foo}
+
+ test {EXPIREAT - Check for EXPIRE alike behavior} {
+ r del x
+ r set x foo
+ r expireat x [expr [clock seconds]+15]
+ r ttl x
+ } {1[345]}
+
+ test {SETEX - Set + Expire combo operation. Check for TTL} {
+ r setex x 12 test
+ r ttl x
+ } {1[012]}
+
+ test {SETEX - Check value} {
+ r get x
+ } {test}
+
+ test {SETEX - Overwrite old key} {
+ r setex y 1 foo
+ r get y
+ } {foo}
+
+ tags {"slow"} {
+ test {SETEX - Wait for the key to expire} {
+ after 1100
+ r get y
+ } {}
+ }
+
+ test {SETEX - Wrong time parameter} {
+ catch {r setex z -10 foo} e
+ set _ $e
+ } {*invalid expire*}
+
+ test {PERSIST can undo an EXPIRE} {
+ r set x foo
+ r expire x 50
+ list [r ttl x] [r persist x] [r ttl x] [r get x]
+ } {50 1 -1 foo}
+
+ test {PERSIST returns 0 against non existing or non volatile keys} {
+ r set x foo
+ list [r persist foo] [r persist nokeyatall]
+ } {0 0}
+
+ test {EXPIRE precision is now the millisecond} {
+ # This test is very likely to do a false positive if the
+ # server is under pressure, so if it does not work give it a few more
+ # chances.
+ for {set j 0} {$j < 30} {incr j} {
+ r del x
+ r setex x 1 somevalue
+ after 800
+ set a [r get x]
+ if {$a ne {somevalue}} continue
+ after 300
+ set b [r get x]
+ if {$b eq {}} break
+ }
+ if {$::verbose} {
+ puts "millisecond expire test attempts: $j"
+ }
+ assert_equal $a {somevalue}
+ assert_equal $b {}
+ }
+
+ test "PSETEX can set sub-second expires" {
+ # This test is very likely to do a false positive if the server is
+ # under pressure, so if it does not work give it a few more chances.
+ for {set j 0} {$j < 50} {incr j} {
+ r del x
+ r psetex x 100 somevalue
+ set a [r get x]
+ after 101
+ set b [r get x]
+
+ if {$a eq {somevalue} && $b eq {}} break
+ }
+ if {$::verbose} { puts "PSETEX sub-second expire test attempts: $j" }
+ list $a $b
+ } {somevalue {}}
+
+ test "PEXPIRE can set sub-second expires" {
+ # This test is very likely to do a false positive if the server is
+ # under pressure, so if it does not work give it a few more chances.
+ for {set j 0} {$j < 50} {incr j} {
+ r set x somevalue
+ r pexpire x 100
+ set c [r get x]
+ after 101
+ set d [r get x]
+
+ if {$c eq {somevalue} && $d eq {}} break
+ }
+ if {$::verbose} { puts "PEXPIRE sub-second expire test attempts: $j" }
+ list $c $d
+ } {somevalue {}}
+
+ test "PEXPIREAT can set sub-second expires" {
+ # This test is very likely to do a false positive if the server is
+ # under pressure, so if it does not work give it a few more chances.
+ for {set j 0} {$j < 50} {incr j} {
+ r set x somevalue
+ set now [r time]
+ r pexpireat x [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+200]
+ set e [r get x]
+ after 201
+ set f [r get x]
+
+ if {$e eq {somevalue} && $f eq {}} break
+ }
+ if {$::verbose} { puts "PEXPIREAT sub-second expire test attempts: $j" }
+ list $e $f
+ } {somevalue {}}
+
+ test {TTL returns time to live in seconds} {
+ r del x
+ r setex x 10 somevalue
+ set ttl [r ttl x]
+ assert {$ttl > 8 && $ttl <= 10}
+ }
+
+ test {PTTL returns time to live in milliseconds} {
+ r del x
+ r setex x 1 somevalue
+ set ttl [r pttl x]
+ assert {$ttl > 500 && $ttl <= 1000}
+ }
+
+ test {TTL / PTTL / EXPIRETIME / PEXPIRETIME return -1 if key has no expire} {
+ r del x
+ r set x hello
+ list [r ttl x] [r pttl x] [r expiretime x] [r pexpiretime x]
+ } {-1 -1 -1 -1}
+
+ test {TTL / PTTL / EXPIRETIME / PEXPIRETIME return -2 if key does not exit} {
+ r del x
+ list [r ttl x] [r pttl x] [r expiretime x] [r pexpiretime x]
+ } {-2 -2 -2 -2}
+
+ test {EXPIRETIME returns absolute expiration time in seconds} {
+ r del x
+ set abs_expire [expr [clock seconds] + 100]
+ r set x somevalue exat $abs_expire
+ assert_equal [r expiretime x] $abs_expire
+ }
+
+ test {PEXPIRETIME returns absolute expiration time in milliseconds} {
+ r del x
+ set abs_expire [expr [clock milliseconds] + 100000]
+ r set x somevalue pxat $abs_expire
+ assert_equal [r pexpiretime x] $abs_expire
+ }
+
+ test {Redis should actively expire keys incrementally} {
+ r flushdb
+ r psetex key1 500 a
+ r psetex key2 500 a
+ r psetex key3 500 a
+ assert_equal 3 [r dbsize]
+ # Redis expires random keys ten times every second so we are
+ # fairly sure that all the three keys should be evicted after
+ # two seconds.
+ wait_for_condition 20 100 {
+ [r dbsize] eq 0
+ } fail {
+ "Keys did not actively expire."
+ }
+ }
+
+ test {Redis should lazy expire keys} {
+ r flushdb
+ r debug set-active-expire 0
+ r psetex key1{t} 500 a
+ r psetex key2{t} 500 a
+ r psetex key3{t} 500 a
+ set size1 [r dbsize]
+ # Redis expires random keys ten times every second so we are
+ # fairly sure that all the three keys should be evicted after
+ # one second.
+ after 1000
+ set size2 [r dbsize]
+ r mget key1{t} key2{t} key3{t}
+ set size3 [r dbsize]
+ r debug set-active-expire 1
+ list $size1 $size2 $size3
+ } {3 3 0} {needs:debug}
+
+ test {EXPIRE should not resurrect keys (issue #1026)} {
+ r debug set-active-expire 0
+ r set foo bar
+ r pexpire foo 500
+ after 1000
+ r expire foo 10
+ r debug set-active-expire 1
+ r exists foo
+ } {0} {needs:debug}
+
+ test {5 keys in, 5 keys out} {
+ r flushdb
+ r set a c
+ r expire a 5
+ r set t c
+ r set e c
+ r set s c
+ r set foo b
+ assert_equal [lsort [r keys *]] {a e foo s t}
+ r del a ; # Do not leak volatile keys to other tests
+ }
+
+ test {EXPIRE with empty string as TTL should report an error} {
+ r set foo bar
+ catch {r expire foo ""} e
+ set e
+ } {*not an integer*}
+
+ test {SET with EX with big integer should report an error} {
+ catch {r set foo bar EX 10000000000000000} e
+ set e
+ } {ERR invalid expire time in 'set' command}
+
+ test {SET with EX with smallest integer should report an error} {
+ catch {r SET foo bar EX -9999999999999999} e
+ set e
+ } {ERR invalid expire time in 'set' command}
+
+ test {GETEX with big integer should report an error} {
+ r set foo bar
+ catch {r GETEX foo EX 10000000000000000} e
+ set e
+ } {ERR invalid expire time in 'getex' command}
+
+ test {GETEX with smallest integer should report an error} {
+ r set foo bar
+ catch {r GETEX foo EX -9999999999999999} e
+ set e
+ } {ERR invalid expire time in 'getex' command}
+
+ test {EXPIRE with big integer overflows when converted to milliseconds} {
+ r set foo bar
+
+ # Hit `when > LLONG_MAX - basetime`
+ assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 9223370399119966}
+
+ # Hit `when > LLONG_MAX / 1000`
+ assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 9223372036854776}
+ assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 10000000000000000}
+ assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 18446744073709561}
+
+ assert_equal {-1} [r ttl foo]
+ }
+
+ test {PEXPIRE with big integer overflow when basetime is added} {
+ r set foo bar
+ catch {r PEXPIRE foo 9223372036854770000} e
+ set e
+ } {ERR invalid expire time in 'pexpire' command}
+
+ test {EXPIRE with big negative integer} {
+ r set foo bar
+
+ # Hit `when < LLONG_MIN / 1000`
+ assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo -9223372036854776}
+ assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo -9999999999999999}
+
+ r ttl foo
+ } {-1}
+
+ test {PEXPIREAT with big integer works} {
+ r set foo bar
+ r PEXPIREAT foo 9223372036854770000
+ } {1}
+
+ test {PEXPIREAT with big negative integer works} {
+ r set foo bar
+ r PEXPIREAT foo -9223372036854770000
+ r ttl foo
+ } {-2}
+
+ # Start a new server with empty data and AOF file.
+ start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} {
+ test {All time-to-live(TTL) in commands are propagated as absolute timestamp in milliseconds in AOF} {
+ # This test makes sure that expire times are propagated as absolute
+ # times to the AOF file and not as relative time, so that when the AOF
+ # is reloaded the TTLs are not being shifted forward to the future.
+ # We want the time to logically pass when the server is restarted!
+
+ set aof [get_last_incr_aof_path r]
+
+ # Apply each TTL-related command to a unique key
+ # SET commands
+ r set foo1 bar ex 100
+ r set foo2 bar px 100000
+ r set foo3 bar exat [expr [clock seconds]+100]
+ r set foo4 bar PXAT [expr [clock milliseconds]+100000]
+ r setex foo5 100 bar
+ r psetex foo6 100000 bar
+ # EXPIRE-family commands
+ r set foo7 bar
+ r expire foo7 100
+ r set foo8 bar
+ r pexpire foo8 100000
+ r set foo9 bar
+ r expireat foo9 [expr [clock seconds]+100]
+ r set foo10 bar
+ r pexpireat foo10 [expr [clock seconds]*1000+100000]
+ r set foo11 bar
+ r expireat foo11 [expr [clock seconds]-100]
+ # GETEX commands
+ r set foo12 bar
+ r getex foo12 ex 100
+ r set foo13 bar
+ r getex foo13 px 100000
+ r set foo14 bar
+ r getex foo14 exat [expr [clock seconds]+100]
+ r set foo15 bar
+ r getex foo15 pxat [expr [clock milliseconds]+100000]
+ # RESTORE commands
+ r set foo16 bar
+ set encoded [r dump foo16]
+ r restore foo17 100000 $encoded
+ r restore foo18 [expr [clock milliseconds]+100000] $encoded absttl
+
+ # Assert that each TTL-relatd command are persisted with absolute timestamps in AOF
+ assert_aof_content $aof {
+ {select *}
+ {set foo1 bar PXAT *}
+ {set foo2 bar PXAT *}
+ {set foo3 bar PXAT *}
+ {set foo4 bar PXAT *}
+ {set foo5 bar PXAT *}
+ {set foo6 bar PXAT *}
+ {set foo7 bar}
+ {pexpireat foo7 *}
+ {set foo8 bar}
+ {pexpireat foo8 *}
+ {set foo9 bar}
+ {pexpireat foo9 *}
+ {set foo10 bar}
+ {pexpireat foo10 *}
+ {set foo11 bar}
+ {del foo11}
+ {set foo12 bar}
+ {pexpireat foo12 *}
+ {set foo13 bar}
+ {pexpireat foo13 *}
+ {set foo14 bar}
+ {pexpireat foo14 *}
+ {set foo15 bar}
+ {pexpireat foo15 *}
+ {set foo16 bar}
+ {restore foo17 * {*} ABSTTL}
+ {restore foo18 * {*} absttl}
+ }
+
+ # Remember the absolute TTLs of all the keys
+ set ttl1 [r pexpiretime foo1]
+ set ttl2 [r pexpiretime foo2]
+ set ttl3 [r pexpiretime foo3]
+ set ttl4 [r pexpiretime foo4]
+ set ttl5 [r pexpiretime foo5]
+ set ttl6 [r pexpiretime foo6]
+ set ttl7 [r pexpiretime foo7]
+ set ttl8 [r pexpiretime foo8]
+ set ttl9 [r pexpiretime foo9]
+ set ttl10 [r pexpiretime foo10]
+ assert_equal "-2" [r pexpiretime foo11] ; # foo11 is gone
+ set ttl12 [r pexpiretime foo12]
+ set ttl13 [r pexpiretime foo13]
+ set ttl14 [r pexpiretime foo14]
+ set ttl15 [r pexpiretime foo15]
+ assert_equal "-1" [r pexpiretime foo16] ; # foo16 has no TTL
+ set ttl17 [r pexpiretime foo17]
+ set ttl18 [r pexpiretime foo18]
+
+ # Let some time pass and reload data from AOF
+ after 2000
+ r debug loadaof
+
+ # Assert that relative TTLs are roughly the same
+ assert_range [r ttl foo1] 90 98
+ assert_range [r ttl foo2] 90 98
+ assert_range [r ttl foo3] 90 98
+ assert_range [r ttl foo4] 90 98
+ assert_range [r ttl foo5] 90 98
+ assert_range [r ttl foo6] 90 98
+ assert_range [r ttl foo7] 90 98
+ assert_range [r ttl foo8] 90 98
+ assert_range [r ttl foo9] 90 98
+ assert_range [r ttl foo10] 90 98
+ assert_equal [r ttl foo11] "-2" ; # foo11 is gone
+ assert_range [r ttl foo12] 90 98
+ assert_range [r ttl foo13] 90 98
+ assert_range [r ttl foo14] 90 98
+ assert_range [r ttl foo15] 90 98
+ assert_equal [r ttl foo16] "-1" ; # foo16 has no TTL
+ assert_range [r ttl foo17] 90 98
+ assert_range [r ttl foo18] 90 98
+
+ # Assert that all keys have restored the same absolute TTLs from AOF
+ assert_equal [r pexpiretime foo1] $ttl1
+ assert_equal [r pexpiretime foo2] $ttl2
+ assert_equal [r pexpiretime foo3] $ttl3
+ assert_equal [r pexpiretime foo4] $ttl4
+ assert_equal [r pexpiretime foo5] $ttl5
+ assert_equal [r pexpiretime foo6] $ttl6
+ assert_equal [r pexpiretime foo7] $ttl7
+ assert_equal [r pexpiretime foo8] $ttl8
+ assert_equal [r pexpiretime foo9] $ttl9
+ assert_equal [r pexpiretime foo10] $ttl10
+ assert_equal [r pexpiretime foo11] "-2" ; # foo11 is gone
+ assert_equal [r pexpiretime foo12] $ttl12
+ assert_equal [r pexpiretime foo13] $ttl13
+ assert_equal [r pexpiretime foo14] $ttl14
+ assert_equal [r pexpiretime foo15] $ttl15
+ assert_equal [r pexpiretime foo16] "-1" ; # foo16 has no TTL
+ assert_equal [r pexpiretime foo17] $ttl17
+ assert_equal [r pexpiretime foo18] $ttl18
+ } {} {needs:debug}
+ }
+
+ test {All TTL in commands are propagated as absolute timestamp in replication stream} {
+ # Make sure that both relative and absolute expire commands are propagated
+ # as absolute to replicas for two reasons:
+ # 1) We want to avoid replicas retaining data much longer than primary due
+ # to replication lag.
+ # 2) We want to unify the way TTLs are replicated in both RDB and replication
+ # stream, which is as absolute timestamps.
+ # See: https://github.com/redis/redis/issues/8433
+
+ r flushall ; # Clean up keyspace to avoid interference by keys from other tests
+ set repl [attach_to_replication_stream]
+ # SET commands
+ r set foo1 bar ex 200
+ r set foo1 bar px 100000
+ r set foo1 bar exat [expr [clock seconds]+100]
+ r set foo1 bar pxat [expr [clock milliseconds]+100000]
+ r setex foo1 100 bar
+ r psetex foo1 100000 bar
+ r set foo2 bar
+ # EXPIRE-family commands
+ r expire foo2 100
+ r pexpire foo2 100000
+ r set foo3 bar
+ r expireat foo3 [expr [clock seconds]+100]
+ r pexpireat foo3 [expr [clock seconds]*1000+100000]
+ r expireat foo3 [expr [clock seconds]-100]
+ # GETEX-family commands
+ r set foo4 bar
+ r getex foo4 ex 200
+ r getex foo4 px 200000
+ r getex foo4 exat [expr [clock seconds]+100]
+ r getex foo4 pxat [expr [clock milliseconds]+100000]
+ # RESTORE commands
+ r set foo5 bar
+ set encoded [r dump foo5]
+ r restore foo6 100000 $encoded
+ r restore foo7 [expr [clock milliseconds]+100000] $encoded absttl
+
+ assert_replication_stream $repl {
+ {select *}
+ {set foo1 bar PXAT *}
+ {set foo1 bar PXAT *}
+ {set foo1 bar PXAT *}
+ {set foo1 bar pxat *}
+ {set foo1 bar PXAT *}
+ {set foo1 bar PXAT *}
+ {set foo2 bar}
+ {pexpireat foo2 *}
+ {pexpireat foo2 *}
+ {set foo3 bar}
+ {pexpireat foo3 *}
+ {pexpireat foo3 *}
+ {del foo3}
+ {set foo4 bar}
+ {pexpireat foo4 *}
+ {pexpireat foo4 *}
+ {pexpireat foo4 *}
+ {pexpireat foo4 *}
+ {set foo5 bar}
+ {restore foo6 * {*} ABSTTL}
+ {restore foo7 * {*} absttl}
+ }
+ close_replication_stream $repl
+ } {} {needs:repl}
+
+ # Start another server to test replication of TTLs
+ start_server {tags {needs:repl external:skip}} {
+ # Set the outer layer server as primary
+ set primary [srv -1 client]
+ set primary_host [srv -1 host]
+ set primary_port [srv -1 port]
+ # Set this inner layer server as replica
+ set replica [srv 0 client]
+
+ test {First server should have role slave after REPLICAOF} {
+ $replica replicaof $primary_host $primary_port
+ wait_for_condition 50 100 {
+ [s 0 role] eq {slave}
+ } else {
+ fail "Replication not started."
+ }
+ }
+
+ test {For all replicated TTL-related commands, absolute expire times are identical on primary and replica} {
+ # Apply each TTL-related command to a unique key on primary
+ # SET commands
+ $primary set foo1 bar ex 100
+ $primary set foo2 bar px 100000
+ $primary set foo3 bar exat [expr [clock seconds]+100]
+ $primary set foo4 bar pxat [expr [clock milliseconds]+100000]
+ $primary setex foo5 100 bar
+ $primary psetex foo6 100000 bar
+ # EXPIRE-family commands
+ $primary set foo7 bar
+ $primary expire foo7 100
+ $primary set foo8 bar
+ $primary pexpire foo8 100000
+ $primary set foo9 bar
+ $primary expireat foo9 [expr [clock seconds]+100]
+ $primary set foo10 bar
+ $primary pexpireat foo10 [expr [clock milliseconds]+100000]
+ # GETEX commands
+ $primary set foo11 bar
+ $primary getex foo11 ex 100
+ $primary set foo12 bar
+ $primary getex foo12 px 100000
+ $primary set foo13 bar
+ $primary getex foo13 exat [expr [clock seconds]+100]
+ $primary set foo14 bar
+ $primary getex foo14 pxat [expr [clock milliseconds]+100000]
+ # RESTORE commands
+ $primary set foo15 bar
+ set encoded [$primary dump foo15]
+ $primary restore foo16 100000 $encoded
+ $primary restore foo17 [expr [clock milliseconds]+100000] $encoded absttl
+
+ # Wait for replica to get the keys and TTLs
+ assert {[$primary wait 1 0] == 1}
+
+ # Verify absolute TTLs are identical on primary and replica for all keys
+ # This is because TTLs are always replicated as absolute values
+ foreach key [$primary keys *] {
+ assert_equal [$primary pexpiretime $key] [$replica pexpiretime $key]
+ }
+ }
+
+ test {expired key which is created in writeable replicas should be deleted by active expiry} {
+ $primary flushall
+ $replica config set replica-read-only no
+ foreach {yes_or_no} {yes no} {
+ $replica config set appendonly $yes_or_no
+ waitForBgrewriteaof $replica
+ set prev_expired [s expired_keys]
+ $replica set foo bar PX 1
+ wait_for_condition 100 10 {
+ [s expired_keys] eq $prev_expired + 1
+ } else {
+ fail "key not expired"
+ }
+ assert_equal {} [$replica get foo]
+ }
+ }
+ }
+
+ test {SET command will remove expire} {
+ r set foo bar EX 100
+ r set foo bar
+ r ttl foo
+ } {-1}
+
+ test {SET - use KEEPTTL option, TTL should not be removed} {
+ r set foo bar EX 100
+ r set foo bar KEEPTTL
+ set ttl [r ttl foo]
+ assert {$ttl <= 100 && $ttl > 90}
+ }
+
+ test {SET - use KEEPTTL option, TTL should not be removed after loadaof} {
+ r config set appendonly yes
+ r set foo bar EX 100
+ r set foo bar2 KEEPTTL
+ after 2000
+ r debug loadaof
+ set ttl [r ttl foo]
+ assert {$ttl <= 98 && $ttl > 90}
+ } {} {needs:debug}
+
+ test {GETEX use of PERSIST option should remove TTL} {
+ r set foo bar EX 100
+ r getex foo PERSIST
+ r ttl foo
+ } {-1}
+
+ test {GETEX use of PERSIST option should remove TTL after loadaof} {
+ r config set appendonly yes
+ r set foo bar EX 100
+ r getex foo PERSIST
+ r debug loadaof
+ r ttl foo
+ } {-1} {needs:debug}
+
+ test {GETEX propagate as to replica as PERSIST, DEL, or nothing} {
+ # In the above tests, many keys with random expiration times are set, flush
+ # the DBs to avoid active expiry kicking in and messing the replication streams.
+ r flushall
+ set repl [attach_to_replication_stream]
+ r set foo bar EX 100
+ r getex foo PERSIST
+ r getex foo
+ r getex foo exat [expr [clock seconds]-100]
+ assert_replication_stream $repl {
+ {select *}
+ {set foo bar PXAT *}
+ {persist foo}
+ {del foo}
+ }
+ close_replication_stream $repl
+ } {} {needs:repl}
+
+ test {EXPIRE with NX option on a key with ttl} {
+ r SET foo bar EX 100
+ assert_equal [r EXPIRE foo 200 NX] 0
+ assert_range [r TTL foo] 50 100
+ } {}
+
+ test {EXPIRE with NX option on a key without ttl} {
+ r SET foo bar
+ assert_equal [r EXPIRE foo 200 NX] 1
+ assert_range [r TTL foo] 100 200
+ } {}
+
+ test {EXPIRE with XX option on a key with ttl} {
+ r SET foo bar EX 100
+ assert_equal [r EXPIRE foo 200 XX] 1
+ assert_range [r TTL foo] 100 200
+ } {}
+
+ test {EXPIRE with XX option on a key without ttl} {
+ r SET foo bar
+ assert_equal [r EXPIRE foo 200 XX] 0
+ assert_equal [r TTL foo] -1
+ } {}
+
+ test {EXPIRE with GT option on a key with lower ttl} {
+ r SET foo bar EX 100
+ assert_equal [r EXPIRE foo 200 GT] 1
+ assert_range [r TTL foo] 100 200
+ } {}
+
+ test {EXPIRE with GT option on a key with higher ttl} {
+ r SET foo bar EX 200
+ assert_equal [r EXPIRE foo 100 GT] 0
+ assert_range [r TTL foo] 100 200
+ } {}
+
+ test {EXPIRE with GT option on a key without ttl} {
+ r SET foo bar
+ assert_equal [r EXPIRE foo 200 GT] 0
+ assert_equal [r TTL foo] -1
+ } {}
+
+ test {EXPIRE with LT option on a key with higher ttl} {
+ r SET foo bar EX 100
+ assert_equal [r EXPIRE foo 200 LT] 0
+ assert_range [r TTL foo] 50 100
+ } {}
+
+ test {EXPIRE with LT option on a key with lower ttl} {
+ r SET foo bar EX 200
+ assert_equal [r EXPIRE foo 100 LT] 1
+ assert_range [r TTL foo] 50 100
+ } {}
+
+ test {EXPIRE with LT option on a key without ttl} {
+ r SET foo bar
+ assert_equal [r EXPIRE foo 100 LT] 1
+ assert_range [r TTL foo] 50 100
+ } {}
+
+ test {EXPIRE with LT and XX option on a key with ttl} {
+ r SET foo bar EX 200
+ assert_equal [r EXPIRE foo 100 LT XX] 1
+ assert_range [r TTL foo] 50 100
+ } {}
+
+ test {EXPIRE with LT and XX option on a key without ttl} {
+ r SET foo bar
+ assert_equal [r EXPIRE foo 200 LT XX] 0
+ assert_equal [r TTL foo] -1
+ } {}
+
+ test {EXPIRE with conflicting options: LT GT} {
+ catch {r EXPIRE foo 200 LT GT} e
+ set e
+ } {ERR GT and LT options at the same time are not compatible}
+
+ test {EXPIRE with conflicting options: NX GT} {
+ catch {r EXPIRE foo 200 NX GT} e
+ set e
+ } {ERR NX and XX, GT or LT options at the same time are not compatible}
+
+ test {EXPIRE with conflicting options: NX LT} {
+ catch {r EXPIRE foo 200 NX LT} e
+ set e
+ } {ERR NX and XX, GT or LT options at the same time are not compatible}
+
+ test {EXPIRE with conflicting options: NX XX} {
+ catch {r EXPIRE foo 200 NX XX} e
+ set e
+ } {ERR NX and XX, GT or LT options at the same time are not compatible}
+
+ test {EXPIRE with unsupported options} {
+ catch {r EXPIRE foo 200 AB} e
+ set e
+ } {ERR Unsupported option AB}
+
+ test {EXPIRE with unsupported options} {
+ catch {r EXPIRE foo 200 XX AB} e
+ set e
+ } {ERR Unsupported option AB}
+
+ test {EXPIRE with negative expiry} {
+ r SET foo bar EX 100
+ assert_equal [r EXPIRE foo -10 LT] 1
+ assert_equal [r TTL foo] -2
+ } {}
+
+ test {EXPIRE with negative expiry on a non-valitale key} {
+ r SET foo bar
+ assert_equal [r EXPIRE foo -10 LT] 1
+ assert_equal [r TTL foo] -2
+ } {}
+
+ test {EXPIRE with non-existed key} {
+ assert_equal [r EXPIRE none 100 NX] 0
+ assert_equal [r EXPIRE none 100 XX] 0
+ assert_equal [r EXPIRE none 100 GT] 0
+ assert_equal [r EXPIRE none 100 LT] 0
+ } {}
+
+ test {Redis should not propagate the read command on lazy expire} {
+ r debug set-active-expire 0
+ r flushall ; # Clean up keyspace to avoid interference by keys from other tests
+ r set foo bar PX 1
+ set repl [attach_to_replication_stream]
+ wait_for_condition 50 100 {
+ [r get foo] eq {}
+ } else {
+ fail "Replication not started."
+ }
+
+ # dummy command to verify nothing else gets into the replication stream.
+ r set x 1
+
+ assert_replication_stream $repl {
+ {select *}
+ {del foo}
+ {set x 1}
+ }
+ close_replication_stream $repl
+ assert_equal [r debug set-active-expire 1] {OK}
+ } {} {needs:debug}
+
+ test {SCAN: Lazy-expire should not be wrapped in MULTI/EXEC} {
+ r debug set-active-expire 0
+ r flushall
+
+ r set foo1 bar PX 1
+ r set foo2 bar PX 1
+ after 2
+
+ set repl [attach_to_replication_stream]
+
+ r scan 0
+
+ assert_replication_stream $repl {
+ {select *}
+ {del foo*}
+ {del foo*}
+ }
+ close_replication_stream $repl
+ assert_equal [r debug set-active-expire 1] {OK}
+ } {} {needs:debug}
+
+ test {RANDOMKEY: Lazy-expire should not be wrapped in MULTI/EXEC} {
+ r debug set-active-expire 0
+ r flushall
+
+ r set foo1 bar PX 1
+ r set foo2 bar PX 1
+ after 2
+
+ set repl [attach_to_replication_stream]
+
+ r randomkey
+
+ assert_replication_stream $repl {
+ {select *}
+ {del foo*}
+ {del foo*}
+ }
+ close_replication_stream $repl
+ assert_equal [r debug set-active-expire 1] {OK}
+ } {} {needs:debug}
+}