RLTest 0.7.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
RLTest/exists_redis.py ADDED
@@ -0,0 +1,122 @@
1
+
2
+ from __future__ import print_function
3
+ import redis
4
+ import subprocess
5
+ import sys
6
+ import os
7
+ import platform
8
+ from .utils import Colors, wait_for_conn
9
+
10
+
11
+ MASTER = 1
12
+ SLAVE = 2
13
+
14
+
15
+ class ExistsRedisEnv(object):
16
+ def __init__(self, addr='localhost:6379', password = None, **kwargs):
17
+ self.host, self.port = addr.split(':')
18
+ self.port = int(self.port)
19
+ self.password = password
20
+ self.useTLS = kwargs['useTLS']
21
+ self.decodeResponses = kwargs.get('decodeResponses', False)
22
+
23
+ @property
24
+ def has_interactive_debugger(self):
25
+ return False
26
+
27
+ def _printEnvData(self, prefix='', role=MASTER):
28
+ print(Colors.Yellow(prefix + 'addr: %s:%d' % (self.host, self.port)))
29
+
30
+ def printEnvData(self, prefix=''):
31
+ print(Colors.Yellow(prefix + 'master:'))
32
+ self._printEnvData(prefix + '\t', MASTER)
33
+
34
+ def startEnv(self, masters = True, slaves = True):
35
+ if not self.isUp():
36
+ raise Exception('env is not up')
37
+
38
+ def stopEnv(self, masters = True, slaves = True):
39
+ pass
40
+
41
+ def getConnection(self, shardId=1):
42
+ return redis.StrictRedis(self.host, self.port, password=self.password, decode_responses=self.decodeResponses)
43
+
44
+ def getSlaveConnection(self):
45
+ raise Exception('asked for slave connection but no slave exists')
46
+
47
+ # List of nodes that initial bootstrapping can be done from
48
+ def getMasterNodesList(self):
49
+ node_info = {"host": None, "port": None, "unix_socket_path": None, "password": None}
50
+ node_info["password"] = self.password
51
+ node_info["host"] = self.host
52
+ node_info["port"] = self.port
53
+ return [node_info]
54
+
55
+ # List containing a connection for each of the master nodes
56
+ def getOSSMasterNodesConnectionList(self):
57
+ return [self.getConnection()]
58
+
59
+ def getConnectionByKey(self, key, command):
60
+ return self.getConnection()
61
+
62
+ def _waitForBgsaveToFinish(self):
63
+ # on new Redis version (6.2 and above)
64
+ # flush trigger background rdb save
65
+ # waiting for rdbsave to finish
66
+ while True:
67
+ if not self.getConnection().execute_command('info', 'Persistence')['rdb_bgsave_in_progress']:
68
+ break
69
+
70
+ def flush(self):
71
+ self.getConnection().flushall()
72
+ self._waitForBgsaveToFinish()
73
+
74
+ def _waitForChild(self, conns):
75
+ import time
76
+ # Wait until file is available
77
+ for con in conns:
78
+ while True:
79
+ info = con.info('persistence')
80
+ if info['aof_rewrite_scheduled'] or info['aof_rewrite_in_progress']:
81
+ time.sleep(0.1)
82
+ else:
83
+ break
84
+
85
+ def dumpAndReload(self, restart=False, shardId=1, timeout_sec=0):
86
+ self._waitForBgsaveToFinish()
87
+ conn = self.getConnection()
88
+ conn.save()
89
+ try:
90
+ conn.execute_command('DEBUG', 'RELOAD')
91
+ except redis.RedisError as err:
92
+ raise err
93
+
94
+ def broadcast(self, *cmd):
95
+ try:
96
+ self.getConnection().execute_command(*cmd)
97
+ except Exception as e:
98
+ print(e)
99
+
100
+ def checkExitCode(self):
101
+ return True
102
+
103
+ def isUp(self):
104
+ return self.getConnection().ping()
105
+
106
+ def isTLS(self):
107
+ return self.useTLS
108
+
109
+ def exists(self, val):
110
+ return self.getConnection().exists(val)
111
+
112
+ def hmset(self, *args):
113
+ return self.getConnection().hmset(*args)
114
+
115
+ def keys(self, reg):
116
+ return self.getConnection().keys(reg)
117
+
118
+ def isUnixSocket(self):
119
+ return False
120
+
121
+ def isTcp(self):
122
+ return True
RLTest/loader.py ADDED
@@ -0,0 +1,174 @@
1
+ from __future__ import print_function
2
+ import os
3
+ import sys
4
+ import importlib.util
5
+ import inspect
6
+ from RLTest.utils import Colors
7
+
8
+
9
+ class TestFunction(object):
10
+ is_class = False
11
+
12
+ def __init__(self, filename, symbol, modulename):
13
+ self.filename = filename
14
+ self.symbol = symbol
15
+ self.modulename = modulename
16
+ self.is_method = False
17
+ self.name = '{}:{}'.format(self.modulename, symbol)
18
+
19
+ def initialize(self):
20
+ module_spec = importlib.util.spec_from_file_location(self.modulename, self.filename)
21
+ module = importlib.util.module_from_spec(module_spec)
22
+ sys.modules[self.modulename] = module
23
+ module_spec.loader.exec_module(module)
24
+ obj = getattr(module, self.symbol)
25
+ self.target = obj
26
+
27
+ def shortname(self):
28
+ return self.target.__name__
29
+
30
+ class TestMethod(object):
31
+ is_class = False
32
+
33
+ def __init__(self, obj, name):
34
+ self.target = obj
35
+ self.name = name
36
+ self.is_method = True
37
+
38
+ def initialize(self):
39
+ pass
40
+
41
+ def shortname(self):
42
+ return self.target.__name__
43
+
44
+ class TestClass(object):
45
+ is_class = True
46
+
47
+ def __init__(self, filename, symbol, modulename, functions):
48
+ self.filename = filename
49
+ self.symbol = symbol
50
+ self.modulename = modulename
51
+ self.functions = functions
52
+ self.name = '{}:{}'.format(self.modulename, symbol)
53
+
54
+ def initialize(self):
55
+ module_spec = importlib.util.spec_from_file_location(self.modulename, self.filename)
56
+ module = importlib.util.module_from_spec(module_spec)
57
+ sys.modules[self.modulename] = module
58
+ module_spec.loader.exec_module(module)
59
+ obj = getattr(module, self.symbol)
60
+ self.clsname = obj.__name__
61
+ self.cls = obj
62
+
63
+ def create_instance(self, *args, **kwargs):
64
+ return self.cls(*args, **kwargs)
65
+
66
+ def get_functions(self, instance):
67
+ fns = []
68
+ for mname in self.functions:
69
+ bound = getattr(instance, mname)
70
+ if not callable(bound):
71
+ continue
72
+ fns.append(TestMethod(bound,
73
+ name='{}:{}.{}'.format(self.modulename, self.clsname, mname)))
74
+ return fns
75
+
76
+
77
+ class TestLoader(object):
78
+ def __init__(self):
79
+ self.tests = []
80
+
81
+ def load_spec(self, arg):
82
+ # if arg is a list, load its elements
83
+ if isinstance(arg, list):
84
+ for spec in arg:
85
+ self.load_spec(spec)
86
+ return
87
+
88
+ # See what kind of spec this is!
89
+ """
90
+ Load tests from single argument form, e.g. foo.py:BarBaz
91
+ """
92
+ if ':' in arg:
93
+ filename, varname = arg.split(':')
94
+ else:
95
+ filename = arg
96
+ varname = None
97
+ if os.path.isdir(filename):
98
+ sys.path.append(filename)
99
+ self.scan_dir(filename)
100
+ return
101
+
102
+ # Ensure the path is in sys.path
103
+ dirname = os.path.abspath(os.path.dirname(filename))
104
+ if dirname not in sys.path:
105
+ sys.path.append(dirname)
106
+
107
+ module_name, _ = os.path.splitext(os.path.basename(filename))
108
+ toplevel_filter, subfilter = None, None
109
+ if varname:
110
+ if '.' in varname:
111
+ toplevel_filter, subfilter = varname.split('.')
112
+ else:
113
+ toplevel_filter = varname
114
+
115
+ self.load_files(dirname, module_name, toplevel_filter, subfilter)
116
+
117
+ def load_files(self, module_dir, module_name, toplevel_filter=None, subfilter=None):
118
+ filename = '%s/%s.py' % (module_dir, module_name)
119
+ try:
120
+ module_spec = importlib.util.spec_from_file_location(module_name, filename)
121
+ module = importlib.util.module_from_spec(module_spec)
122
+ sys.modules[module_name] = module
123
+ module_spec.loader.exec_module(module)
124
+ for symbol in dir(module):
125
+ if not self.filter_modulevar(symbol, toplevel_filter):
126
+ continue
127
+
128
+ obj = getattr(module, symbol)
129
+ if inspect.isclass(obj):
130
+ methnames = [mname for mname in dir(obj)
131
+ if self.filter_method(mname, subfilter)]
132
+ self.tests.append(TestClass(filename, symbol, module_name, methnames))
133
+ elif inspect.isfunction(obj):
134
+ self.tests.append(TestFunction(filename, symbol, module_name))
135
+ except OSError as e:
136
+ print(Colors.Red("Can't access file %s." % filename))
137
+ raise e
138
+ except Exception as e:
139
+ print(Colors.Red("Problems in file %s: %s" % (filename, e)))
140
+ raise e
141
+
142
+ def scan_dir(self, testdir):
143
+ for filename in os.listdir(testdir):
144
+ if filename.startswith('test') and filename.endswith('.py'):
145
+ module_name, ext = os.path.splitext(filename)
146
+ self.load_files(testdir, module_name)
147
+
148
+ def filter_modulevar(self, candidate, toplevel_filter):
149
+ if not candidate.lower().startswith('test'):
150
+ return False
151
+ if toplevel_filter and candidate != toplevel_filter:
152
+ return False
153
+ return True
154
+
155
+ def filter_method(self, candidate, subfilter):
156
+ if not candidate.lower().startswith('test'):
157
+ return False
158
+ if subfilter and candidate != subfilter:
159
+ return False
160
+
161
+ return True
162
+
163
+ def __iter__(self):
164
+ return iter(self.tests)
165
+
166
+ def print_tests(self):
167
+ tests = []
168
+ for t in self.tests:
169
+ if t.is_class:
170
+ for m in t.functions:
171
+ tests.append(f"{t.name}.{m}")
172
+ else:
173
+ tests.append(t.name)
174
+ print(*sorted(tests), sep='\n')
RLTest/random_port.py ADDED
@@ -0,0 +1,63 @@
1
+ import random
2
+ import socket
3
+ import struct
4
+ import fcntl
5
+ import os
6
+ import errno
7
+ import json
8
+
9
+ def _check_alive(pid):
10
+ try:
11
+ os.kill(pid, 0)
12
+ return True
13
+ except OSError as e:
14
+ if e.errno == errno.EPERM:
15
+ return True
16
+ return False
17
+
18
+
19
+ def register_port(port):
20
+ fp = open('/tmp/rltest_portfile.lock', 'a+')
21
+ fcntl.flock(fp.fileno(), fcntl.LOCK_EX)
22
+ fp.seek(0, 2) # seek from end
23
+ if fp.tell() == 0:
24
+ entries = {}
25
+ else:
26
+ fp.seek(0, 0)
27
+ entries = json.load(fp)
28
+ # remove not responsive processes
29
+ entries = { p:pid for p, pid in entries.items() if _check_alive(pid) }
30
+
31
+ if str(port) in entries:
32
+ ret = False
33
+ else:
34
+ entries[str(port)] = os.getpid()
35
+ ret = True
36
+
37
+ fp.seek(0, 0)
38
+ fp.truncate()
39
+ json.dump(entries, fp)
40
+ fp.close()
41
+ return ret
42
+
43
+
44
+ def get_random_port():
45
+ for _ in range(10000):
46
+ p = random.randint(10000, 20000)
47
+ # Try to open and bind the socket
48
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
49
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
50
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
51
+ try:
52
+ s.bind(('', p))
53
+ s.close()
54
+ if not register_port(p):
55
+ continue
56
+ return p
57
+ except Exception as e:
58
+ if hasattr(e, 'errno') and e.errno in (errno.EADDRINUSE, errno.EADDRNOTAVAIL):
59
+ pass
60
+ else:
61
+ raise e
62
+
63
+ raise Exception('Could not find open port to listen on!')
@@ -0,0 +1,241 @@
1
+ from __future__ import print_function
2
+
3
+ from .redis_std import StandardEnv
4
+ from redis.cluster import ClusterNode
5
+ import redis
6
+ import time
7
+ from RLTest.utils import Colors
8
+
9
+
10
+ class ClusterEnv(object):
11
+ def __init__(self, **kwargs):
12
+ self.shards = []
13
+ self.envIsUp = False
14
+ self.envIsHealthy = False
15
+ self.modulePath = kwargs['modulePath']
16
+ self.moduleArgs = kwargs['moduleArgs']
17
+ self.password = kwargs['password']
18
+ self.shardsCount = kwargs.pop('shardsCount')
19
+ useSlaves = kwargs.get('useSlaves', False)
20
+ self.useTLS = kwargs['useTLS']
21
+ self.decodeResponses = kwargs.get('decodeResponses', False)
22
+ self.tlsPassphrase = kwargs.get('tlsPassphrase', None)
23
+ self.protocol = kwargs.get('protocol', 2)
24
+ self.terminateRetries = kwargs.get('terminateRetries', None)
25
+ self.terminateRetrySecs = kwargs.get('terminateRetrySecs', None)
26
+ startPort = kwargs.pop('port', 10000)
27
+ totalRedises = self.shardsCount * (2 if useSlaves else 1)
28
+ randomizePorts = kwargs.pop('randomizePorts', False)
29
+ for i in range(0, totalRedises, (2 if useSlaves else 1)):
30
+ port = 0 if randomizePorts else startPort
31
+ shard = StandardEnv(port=port, serverId=(i + 1),
32
+ clusterEnabled=True, **kwargs)
33
+ self.shards.append(shard)
34
+ startPort += 2
35
+
36
+ def printEnvData(self, prefix=''):
37
+ print(Colors.Yellow(prefix + 'info:'))
38
+ print(Colors.Yellow(prefix + '\tshards count:%d' % len(self.shards)))
39
+ if self.modulePath:
40
+ print(Colors.Yellow(prefix + '\tzip module path:%s' % self.modulePath))
41
+ if self.moduleArgs:
42
+ print(Colors.Yellow(prefix + '\tmodule args:%s' % self.moduleArgs))
43
+ for i, shard in enumerate(self.shards):
44
+ print(Colors.Yellow(prefix + 'shard: %d' % (i + 1)))
45
+ shard.printEnvData(prefix + '\t')
46
+
47
+ def getInformationBeforeDispose(self):
48
+ return [shard.getInformationBeforeDispose() for shard in self.shards]
49
+
50
+ def getInformationAfterDispose(self):
51
+ return [shard.getInformationAfterDispose() for shard in self.shards]
52
+
53
+ def waitCluster(self, timeout_sec=40):
54
+ st = time.time()
55
+ ok = 0
56
+
57
+ while st + timeout_sec > time.time():
58
+ ok = 0
59
+ for shard in self.shards:
60
+ con = shard.getConnection()
61
+ try:
62
+ status = con.execute_command('CLUSTER', 'INFO')
63
+ except Exception as e:
64
+ print('got error on cluster info, will try again, %s' % str(e))
65
+ continue
66
+ if 'cluster_state:ok' in str(status):
67
+ ok += 1
68
+ if ok == len(self.shards):
69
+ for shard in self.shards:
70
+ try:
71
+ shard.getConnection().execute_command('FT.CLUSTERREFRESH')
72
+ except Exception:
73
+ pass
74
+ try:
75
+ shard.getConnection().execute_command('SEARCH.CLUSTERREFRESH')
76
+ except Exception:
77
+ pass
78
+ return
79
+
80
+ time.sleep(0.1)
81
+ raise RuntimeError(
82
+ "Cluster OK wait loop timed out after %s seconds" % timeout_sec)
83
+
84
+ def startEnv(self, masters=True, slaves=True):
85
+ if self.envIsUp == True:
86
+ return # env is already up
87
+ try:
88
+ for shard in self.shards:
89
+ shard.startEnv(masters, slaves)
90
+ except Exception:
91
+ for shard in self.shards:
92
+ shard.stopEnv()
93
+ raise
94
+
95
+ slots_per_node = int(16384 / len(self.shards)) + 1
96
+ for i, shard in enumerate(self.shards):
97
+ con = shard.getConnection()
98
+ for s in self.shards:
99
+ con.execute_command('CLUSTER', 'MEET',
100
+ '127.0.0.1', s.getMasterPort())
101
+
102
+ start_slot = i * slots_per_node
103
+ end_slot = start_slot + slots_per_node
104
+ if end_slot > 16384:
105
+ end_slot = 16384
106
+
107
+ try:
108
+ con.execute_command('CLUSTER', 'ADDSLOTS', *(str(x)
109
+ for x in range(start_slot, end_slot)))
110
+ except Exception:
111
+ pass
112
+
113
+ self.waitCluster()
114
+ self.envIsUp = True
115
+ self.envIsHealthy = True
116
+
117
+ def stopEnvWithSegFault(self, masters=True, slaves=True):
118
+ for shard in self.shards:
119
+ shard.stopEnvWithSegFault(masters, slaves)
120
+
121
+ def stopEnv(self, masters=True, slaves=True):
122
+ self.envIsUp = False
123
+ self.envIsHealthy = False
124
+ for shard in self.shards:
125
+ shard.stopEnv(masters, slaves)
126
+ self.envIsUp = self.envIsUp or shard.envIsUp
127
+ self.envIsHealthy = self.envIsHealthy and shard.envIsUp
128
+
129
+ def getConnection(self, shardId=1):
130
+ return self.shards[shardId - 1].getConnection()
131
+
132
+ def getClusterConnection(self):
133
+ statupNode = [ClusterNode(a['host'], a['port']) for a in self.getMasterNodesList()]
134
+ if self.useTLS:
135
+ return redis.RedisCluster(
136
+ ssl=True,
137
+ ssl_keyfile=self.shards[0].getTLSKeyFile(),
138
+ ssl_certfile=self.shards[0].getTLSCertFile(),
139
+ ssl_cert_reqs=None,
140
+ ssl_ca_certs=self.shards[0].getTLSCACertFile(),
141
+ ssl_password=self.tlsPassphrase,
142
+ password=self.password,
143
+ startup_nodes=statupNode,
144
+ decode_responses=self.decodeResponses,
145
+ protocol=self.protocol,
146
+ terminateRetries=self.terminateRetries, terminateRetrySecs=self.terminateRetrySecs
147
+ )
148
+ else:
149
+ return redis.RedisCluster(
150
+ startup_nodes=statupNode,
151
+ decode_responses=self.decodeResponses, password=self.password,
152
+ protocol=self.protocol,
153
+ terminateRetries=self.terminateRetries, terminateRetrySecs=self.terminateRetrySecs)
154
+
155
+ def getSlaveConnection(self):
156
+ raise Exception('unsupported')
157
+
158
+ # List of nodes that initial bootstrapping can be done from
159
+ def getMasterNodesList(self):
160
+ full_master_list = []
161
+ for shard in self.shards:
162
+ node_info_list = shard.getMasterNodesList()
163
+ full_master_list.append(node_info_list[0])
164
+ return full_master_list
165
+
166
+ # List containing a connection for each of the master nodes
167
+ def getOSSMasterNodesConnectionList(self):
168
+ full_master_connection_list = []
169
+ for shard in self.shards:
170
+ full_master_connection_list.append(shard.getConnection())
171
+ return full_master_connection_list
172
+
173
+ # Gets a cluster connection by key. On std redis the default connection is returned.
174
+ def getConnectionByKey(self, key, command):
175
+ clusterConn = self.getClusterConnection()
176
+ target_node = clusterConn._determine_nodes(command, key) # we will always which will give us the node responsible for the key
177
+ return clusterConn.get_redis_connection(target_node[0])
178
+
179
+ def addShardToCluster(self, redisBinaryPath, output_files_format, **kwargs):
180
+ kwargs.pop('port')
181
+ port = self.shards[-1].port + 2 # use a fresh port
182
+ self.shardsCount += 1
183
+ new_shard = StandardEnv(redisBinaryPath, port, outputFilesFormat=output_files_format,
184
+ serverId=self.shardsCount, clusterEnabled=True, **kwargs)
185
+ try:
186
+ new_shard.startEnv()
187
+ except Exception:
188
+ new_shard.stopEnv()
189
+ raise
190
+ self.shards.append(new_shard)
191
+ # Notify other shards that the new shard is available and wait for the topology change to be acknowledged.
192
+ conn = new_shard.getConnection()
193
+ for s in self.shards:
194
+ conn.execute_command('CLUSTER', 'MEET', '127.0.0.1', s.getMasterPort())
195
+ self.waitCluster()
196
+
197
+ def flush(self):
198
+ self.getClusterConnection().flushall()
199
+
200
+ def dumpAndReload(self, restart=False, shardId=None, timeout_sec=40):
201
+ if shardId is None:
202
+ for shard in self.shards:
203
+ shard.dumpAndReload(restart=restart)
204
+ self.waitCluster(timeout_sec=timeout_sec)
205
+ else:
206
+ self.shards[shardId -
207
+ 1].dumpAndReload(restart=restart, shardId=None, timeout_sec=timeout_sec)
208
+
209
+ def broadcast(self, *cmd):
210
+ for shard in self.shards:
211
+ shard.broadcast(*cmd)
212
+
213
+ def checkExitCode(self):
214
+ for shard in self.shards:
215
+ if not shard.checkExitCode():
216
+ return False
217
+ return True
218
+
219
+ def isUp(self):
220
+ return self.envIsUp or self.envIsHealthy and self.waitCluster()
221
+
222
+ def isHealthy(self):
223
+ return self.envIsHealthy
224
+
225
+ def isUnixSocket(self):
226
+ return False
227
+
228
+ def isTcp(self):
229
+ return True
230
+
231
+ def isTLS(self):
232
+ return self.useTLS
233
+
234
+ def exists(self, val):
235
+ return self.getClusterConnection().exists(val)
236
+
237
+ def hmset(self, *args):
238
+ return self.getClusterConnection().hmset(*args)
239
+
240
+ def keys(self, reg):
241
+ return self.getClusterConnection().keys(reg)