You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kvrocks.apache.org by ti...@apache.org on 2022/10/07 14:29:35 UTC

[incubator-kvrocks] branch unstable updated: Move TCL test unit/expire to Go case (#955)

This is an automated email from the ASF dual-hosted git repository.

tison pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/incubator-kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 6e52105  Move TCL test unit/expire to Go case  (#955)
6e52105 is described below

commit 6e521051ba81aa168312ed695a7bbaf06645a158
Author: HaveAnOrangeCat <ma...@gmail.com>
AuthorDate: Fri Oct 7 22:29:29 2022 +0800

    Move TCL test unit/expire to Go case  (#955)
    
    Co-authored-by: MaoChongxin <ch...@shopee.com>
    Co-authored-by: tison <wa...@gmail.com>
---
 tests/gocase/unit/expire/expire_test.go | 218 ++++++++++++++++++++++++++++++++
 tests/gocase/util/assertions.go         |  20 ++-
 tests/tcl/tests/test_helper.tcl         |   1 -
 tests/tcl/tests/unit/expire.tcl         | 206 ------------------------------
 4 files changed, 236 insertions(+), 209 deletions(-)

diff --git a/tests/gocase/unit/expire/expire_test.go b/tests/gocase/unit/expire/expire_test.go
new file mode 100644
index 0000000..158a7cb
--- /dev/null
+++ b/tests/gocase/unit/expire/expire_test.go
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package expire
+
+import (
+	"context"
+	"sort"
+	"testing"
+	"time"
+
+	"github.com/apache/incubator-kvrocks/tests/gocase/util"
+	"github.com/stretchr/testify/require"
+)
+
+func TestExpire(t *testing.T) {
+	svr := util.StartServer(t, map[string]string{})
+	defer svr.Close()
+
+	ctx := context.Background()
+	rdb := svr.NewClient()
+	defer func() { require.NoError(t, rdb.Close()) }()
+
+	t.Run("EXPIRE - set timeouts multiple times", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "x", "foobar", 0).Err())
+		require.True(t, rdb.Expire(ctx, "x", 5*time.Second).Val())
+		util.BetweenValues(t, rdb.TTL(ctx, "x").Val(), 4*time.Second, 5*time.Second)
+		require.True(t, rdb.Expire(ctx, "x", 10*time.Second).Val())
+		require.Equal(t, 10*time.Second, rdb.TTL(ctx, "x").Val())
+		require.NoError(t, rdb.Expire(ctx, "x", 2*time.Second).Err())
+	})
+
+	t.Run("EXPIRE - It should be still possible to read 'x'", func(t *testing.T) {
+		require.Equal(t, "foobar", rdb.Get(ctx, "x").Val())
+	})
+
+	t.Run("EXPIRE - After 3.1 seconds the key should no longer be here", func(t *testing.T) {
+		time.Sleep(3100 * time.Millisecond)
+		require.Equal(t, "", rdb.Get(ctx, "x").Val())
+		require.EqualValues(t, 0, rdb.Exists(ctx, "x").Val())
+	})
+
+	t.Run("EXPIRE - write on expire should work", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "x").Err())
+		require.NoError(t, rdb.LPush(ctx, "x", "foo").Err())
+		require.NoError(t, rdb.Expire(ctx, "x", 1000*time.Second).Err())
+		require.NoError(t, rdb.LPush(ctx, "x", "bar").Err())
+		require.Equal(t, []string{"bar", "foo"}, rdb.LRange(ctx, "x", 0, -1).Val())
+	})
+
+	t.Run("EXPIREAT - Check for EXPIRE alike behavior", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "x").Err())
+		require.NoError(t, rdb.Set(ctx, "x", "foo", 0).Err())
+		require.NoError(t, rdb.ExpireAt(ctx, "x", time.Now().Add(15*time.Second)).Err())
+		util.BetweenValues(t, rdb.TTL(ctx, "x").Val(), 13*time.Second, 16*time.Second)
+	})
+
+	t.Run("SETEX - Set + Expire combo operation. Check for TTL", func(t *testing.T) {
+		require.NoError(t, rdb.SetEx(ctx, "x", "test", 12*time.Second).Err())
+		util.BetweenValues(t, rdb.TTL(ctx, "x").Val(), 10*time.Second, 12*time.Second)
+	})
+
+	t.Run("SETEX - Check value", func(t *testing.T) {
+		require.Equal(t, "test", rdb.Get(ctx, "x").Val())
+	})
+
+	t.Run("SETEX - Overwrite old key", func(t *testing.T) {
+		require.NoError(t, rdb.SetEx(ctx, "y", "foo", 1*time.Second).Err())
+		require.Equal(t, "foo", rdb.Get(ctx, "y").Val())
+	})
+
+	t.Run("SETEX - Wait for the key to expire", func(t *testing.T) {
+		time.Sleep(2100 * time.Millisecond)
+		require.Equal(t, "", rdb.Get(ctx, "y").Val())
+	})
+
+	t.Run("SETEX - Wrong time parameter", func(t *testing.T) {
+		util.ErrorRegexp(t, rdb.SetEx(ctx, "z", "foo", -10).Err(), ".*invalid expire*.")
+	})
+
+	t.Run("PERSIST can undo an EXPIRE", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "x", "foo", 0).Err())
+		require.NoError(t, rdb.Expire(ctx, "x", 12*time.Second).Err())
+		util.BetweenValues(t, rdb.TTL(ctx, "x").Val(), 10*time.Second, 12*time.Second)
+		require.True(t, rdb.Persist(ctx, "x").Val())
+		require.EqualValues(t, -1, rdb.TTL(ctx, "x").Val())
+		require.Equal(t, "foo", rdb.Get(ctx, "x").Val())
+	})
+
+	t.Run("PERSIST returns 0 against non existing or non volatile keys", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "x", "foo", 0).Err())
+		require.False(t, rdb.Persist(ctx, "foo").Val())
+		require.False(t, rdb.Persist(ctx, "nokeyatall").Val())
+	})
+
+	t.Run("EXPIRE precision is now the millisecond", func(t *testing.T) {
+		// 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.
+		a, b := "", ""
+		util.RetryEventually(t, func() bool {
+			require.NoError(t, rdb.Del(ctx, "x").Err())
+			require.NoError(t, rdb.SetEx(ctx, "x", "somevalue", 1*time.Second).Err())
+			time.Sleep(900 * time.Millisecond)
+			a = rdb.Get(ctx, "x").Val()
+			time.Sleep(1100 * time.Millisecond)
+			b = rdb.Get(ctx, "x").Val()
+			return a == "somevalue" && b == ""
+		}, 3)
+		require.Equal(t, "somevalue", a)
+		require.Equal(t, "", b)
+	})
+
+	t.Run("PEXPIRE/PSETEX/PEXPIREAT can set sub-second expires", func(t *testing.T) {
+		// 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.
+		a, b, c, d, e, f := "", "", "", "", "", ""
+		util.RetryEventually(t, func() bool {
+			require.NoError(t, rdb.Del(ctx, "x", "y", "z").Err())
+			require.NoError(t, rdb.Set(ctx, "x", "somevalue", 100*time.Millisecond).Err())
+			time.Sleep(80 * time.Millisecond)
+			a = rdb.Get(ctx, "x").Val()
+			time.Sleep(2100 * time.Millisecond)
+			b = rdb.Get(ctx, "x").Val()
+
+			require.NoError(t, rdb.Set(ctx, "x", "somevalue", 0).Err())
+			require.NoError(t, rdb.PExpire(ctx, "x", 100*time.Millisecond).Err())
+			time.Sleep(80 * time.Millisecond)
+			c = rdb.Get(ctx, "x").Val()
+			time.Sleep(2100 * time.Millisecond)
+			d = rdb.Get(ctx, "x").Val()
+
+			require.NoError(t, rdb.Set(ctx, "x", "somevalue", 0).Err())
+			require.NoError(t, rdb.PExpireAt(ctx, "x", time.UnixMilli(time.Now().Unix()*1000+100)).Err())
+			time.Sleep(80 * time.Millisecond)
+			e = rdb.Get(ctx, "x").Val()
+			time.Sleep(2100 * time.Millisecond)
+			f = rdb.Get(ctx, "x").Val()
+
+			return a == "somevalue" && b == "" && c == "somevalue" && d == "" && e == "somevalue" && f == ""
+		}, 3)
+		require.Equal(t, "somevalue", a)
+		require.Equal(t, "", b)
+	})
+
+	t.Run("TTL returns time to live in seconds", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "x").Err())
+		require.NoError(t, rdb.SetEx(ctx, "x", "somevalue", 10*time.Second).Err())
+		util.BetweenValues(t, rdb.TTL(ctx, "x").Val(), 8*time.Second, 10*time.Second)
+	})
+
+	t.Run("PTTL returns time to live in milliseconds", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "x").Err())
+		require.NoError(t, rdb.SetEx(ctx, "x", "somevalue", 1*time.Second).Err())
+		util.BetweenValues(t, rdb.PTTL(ctx, "x").Val(), 900*time.Millisecond, 1000*time.Millisecond)
+	})
+
+	t.Run("TTL / PTTL return -1 if key has no expire", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "x").Err())
+		require.NoError(t, rdb.Set(ctx, "x", "hello", 0).Err())
+		require.EqualValues(t, -1, rdb.TTL(ctx, "x").Val())
+		require.EqualValues(t, -1, rdb.PTTL(ctx, "x").Val())
+	})
+
+	t.Run("TTL / PTTL return -2 if key does not exit", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "x").Err())
+		require.EqualValues(t, -2, rdb.TTL(ctx, "x").Val())
+		require.EqualValues(t, -2, rdb.PTTL(ctx, "x").Val())
+	})
+
+	t.Run("Redis should actively expire keys incrementally", func(t *testing.T) {
+		require.NoError(t, rdb.FlushDB(ctx).Err())
+		require.NoError(t, rdb.Do(ctx, "PSETEX", "key1", 500, "a").Err())
+		require.NoError(t, rdb.Do(ctx, "PSETEX", "key2", 500, "a").Err())
+		require.NoError(t, rdb.Do(ctx, "PSETEX", "key3", 500, "a").Err())
+		require.NoError(t, rdb.Do(ctx, "DBSIZE", "scan").Err())
+		time.Sleep(100 * time.Millisecond)
+		require.EqualValues(t, 3, rdb.DBSize(ctx).Val())
+		time.Sleep(2000 * time.Millisecond)
+		require.NoError(t, rdb.Do(ctx, "DBSIZE", "scan").Err())
+		time.Sleep(100 * time.Millisecond)
+		require.EqualValues(t, 0, rdb.DBSize(ctx).Val())
+	})
+
+	t.Run("5 keys in, 5 keys out", func(t *testing.T) {
+		require.NoError(t, rdb.FlushDB(ctx).Err())
+		require.NoError(t, rdb.Set(ctx, "a", "c", 0).Err())
+		require.NoError(t, rdb.Expire(ctx, "a", 5*time.Second).Err())
+		require.NoError(t, rdb.Set(ctx, "t", "c", 0).Err())
+		require.NoError(t, rdb.Set(ctx, "e", "c", 0).Err())
+		require.NoError(t, rdb.Set(ctx, "s", "c", 0).Err())
+		require.NoError(t, rdb.Set(ctx, "foo", "b", 0).Err())
+		res := rdb.Keys(ctx, "*").Val()
+		sort.Strings(res)
+		require.Equal(t, []string{"a", "e", "foo", "s", "t"}, res)
+	})
+
+	t.Run("EXPIRE with empty string as TTL should report an error", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err())
+		util.ErrorRegexp(t, rdb.Do(ctx, "expire", "foo", "").Err(), ".*not an integer*.")
+	})
+
+}
diff --git a/tests/gocase/util/assertions.go b/tests/gocase/util/assertions.go
index 41bc0fe..32dccad 100644
--- a/tests/gocase/util/assertions.go
+++ b/tests/gocase/util/assertions.go
@@ -23,9 +23,25 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	"golang.org/x/exp/constraints"
 )
 
 func ErrorRegexp(t testing.TB, err error, rx interface{}, msgAndArgs ...interface{}) {
-	require.Error(t, err, msgAndArgs)
-	require.Regexp(t, rx, err.Error(), msgAndArgs)
+	require.Error(t, err, msgAndArgs...)
+	require.Regexp(t, rx, err.Error(), msgAndArgs...)
+}
+
+func BetweenValues[T constraints.Ordered](t testing.TB, d, start, end T, msgAndArgs ...interface{}) {
+	require.GreaterOrEqual(t, d, start, msgAndArgs...)
+	require.LessOrEqual(t, d, end, msgAndArgs...)
+}
+
+func RetryEventually(t testing.TB, condition func() bool, maxAttempts int, msgAndArgs ...interface{}) {
+	require.Greater(t, maxAttempts, 0, msgAndArgs...)
+	for i := 0; i < maxAttempts; i++ {
+		if condition() {
+			return
+		}
+	}
+	require.Fail(t, "Condition never satisfied", msgAndArgs...)
 }
diff --git a/tests/tcl/tests/test_helper.tcl b/tests/tcl/tests/test_helper.tcl
index a003a7b..dfb7801 100644
--- a/tests/tcl/tests/test_helper.tcl
+++ b/tests/tcl/tests/test_helper.tcl
@@ -37,7 +37,6 @@ set ::all_tests {
     unit/type/list
     unit/type/zset
     unit/type/stream
-    unit/expire
     unit/pubsub
     unit/geo
     integration/slotmigrate
diff --git a/tests/tcl/tests/unit/expire.tcl b/tests/tcl/tests/unit/expire.tcl
deleted file mode 100644
index c788a0e..0000000
--- a/tests/tcl/tests/unit/expire.tcl
+++ /dev/null
@@ -1,206 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# Copyright (c) 2006-2020, Salvatore Sanfilippo
-# See bundled license file licenses/LICENSE.redis for details.
-
-# This file is copied and modified from the Redis project,
-# which started out as: https://github.com/redis/redis/blob/dbcc0a8/tests/unit/expire.tcl
-
-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 3.1 seconds the key should no longer be here} {
-            after 3100
-            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[3456]}
-
-    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 2100
-            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 12
-        list [r ttl x] [r persist x] [r ttl x] [r get x]
-    } {1[012] 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 pricision 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 < 3} {incr j} {
-            r del x
-            r setex x 1 somevalue
-            after 900
-            set a [r get x]
-            after 1100
-            set b [r get x]
-            if {$a eq {somevalue} && $b eq {}} break
-        }
-        list $a $b
-    } {somevalue {}}
-
-    test {PEXPIRE/PSETEX/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 < 3} {incr j} {
-            r del x y z
-            r psetex x 100 somevalue
-            after 80
-            set a [r get x]
-            after 2100
-            set b [r get x]
-
-            r set x somevalue
-            r pexpire x 100
-            after 80
-            set c [r get x]
-            after 2100
-            set d [r get x]
-
-            r set x somevalue
-            r pexpireat x [expr ([clock seconds]*1000)+100]
-            after 80
-            set e [r get x]
-            after 2100
-            set f [r get x]
-
-            if {$a eq {somevalue} && $b eq {} &&
-                $c eq {somevalue} && $d eq {} &&
-                $e eq {somevalue} && $f eq {}} break
-        }
-        list $a $b
-    } {somevalue {}}
-
-    test {TTL returns tiem 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 > 900 && $ttl <= 1000}
-    }
-
-    test {TTL / PTTL return -1 if key has no expire} {
-        r del x
-        r set x hello
-        list [r ttl x] [r pttl x]
-    } {-1 -1}
-
-    test {TTL / PTTL return -2 if key does not exit} {
-        r del x
-        list [r ttl x] [r pttl x]
-    } {-2 -2}
-
-    test {Redis should actively expire keys incrementally} {
-        r flushdb
-        r psetex key1 500 a
-        r psetex key2 500 a
-        r psetex key3 500 a
-        r dbsize scan
-        after 100
-        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 2000
-        r dbsize scan
-        after 100
-        set size2 [r dbsize]
-        list $size1 $size2
-    } {3 0}
-
-    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
-        lsort [r keys *]
-    } {a e foo s t}
-
-    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*}
-}