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/09/21 06:19:39 UTC

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

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 e8721a2  Move TCL test unit/scripting to Go case  (#900)
e8721a2 is described below

commit e8721a270d23345f385af6db0617c3549d6e6812
Author: tison <wa...@gmail.com>
AuthorDate: Wed Sep 21 14:19:34 2022 +0800

    Move TCL test unit/scripting to Go case  (#900)
---
 tests/gocase/unit/info/info_test.go           |  34 +-
 tests/gocase/unit/scripting/scripting_test.go | 505 ++++++++++++++++++++++++++
 tests/gocase/util/conditions.go               |  55 +++
 tests/gocase/util/constants.go                |   1 +
 tests/gocase/util/server.go                   |  10 +-
 tests/tcl/tests/test_helper.tcl               |   1 -
 tests/tcl/tests/unit/scripting.tcl            | 436 ----------------------
 7 files changed, 582 insertions(+), 460 deletions(-)

diff --git a/tests/gocase/unit/info/info_test.go b/tests/gocase/unit/info/info_test.go
index 1fde8cf..8aab000 100644
--- a/tests/gocase/unit/info/info_test.go
+++ b/tests/gocase/unit/info/info_test.go
@@ -22,9 +22,7 @@ package command
 import (
 	"context"
 	"fmt"
-	"regexp"
 	"strconv"
-	"strings"
 	"testing"
 	"time"
 
@@ -40,14 +38,6 @@ func TestInfo(t *testing.T) {
 	rdb := srv.NewClient()
 	defer func() { require.NoError(t, rdb.Close()) }()
 
-	FindInfoEntry := func(t *testing.T, section string, key string) string {
-		r := rdb.Info(ctx, section)
-		p := regexp.MustCompile(fmt.Sprintf("%s:(.+)", key))
-		ms := p.FindStringSubmatch(r.Val())
-		require.Len(t, ms, 2)
-		return strings.TrimSpace(ms[1])
-	}
-
 	MustAtoi := func(t *testing.T, s string) int {
 		i, err := strconv.Atoi(s)
 		require.NoError(t, err)
@@ -65,21 +55,21 @@ func TestInfo(t *testing.T) {
 			time.Sleep(time.Second)
 		}
 
-		r := FindInfoEntry(t, "rocksdb", "put_per_sec")
+		r := util.FindInfoEntry(t, ctx, rdb, "put_per_sec", "rocksdb")
 		require.Greater(t, MustAtoi(t, r), 0)
-		r = FindInfoEntry(t, "rocksdb", "get_per_sec")
+		r = util.FindInfoEntry(t, ctx, rdb, "get_per_sec", "rocksdb")
 		require.Greater(t, MustAtoi(t, r), 0)
-		r = FindInfoEntry(t, "rocksdb", "seek_per_sec")
+		r = util.FindInfoEntry(t, ctx, rdb, "seek_per_sec", "rocksdb")
 		require.Greater(t, MustAtoi(t, r), 0)
-		r = FindInfoEntry(t, "rocksdb", "next_per_sec")
+		r = util.FindInfoEntry(t, ctx, rdb, "next_per_sec", "rocksdb")
 		require.Greater(t, MustAtoi(t, r), 0)
 	})
 
 	t.Run("get bgsave information by INFO", func(t *testing.T) {
-		require.Equal(t, "0", FindInfoEntry(t, "persistence", "bgsave_in_progress"))
-		require.Equal(t, "-1", FindInfoEntry(t, "persistence", "last_bgsave_time"))
-		require.Equal(t, "ok", FindInfoEntry(t, "persistence", "last_bgsave_status"))
-		require.Equal(t, "-1", FindInfoEntry(t, "persistence", "last_bgsave_time_sec"))
+		require.Equal(t, "0", util.FindInfoEntry(t, ctx, rdb, "bgsave_in_progress", "persistence"))
+		require.Equal(t, "-1", util.FindInfoEntry(t, ctx, rdb, "last_bgsave_time", "persistence"))
+		require.Equal(t, "ok", util.FindInfoEntry(t, ctx, rdb, "last_bgsave_status", "persistence"))
+		require.Equal(t, "-1", util.FindInfoEntry(t, ctx, rdb, "last_bgsave_time_sec", "persistence"))
 
 		r := rdb.Do(ctx, "bgsave")
 		v, err := r.Text()
@@ -87,14 +77,14 @@ func TestInfo(t *testing.T) {
 		require.Equal(t, "OK", v)
 
 		require.Eventually(t, func() bool {
-			e := MustAtoi(t, FindInfoEntry(t, "persistence", "bgsave_in_progress"))
+			e := MustAtoi(t, util.FindInfoEntry(t, ctx, rdb, "bgsave_in_progress", "persistence"))
 			return e == 0
 		}, 5*time.Second, 100*time.Millisecond)
 
-		lastBgsaveTime := MustAtoi(t, FindInfoEntry(t, "persistence", "last_bgsave_time"))
+		lastBgsaveTime := MustAtoi(t, util.FindInfoEntry(t, ctx, rdb, "last_bgsave_time", "persistence"))
 		require.Greater(t, lastBgsaveTime, 1640507660)
-		require.Equal(t, "ok", FindInfoEntry(t, "persistence", "last_bgsave_status"))
-		lastBgsaveTimeSec := MustAtoi(t, FindInfoEntry(t, "persistence", "last_bgsave_time_sec"))
+		require.Equal(t, "ok", util.FindInfoEntry(t, ctx, rdb, "last_bgsave_status", "persistence"))
+		lastBgsaveTimeSec := MustAtoi(t, util.FindInfoEntry(t, ctx, rdb, "last_bgsave_time_sec", "persistence"))
 		require.GreaterOrEqual(t, lastBgsaveTimeSec, 0)
 		require.Less(t, lastBgsaveTimeSec, 3)
 	})
diff --git a/tests/gocase/unit/scripting/scripting_test.go b/tests/gocase/unit/scripting/scripting_test.go
new file mode 100644
index 0000000..c5e7aba
--- /dev/null
+++ b/tests/gocase/unit/scripting/scripting_test.go
@@ -0,0 +1,505 @@
+/*
+ * 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 scripting
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/apache/incubator-kvrocks/tests/gocase/util"
+	"github.com/stretchr/testify/require"
+)
+
+func TestScripting(t *testing.T) {
+	srv := util.StartServer(t, map[string]string{})
+	defer srv.Close()
+
+	ctx := context.Background()
+	rdb := srv.NewClient()
+	defer func() { require.NoError(t, rdb.Close()) }()
+
+	t.Run("EVAL - Does Lua interpreter replies to our requests?", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return 'hello'`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "hello", r.Val())
+	})
+
+	t.Run("EVAL - Lua integer -> Redis protocol type conversion", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return 100.5`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, int64(100), r.Val())
+	})
+
+	t.Run("EVAL - Lua string -> Redis protocol type conversion", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return 'hello world'`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "hello world", r.Val())
+	})
+
+	t.Run("EVAL - Lua true boolean -> Redis protocol type conversion", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return true`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, int64(1), r.Val())
+	})
+
+	t.Run("EVAL - Lua false boolean -> Redis protocol type conversion", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return false`, []string{})
+		require.EqualError(t, r.Err(), util.ErrRedisNil)
+		require.Nil(t, r.Val())
+	})
+
+	t.Run("EVAL - Lua status code reply -> Redis protocol type conversion", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return {ok='fine'}`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "fine", r.Val())
+	})
+
+	t.Run("EVAL - Lua error reply -> Redis protocol type conversion", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return {err='this is an error'}`, []string{})
+		require.EqualError(t, r.Err(), "this is an error")
+		require.Nil(t, r.Val())
+	})
+
+	t.Run("Script return recursive object", func(t *testing.T) {
+		c := srv.NewTCPClient()
+		defer func() { require.NoError(t, c.Close()) }()
+		require.NoError(t, c.WriteArgs("EVAL", `return "hello"`, "0"))
+		r, err := c.ReadLine()
+		require.NoError(t, err)
+		require.Equal(t, "$5", r)
+		r, err = c.ReadLine()
+		require.NoError(t, err)
+		require.Equal(t, "hello", r)
+	})
+
+	t.Run("EVAL - Lua table -> Redis protocol type conversion", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return {1,2,3,'ciao',{1,2}}`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[1 2 3 ciao [1 2]]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - Are the KEYS and ARGV arrays populated correctly?", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}`, []string{"a", "b"}, "c", "d")
+		require.NoError(t, r.Err())
+		require.Equal(t, "[a b c d]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - is Lua able to call Redis API?", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "mykey", "myval", 0).Err())
+		r := rdb.Eval(ctx, `return redis.call('get',KEYS[1])`, []string{"mykey"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "myval", r.Val())
+	})
+
+	t.Run("EVALSHA - Can we call a SHA1 if already defined?", func(t *testing.T) {
+		r := rdb.EvalSha(ctx, "fd758d1589d044dd850a6f05d52f2eefd27f033f", []string{"mykey"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "myval", r.Val())
+	})
+
+	t.Run("EVALSHA - Can we call a SHA1 in uppercase?", func(t *testing.T) {
+		r := rdb.EvalSha(ctx, "FD758D1589D044DD850A6F05D52F2EEFD27F033F", []string{"mykey"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "myval", r.Val())
+	})
+
+	t.Run("EVALSHA - Do we get an error on invalid SHA1?", func(t *testing.T) {
+		r := rdb.EvalSha(ctx, "NotValidShaSUM", []string{})
+		util.ErrorRegexp(t, r.Err(), "ERR NOSCRIPT.*")
+		require.Nil(t, r.Val())
+	})
+
+	t.Run("EVALSHA - Do we get an error on non defined SHA1?", func(t *testing.T) {
+		r := rdb.EvalSha(ctx, "ffd632c7d33e571e9f24556ebed26c3479a87130", []string{})
+		util.ErrorRegexp(t, r.Err(), "ERR NOSCRIPT.*")
+		require.Nil(t, r.Val())
+	})
+
+	t.Run("EVAL - Redis integer -> Lua type conversion", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "x", 0, 0).Err())
+		r := rdb.Eval(ctx, `
+local foo = redis.pcall('incr',KEYS[1])
+return {type(foo),foo}
+`, []string{"x"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[number 1]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - Redis bulk -> Lua type conversion", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "mykey", "myval", 0).Err())
+		r := rdb.Eval(ctx, `
+local foo = redis.pcall('get',KEYS[1])
+return {type(foo),foo}
+`, []string{"mykey"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[string myval]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - Redis multi bulk -> Lua type conversion", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "mylist").Err())
+		require.NoError(t, rdb.RPush(ctx, "mylist", "a", "b", "c").Err())
+		r := rdb.Eval(ctx, `
+local foo = redis.pcall('lrange',KEYS[1],0,-1)
+return {type(foo),foo[1],foo[2],foo[3],# foo}
+`, []string{"mylist"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[table a b c 3]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - Redis status reply -> Lua type conversion", func(t *testing.T) {
+		r := rdb.Eval(ctx, `
+local foo = redis.pcall('set',KEYS[1],'myval')
+return {type(foo),foo['ok']}
+`, []string{"mykey"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[table OK]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - Redis error reply -> Lua type conversion", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "mykey", "myval", 0).Err())
+		r := rdb.Eval(ctx, `
+local foo = redis.pcall('incr',KEYS[1])
+return {type(foo),foo['err']}
+`, []string{"mykey"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[table Invalid argument: value is not an integer or out of range]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - Redis nil bulk reply -> Lua type conversion", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "mykey").Err())
+		r := rdb.Eval(ctx, `
+local foo = redis.pcall('get',KEYS[1])
+return {type(foo),foo == false}
+`, []string{"mykey"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[boolean 1]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - Scripts can't run certain commands", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return redis.pcall('blpop','x',0)`, []string{})
+		require.ErrorContains(t, r.Err(), "not allowed")
+	})
+
+	t.Run("EVAL - Scripts can run certain commands", func(t *testing.T) {
+		r := rdb.Eval(ctx, `redis.pcall('randomkey'); return redis.pcall('set','x','ciao')`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "OK", r.Val())
+	})
+
+	t.Run("EVAL - No arguments to redis.call/pcall is considered an error", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return redis.call()`, []string{})
+		require.ErrorContains(t, r.Err(), "one argument")
+	})
+
+	t.Run("EVAL - redis.call variant raises a Lua error on Redis cmd error", func(t *testing.T) {
+		r := rdb.Eval(ctx, `redis.call('nosuchcommand')`, []string{})
+		require.ErrorContains(t, r.Err(), "Unknown Redis")
+		r = rdb.Eval(ctx, `redis.call('get','a','b','c')`, []string{})
+		require.ErrorContains(t, r.Err(), "number of args")
+		require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err())
+		r = rdb.Eval(ctx, `redis.call('lpush',KEYS[1],'val')`, []string{"foo"})
+		require.ErrorContains(t, r.Err(), "against a key")
+	})
+
+	t.Run("EVAL - JSON numeric decoding", func(t *testing.T) {
+		r := rdb.Eval(ctx, `
+return
+  table.concat(
+    cjson.decode(
+      "[0.0, -5e3, -1, 0.3e-3, 1023.2, 0e10]"), " ")
+`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "0 -5000 -1 0.0003 1023.2 0", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - JSON string decoding", func(t *testing.T) {
+		r := rdb.Eval(ctx, `
+local decoded = cjson.decode('{"keya": "a", "keyb": "b"}')
+return {decoded.keya, decoded.keyb}
+`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[a b]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - cmsgpack can pack double?", func(t *testing.T) {
+		r := rdb.Eval(ctx, `
+local encoded = cmsgpack.pack(0.1)
+local h = ""
+for i = 1, #encoded do
+	h = h .. string.format("%02x",string.byte(encoded,i))
+end
+return h
+`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "cb3fb999999999999a", r.Val())
+	})
+
+	t.Run("EVAL - cmsgpack can pack negative int64?", func(t *testing.T) {
+		r := rdb.Eval(ctx, `
+local encoded = cmsgpack.pack(-1099511627776)
+local h = ""
+for i = 1, #encoded do
+	h = h .. string.format("%02x",string.byte(encoded,i))
+end
+return h
+`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "d3ffffff0000000000", r.Val())
+	})
+
+	t.Run("EVAL - cmsgpack can pack and unpack circular references?", func(t *testing.T) {
+		r := rdb.Eval(ctx, `
+local a = {x=nil,y=5}
+local b = {x=a}
+a['x'] = b
+local encoded = cmsgpack.pack(a)
+local h = ""
+-- cmsgpack encodes to a depth of 16, but can't encode
+-- references, so the encoded object has a deep copy recursive
+-- depth of 16.
+for i = 1, #encoded do
+	h = h .. string.format("%02x",string.byte(encoded,i))
+end
+-- when unpacked, re.x.x != re because the unpack creates
+-- individual tables down to a depth of 16.
+-- (that's why the encoded output is so large)
+local re = cmsgpack.unpack(encoded)
+assert(re)
+assert(re.x)
+assert(re.x.x.y == re.y)
+assert(re.x.x.x.x.y == re.y)
+assert(re.x.x.x.x.x.x.y == re.y)
+assert(re.x.x.x.x.x.x.x.x.x.x.y == re.y)
+-- maximum working depth:
+assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.y == re.y)
+-- now the last x would be b above and has no y
+assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x)
+-- so, the final x.x is at the depth limit and was assigned nil
+assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x == nil)
+assert(h == "82a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a178c0a17905a17905a17905a17905a17905a17905a17905a17905" or h == "82a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a178c0")
+return {re.x.x.x.x.x.x.x.x.y == re.y, re.y == 5}
+`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[1 1]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("EVAL - Numerical sanity check from bitop", func(t *testing.T) {
+		r := rdb.Eval(ctx, `
+assert(0x7fffffff == 2147483647, "broken hex literals");
+assert(0xffffffff == -1 or 0xffffffff == 2^32-1,
+	"broken hex literals");
+assert(tostring(-1) == "-1", "broken tostring()");
+assert(tostring(0xffffffff) == "-1" or
+	tostring(0xffffffff) == "4294967295",
+	"broken tostring()")
+`, []string{})
+		require.EqualError(t, r.Err(), util.ErrRedisNil)
+		require.Nil(t, r.Val())
+	})
+
+	t.Run("EVAL - Verify minimal bitop functionality", func(t *testing.T) {
+		r := rdb.Eval(ctx, `
+assert(bit.tobit(1) == 1);
+assert(bit.band(1) == 1);
+assert(bit.bxor(1,2) == 3);
+assert(bit.bor(1,2,4,8,16,32,64,128) == 255)
+`, []string{})
+		require.EqualError(t, r.Err(), util.ErrRedisNil)
+		require.Nil(t, r.Val())
+	})
+
+	t.Run("EVAL - Able to parse trailing comments", func(t *testing.T) {
+		r := rdb.Eval(ctx, `return 'hello' --trailing comment`, []string{})
+		require.NoError(t, r.Err())
+		require.Equal(t, "hello", r.Val())
+	})
+
+	t.Run("EVAL does not leak in the Lua stack", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "x", 0, 0).Err())
+
+		// use a non-blocking client to speed up the loop.
+		c := srv.NewTCPClient()
+		defer func() { require.NoError(t, c.Close()) }()
+
+		for i := 0; i < 10000; i++ {
+			require.NoError(t, c.WriteArgs("EVAL", `return redis.call("incr",KEYS[1])`, "1", "x"))
+		}
+		for i := 0; i < 10000; i++ {
+			_, err := c.ReadLine()
+			require.NoError(t, err)
+		}
+
+		require.EqualValues(t, "10000", rdb.Get(ctx, "x").Val())
+	})
+
+	t.Run("SCRIPTING FLUSH - is able to clear the scripts cache?", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "mykey", "myval", 0).Err())
+		r := rdb.EvalSha(ctx, "fd758d1589d044dd850a6f05d52f2eefd27f033f", []string{"mykey"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "myval", r.Val())
+		require.NoError(t, rdb.ScriptFlush(ctx).Err())
+		r = rdb.EvalSha(ctx, "fd758d1589d044dd850a6f05d52f2eefd27f033f", []string{"mykey"})
+		util.ErrorRegexp(t, r.Err(), "ERR NOSCRIPT.*")
+	})
+
+	t.Run("SCRIPT EXISTS - can detect already defined scripts?", func(t *testing.T) {
+		r1 := rdb.Eval(ctx, "return 1+1", []string{})
+		require.NoError(t, r1.Err())
+		require.Equal(t, int64(2), r1.Val())
+		r2 := rdb.ScriptExists(ctx, "a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9", "a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bda")
+		require.NoError(t, r2.Err())
+		require.Equal(t, []bool{true, false}, r2.Val())
+	})
+
+	t.Run("SCRIPT LOAD - is able to register scripts in the scripting cache", func(t *testing.T) {
+		r1 := rdb.ScriptLoad(ctx, "return 'loaded'")
+		require.NoError(t, r1.Err())
+		require.Equal(t, "b534286061d4b9e4026607613b95c06c06015ae8", r1.Val())
+		r2 := rdb.EvalSha(ctx, "b534286061d4b9e4026607613b95c06c06015ae8", []string{})
+		require.NoError(t, r2.Err())
+		require.Equal(t, "loaded", r2.Val())
+	})
+
+	t.Run("Globals protection reading an undeclared global variable", func(t *testing.T) {
+		r2 := rdb.Eval(ctx, `return a`, []string{})
+		util.ErrorRegexp(t, r2.Err(), ".*ERR.*attempted to access .* global.*")
+	})
+
+	t.Run("Globals protection setting an undeclared global variable", func(t *testing.T) {
+		r2 := rdb.Eval(ctx, `a=10`, []string{})
+		util.ErrorRegexp(t, r2.Err(), ".*ERR.*attempted to create global.*")
+	})
+
+	t.Run("Test an example script DECR_IF_GT", func(t *testing.T) {
+		scriptDecrIfGt := `
+local current
+
+current = redis.call('get',KEYS[1])
+if not current then return nil end
+if current > ARGV[1] then
+	return redis.call('decr',KEYS[1])
+else
+	return redis.call('get',KEYS[1])
+end
+`
+		require.NoError(t, rdb.Set(ctx, "foo", 5, 0).Err())
+		r := rdb.Eval(ctx, scriptDecrIfGt, []string{"foo"}, 2)
+		require.NoError(t, r.Err())
+		require.Equal(t, int64(4), r.Val())
+		r = rdb.Eval(ctx, scriptDecrIfGt, []string{"foo"}, 2)
+		require.NoError(t, r.Err())
+		require.Equal(t, int64(3), r.Val())
+		r = rdb.Eval(ctx, scriptDecrIfGt, []string{"foo"}, 2)
+		require.NoError(t, r.Err())
+		require.Equal(t, int64(2), r.Val())
+		r = rdb.Eval(ctx, scriptDecrIfGt, []string{"foo"}, 2)
+		require.NoError(t, r.Err())
+		require.Equal(t, "2", r.Val())
+		r = rdb.Eval(ctx, scriptDecrIfGt, []string{"foo"}, 2)
+		require.NoError(t, r.Err())
+		require.Equal(t, "2", r.Val())
+	})
+
+	t.Run("Scripting engine PRNG can be seeded correctly", func(t *testing.T) {
+		rand1 := rdb.Eval(ctx, `
+math.randomseed(ARGV[1]); return tostring(math.random())
+`, []string{}, 10).Val()
+		rand2 := rdb.Eval(ctx, `
+math.randomseed(ARGV[1]); return tostring(math.random())
+`, []string{}, 10).Val()
+		rand3 := rdb.Eval(ctx, `
+math.randomseed(ARGV[1]); return tostring(math.random())
+`, []string{}, 20).Val()
+		require.Equal(t, rand1, rand2)
+		require.NotEqual(t, rand2, rand3)
+	})
+
+	t.Run("In the context of Lua the output of random commands gets ordered", func(t *testing.T) {
+		require.NoError(t, rdb.Del(ctx, "myset").Err())
+		require.NoError(t, rdb.SAdd(ctx, "myset", "a", "b", "c", "d", "e", "f", "g", "h", "i", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "z", "aa", "aaa", "azz").Err())
+		r := rdb.Eval(ctx, `return redis.call('smembers',KEYS[1])`, []string{"myset"})
+		require.NoError(t, r.Err())
+		require.Equal(t, "[a aa aaa azz b c d e f g h i l m n o p q r s t u v z]", fmt.Sprintf("%v", r.Val()))
+	})
+
+	t.Run("Make sure redis.log() works", func(t *testing.T) {
+		require.EqualError(t, rdb.Eval(ctx, `return redis.log(redis.LOG_DEBUG, 'debug level');`, []string{}).Err(), util.ErrRedisNil)
+		require.EqualError(t, rdb.Eval(ctx, `return redis.log(redis.LOG_VERBOSE, 'debug level');`, []string{}).Err(), util.ErrRedisNil)
+		require.EqualError(t, rdb.Eval(ctx, `return redis.log(redis.LOG_NOTICE, 'debug level');`, []string{}).Err(), util.ErrRedisNil)
+		require.EqualError(t, rdb.Eval(ctx, `return redis.log(redis.LOG_WARNING, 'debug level');`, []string{}).Err(), util.ErrRedisNil)
+	})
+
+	t.Run("EVAL_RO - successful case", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err())
+		r := rdb.Do(ctx, "EVAL_RO", `return redis.call('get', KEYS[1]);`, "1", "foo")
+		require.NoError(t, r.Err())
+		require.Equal(t, "bar", r.Val())
+	})
+
+	t.Run("EVALSHA_RO - successful case", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err())
+		r := rdb.Do(ctx, "EVALSHA_RO", "796941151549c416aa77522fb347487236c05e46", "1", "foo")
+		require.NoError(t, r.Err())
+		require.Equal(t, "bar", r.Val())
+	})
+
+	t.Run("EVAL_RO - cannot run write commands", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err())
+		r := rdb.Do(ctx, "EVAL_RO", `redis.call('del', KEYS[1]);`, "1", "foo")
+		util.ErrorRegexp(t, r.Err(), "ERR .* Write commands are not allowed from read-only scripts")
+	})
+
+	t.Run("EVALSHA_RO - cannot run write commands", func(t *testing.T) {
+		require.NoError(t, rdb.Set(ctx, "foo", "bar", 0).Err())
+		r := rdb.Do(ctx, "EVALSHA_RO", "a1e63e1cd1bd1d5413851949332cfb9da4ee6dc0", "1", "foo")
+		util.ErrorRegexp(t, r.Err(), "ERR .* Write commands are not allowed from read-only scripts")
+	})
+}
+
+func TestScriptingMasterSlave(t *testing.T) {
+	master := util.StartServer(t, map[string]string{})
+	defer master.Close()
+	masterClient := master.NewClient()
+	defer func() { require.NoError(t, masterClient.Close()) }()
+
+	slave := util.StartServer(t, map[string]string{})
+	defer slave.Close()
+	slaveClient := slave.NewClient()
+	defer func() { require.NoError(t, slaveClient.Close()) }()
+
+	ctx := context.Background()
+
+	require.NoError(t, slaveClient.SlaveOf(ctx, master.Host(), fmt.Sprintf("%d", master.Port())).Err())
+	util.WaitForSync(t, ctx, slaveClient)
+
+	t.Run("SCRIPTING: script load on master, read on slave", func(t *testing.T) {
+		sha := masterClient.ScriptLoad(ctx, `return 'script loaded'`).Val()
+		require.Equal(t, "4167ea82ed9c381c7659f7cf93f394219147e8c4", sha)
+		util.WaitForOffsetSync(t, ctx, masterClient, slaveClient)
+		require.Equal(t, []bool{true}, masterClient.ScriptExists(ctx, sha).Val())
+		require.Equal(t, []bool{true}, slaveClient.ScriptExists(ctx, sha).Val())
+
+		require.NoError(t, masterClient.ScriptFlush(ctx).Err())
+		util.WaitForOffsetSync(t, ctx, masterClient, slaveClient)
+		require.Equal(t, []bool{false}, masterClient.ScriptExists(ctx, sha).Val())
+		require.Equal(t, []bool{false}, slaveClient.ScriptExists(ctx, sha).Val())
+	})
+}
diff --git a/tests/gocase/util/conditions.go b/tests/gocase/util/conditions.go
new file mode 100644
index 0000000..d6ac7a8
--- /dev/null
+++ b/tests/gocase/util/conditions.go
@@ -0,0 +1,55 @@
+/*
+ * 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 util
+
+import (
+	"context"
+	"fmt"
+	"regexp"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/go-redis/redis/v9"
+	"github.com/stretchr/testify/require"
+)
+
+func FindInfoEntry(t *testing.T, ctx context.Context, rdb *redis.Client, key string, section ...string) string {
+	r := rdb.Info(ctx, section...)
+	p := regexp.MustCompile(fmt.Sprintf("%s:(.+)", key))
+	ms := p.FindStringSubmatch(r.Val())
+	require.Len(t, ms, 2)
+	return strings.TrimSpace(ms[1])
+}
+
+func WaitForSync(t *testing.T, ctx context.Context, slave *redis.Client) {
+	require.Eventually(t, func() bool {
+		r := FindInfoEntry(t, ctx, slave, "master_link_status")
+		return r == "up"
+	}, 5*time.Second, 100*time.Millisecond)
+}
+
+func WaitForOffsetSync(t *testing.T, ctx context.Context, master, slave *redis.Client) {
+	require.Eventually(t, func() bool {
+		o1 := FindInfoEntry(t, ctx, master, "master_repl_offset")
+		o2 := FindInfoEntry(t, ctx, slave, "master_repl_offset")
+		return o1 == o2
+	}, 5*time.Second, 100*time.Millisecond)
+}
diff --git a/tests/gocase/util/constants.go b/tests/gocase/util/constants.go
index 9c90e7e..cdb8bf4 100644
--- a/tests/gocase/util/constants.go
+++ b/tests/gocase/util/constants.go
@@ -20,3 +20,4 @@
 package util
 
 const DefaultDelta = 0.000001
+const ErrRedisNil = "redis: nil"
diff --git a/tests/gocase/util/server.go b/tests/gocase/util/server.go
index 136e60a..10cca14 100644
--- a/tests/gocase/util/server.go
+++ b/tests/gocase/util/server.go
@@ -36,11 +36,19 @@ import (
 type KvrocksServer struct {
 	t    testing.TB
 	cmd  *exec.Cmd
-	addr net.Addr
+	addr *net.TCPAddr
 
 	clean func()
 }
 
+func (s *KvrocksServer) Host() string {
+	return s.addr.AddrPort().Addr().String()
+}
+
+func (s *KvrocksServer) Port() uint16 {
+	return s.addr.AddrPort().Port()
+}
+
 func (s *KvrocksServer) NewClient() *redis.Client {
 	return s.NewClientWithOption(&redis.Options{Addr: s.addr.String()})
 }
diff --git a/tests/tcl/tests/test_helper.tcl b/tests/tcl/tests/test_helper.tcl
index 24fc3ad..d16a59c 100644
--- a/tests/tcl/tests/test_helper.tcl
+++ b/tests/tcl/tests/test_helper.tcl
@@ -45,7 +45,6 @@ set ::all_tests {
     unit/pubsub
     unit/introspection
     unit/geo
-    unit/scripting
     integration/slotmigrate
     integration/slotimport
     integration/redis-cli
diff --git a/tests/tcl/tests/unit/scripting.tcl b/tests/tcl/tests/unit/scripting.tcl
deleted file mode 100644
index 41268be..0000000
--- a/tests/tcl/tests/unit/scripting.tcl
+++ /dev/null
@@ -1,436 +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/scripting.tcl
-
-start_server {tags {"scripting"}} {
-    test {EVAL - Does Lua interpreter replies to our requests?} {
-        r eval {return 'hello'} 0
-    } {hello}
-    test {EVAL - Lua integer -> Redis protocol type conversion} {
-        r eval {return 100.5} 0
-    } {100}
-
-    test {EVAL - Lua string -> Redis protocol type conversion} {
-        r eval {return 'hello world'} 0
-    } {hello world}
-
-    test {EVAL - Lua true boolean -> Redis protocol type conversion} {
-        r eval {return true} 0
-    } {1}
-
-    test {EVAL - Lua false boolean -> Redis protocol type conversion} {
-        r eval {return false} 0
-    } {}
-
-    test {EVAL - Lua status code reply -> Redis protocol type conversion} {
-        r eval {return {ok='fine'}} 0
-    } {fine}
-
-    test {EVAL - Lua error reply -> Redis protocol type conversion} {
-        catch {
-            r eval {return {err='this is an error'}} 0
-        } e
-        set _ $e
-    } {this is an error}
-
-    test {Script return recursive object} {
-        r readraw 1
-        set bulk_len [r eval {return "hello"} 0]
-        set bulk [r read]
-        r readraw 0
-        assert_equal $bulk_len {$5}
-        assert_equal $bulk hello
-    } {}
-
-    test {EVAL - Lua table -> Redis protocol type conversion} {
-        r eval {return {1,2,3,'ciao',{1,2}}} 0
-    } {1 2 3 ciao {1 2}}
-
-    test {EVAL - Are the KEYS and ARGV arrays populated correctly?} {
-        r eval {return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}} 2 a b c d
-    } {a b c d}
-
-    test {EVAL - is Lua able to call Redis API?} {
-        r set mykey myval
-        r eval {return redis.call('get',KEYS[1])} 1 mykey
-    } {myval}
-
-    test {EVALSHA - Can we call a SHA1 if already defined?} {
-        r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey
-    } {myval}
-
-    test {EVALSHA - Can we call a SHA1 in uppercase?} {
-        r evalsha FD758D1589D044DD850A6F05D52F2EEFD27F033F 1 mykey
-    } {myval}
-
-    test {EVALSHA - Do we get an error on invalid SHA1?} {
-        catch {r evalsha NotValidShaSUM 0} e
-        set _ $e
-    } {ERR NOSCRIPT*}
-
-    test {EVALSHA - Do we get an error on non defined SHA1?} {
-        catch {r evalsha ffd632c7d33e571e9f24556ebed26c3479a87130 0} e
-        set _ $e
-    } {ERR NOSCRIPT*}
-
-    test {EVAL - Redis integer -> Lua type conversion} {
-        r set x 0
-        r eval {
-            local foo = redis.pcall('incr',KEYS[1])
-            return {type(foo),foo}
-        } 1 x
-    } {number 1}
-
-    test {EVAL - Redis bulk -> Lua type conversion} {
-        r set mykey myval
-        r eval {
-            local foo = redis.pcall('get',KEYS[1])
-            return {type(foo),foo}
-        } 1 mykey
-    } {string myval}
-
-    test {EVAL - Redis integer -> Lua type conversion} {
-        r set x 0
-        r eval {
-            local foo = redis.pcall('incr',KEYS[1])
-            return {type(foo),foo}
-        } 1 x
-    } {number 1}
-
-    test {EVAL - Redis bulk -> Lua type conversion} {
-        r set mykey myval
-        r eval {
-            local foo = redis.pcall('get',KEYS[1])
-            return {type(foo),foo}
-        } 1 mykey
-    } {string myval}
-    test {EVAL - Redis multi bulk -> Lua type conversion} {
-        r del mylist
-        r rpush mylist a
-        r rpush mylist b
-        r rpush mylist c
-        r eval {
-            local foo = redis.pcall('lrange',KEYS[1],0,-1)
-            return {type(foo),foo[1],foo[2],foo[3],# foo}
-        } 1 mylist
-    } {table a b c 3}
-
-    test {EVAL - Redis status reply -> Lua type conversion} {
-        r eval {
-            local foo = redis.pcall('set',KEYS[1],'myval')
-            return {type(foo),foo['ok']}
-        } 1 mykey
-    } {table OK}
-
-    test {EVAL - Redis error reply -> Lua type conversion} {
-        r set mykey myval
-        r eval {
-            local foo = redis.pcall('incr',KEYS[1])
-            return {type(foo),foo['err']}
-        } 1 mykey
-    } {table {Invalid argument: value is not an integer or out of range}}
-    test {EVAL - Redis nil bulk reply -> Lua type conversion} {
-        r del mykey
-        r eval {
-            local foo = redis.pcall('get',KEYS[1])
-            return {type(foo),foo == false}
-        } 1 mykey
-    } {boolean 1}
-    test {EVAL - Scripts can't run certain commands} {
-        set e {}
-        catch {r eval {return redis.pcall('blpop','x',0)} 0} e
-        set e
-    } {*not allowed*}
-
-    test {EVAL - Scripts can't run certain commands} {
-        set e {}
-        catch {
-            r eval "redis.pcall('randomkey'); return redis.pcall('set','x','ciao')" 0
-        } e
-        set e
-    } {OK}
-
-    test {EVAL - No arguments to redis.call/pcall is considered an error} {
-        set e {}
-        catch {r eval {return redis.call()} 0} e
-        set e
-    } {*one argument*}
-
-    test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
-        set e {}
-        catch {
-            r eval "redis.call('nosuchcommand')" 0
-        } e
-        set e
-    } {*Unknown Redis*}
-    test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
-        set e {}
-        catch {
-            r eval "redis.call('get','a','b','c')" 0
-        } e
-        set e
-    } {*number of args*}
-
-    test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
-        set e {}
-        r set foo bar
-        catch {
-            r eval {redis.call('lpush',KEYS[1],'val')} 1 foo
-        } e
-        set e
-    } {*against a key*}
-
-    test {EVAL - JSON numeric decoding} {
-        # We must return the table as a string because otherwise
-        # Redis converts floats to ints and we get 0 and 1023 instead
-        # of 0.0003 and 1023.2 as the parsed output.
-        r eval {return
-                 table.concat(
-                   cjson.decode(
-                    "[0.0, -5e3, -1, 0.3e-3, 1023.2, 0e10]"), " ")
-        } 0
-    } {0 -5000 -1 0.0003 1023.2 0}
-
-    test {EVAL - JSON string decoding} {
-        r eval {local decoded = cjson.decode('{"keya": "a", "keyb": "b"}')
-                return {decoded.keya, decoded.keyb}
-        } 0
-    } {a b}
-
-    test {EVAL - cmsgpack can pack double?} {
-        r eval {local encoded = cmsgpack.pack(0.1)
-                local h = ""
-                for i = 1, #encoded do
-                    h = h .. string.format("%02x",string.byte(encoded,i))
-                end
-                return h
-        } 0
-    } {cb3fb999999999999a}
-
-    test {EVAL - cmsgpack can pack negative int64?} {
-        r eval {local encoded = cmsgpack.pack(-1099511627776)
-                local h = ""
-                for i = 1, #encoded do
-                    h = h .. string.format("%02x",string.byte(encoded,i))
-                end
-                return h
-        } 0
-    } {d3ffffff0000000000}
-    test {EVAL - cmsgpack can pack and unpack circular references?} {
-        r eval {local a = {x=nil,y=5}
-                local b = {x=a}
-                a['x'] = b
-                local encoded = cmsgpack.pack(a)
-                local h = ""
-                -- cmsgpack encodes to a depth of 16, but can't encode
-                -- references, so the encoded object has a deep copy recursive
-                -- depth of 16.
-                for i = 1, #encoded do
-                    h = h .. string.format("%02x",string.byte(encoded,i))
-                end
-                -- when unpacked, re.x.x != re because the unpack creates
-                -- individual tables down to a depth of 16.
-                -- (that's why the encoded output is so large)
-                local re = cmsgpack.unpack(encoded)
-                assert(re)
-                assert(re.x)
-                assert(re.x.x.y == re.y)
-                assert(re.x.x.x.x.y == re.y)
-                assert(re.x.x.x.x.x.x.y == re.y)
-                assert(re.x.x.x.x.x.x.x.x.x.x.y == re.y)
-                -- maximum working depth:
-                assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.y == re.y)
-                -- now the last x would be b above and has no y
-                assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x)
-                -- so, the final x.x is at the depth limit and was assigned nil
-                assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x == nil)
-                assert(h == "82a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a17882a17881a178c0a17905a17905a17905a17905a17905a17905a17905a17905" or h == "82a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a178c0")
-                return {re.x.x.x.x.x.x.x.x.y == re.y, re.y == 5}
-        } 0
-    } {1 1}
-    test {EVAL - Numerical sanity check from bitop} {
-        r eval {assert(0x7fffffff == 2147483647, "broken hex literals");
-                assert(0xffffffff == -1 or 0xffffffff == 2^32-1,
-                    "broken hex literals");
-                assert(tostring(-1) == "-1", "broken tostring()");
-                assert(tostring(0xffffffff) == "-1" or
-                    tostring(0xffffffff) == "4294967295",
-                    "broken tostring()")
-        } 0
-    } {}
-
-    test {EVAL - Verify minimal bitop functionality} {
-        r eval {assert(bit.tobit(1) == 1);
-                assert(bit.band(1) == 1);
-                assert(bit.bxor(1,2) == 3);
-                assert(bit.bor(1,2,4,8,16,32,64,128) == 255)
-        } 0
-    } {}
-
-    test {EVAL - Able to parse trailing comments} {
-        r eval {return 'hello' --trailing comment} 0
-    } {hello}
-
-    test {EVAL does not leak in the Lua stack} {
-        r set x 0
-        # Use a non blocking client to speedup the loop.
-        set rd [redis_deferring_client]
-        for {set j 0} {$j < 10000} {incr j} {
-            $rd eval {return redis.call("incr",KEYS[1])} 1 x
-        }
-        for {set j 0} {$j < 10000} {incr j} {
-            $rd read
-        }
-        assert {[s used_memory_lua] < 1024*100}
-        $rd close
-        r get x
-    } {10000}
-
-    test {SCRIPTING FLUSH - is able to clear the scripts cache?} {
-        r set mykey myval
-        set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey]
-        assert_equal $v myval
-        set e ""
-        r script flush
-        catch {r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey} e
-        set e
-    } {ERR NOSCRIPT*}
-
-    test {SCRIPT EXISTS - can detect already defined scripts?} {
-        r eval "return 1+1" 0
-        r script exists a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9 a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bda
-    } {1 0}
-
-    test {SCRIPT LOAD - is able to register scripts in the scripting cache} {
-        list \
-            [r script load "return 'loaded'"] \
-            [r evalsha b534286061d4b9e4026607613b95c06c06015ae8 0]
-    } {b534286061d4b9e4026607613b95c06c06015ae8 loaded}
-
-    test {Globals protection reading an undeclared global variable} {
-        catch {r eval {return a} 0} e
-        set e
-    } {*ERR*attempted to access * global*}
-
-    test {Globals protection setting an undeclared global*} {
-        catch {r eval {a=10} 0} e
-        set e
-    } {*ERR*attempted to create global*}
-
-    test {Test an example script DECR_IF_GT} {
-        set decr_if_gt {
-            local current
-
-            current = redis.call('get',KEYS[1])
-            if not current then return nil end
-            if current > ARGV[1] then
-                return redis.call('decr',KEYS[1])
-            else
-                return redis.call('get',KEYS[1])
-            end
-        }
-        r set foo 5
-        set res {}
-        lappend res [r eval $decr_if_gt 1 foo 2]
-        lappend res [r eval $decr_if_gt 1 foo 2]
-        lappend res [r eval $decr_if_gt 1 foo 2]
-        lappend res [r eval $decr_if_gt 1 foo 2]
-        lappend res [r eval $decr_if_gt 1 foo 2]
-        set res
-    } {4 3 2 2 2}
-
-    test {Scripting engine PRNG can be seeded correctly} {
-        set rand1 [r eval {
-            math.randomseed(ARGV[1]); return tostring(math.random())
-        } 0 10]
-        set rand2 [r eval {
-            math.randomseed(ARGV[1]); return tostring(math.random())
-        } 0 10]
-        set rand3 [r eval {
-            math.randomseed(ARGV[1]); return tostring(math.random())
-        } 0 20]
-        assert_equal $rand1 $rand2
-        assert {$rand2 ne $rand3}
-    }
-    test "In the context of Lua the output of random commands gets ordered" {
-        r del myset
-        r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
-        r eval {return redis.call('smembers',KEYS[1])} 1 myset
-    } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
-
-    test "Make sure redis.log() works" {
-        set v [r eval { return redis.log(redis.LOG_DEBUG, 'debug level'); } 0]
-        assert_equal "" $v
-        set v [r eval { return redis.log(redis.LOG_VERBOSE, 'verbose level'); } 0]
-        assert_equal "" $v
-        set v [r eval { return redis.log(redis.LOG_NOTICE, 'notice level'); } 0]
-        assert_equal "" $v
-        set v [r eval { return redis.log(redis.LOG_WARNING, 'warning level'); } 0]
-        assert_equal "" $v
-    } {}
-
-    test {EVAL_RO - Successful case} {
-        r set foo bar
-        assert_equal bar [r eval_ro {return redis.call('get', KEYS[1]);} 1 foo]
-    }
-
-    test {EVALSHA_RO - Successful case} {
-        r set foo bar
-        assert_equal bar [r evalsha_ro 796941151549c416aa77522fb347487236c05e46 1 foo]
-    }
-
-    test {EVAL_RO - Cannot run write commands} {
-        r set foo bar
-        catch {r eval_ro {redis.call('del', KEYS[1]);} 1 foo} e
-        set e
-    } {ERR * Write commands are not allowed from read-only scripts}
-
-    test {EVALSHA_RO - Cannot run write commands} {
-        r set foo bar
-        catch {r evalsha_ro a1e63e1cd1bd1d5413851949332cfb9da4ee6dc0 1 foo} e
-        set e
-    } {ERR * Write commands are not allowed from read-only scripts}
-}
-
-start_server {tags {"repl"}} {
-    start_server {} {
-        set master [srv -1 client]
-        set master_host [srv -1 host]
-        set master_port [srv -1 port]
-        set slave [srv 0 client]
-
-        $slave slaveof $master_host $master_port
-        wait_for_sync $slave
-
-        test {SCRIPTING: script load on master, read on slave} {
-            set sha [$master script load "return 'script loaded'"]
-            assert_equal 4167ea82ed9c381c7659f7cf93f394219147e8c4 $sha
-            wait_for_ofs_sync $master $slave
-            assert_equal 1 [$master script exists $sha]
-            assert_equal 1 [$slave script exists $sha]
-
-            $master script flush
-            wait_for_ofs_sync $master $slave
-            assert_equal 0 [$slave script exists $sha]
-        }
-    }
-}