redis-allocator 0.0.1__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.
- redis_allocator/__init__.py +28 -0
- redis_allocator/allocator.py +601 -0
- redis_allocator/lock.py +682 -0
- redis_allocator/task_queue.py +382 -0
- redis_allocator-0.0.1.dist-info/METADATA +229 -0
- redis_allocator-0.0.1.dist-info/RECORD +14 -0
- redis_allocator-0.0.1.dist-info/WHEEL +5 -0
- redis_allocator-0.0.1.dist-info/licenses/LICENSE +21 -0
- redis_allocator-0.0.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/conftest.py +46 -0
- tests/test_allocator.py +525 -0
- tests/test_lock.py +851 -0
- tests/test_task_queue.py +778 -0
tests/conftest.py
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
"""Fixtures for tests."""
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
import fakeredis
|
5
|
+
from redis.client import Redis
|
6
|
+
from redis_allocator.lock import RedisLock, RedisLockPool, ThreadLock, ThreadLockPool
|
7
|
+
|
8
|
+
|
9
|
+
@pytest.fixture
|
10
|
+
def redis_client():
|
11
|
+
"""Create a fakeredis client for testing."""
|
12
|
+
return fakeredis.FakeRedis(decode_responses=True)
|
13
|
+
|
14
|
+
|
15
|
+
@pytest.fixture
|
16
|
+
def redis_client_raw():
|
17
|
+
"""Create a fakeredis client with decode_responses=False for testing."""
|
18
|
+
return fakeredis.FakeRedis(decode_responses=False)
|
19
|
+
|
20
|
+
|
21
|
+
@pytest.fixture
|
22
|
+
def redis_lock(redis_client: Redis):
|
23
|
+
"""Create a RedisLock for testing."""
|
24
|
+
return RedisLock(redis_client, 'test-lock')
|
25
|
+
|
26
|
+
|
27
|
+
@pytest.fixture
|
28
|
+
def redis_lock_pool(redis_client: Redis):
|
29
|
+
"""Create a RedisLockPool for testing."""
|
30
|
+
pool = RedisLockPool(redis_client, 'test-pool')
|
31
|
+
yield pool
|
32
|
+
pool.clear()
|
33
|
+
|
34
|
+
|
35
|
+
@pytest.fixture
|
36
|
+
def thread_lock():
|
37
|
+
"""Create a ThreadLock for testing."""
|
38
|
+
return ThreadLock()
|
39
|
+
|
40
|
+
|
41
|
+
@pytest.fixture
|
42
|
+
def thread_lock_pool():
|
43
|
+
"""Create a ThreadLockPool for testing."""
|
44
|
+
pool = ThreadLockPool()
|
45
|
+
yield pool
|
46
|
+
pool.clear()
|
tests/test_allocator.py
ADDED
@@ -0,0 +1,525 @@
|
|
1
|
+
# flake8: noqa: F401
|
2
|
+
"""Tests for the Redis-based distributed memory allocation system.
|
3
|
+
|
4
|
+
This module tests the functionality of:
|
5
|
+
1. RedisThreadHealthCheckPool - For thread health monitoring
|
6
|
+
2. RedisAllocator - For distributed resource allocation
|
7
|
+
3. RedisAllocatorObject - For managing allocated resources
|
8
|
+
"""
|
9
|
+
import pytest
|
10
|
+
from unittest.mock import MagicMock, patch, call
|
11
|
+
from redis import RedisError
|
12
|
+
from redis_allocator.allocator import RedisAllocator, RedisThreadHealthCheckPool, RedisAllocatorObject, RedisAllocatableClass, RedisLockPool
|
13
|
+
|
14
|
+
|
15
|
+
# Use the _TestObject naming to avoid pytest trying to collect it as a test class
|
16
|
+
class _TestObject(RedisAllocatableClass):
|
17
|
+
"""Test implementation of RedisAllocatableClass for testing."""
|
18
|
+
|
19
|
+
def __init__(self):
|
20
|
+
self.config_key = None
|
21
|
+
self.config_params = None
|
22
|
+
self.closed = False
|
23
|
+
|
24
|
+
def set_config(self, key, params):
|
25
|
+
"""Set configuration parameters."""
|
26
|
+
self.config_key = key
|
27
|
+
self.config_params = params
|
28
|
+
|
29
|
+
def close(self):
|
30
|
+
"""Mark the object as closed."""
|
31
|
+
self.closed = True
|
32
|
+
|
33
|
+
def name(self):
|
34
|
+
"""Return a name for soft binding."""
|
35
|
+
return "test_object"
|
36
|
+
|
37
|
+
|
38
|
+
@pytest.fixture
|
39
|
+
def test_object():
|
40
|
+
"""Create a test object implementing RedisAllocatableClass."""
|
41
|
+
return _TestObject()
|
42
|
+
|
43
|
+
|
44
|
+
@pytest.fixture
|
45
|
+
def allocator(redis_client):
|
46
|
+
"""Create a RedisAllocator instance for testing."""
|
47
|
+
alloc = RedisAllocator(
|
48
|
+
redis_client,
|
49
|
+
'test',
|
50
|
+
'alloc-lock',
|
51
|
+
shared=False
|
52
|
+
)
|
53
|
+
# Set up initial keys
|
54
|
+
alloc.extend(['key1', 'key2', 'key3'])
|
55
|
+
return alloc
|
56
|
+
|
57
|
+
|
58
|
+
@pytest.fixture
|
59
|
+
def shared_allocator(redis_client):
|
60
|
+
"""Create a shared RedisAllocator instance for testing."""
|
61
|
+
alloc = RedisAllocator(
|
62
|
+
redis_client,
|
63
|
+
'test',
|
64
|
+
'shared-alloc',
|
65
|
+
shared=True
|
66
|
+
)
|
67
|
+
# Set up initial keys
|
68
|
+
alloc.extend(['key1', 'key2', 'key3'])
|
69
|
+
return alloc
|
70
|
+
|
71
|
+
|
72
|
+
@pytest.fixture
|
73
|
+
def health_checker(redis_client):
|
74
|
+
"""Create a RedisThreadHealthCheckPool instance for testing."""
|
75
|
+
return RedisThreadHealthCheckPool(
|
76
|
+
redis_client,
|
77
|
+
'test',
|
78
|
+
timeout=60
|
79
|
+
)
|
80
|
+
|
81
|
+
|
82
|
+
class TestRedisThreadHealthCheckPool:
|
83
|
+
"""Tests for the RedisThreadHealthCheckPool class."""
|
84
|
+
|
85
|
+
def test_initialization(self, health_checker, redis_client):
|
86
|
+
"""Test that initialization correctly registers the thread and sets up monitoring."""
|
87
|
+
# Initialization should register the current thread
|
88
|
+
assert health_checker.current_thread_id is not None
|
89
|
+
# Initialization calls update and extend, no need to check Redis calls directly
|
90
|
+
# since we're testing the object's behavior, not implementation details
|
91
|
+
assert hasattr(health_checker, 'timeout')
|
92
|
+
|
93
|
+
def test_update(self, health_checker, redis_client):
|
94
|
+
"""Test that update refreshes the thread's health status."""
|
95
|
+
# Override the parent class's update method to verify our object behavior
|
96
|
+
with patch.object(RedisLockPool, 'update') as mock_update:
|
97
|
+
# Call update
|
98
|
+
health_checker.update()
|
99
|
+
|
100
|
+
# Should call the parent's update method with thread ID and timeout
|
101
|
+
mock_update.assert_called_once_with(health_checker.current_thread_id, timeout=health_checker.timeout)
|
102
|
+
|
103
|
+
def test_finalize(self, health_checker, redis_client):
|
104
|
+
"""Test that finalize cleans up thread resources."""
|
105
|
+
# Override the parent class's methods to verify our object behavior
|
106
|
+
with patch.object(RedisLockPool, 'shrink') as mock_shrink:
|
107
|
+
with patch.object(RedisLockPool, 'unlock') as mock_unlock:
|
108
|
+
# Call finalize
|
109
|
+
health_checker.finalize()
|
110
|
+
|
111
|
+
# Should call shrink with thread ID
|
112
|
+
mock_shrink.assert_called_once_with([health_checker.current_thread_id])
|
113
|
+
# Should call unlock with thread ID
|
114
|
+
mock_unlock.assert_called_once_with(health_checker.current_thread_id)
|
115
|
+
|
116
|
+
def test_custom_timeout(self, redis_client):
|
117
|
+
"""Test initialization with a custom timeout value."""
|
118
|
+
custom_timeout = 120
|
119
|
+
checker = RedisThreadHealthCheckPool(redis_client, 'test', timeout=custom_timeout)
|
120
|
+
assert checker.timeout == custom_timeout
|
121
|
+
|
122
|
+
def test_multiple_initialize_calls(self, health_checker):
|
123
|
+
"""Test calling initialize multiple times."""
|
124
|
+
with patch.object(RedisLockPool, 'update') as mock_update:
|
125
|
+
with patch.object(RedisLockPool, 'extend') as mock_extend:
|
126
|
+
# Call initialize again
|
127
|
+
health_checker.initialize()
|
128
|
+
health_checker.initialize()
|
129
|
+
|
130
|
+
# Should have called update and extend each time
|
131
|
+
assert mock_update.call_count == 2
|
132
|
+
assert mock_extend.call_count == 2
|
133
|
+
|
134
|
+
|
135
|
+
class TestRedisAllocatorObject:
|
136
|
+
"""Tests for the RedisAllocatorObject class."""
|
137
|
+
|
138
|
+
def test_initialization(self, allocator, test_object):
|
139
|
+
"""Test that initialization correctly sets up the object."""
|
140
|
+
# Create a test params dict
|
141
|
+
params = {"param1": "value1", "param2": "value2"}
|
142
|
+
|
143
|
+
# Create a RedisAllocatorObject
|
144
|
+
obj = RedisAllocatorObject(allocator, "test_key", test_object, params)
|
145
|
+
|
146
|
+
# Verify properties
|
147
|
+
assert obj._allocator == allocator
|
148
|
+
assert obj.key == "test_key"
|
149
|
+
assert obj.obj == test_object
|
150
|
+
assert obj.params == params
|
151
|
+
|
152
|
+
# Verify set_config was called on the wrapped object
|
153
|
+
assert test_object.config_key == "test_key"
|
154
|
+
assert test_object.config_params == params
|
155
|
+
|
156
|
+
def test_initialization_with_defaults(self, allocator):
|
157
|
+
"""Test initialization with default None values."""
|
158
|
+
# Create a RedisAllocatorObject with default None values
|
159
|
+
obj = RedisAllocatorObject(allocator, "test_key")
|
160
|
+
|
161
|
+
# Verify properties
|
162
|
+
assert obj._allocator == allocator
|
163
|
+
assert obj.key == "test_key"
|
164
|
+
assert obj.obj is None
|
165
|
+
assert obj.params is None
|
166
|
+
|
167
|
+
def test_update(self, allocator, test_object):
|
168
|
+
"""Test the update method (renamed from lock)."""
|
169
|
+
# Create a RedisAllocatorObject
|
170
|
+
obj = RedisAllocatorObject(allocator, "test_key", test_object, {})
|
171
|
+
|
172
|
+
# Reset mock
|
173
|
+
allocator.update = MagicMock()
|
174
|
+
|
175
|
+
# Call update with positive timeout
|
176
|
+
obj.update(60)
|
177
|
+
|
178
|
+
# Verify update was called
|
179
|
+
allocator.update.assert_called_once_with("test_key", timeout=60)
|
180
|
+
|
181
|
+
def test_update_with_zero_timeout(self, allocator, test_object):
|
182
|
+
"""Test update with zero timeout, which should free the object."""
|
183
|
+
# Create a RedisAllocatorObject
|
184
|
+
obj = RedisAllocatorObject(allocator, "test_key", test_object, {})
|
185
|
+
|
186
|
+
# Reset mocks
|
187
|
+
allocator.update = MagicMock()
|
188
|
+
allocator.free = MagicMock()
|
189
|
+
|
190
|
+
# Call update with zero timeout
|
191
|
+
obj.update(0)
|
192
|
+
|
193
|
+
# Verify free was called instead of update
|
194
|
+
allocator.update.assert_not_called()
|
195
|
+
allocator.free.assert_called_once_with(obj)
|
196
|
+
|
197
|
+
def test_close(self, allocator, test_object):
|
198
|
+
"""Test the close method."""
|
199
|
+
# Create a RedisAllocatorObject
|
200
|
+
obj = RedisAllocatorObject(allocator, "test_key", test_object, {})
|
201
|
+
|
202
|
+
# Call close
|
203
|
+
obj.close()
|
204
|
+
|
205
|
+
# Verify close was called on the wrapped object
|
206
|
+
assert test_object.closed
|
207
|
+
|
208
|
+
def test_close_with_none_object(self, allocator):
|
209
|
+
"""Test the close method with None object."""
|
210
|
+
# Create a RedisAllocatorObject with None object
|
211
|
+
obj = RedisAllocatorObject(allocator, "test_key")
|
212
|
+
|
213
|
+
# Call close should not raise any exception
|
214
|
+
obj.close()
|
215
|
+
|
216
|
+
def test_del(self, allocator, test_object):
|
217
|
+
"""Test the __del__ method."""
|
218
|
+
# Create a RedisAllocatorObject
|
219
|
+
obj = RedisAllocatorObject(allocator, "test_key", test_object, {})
|
220
|
+
|
221
|
+
# Patch close method to verify it gets called
|
222
|
+
obj.close = MagicMock()
|
223
|
+
|
224
|
+
# Simulate __del__ being called
|
225
|
+
obj.__del__()
|
226
|
+
|
227
|
+
# Verify close was called
|
228
|
+
obj.close.assert_called_once()
|
229
|
+
|
230
|
+
|
231
|
+
class TestRedisAllocator:
|
232
|
+
"""Tests for the RedisAllocator class."""
|
233
|
+
|
234
|
+
def test_initialization(self, redis_client):
|
235
|
+
"""Test the initialization of RedisAllocator."""
|
236
|
+
allocator = RedisAllocator(redis_client, 'test', 'alloc-lock')
|
237
|
+
|
238
|
+
# Should have an empty WeakValueDictionary for objects
|
239
|
+
assert len(allocator.objects) == 0
|
240
|
+
# Should be initialized with default values
|
241
|
+
assert allocator.shared is False
|
242
|
+
# Should have default soft_bind_timeout
|
243
|
+
assert allocator.soft_bind_timeout == 3600
|
244
|
+
|
245
|
+
def test_initialization_with_custom_values(self, redis_client):
|
246
|
+
"""Test initialization with custom values."""
|
247
|
+
eps = 1e-8
|
248
|
+
allocator = RedisAllocator(
|
249
|
+
redis_client,
|
250
|
+
'custom_prefix',
|
251
|
+
suffix='custom_suffix',
|
252
|
+
eps=eps,
|
253
|
+
shared=True
|
254
|
+
)
|
255
|
+
|
256
|
+
# Should have custom values
|
257
|
+
assert allocator.prefix == 'custom_prefix'
|
258
|
+
assert allocator.suffix == 'custom_suffix'
|
259
|
+
assert allocator.eps == eps
|
260
|
+
assert allocator.shared is True
|
261
|
+
|
262
|
+
def test_object_key_non_shared(self, allocator, test_object):
|
263
|
+
"""Test the object_key method in non-shared mode."""
|
264
|
+
# In non-shared mode, should return the key as is
|
265
|
+
allocator.shared = False
|
266
|
+
result = allocator.object_key("test_key", test_object)
|
267
|
+
assert result == "test_key"
|
268
|
+
|
269
|
+
def test_object_key_shared(self, allocator, test_object):
|
270
|
+
"""Test the object_key method in shared mode."""
|
271
|
+
# In shared mode, should return key:obj
|
272
|
+
allocator.shared = True
|
273
|
+
result = allocator.object_key("test_key", test_object)
|
274
|
+
assert result == f"test_key:{test_object}"
|
275
|
+
|
276
|
+
def test_object_key_with_none(self, allocator):
|
277
|
+
"""Test the object_key method with None object."""
|
278
|
+
# With None object, should still work
|
279
|
+
allocator.shared = True
|
280
|
+
result = allocator.object_key("test_key", None)
|
281
|
+
assert result == "test_key:None"
|
282
|
+
|
283
|
+
allocator.shared = False
|
284
|
+
result = allocator.object_key("test_key", None)
|
285
|
+
assert result == "test_key"
|
286
|
+
|
287
|
+
def test_extend(self, allocator, redis_client):
|
288
|
+
"""Test the extend method."""
|
289
|
+
# Clear any existing data
|
290
|
+
redis_client.flushall()
|
291
|
+
|
292
|
+
# Call extend
|
293
|
+
allocator.extend(["key4", "key5"])
|
294
|
+
|
295
|
+
# Verify keys were added
|
296
|
+
assert "key4" in allocator
|
297
|
+
assert "key5" in allocator
|
298
|
+
|
299
|
+
def test_extend_empty(self, allocator, redis_client):
|
300
|
+
"""Test extend with empty keys."""
|
301
|
+
# Clear any existing data
|
302
|
+
redis_client.flushall()
|
303
|
+
|
304
|
+
# Call extend with empty list
|
305
|
+
allocator.extend([])
|
306
|
+
allocator.extend(None)
|
307
|
+
|
308
|
+
# No keys should be added
|
309
|
+
assert len(list(allocator.keys())) == 0
|
310
|
+
|
311
|
+
def test_shrink(self, allocator, redis_client):
|
312
|
+
"""Test the shrink method."""
|
313
|
+
# Clear any existing data
|
314
|
+
redis_client.flushall()
|
315
|
+
|
316
|
+
# Add some keys first
|
317
|
+
allocator.extend(["key1", "key2", "key3"])
|
318
|
+
|
319
|
+
# Call shrink
|
320
|
+
allocator.shrink(["key1", "key2"])
|
321
|
+
|
322
|
+
# Verify keys were removed
|
323
|
+
assert "key1" not in allocator
|
324
|
+
assert "key2" not in allocator
|
325
|
+
assert "key3" in allocator
|
326
|
+
|
327
|
+
def test_shrink_empty(self, allocator, redis_client):
|
328
|
+
"""Test shrink with empty keys."""
|
329
|
+
# Clear any existing data
|
330
|
+
redis_client.flushall()
|
331
|
+
|
332
|
+
# Add some keys first
|
333
|
+
allocator.extend(["key1", "key2"])
|
334
|
+
|
335
|
+
# Call shrink with empty list
|
336
|
+
allocator.shrink([])
|
337
|
+
allocator.shrink(None)
|
338
|
+
|
339
|
+
# Keys should remain unchanged
|
340
|
+
assert "key1" in allocator
|
341
|
+
assert "key2" in allocator
|
342
|
+
|
343
|
+
def test_assign(self, allocator, redis_client):
|
344
|
+
"""Test the assign method."""
|
345
|
+
# Clear any existing data
|
346
|
+
redis_client.flushall()
|
347
|
+
|
348
|
+
# Add some initial keys
|
349
|
+
allocator.extend(["key1", "key2"])
|
350
|
+
|
351
|
+
# Call assign with new keys
|
352
|
+
allocator.assign(["key3", "key4"])
|
353
|
+
|
354
|
+
# Verify old keys are gone and new keys are present
|
355
|
+
assert "key1" not in allocator
|
356
|
+
assert "key2" not in allocator
|
357
|
+
assert "key3" in allocator
|
358
|
+
assert "key4" in allocator
|
359
|
+
|
360
|
+
# Call assign with None
|
361
|
+
allocator.assign(None)
|
362
|
+
|
363
|
+
# All keys should be gone
|
364
|
+
assert len(list(allocator.keys())) == 0
|
365
|
+
|
366
|
+
def test_assign_empty(self, allocator, redis_client):
|
367
|
+
"""Test assign with empty keys."""
|
368
|
+
# Clear any existing data
|
369
|
+
redis_client.flushall()
|
370
|
+
|
371
|
+
# Add some initial keys
|
372
|
+
allocator.extend(["key1", "key2"])
|
373
|
+
|
374
|
+
# Call assign with empty list
|
375
|
+
allocator.assign([])
|
376
|
+
|
377
|
+
# All keys should be gone
|
378
|
+
assert len(list(allocator.keys())) == 0
|
379
|
+
|
380
|
+
def test_clear(self, allocator, redis_client):
|
381
|
+
"""Test the clear method."""
|
382
|
+
# Clear any existing data
|
383
|
+
redis_client.flushall()
|
384
|
+
|
385
|
+
# Add some keys
|
386
|
+
allocator.extend(["key1", "key2"])
|
387
|
+
|
388
|
+
# Call clear
|
389
|
+
allocator.clear()
|
390
|
+
|
391
|
+
# All keys should be gone
|
392
|
+
assert len(list(allocator.keys())) == 0
|
393
|
+
|
394
|
+
def test_redis_error_in_clear(self, allocator, redis_client):
|
395
|
+
"""Test handling Redis errors in clear."""
|
396
|
+
# Clear any existing data
|
397
|
+
redis_client.flushall()
|
398
|
+
|
399
|
+
# Add some keys
|
400
|
+
allocator.extend(["key1", "key2"])
|
401
|
+
|
402
|
+
# Mock Redis error
|
403
|
+
redis_client.delete = lambda *args: (_ for _ in ()).throw(RedisError("Test error"))
|
404
|
+
|
405
|
+
# Call clear should raise RedisError
|
406
|
+
with pytest.raises(RedisError):
|
407
|
+
allocator.clear()
|
408
|
+
|
409
|
+
def test_keys(self, allocator, redis_client):
|
410
|
+
"""Test the keys method."""
|
411
|
+
# Clear any existing data
|
412
|
+
redis_client.flushall()
|
413
|
+
|
414
|
+
# Add some keys
|
415
|
+
allocator.extend(["key1", "key2", "key3"])
|
416
|
+
|
417
|
+
# Get keys
|
418
|
+
result = list(allocator.keys())
|
419
|
+
|
420
|
+
# Verify we got all keys
|
421
|
+
assert set(result) == {"key1", "key2", "key3"}
|
422
|
+
|
423
|
+
def test_redis_error_in_keys(self, allocator, redis_client):
|
424
|
+
"""Test handling Redis errors in keys."""
|
425
|
+
# Clear any existing data
|
426
|
+
redis_client.flushall()
|
427
|
+
|
428
|
+
# Add some keys
|
429
|
+
allocator.extend(["key1", "key2"])
|
430
|
+
|
431
|
+
# Mock Redis error
|
432
|
+
redis_client.hkeys = lambda *args: (_ for _ in ()).throw(RedisError("Test error"))
|
433
|
+
|
434
|
+
# Getting keys should raise RedisError
|
435
|
+
with pytest.raises(RedisError):
|
436
|
+
list(allocator.keys())
|
437
|
+
|
438
|
+
def test_contains(self, allocator, redis_client):
|
439
|
+
"""Test the __contains__ method."""
|
440
|
+
# Clear any existing data
|
441
|
+
redis_client.flushall()
|
442
|
+
|
443
|
+
# Add some keys
|
444
|
+
allocator.extend(["key1", "key2"])
|
445
|
+
|
446
|
+
# Check containment
|
447
|
+
assert "key1" in allocator
|
448
|
+
assert "key2" in allocator
|
449
|
+
assert "key3" not in allocator
|
450
|
+
|
451
|
+
def test_redis_error_in_contains(self, allocator, redis_client):
|
452
|
+
"""Test handling Redis errors in __contains__."""
|
453
|
+
# Clear any existing data
|
454
|
+
redis_client.flushall()
|
455
|
+
|
456
|
+
# Add some keys
|
457
|
+
allocator.extend(["key1", "key2"])
|
458
|
+
|
459
|
+
# Mock Redis error
|
460
|
+
redis_client.hexists = lambda *args: (_ for _ in ()).throw(RedisError("Test error"))
|
461
|
+
|
462
|
+
# Checking containment should raise RedisError
|
463
|
+
with pytest.raises(RedisError):
|
464
|
+
"key1" in allocator
|
465
|
+
|
466
|
+
def test_update_soft_bind(self, allocator, redis_client):
|
467
|
+
"""Test the update_soft_bind method."""
|
468
|
+
# Set up mock
|
469
|
+
allocator.update = MagicMock()
|
470
|
+
|
471
|
+
# Call update_soft_bind
|
472
|
+
allocator.update_soft_bind("test_name", "test_key")
|
473
|
+
|
474
|
+
# Verify update was called with the right parameters
|
475
|
+
allocator.update.assert_called_once_with(
|
476
|
+
allocator._soft_bind_name("test_name"),
|
477
|
+
"test_key",
|
478
|
+
timeout=allocator.soft_bind_timeout
|
479
|
+
)
|
480
|
+
|
481
|
+
def test_unbind_soft_bind(self, allocator, redis_client):
|
482
|
+
"""Test the unbind_soft_bind method."""
|
483
|
+
# Set up mock
|
484
|
+
allocator.unlock = MagicMock()
|
485
|
+
|
486
|
+
# Call unbind_soft_bind
|
487
|
+
allocator.unbind_soft_bind("test_name")
|
488
|
+
|
489
|
+
# Verify unlock was called with the right parameter
|
490
|
+
allocator.unlock.assert_called_once_with(allocator._soft_bind_name("test_name"))
|
491
|
+
|
492
|
+
def test_soft_bind_with_empty_name(self, allocator):
|
493
|
+
"""Test soft bind methods with empty name."""
|
494
|
+
# Set up mocks
|
495
|
+
allocator.update = MagicMock()
|
496
|
+
allocator.unlock = MagicMock()
|
497
|
+
|
498
|
+
# Call methods with empty name
|
499
|
+
allocator.update_soft_bind("", "test_key")
|
500
|
+
allocator.unbind_soft_bind("")
|
501
|
+
|
502
|
+
# Should still call the underlying methods with empty string
|
503
|
+
allocator.update.assert_called_once()
|
504
|
+
allocator.unlock.assert_called_once()
|
505
|
+
|
506
|
+
# The soft bind name should be generated even with empty string
|
507
|
+
assert allocator._soft_bind_name("") != ""
|
508
|
+
|
509
|
+
def test_shared_vs_non_shared_allocation(self, allocator, shared_allocator):
|
510
|
+
"""Test difference between shared and non-shared allocation."""
|
511
|
+
# Set up mocks for both allocators
|
512
|
+
allocator._malloc_script = MagicMock(return_value="key1")
|
513
|
+
shared_allocator._malloc_script = MagicMock(return_value="key1")
|
514
|
+
|
515
|
+
# Check the malloc_lua_script property for both allocators
|
516
|
+
non_shared_script = allocator._malloc_lua_script
|
517
|
+
shared_script = shared_allocator._malloc_lua_script
|
518
|
+
|
519
|
+
# The scripts should be different, with shared=0 in non-shared and shared=1 in shared
|
520
|
+
assert "local shared = 0" in non_shared_script
|
521
|
+
assert "local shared = 1" in shared_script
|
522
|
+
|
523
|
+
# Both can allocate the same key, but behavior should differ
|
524
|
+
assert allocator.malloc_key() == "key1"
|
525
|
+
assert shared_allocator.malloc_key() == "key1"
|