diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34c20b19..8c98d5db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ jobs: strategy: matrix: redis-version: [ "2.8", "3.0", "4.0", "5.0", "6.0", "7.0" ] + fail-fast: false container: ubuntu:latest steps: - name: Git checkout @@ -30,6 +31,45 @@ jobs: cp src/redis-server bin/redis-server echo "$GITHUB_WORKSPACE/redis/bin" >> $GITHUB_PATH + - name: clone and make TairString Module + if: contains( '5.0, 6.0, 7.0', matrix.redis-version) + run: | + cd $GITHUB_WORKSPACE + apt-get install -y cmake + git clone https://github.com/tair-opensource/TairString.git + cd TairString + mkdir build + cd build + cmake ../ && make -j + cp $GITHUB_WORKSPACE/TairString/lib/tairstring_module.so /lib + + + + - name: clone and make TairHash Module + if: contains( '5.0, 6.0, 7.0', matrix.redis-version) + run: | + cd $GITHUB_WORKSPACE + git clone https://github.com/tair-opensource/TairHash.git + cd TairHash + mkdir build + cd build + cmake ../ && make -j + cp $GITHUB_WORKSPACE/TairHash/lib/tairhash_module.so /lib + + + + - name: clone and make TairZset Module + if: contains( '5.0, 6.0, 7.0', matrix.redis-version) + run: | + cd $GITHUB_WORKSPACE + git clone https://github.com/tair-opensource/TairZset.git + cd TairZset + mkdir build + cd build + cmake ../ && make -j + cp $GITHUB_WORKSPACE/TairZset/lib/tairzset_module.so /lib + + - name: Setup Python uses: actions/setup-python@v4 with: diff --git a/internal/commands/table.go b/internal/commands/table.go index 686e9ade..6053b778 100644 --- a/internal/commands/table.go +++ b/internal/commands/table.go @@ -4505,4 +4505,58 @@ var redisCommands = map[string]redisCommand{ }, }, }, + "EXHSET": { + "TAIRHASH", + []keySpec{ + { + "index", + 1, + "", + 0, + "range", + 0, + 1, + 0, + 0, + 0, + 0, + }, + }, + }, + "EXSET": { + "TAIRSTRING", + []keySpec{ + { + "index", + 1, + "", + 0, + "range", + 0, + 1, + 0, + 0, + 0, + 0, + }, + }, + }, + "EXZADD": { + "TAIRZSET", + []keySpec{ + { + "index", + 1, + "", + 0, + "range", + 0, + 1, + 0, + 0, + 0, + 0, + }, + }, + }, } diff --git a/internal/rdb/structure/module2_struct.go b/internal/rdb/structure/module2_struct.go new file mode 100644 index 00000000..ca3da026 --- /dev/null +++ b/internal/rdb/structure/module2_struct.go @@ -0,0 +1,71 @@ +package structure + +import ( + "fmt" + "io" + "log" + "strconv" +) + +const ( + rdbModuleOpcodeEOF = 0 // End of module value. + rdbModuleOpcodeSINT = 1 // Signed integer. + rdbModuleOpcodeUINT = 2 // Unsigned integer. + rdbModuleOpcodeFLOAT = 3 // Float. + rdbModuleOpcodeDOUBLE = 4 // Double. + rdbModuleOpcodeSTRING = 5 // String. +) + +func ReadModuleUnsigned(rd io.Reader) string { + opcode := ReadByte(rd) + if opcode != rdbModuleOpcodeUINT { + log.Panicf("Unknown module unsigned encode type") + } + value := ReadLength(rd) + return strconv.FormatUint(value, 10) +} + +func ReadModuleSigned(rd io.Reader) string { + opcode := ReadByte(rd) + if opcode != rdbModuleOpcodeSINT { + log.Panicf("Unknown module signed encode type") + } + value := ReadLength(rd) + return strconv.FormatUint(value, 10) +} + +func ReadModuleFloat(rd io.Reader) string { + opcode := ReadByte(rd) + if opcode != rdbModuleOpcodeDOUBLE { + + log.Panicf("Unknown module double encode type") + } + value := ReadDouble(rd) + return fmt.Sprintf("%f", value) +} + +func ReadModuleDouble(rd io.Reader) string { + opcode := ReadByte(rd) + if opcode != rdbModuleOpcodeDOUBLE { + log.Panicf("Unknown module double encode type") + } + value := ReadDouble(rd) + return fmt.Sprintf("%f", value) +} + +func ReadModuleString(rd io.Reader) string { + opcode := ReadByte(rd) + if opcode != rdbModuleOpcodeSTRING { + log.Panicf("Unknown module string encode type") + } + return ReadString(rd) +} + +func ReadModuleEof(rd io.Reader) error { + eof := ReadLength(rd) + if eof != rdbModuleOpcodeEOF { + log.Panicf("The RDB file is not teminated by the proper module value EOF marker") + } + return nil + +} diff --git a/internal/rdb/types/interface.go b/internal/rdb/types/interface.go index 1e237d75..03ba67e3 100644 --- a/internal/rdb/types/interface.go +++ b/internal/rdb/types/interface.go @@ -91,8 +91,7 @@ func ParseObject(rd io.Reader, typeByte byte, key string) RedisObject { o.LoadFromBuffer(rd, key, typeByte) return o case rdbTypeModule, rdbTypeModule2: // module - o := new(ModuleObject) - o.LoadFromBuffer(rd, key, typeByte) + o := PareseModuleType(rd, key, typeByte) return o } log.Panicf("unknown type byte: %d", typeByte) diff --git a/internal/rdb/types/module2.go b/internal/rdb/types/module2.go index 73e01b06..3646f781 100644 --- a/internal/rdb/types/module2.go +++ b/internal/rdb/types/module2.go @@ -1,40 +1,39 @@ package types import ( + "io" + "RedisShake/internal/log" "RedisShake/internal/rdb/structure" - "io" ) -type ModuleObject struct { +type ModuleObject interface { + RedisObject } -func (o *ModuleObject) LoadFromBuffer(rd io.Reader, key string, typeByte byte) { +func PareseModuleType(rd io.Reader, key string, typeByte byte) ModuleObject { if typeByte == rdbTypeModule { log.Panicf("module type with version 1 is not supported, key=[%s]", key) } moduleId := structure.ReadLength(rd) moduleName := moduleTypeNameByID(moduleId) - opcode := structure.ReadByte(rd) - for opcode != rdbModuleOpcodeEOF { - switch opcode { - case rdbModuleOpcodeSINT: - case rdbModuleOpcodeUINT: - structure.ReadLength(rd) - case rdbModuleOpcodeFLOAT: - structure.ReadFloat(rd) - case rdbModuleOpcodeDOUBLE: - structure.ReadDouble(rd) - case rdbModuleOpcodeSTRING: - structure.ReadString(rd) - default: - log.Panicf("unknown module opcode=[%d], module name=[%s]", opcode, moduleName) - } - opcode = structure.ReadByte(rd) + switch moduleName { + case "exstrtype": + o := new(TairStringObject) + o.LoadFromBuffer(rd, key, typeByte) + return o + case "tairhash-": + o := new(TairHashObject) + o.LoadFromBuffer(rd, key, typeByte) + return o + case "tairzset_": + o := new(TairZsetObject) + o.LoadFromBuffer(rd, key, typeByte) + return o + default: + log.Panicf("unsupported module type: %s", moduleName) + return nil + } -} -func (o *ModuleObject) Rewrite() []RedisCmd { - log.Panicf("module Rewrite not implemented") - return nil } diff --git a/internal/rdb/types/tairhash.go b/internal/rdb/types/tairhash.go new file mode 100644 index 00000000..7476bd82 --- /dev/null +++ b/internal/rdb/types/tairhash.go @@ -0,0 +1,57 @@ +package types + +import ( + "io" + "strconv" + + "RedisShake/internal/rdb/structure" +) + +type TairHashValue struct { + skey string + version string + expire string + fieldValue string +} + +type TairHashObject struct { + dictSize string + key string + value []TairHashValue +} + +func (o *TairHashObject) LoadFromBuffer(rd io.Reader, key string, typeByte byte) { + o.dictSize = structure.ReadModuleUnsigned(rd) + o.key = structure.ReadModuleString(rd) + + size, _ := strconv.Atoi(o.dictSize) + for i := 0; i < size; i++ { + hashValue := TairHashValue{ + structure.ReadModuleString(rd), + structure.ReadModuleUnsigned(rd), + structure.ReadModuleUnsigned(rd), + structure.ReadModuleString(rd), + } + o.value = append(o.value, hashValue) + } + structure.ReadModuleEof(rd) +} + +func (o *TairHashObject) Rewrite() []RedisCmd { + var cmds []RedisCmd + size, _ := strconv.Atoi(o.dictSize) + for i := 0; i < size; i++ { + cmd := []string{} + expire, _ := strconv.Atoi(o.value[i].expire) + if expire == 0 { + cmd = append(cmd, "EXHSET", o.key, o.value[i].skey, o.value[i].fieldValue) + } else { + cmd = append(cmd, "EXHSET", o.key, o.value[i].skey, o.value[i].fieldValue, + "ABS", o.value[i].version, + "PXAT", o.value[i].expire) + } + + cmds = append(cmds, cmd) + } + return cmds +} diff --git a/internal/rdb/types/tairstring.go b/internal/rdb/types/tairstring.go new file mode 100644 index 00000000..e5db7dbd --- /dev/null +++ b/internal/rdb/types/tairstring.go @@ -0,0 +1,32 @@ +package types + +import ( + "io" + + "RedisShake/internal/rdb/structure" +) + +type TairStringValue struct { + version string + flags string + tairValue string +} + +type TairStringObject struct { + value TairStringValue + key string +} + +func (o *TairStringObject) LoadFromBuffer(rd io.Reader, key string, typeByte byte) { + o.key = key + o.value.version = structure.ReadModuleUnsigned(rd) + o.value.flags = structure.ReadModuleUnsigned(rd) + o.value.tairValue = structure.ReadModuleString(rd) + structure.ReadModuleEof(rd) +} + +func (o *TairStringObject) Rewrite() []RedisCmd { + cmd := RedisCmd{} + cmd = append(cmd, "EXSET", o.key, o.value.tairValue, "ABS", o.value.version, "FLAGS", o.value.flags) + return []RedisCmd{cmd} +} diff --git a/internal/rdb/types/tairzset.go b/internal/rdb/types/tairzset.go new file mode 100644 index 00000000..52f75b54 --- /dev/null +++ b/internal/rdb/types/tairzset.go @@ -0,0 +1,46 @@ +package types + +import ( + "io" + "strconv" + "strings" + + "RedisShake/internal/rdb/structure" +) + +type TairZsetObject struct { + key string + length string + scoreNum string + value map[string][]string +} + +func (o *TairZsetObject) LoadFromBuffer(rd io.Reader, key string, typeByte byte) { + o.key = key + o.length = structure.ReadModuleUnsigned(rd) + o.scoreNum = structure.ReadModuleUnsigned(rd) + + len, _ := strconv.Atoi(o.length) + scoreNum, _ := strconv.Atoi(o.scoreNum) + valueMap := make(map[string][]string) + for i := 0; i < len; i++ { + key := structure.ReadModuleString(rd) + values := []string{} + for j := 0; j < scoreNum; j++ { + values = append(values, structure.ReadModuleDouble(rd)) + } + valueMap[key] = values + } + o.value = valueMap + structure.ReadModuleEof(rd) +} + +func (o *TairZsetObject) Rewrite() []RedisCmd { + var cmds []RedisCmd + for k, v := range o.value { + score := strings.Join(v, "#") + cmd := RedisCmd{"EXZADD", o.key, score, k} + cmds = append(cmds, cmd) + } + return cmds +} diff --git a/scripts/commands/exhset.json b/scripts/commands/exhset.json new file mode 100644 index 00000000..425110c0 --- /dev/null +++ b/scripts/commands/exhset.json @@ -0,0 +1,29 @@ +{ + "EXHSET": { + "summary": "TairHash, Insert a field into the TairHash specified by the key", + "complexity": "O(1)", + "group": "TairHash", + "container": "TairHash", + "function": "exhsetCommand", + "key_specs": [ + { + "flags": [ + "RW", + "UPDATE" + ], + "begin_search": { + "index": { + "pos": 1 + } + }, + "find_keys": { + "range": { + "lastkey": 0, + "step": 1, + "limit": 0 + } + } + } + ] + } +} diff --git a/scripts/commands/exset.json b/scripts/commands/exset.json new file mode 100644 index 00000000..322b93a1 --- /dev/null +++ b/scripts/commands/exset.json @@ -0,0 +1,29 @@ +{ + "EXSET": { + "summary": "TairString, Set the value of a key", + "complexity": "O(1)", + "group": "TairString", + "container": "TairString", + "function": "exsetCommand", + "key_specs": [ + { + "flags": [ + "RW", + "UPDATE" + ], + "begin_search": { + "index": { + "pos": 1 + } + }, + "find_keys": { + "range": { + "lastkey": 0, + "step": 1, + "limit": 0 + } + } + } + ] + } +} diff --git a/scripts/commands/exzadd.json b/scripts/commands/exzadd.json new file mode 100644 index 00000000..c00ebc74 --- /dev/null +++ b/scripts/commands/exzadd.json @@ -0,0 +1,29 @@ +{ + "EXZADD": { + "summary": "TairZset, Adds all the specified members with the specified (multi)scores to the tairzset stored at key", + "complexity": "O(N)", + "group": "TairZset", + "container": "TairZset", + "function": "exzaddCommand", + "key_specs": [ + { + "flags": [ + "RW", + "UPDATE" + ], + "begin_search": { + "index": { + "pos": 1 + } + }, + "find_keys": { + "range": { + "lastkey": 0, + "step": 1, + "limit": 0 + } + } + } + ] + } +} diff --git a/scripts/table.json b/scripts/table.json index 11987b5c..86835529 100644 --- a/scripts/table.json +++ b/scripts/table.json @@ -3582,7 +3582,62 @@ } } ] + }, + "TairHash": { + "EXHSET": [ + { + "begin_search": { + "index": { + "pos": 1 + } + }, + "find_keys": { + "range": { + "lastkey": 0, + "step": 1, + "limit": 0 + } + } + } + ] + }, + "TairString": { + "EXSET": [ + { + "begin_search": { + "index": { + "pos": 1 + } + }, + "find_keys": { + "range": { + "lastkey": 0, + "step": 1, + "limit": 0 + } + } + } + ] + }, + "TairZset": { + "EXZADD ": [ + { + "begin_search": { + "index": { + "pos": 1 + } + }, + "find_keys": { + "range": { + "lastkey": 0, + "step": 1, + "limit": 0 + } + } + } + ] } + }, "container": [ "MODULE", diff --git a/tests/helpers/commands/__init__.py b/tests/helpers/commands/__init__.py index a1027747..b8a42486 100644 --- a/tests/helpers/commands/__init__.py +++ b/tests/helpers/commands/__init__.py @@ -1,2 +1,5 @@ from .select import SelectChecker from .string import StringChecker +from .tair_string import TairStringChecker +from .tair_hash import TairHashChecker +from .tair_zset import TairZsetChecker \ No newline at end of file diff --git a/tests/helpers/commands/tair_hash.py b/tests/helpers/commands/tair_hash.py new file mode 100644 index 00000000..d47abcf2 --- /dev/null +++ b/tests/helpers/commands/tair_hash.py @@ -0,0 +1,55 @@ +import pybbt + +from helpers.commands.checker import Checker +from helpers.redis import Redis +from helpers.constant import REDIS_SERVER_VERSION + + +class TairHashChecker(Checker): + PREFIX = "tairHash" + + def __init__(self): + self.cnt = 0 + + def add_data(self, r: Redis, cross_slots_cmd: bool): + if REDIS_SERVER_VERSION < 5.0: + return + p = r.pipeline() + # different parameters type + p.execute_command("EXHSET",f"{self.PREFIX}_{self.cnt}", "field", "value") + p.execute_command("EXHSET",f"{self.PREFIX}_{self.cnt}_ABS", "field_abs", "value_abs", "ABS", 2) + p.execute_command("EXHSET",f"{self.PREFIX}_{self.cnt}_EX", "field_ex", "value_ex", "EX", 20000) + + # different key + # different field + p.execute_command("EXHSET",f"{self.PREFIX}_{self.cnt}_ALL_01", "field_all_01", "value_all_01", "EX", 20000, "ABS", 2) + p.execute_command("EXHSET",f"{self.PREFIX}_{self.cnt}_ALL_01", "field_all_02", "value_all_02", "EX", 20000, "ABS", 3) + + p.execute_command("EXHSET",f"{self.PREFIX}_{self.cnt}_ALL_02", "field_all_01", "value_all_01", "EX", 20000, "ABS", 2) + p.execute_command("EXHSET",f"{self.PREFIX}_{self.cnt}_ALL_02", "field_all_02", "value_all_02", "EX", 20000, "ABS", 3) + + ret = p.execute() + # pybbt.ASSERT_EQ(ret, [b"1", b"1", b"1", b"1",b"1", b"1",b"1"]) + pybbt.ASSERT_EQ(ret, [1, 1, 1, 1, 1, 1, 1]) + + self.cnt += 1 + + def check_data(self, r: Redis, cross_slots_cmd: bool): + if REDIS_SERVER_VERSION < 5.0: + return + for i in range(self.cnt): + p = r.pipeline() + + p.execute_command("EXHGET",f"{self.PREFIX}_{i}", "field") + p.execute_command("EXHGET",f"{self.PREFIX}_{i}_ABS", "field_abs") + p.execute_command("EXHGET",f"{self.PREFIX}_{i}_EX", "field_ex") + + p.execute_command("EXHGET",f"{self.PREFIX}_{i}_ALL_01", "field_all_01") + p.execute_command("EXHGET",f"{self.PREFIX}_{i}_ALL_01", "field_all_02") + + p.execute_command("EXHGET",f"{self.PREFIX}_{i}_ALL_02", "field_all_01") + p.execute_command("EXHGET",f"{self.PREFIX}_{i}_ALL_02", "field_all_02") + + ret = p.execute() + # 需要确定一下如果一个命令返回多个值是如何封装的 + pybbt.ASSERT_EQ(ret, [b"value", b"value_abs", b"value_ex", b"value_all_01", b"value_all_02", b"value_all_01", b"value_all_02",]) \ No newline at end of file diff --git a/tests/helpers/commands/tair_string.py b/tests/helpers/commands/tair_string.py new file mode 100644 index 00000000..821d5a34 --- /dev/null +++ b/tests/helpers/commands/tair_string.py @@ -0,0 +1,43 @@ +import pybbt + +from helpers.commands.checker import Checker +from helpers.redis import Redis +from helpers.constant import REDIS_SERVER_VERSION + + +class TairStringChecker(Checker): + PREFIX = "tairString" + + def __init__(self): + self.cnt = 0 + + def add_data(self, r: Redis, cross_slots_cmd: bool): + if REDIS_SERVER_VERSION < 5.0: + return + p = r.pipeline() + # different parameters type + p.execute_command("EXSET",f"{self.PREFIX}_{self.cnt}_ABS", "abs_value", "VER",2) + p.execute_command("EXSET",f"{self.PREFIX}_{self.cnt}_FLAGS", "flags_value", "FLAGS", 2) + p.execute_command("Exset",f"{self.PREFIX}_{self.cnt}_EX", "ex_value", "EX", 20000) + + # different key + p.execute_command("Exset",f"{self.PREFIX}_{self.cnt}_ALL_01", "all_value_01", "EX", 20000, "ABS", 3, "FLAGS", 4) + p.execute_command("Exset",f"{self.PREFIX}_{self.cnt}_ALL_02", "all_value_02", "EX", 20000, "ABS", 4, "FLAGS", 5) + ret = p.execute() + pybbt.ASSERT_EQ(ret, [b"OK", b"OK", b"OK", b"OK", b"OK"]) + self.cnt += 1 + + def check_data(self, r: Redis, cross_slots_cmd: bool): + if REDIS_SERVER_VERSION < 5.0: + return + for i in range(self.cnt): + p = r.pipeline() + + p.execute_command("EXGET",f"{self.PREFIX}_{i}_ABS") + p.execute_command("EXGET",f"{self.PREFIX}_{i}_FLAGS", "WITHFLAGS") + p.execute_command("EXGET",f"{self.PREFIX}_{i}_EX") + p.execute_command("EXGET",f"{self.PREFIX}_{i}_ALL_01", "WITHFLAGS") + p.execute_command("EXGET",f"{self.PREFIX}_{i}_ALL_02", "WITHFLAGS") + + ret = p.execute() + pybbt.ASSERT_EQ(ret, [[b"abs_value", 1], [b"flags_value", 1, 2], [b"ex_value", 1], [b"all_value_01", 3, 4], [b"all_value_02", 4, 5]]) diff --git a/tests/helpers/commands/tair_zset.py b/tests/helpers/commands/tair_zset.py new file mode 100644 index 00000000..fff80c4c --- /dev/null +++ b/tests/helpers/commands/tair_zset.py @@ -0,0 +1,47 @@ +import pybbt + +from helpers.commands.checker import Checker +from helpers.redis import Redis +from helpers.constant import REDIS_SERVER_VERSION + + +class TairZsetChecker(Checker): + PREFIX = "tairZset" + + def __init__(self): + self.cnt = 0 + + def add_data(self, r: Redis, cross_slots_cmd: bool): + if REDIS_SERVER_VERSION < 5.0: + return + p = r.pipeline() + + # different key + # int or float + p.execute_command("EXZADD",f"{self.PREFIX}_{self.cnt}_key01", "1.1#1.2", "mem01","2.2#2.3", "mem02") + p.execute_command("EXZADD",f"{self.PREFIX}_{self.cnt}_key01", "3.3#3.4", "mem03","4.4#4.5", "mem04") + p.execute_command("EXZADD",f"{self.PREFIX}_{self.cnt}_key02", "1.1#1.2", "mem01") + p.execute_command("EXZADD",f"{self.PREFIX}_{self.cnt}_key02", "2.2#2.3", "mem02") + ret = p.execute() + pybbt.ASSERT_EQ(ret, [2, 2, 1, 1]) + + self.cnt += 1 + + def check_data(self, r: Redis, cross_slots_cmd: bool): + if REDIS_SERVER_VERSION < 5.0: + return + for i in range(self.cnt): + p = r.pipeline() + p.execute_command("EXZSCORE",f"{self.PREFIX}_{i}_key01", "mem01") + p.execute_command("EXZSCORE",f"{self.PREFIX}_{i}_key01", "mem02") + p.execute_command("EXZSCORE",f"{self.PREFIX}_{i}_key01", "mem03") + p.execute_command("EXZSCORE",f"{self.PREFIX}_{i}_key01", "mem04") + p.execute_command("EXZSCORE",f"{self.PREFIX}_{i}_key02", "mem01") + p.execute_command("EXZSCORE",f"{self.PREFIX}_{i}_key02", "mem02") + + ret = p.execute() + pybbt.ASSERT_EQ(ret, + [b'1.1000000000000001#1.2', + b'2.2000000000000002#2.2999999999999998', b'3.2999999999999998#3.3999999999999999', + b'4.4000000000000004#4.5', b'1.1000000000000001#1.2', + b'2.2000000000000002#2.2999999999999998'] ) diff --git a/tests/helpers/data_inserter.py b/tests/helpers/data_inserter.py index d513205f..504ed1e1 100644 --- a/tests/helpers/data_inserter.py +++ b/tests/helpers/data_inserter.py @@ -1,5 +1,6 @@ -from helpers.commands import SelectChecker, StringChecker +from helpers.commands import SelectChecker, StringChecker, TairStringChecker, TairHashChecker, TairZsetChecker from helpers.redis import Redis +from helpers.constant import PATH_REDIS_SERVER, REDIS_SERVER_VERSION class DataInserter: @@ -7,8 +8,12 @@ def __init__(self, ): self.checkers = [ StringChecker(), SelectChecker(), + TairStringChecker(), + TairHashChecker(), + TairZsetChecker(), ] + def add_data(self, r: Redis, cross_slots_cmd: bool): for checker in self.checkers: checker.add_data(r, cross_slots_cmd) diff --git a/tests/helpers/redis.py b/tests/helpers/redis.py index 3452f521..b90cf7b9 100644 --- a/tests/helpers/redis.py +++ b/tests/helpers/redis.py @@ -17,7 +17,15 @@ def __init__(self, args=None): self.port = get_free_port() self.dir = f"{self.case_ctx.dir}/redis_{self.port}" args.extend(["--port", str(self.port)]) - self.server = pybbt.Launcher(args=[PATH_REDIS_SERVER] + args, work_dir=self.dir) + + if REDIS_SERVER_VERSION > 4.0: + args.extend(["--loadmodule", "tairstring_module.so"]) + args.extend(["--loadmodule", "tairhash_module.so"]) + args.extend(["--loadmodule", "tairzset_module.so"]) + self.server = pybbt.Launcher(args=[PATH_REDIS_SERVER] + args, work_dir=self.dir) + else: + self.server = pybbt.Launcher(args=[PATH_REDIS_SERVER] + args, work_dir=self.dir) + self._wait_start() self.client = redis.Redis(host=self.host, port=self.port) self.case_ctx.add_exit_hook(lambda: self.server.stop()) @@ -58,4 +66,4 @@ def is_cluster(self): return self.client.info()["cluster_enabled"] def dbsize(self): - return self.client.dbsize() + return self.client.dbsize() \ No newline at end of file