redis-allocator 0.0.1__py3-none-any.whl → 0.3.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 +5 -1
- redis_allocator/_version.py +1 -0
- redis_allocator/allocator.py +816 -280
- redis_allocator/lock.py +66 -17
- redis_allocator/task_queue.py +81 -57
- redis_allocator-0.3.1.dist-info/METADATA +529 -0
- redis_allocator-0.3.1.dist-info/RECORD +15 -0
- {redis_allocator-0.0.1.dist-info → redis_allocator-0.3.1.dist-info}/licenses/LICENSE +21 -21
- tests/conftest.py +160 -46
- tests/test_allocator.py +461 -488
- tests/test_lock.py +675 -338
- tests/test_task_queue.py +136 -136
- redis_allocator-0.0.1.dist-info/METADATA +0 -229
- redis_allocator-0.0.1.dist-info/RECORD +0 -14
- {redis_allocator-0.0.1.dist-info → redis_allocator-0.3.1.dist-info}/WHEEL +0 -0
- {redis_allocator-0.0.1.dist-info → redis_allocator-0.3.1.dist-info}/top_level.txt +0 -0
tests/test_task_queue.py
CHANGED
@@ -37,19 +37,19 @@ class TestRedisTaskQueue:
|
|
37
37
|
# Create and set a task
|
38
38
|
task = task_queue.build_task("task1", "test", {"param1": "value1"})
|
39
39
|
task_id = task_queue.set_task(task)
|
40
|
-
|
40
|
+
|
41
41
|
# Mock the pickle and base64 operations to avoid serialization issues
|
42
42
|
task_copy = task_queue.build_task("task1", "test", {"param1": "value1"})
|
43
|
-
|
43
|
+
|
44
44
|
# Verify the task was saved
|
45
45
|
assert task_id == "task1"
|
46
46
|
assert redis_client.exists(task_queue._result_key("task1"))
|
47
|
-
|
47
|
+
|
48
48
|
# Patch the get_task method to return our mocked task
|
49
49
|
original_get_task = task_queue.get_task
|
50
50
|
try:
|
51
51
|
task_queue.get_task = lambda task_id, once=False: task_copy
|
52
|
-
|
52
|
+
|
53
53
|
# Get the task back
|
54
54
|
retrieved_task = task_queue.get_task("task1")
|
55
55
|
assert retrieved_task.id == "task1"
|
@@ -64,10 +64,10 @@ class TestRedisTaskQueue:
|
|
64
64
|
# Create and set a task
|
65
65
|
task = task_queue.build_task("task1", "test", {"param1": "value1"})
|
66
66
|
task_queue.set_task(task)
|
67
|
-
|
67
|
+
|
68
68
|
# Verify task exists
|
69
69
|
assert task_queue.has_task("task1") is True
|
70
|
-
|
70
|
+
|
71
71
|
# Verify non-existent task doesn't exist
|
72
72
|
assert task_queue.has_task("task2") is False
|
73
73
|
|
@@ -75,7 +75,7 @@ class TestRedisTaskQueue:
|
|
75
75
|
"""Test executing a task locally."""
|
76
76
|
task = task_queue.build_task("task1", "test", {"param1": "value1"})
|
77
77
|
result = task_queue.execute_task_locally(task)
|
78
|
-
|
78
|
+
|
79
79
|
assert result == "result-task1"
|
80
80
|
assert task.result == "result-task1"
|
81
81
|
assert task.error is None
|
@@ -84,15 +84,15 @@ class TestRedisTaskQueue:
|
|
84
84
|
"""Test local execution with an error."""
|
85
85
|
# Set up a task that will fail
|
86
86
|
task = task_queue.build_task("error_task", "test", {"param1": "value1"})
|
87
|
-
|
87
|
+
|
88
88
|
# Mock the task_fn to raise an exception
|
89
89
|
error = ValueError("Test error")
|
90
90
|
mocker.patch.object(task_queue, 'task_fn', side_effect=error)
|
91
|
-
|
91
|
+
|
92
92
|
# Execute the task and verify it fails properly
|
93
93
|
with pytest.raises(ValueError, match="Test error"):
|
94
94
|
task_queue.execute_task_locally(task)
|
95
|
-
|
95
|
+
|
96
96
|
# Verify the error was captured in the task
|
97
97
|
assert task.error == error
|
98
98
|
assert task.result is None
|
@@ -101,14 +101,14 @@ class TestRedisTaskQueue:
|
|
101
101
|
"""Test remote task execution."""
|
102
102
|
# Create a task
|
103
103
|
task = task_queue.build_task("task2", "test", {"param1": "value2"})
|
104
|
-
|
104
|
+
|
105
105
|
# Set up a listener for the test queue
|
106
106
|
task_queue.set_queue_listened("test")
|
107
|
-
|
107
|
+
|
108
108
|
# Start a thread that will listen for the task and execute it
|
109
109
|
# (Simulating a remote worker)
|
110
110
|
processed_event = threading.Event()
|
111
|
-
|
111
|
+
|
112
112
|
def process_task():
|
113
113
|
# Get the task from the queue
|
114
114
|
queue_key = task_queue._queue_key("test")
|
@@ -122,36 +122,36 @@ class TestRedisTaskQueue:
|
|
122
122
|
task.result = result
|
123
123
|
task.save()
|
124
124
|
processed_event.set()
|
125
|
-
|
125
|
+
|
126
126
|
# Start the worker thread
|
127
127
|
worker_thread = threading.Thread(target=process_task)
|
128
128
|
worker_thread.daemon = True
|
129
129
|
worker_thread.start()
|
130
|
-
|
130
|
+
|
131
131
|
try:
|
132
132
|
# Replace execute_task_remotely with our own implementation
|
133
133
|
def mock_execute_remotely(task, timeout=None, once=False):
|
134
134
|
# Push the task to Redis queue
|
135
135
|
task_queue.set_task(task)
|
136
136
|
redis_client.rpush(task_queue._queue_key(task.name), task.id)
|
137
|
-
|
137
|
+
|
138
138
|
# Wait for worker to process it (with a reasonable timeout)
|
139
139
|
if not processed_event.wait(timeout=3):
|
140
140
|
raise TimeoutError("Worker did not process task within timeout")
|
141
|
-
|
141
|
+
|
142
142
|
# Get the processed task with result
|
143
143
|
processed_task = task_queue.get_task(task.id, once)
|
144
144
|
return processed_task.result
|
145
|
-
|
145
|
+
|
146
146
|
# Apply the mock
|
147
147
|
mocker.patch.object(task_queue, 'execute_task_remotely', side_effect=mock_execute_remotely)
|
148
|
-
|
148
|
+
|
149
149
|
# Execute the task remotely
|
150
150
|
result = task_queue.execute_task_remotely(task, timeout=3)
|
151
|
-
|
151
|
+
|
152
152
|
# Verify the result
|
153
153
|
assert result == f"result-{task.id}"
|
154
|
-
|
154
|
+
|
155
155
|
finally:
|
156
156
|
# Make sure thread is done
|
157
157
|
worker_thread.join(timeout=1)
|
@@ -160,14 +160,14 @@ class TestRedisTaskQueue:
|
|
160
160
|
"""Test remote execution with an error."""
|
161
161
|
# Create a task that will fail remotely
|
162
162
|
task = task_queue.build_task("error_remote", "test", {"param1": "value3"})
|
163
|
-
|
163
|
+
|
164
164
|
# Set up a listener for the test queue
|
165
165
|
task_queue.set_queue_listened("test")
|
166
|
-
|
166
|
+
|
167
167
|
# Start a thread that will listen for the task and execute it with an error
|
168
168
|
# (Simulating a remote worker)
|
169
169
|
processed_event = threading.Event()
|
170
|
-
|
170
|
+
|
171
171
|
def process_task():
|
172
172
|
# Get the task from the queue
|
173
173
|
queue_key = task_queue._queue_key("test")
|
@@ -180,17 +180,17 @@ class TestRedisTaskQueue:
|
|
180
180
|
task.error = ValueError("Remote error")
|
181
181
|
task.save()
|
182
182
|
processed_event.set()
|
183
|
-
|
183
|
+
|
184
184
|
# Start the worker thread
|
185
185
|
worker_thread = threading.Thread(target=process_task)
|
186
186
|
worker_thread.daemon = True
|
187
187
|
worker_thread.start()
|
188
|
-
|
188
|
+
|
189
189
|
# Execute the task remotely and expect an error
|
190
190
|
try:
|
191
191
|
with pytest.raises(ValueError, match="Remote error"):
|
192
192
|
task_queue.execute_task_remotely(task, timeout=3)
|
193
|
-
|
193
|
+
|
194
194
|
# Verify the thread processed the task
|
195
195
|
assert processed_event.is_set()
|
196
196
|
finally:
|
@@ -201,21 +201,21 @@ class TestRedisTaskQueue:
|
|
201
201
|
"""Test remote execution with timeout."""
|
202
202
|
# Create a task
|
203
203
|
task = task_queue.build_task("timeout_task", "test", {"param1": "value4"})
|
204
|
-
|
204
|
+
|
205
205
|
# Set timeout to a very small value to ensure it times out
|
206
206
|
timeout = 0.1
|
207
|
-
|
207
|
+
|
208
208
|
# Mock time.sleep to avoid actual sleeping
|
209
209
|
time_sleep_mock = mocker.patch('time.sleep')
|
210
|
-
|
210
|
+
|
211
211
|
# Mock get_task to always return None (simulating no worker processing the task)
|
212
212
|
mocker.patch.object(task_queue, 'get_task', return_value=None)
|
213
|
-
|
213
|
+
|
214
214
|
# Use a task that will not get processed by any worker
|
215
215
|
# Execute the task remotely with a small timeout and expect a timeout
|
216
216
|
with pytest.raises(TimeoutError):
|
217
217
|
task_queue.execute_task_remotely(task, timeout=timeout)
|
218
|
-
|
218
|
+
|
219
219
|
# Verify time.sleep was called at least once
|
220
220
|
time_sleep_mock.assert_called()
|
221
221
|
|
@@ -223,7 +223,7 @@ class TestRedisTaskQueue:
|
|
223
223
|
"""Test query with local execution policy."""
|
224
224
|
# Call query with local policy
|
225
225
|
result = task_queue.query("task3", "test", {}, policy=TaskExecutePolicy.Local)
|
226
|
-
|
226
|
+
|
227
227
|
# Verify result
|
228
228
|
assert result == "result-task3"
|
229
229
|
|
@@ -231,11 +231,11 @@ class TestRedisTaskQueue:
|
|
231
231
|
"""Test query with remote execution policy."""
|
232
232
|
# Set up a listener for the test queue
|
233
233
|
task_queue.set_queue_listened("test")
|
234
|
-
|
234
|
+
|
235
235
|
# Start a thread that will listen for the task and execute it
|
236
236
|
# (Simulating a remote worker)
|
237
237
|
processed_event = threading.Event()
|
238
|
-
|
238
|
+
|
239
239
|
def process_task():
|
240
240
|
# Get the task from the queue
|
241
241
|
queue_key = task_queue._queue_key("test")
|
@@ -249,33 +249,33 @@ class TestRedisTaskQueue:
|
|
249
249
|
task.result = result
|
250
250
|
task.save()
|
251
251
|
processed_event.set()
|
252
|
-
|
252
|
+
|
253
253
|
# Start the worker thread
|
254
254
|
worker_thread = threading.Thread(target=process_task)
|
255
255
|
worker_thread.daemon = True
|
256
256
|
worker_thread.start()
|
257
|
-
|
257
|
+
|
258
258
|
try:
|
259
259
|
# Replace execute_task_remotely with our own implementation
|
260
260
|
def mock_execute_remotely(task, timeout=None, once=False):
|
261
261
|
# Push the task to Redis queue
|
262
262
|
task_queue.set_task(task)
|
263
263
|
redis_client.rpush(task_queue._queue_key(task.name), task.id)
|
264
|
-
|
264
|
+
|
265
265
|
# Wait for worker to process it (with a reasonable timeout)
|
266
266
|
if not processed_event.wait(timeout=3):
|
267
267
|
raise TimeoutError("Worker did not process task within timeout")
|
268
|
-
|
268
|
+
|
269
269
|
# Get the processed task with result
|
270
270
|
processed_task = task_queue.get_task(task.id, once)
|
271
271
|
return processed_task.result
|
272
|
-
|
272
|
+
|
273
273
|
# Apply the mock
|
274
274
|
mocker.patch.object(task_queue, 'execute_task_remotely', side_effect=mock_execute_remotely)
|
275
|
-
|
275
|
+
|
276
276
|
# Call query with remote policy
|
277
277
|
result = task_queue.query("task4", "test", {}, policy=TaskExecutePolicy.Remote, timeout=3)
|
278
|
-
|
278
|
+
|
279
279
|
# Verify result
|
280
280
|
assert result == "result-task4"
|
281
281
|
finally:
|
@@ -286,7 +286,7 @@ class TestRedisTaskQueue:
|
|
286
286
|
"""Test query with local-first execution policy."""
|
287
287
|
# Call query with local-first policy
|
288
288
|
result = task_queue.query("task5", "test", {}, policy=TaskExecutePolicy.LocalFirst)
|
289
|
-
|
289
|
+
|
290
290
|
# Verify result - should execute locally
|
291
291
|
assert result == "result-task5"
|
292
292
|
|
@@ -295,17 +295,17 @@ class TestRedisTaskQueue:
|
|
295
295
|
# We'll only mock the task_fn to simulate a local execution failure
|
296
296
|
error = ValueError("Local error")
|
297
297
|
mocker.patch.object(task_queue, 'task_fn', side_effect=error)
|
298
|
-
|
298
|
+
|
299
299
|
# Mock logger.exception to prevent actual logging
|
300
300
|
mocker.patch('redis_allocator.task_queue.logger.exception')
|
301
|
-
|
301
|
+
|
302
302
|
# Set up a listener for the test queue
|
303
303
|
task_queue.set_queue_listened("test")
|
304
|
-
|
304
|
+
|
305
305
|
# Start a thread that will listen for the task and execute it
|
306
306
|
# (Simulating a remote worker)
|
307
307
|
processed_event = threading.Event()
|
308
|
-
|
308
|
+
|
309
309
|
def process_task():
|
310
310
|
# Get the task from the queue
|
311
311
|
queue_key = task_queue._queue_key("test")
|
@@ -319,36 +319,36 @@ class TestRedisTaskQueue:
|
|
319
319
|
task.result = f"remote-result-{task.id}"
|
320
320
|
task.save()
|
321
321
|
processed_event.set()
|
322
|
-
|
322
|
+
|
323
323
|
# Start the worker thread
|
324
324
|
worker_thread = threading.Thread(target=process_task)
|
325
325
|
worker_thread.daemon = True
|
326
326
|
worker_thread.start()
|
327
|
-
|
327
|
+
|
328
328
|
try:
|
329
329
|
# Make the execute_task_remotely method faster by skipping actual sleep
|
330
330
|
def mocked_execute_remotely(task, timeout=None, once=False):
|
331
331
|
# Push to queue but skip the actual sleeping
|
332
332
|
redis_client.rpush(task_queue._queue_key(task.name), task.id)
|
333
|
-
|
333
|
+
|
334
334
|
# Wait a moment for the worker thread to process
|
335
335
|
processed_event.wait(timeout=1)
|
336
|
-
|
336
|
+
|
337
337
|
# Get the task with result
|
338
338
|
result = task_queue.get_task(task.id, once)
|
339
339
|
if result is not None and result.result is not None:
|
340
340
|
return result.result
|
341
341
|
raise TimeoutError(f'Task {task.id} in {task.name} has expired')
|
342
|
-
|
342
|
+
|
343
343
|
# Replace with mock
|
344
344
|
mocker.patch.object(task_queue, 'execute_task_remotely', side_effect=mocked_execute_remotely)
|
345
|
-
|
345
|
+
|
346
346
|
# Call query with local-first policy (which should fail locally and fall back to remote)
|
347
347
|
result = task_queue.query("task6", "test", {}, policy=TaskExecutePolicy.LocalFirst, timeout=3)
|
348
|
-
|
348
|
+
|
349
349
|
# Verify the thread processed the task
|
350
350
|
assert processed_event.is_set()
|
351
|
-
|
351
|
+
|
352
352
|
# Verify result from remote execution
|
353
353
|
assert result == "remote-result-task6"
|
354
354
|
finally:
|
@@ -359,11 +359,11 @@ class TestRedisTaskQueue:
|
|
359
359
|
"""Test query with remote-first execution policy."""
|
360
360
|
# Set up a listener for the test queue
|
361
361
|
task_queue.set_queue_listened("test")
|
362
|
-
|
362
|
+
|
363
363
|
# Start a thread that will listen for the task and execute it
|
364
364
|
# (Simulating a remote worker)
|
365
365
|
processed_event = threading.Event()
|
366
|
-
|
366
|
+
|
367
367
|
def process_task():
|
368
368
|
# Get the task from the queue
|
369
369
|
queue_key = task_queue._queue_key("test")
|
@@ -376,33 +376,33 @@ class TestRedisTaskQueue:
|
|
376
376
|
task.result = f"remote-result-{task.id}"
|
377
377
|
task.save()
|
378
378
|
processed_event.set()
|
379
|
-
|
379
|
+
|
380
380
|
# Start the worker thread
|
381
381
|
worker_thread = threading.Thread(target=process_task)
|
382
382
|
worker_thread.daemon = True
|
383
383
|
worker_thread.start()
|
384
|
-
|
384
|
+
|
385
385
|
try:
|
386
386
|
# Replace execute_task_remotely with our own implementation
|
387
387
|
def mock_execute_remotely(task, timeout=None, once=False):
|
388
388
|
# Push the task to Redis queue
|
389
389
|
task_queue.set_task(task)
|
390
390
|
redis_client.rpush(task_queue._queue_key(task.name), task.id)
|
391
|
-
|
391
|
+
|
392
392
|
# Wait for worker to process it (with a reasonable timeout)
|
393
393
|
if not processed_event.wait(timeout=3):
|
394
394
|
raise TimeoutError("Worker did not process task within timeout")
|
395
|
-
|
395
|
+
|
396
396
|
# Get the processed task with result
|
397
397
|
processed_task = task_queue.get_task(task.id, once)
|
398
398
|
return processed_task.result
|
399
|
-
|
399
|
+
|
400
400
|
# Apply the mock
|
401
401
|
mocker.patch.object(task_queue, 'execute_task_remotely', side_effect=mock_execute_remotely)
|
402
|
-
|
402
|
+
|
403
403
|
# Call query with remote-first policy
|
404
404
|
result = task_queue.query("task7", "test", {}, policy=TaskExecutePolicy.RemoteFirst, timeout=3)
|
405
|
-
|
405
|
+
|
406
406
|
# Verify result
|
407
407
|
assert result == "remote-result-task7"
|
408
408
|
finally:
|
@@ -414,18 +414,18 @@ class TestRedisTaskQueue:
|
|
414
414
|
# We'll mock execute_task_remotely to fail
|
415
415
|
# This simulates a remote execution failure more reliably
|
416
416
|
mocker.patch.object(
|
417
|
-
task_queue,
|
418
|
-
'execute_task_remotely',
|
417
|
+
task_queue,
|
418
|
+
'execute_task_remotely',
|
419
419
|
side_effect=TimeoutError("No remote listeners available")
|
420
420
|
)
|
421
|
-
|
421
|
+
|
422
422
|
# Mock logger.exception to prevent actual logging
|
423
423
|
mocker.patch('redis_allocator.task_queue.logger.exception')
|
424
|
-
|
424
|
+
|
425
425
|
# Call query with remote-first policy
|
426
426
|
# The remote execution should fail, and it will fall back to local
|
427
427
|
result = task_queue.query("task8", "test", {}, policy=TaskExecutePolicy.RemoteFirst)
|
428
|
-
|
428
|
+
|
429
429
|
# Verify local result (since remote failed)
|
430
430
|
assert result == "result-task8"
|
431
431
|
|
@@ -433,11 +433,11 @@ class TestRedisTaskQueue:
|
|
433
433
|
"""Test query with auto policy when a listener exists."""
|
434
434
|
# Set up a listener for the test queue
|
435
435
|
task_queue.set_queue_listened("test")
|
436
|
-
|
436
|
+
|
437
437
|
# Start a thread that will listen for the task and execute it
|
438
438
|
# (Simulating a remote worker)
|
439
439
|
processed_event = threading.Event()
|
440
|
-
|
440
|
+
|
441
441
|
def process_task():
|
442
442
|
# Get the task from the queue
|
443
443
|
queue_key = task_queue._queue_key("test")
|
@@ -450,33 +450,33 @@ class TestRedisTaskQueue:
|
|
450
450
|
task.result = f"remote-result-{task.id}"
|
451
451
|
task.save()
|
452
452
|
processed_event.set()
|
453
|
-
|
453
|
+
|
454
454
|
# Start the worker thread
|
455
455
|
worker_thread = threading.Thread(target=process_task)
|
456
456
|
worker_thread.daemon = True
|
457
457
|
worker_thread.start()
|
458
|
-
|
458
|
+
|
459
459
|
try:
|
460
460
|
# Replace execute_task_remotely with our own implementation
|
461
461
|
def mock_execute_remotely(task, timeout=None, once=False):
|
462
462
|
# Push the task to Redis queue
|
463
463
|
task_queue.set_task(task)
|
464
464
|
redis_client.rpush(task_queue._queue_key(task.name), task.id)
|
465
|
-
|
465
|
+
|
466
466
|
# Wait for worker to process it (with a reasonable timeout)
|
467
467
|
if not processed_event.wait(timeout=3):
|
468
468
|
raise TimeoutError("Worker did not process task within timeout")
|
469
|
-
|
469
|
+
|
470
470
|
# Get the processed task with result
|
471
471
|
processed_task = task_queue.get_task(task.id, once)
|
472
472
|
return processed_task.result
|
473
|
-
|
473
|
+
|
474
474
|
# Apply the mock
|
475
475
|
mocker.patch.object(task_queue, 'execute_task_remotely', side_effect=mock_execute_remotely)
|
476
|
-
|
476
|
+
|
477
477
|
# Call query with auto policy when a listener exists - should execute remotely
|
478
478
|
result = task_queue.query("task9", "test", {}, policy=TaskExecutePolicy.Auto, timeout=3)
|
479
|
-
|
479
|
+
|
480
480
|
# Verify result from remote execution
|
481
481
|
assert result == "remote-result-task9"
|
482
482
|
finally:
|
@@ -487,10 +487,10 @@ class TestRedisTaskQueue:
|
|
487
487
|
"""Test query with auto policy when no listener exists."""
|
488
488
|
# Make sure there is no active listener
|
489
489
|
# Just don't call set_queue_listened()
|
490
|
-
|
490
|
+
|
491
491
|
# Call query with auto policy when no listener exists - should execute locally
|
492
492
|
result = task_queue.query("task10", "test", {}, policy=TaskExecutePolicy.Auto)
|
493
|
-
|
493
|
+
|
494
494
|
# Verify result from local execution
|
495
495
|
assert result == "result-task10"
|
496
496
|
|
@@ -498,16 +498,16 @@ class TestRedisTaskQueue:
|
|
498
498
|
"""Test updating task progress."""
|
499
499
|
# Create a task
|
500
500
|
task = task_queue.build_task("task11", "test", {"param1": "value1"})
|
501
|
-
|
501
|
+
|
502
502
|
# Fixed time for consistent testing
|
503
503
|
fixed_time_str = "2022-04-15T10:00:00Z" # 使用UTC时间
|
504
504
|
dt = datetime.datetime.fromisoformat(fixed_time_str.replace('Z', '+00:00'))
|
505
|
-
|
505
|
+
|
506
506
|
# Use freezegun to set a fixed time
|
507
507
|
with freeze_time(dt):
|
508
508
|
# Update progress
|
509
509
|
task.update(50.0, 100.0)
|
510
|
-
|
510
|
+
|
511
511
|
# Verify progress was updated
|
512
512
|
assert task.current_progress == 50.0
|
513
513
|
assert task.total_progress == 100.0
|
@@ -517,7 +517,7 @@ class TestRedisTaskQueue:
|
|
517
517
|
"""Test updating progress for an expired task."""
|
518
518
|
# Current time for reference
|
519
519
|
current_time = time.time()
|
520
|
-
|
520
|
+
|
521
521
|
# Create a task with expiry in the past (100 seconds ago)
|
522
522
|
with freeze_time(datetime.datetime.fromtimestamp(current_time - 200)): # Create a time point far in the past
|
523
523
|
# Create task with expiry = current_time - 100 (which is expired from current perspective)
|
@@ -528,7 +528,7 @@ class TestRedisTaskQueue:
|
|
528
528
|
expiry=current_time - 100, # Set expiry to be 100 seconds before current time
|
529
529
|
_save=lambda: None
|
530
530
|
)
|
531
|
-
|
531
|
+
|
532
532
|
# Move to current time and try to update the expired task
|
533
533
|
with freeze_time(datetime.datetime.fromtimestamp(current_time)):
|
534
534
|
# Try to update progress and expect TimeoutError
|
@@ -540,42 +540,42 @@ class TestRedisTaskQueue:
|
|
540
540
|
# Set up task queue names
|
541
541
|
names = ['test_queue']
|
542
542
|
queue_key = task_queue._queue_key(names[0])
|
543
|
-
|
543
|
+
|
544
544
|
# Create a task to be processed
|
545
545
|
task = task_queue.build_task("listen_task", "test_queue", {"param": "value"})
|
546
|
-
|
546
|
+
|
547
547
|
# Save the task
|
548
548
|
task_queue.set_task(task)
|
549
|
-
|
549
|
+
|
550
550
|
# Directly push task ID to queue
|
551
551
|
redis_client.rpush(queue_key, task.id)
|
552
|
-
|
552
|
+
|
553
553
|
# Manually run one iteration of the listen loop
|
554
554
|
# This avoids the infinite loop in the actual listen method
|
555
555
|
queue_names = [task_queue._queue_key(name) for name in names]
|
556
|
-
|
556
|
+
|
557
557
|
# Process task
|
558
558
|
task_data = redis_client.blpop(queue_names, timeout=task_queue.interval)
|
559
559
|
assert task_data is not None
|
560
|
-
|
560
|
+
|
561
561
|
_queue_key, task_id = task_data
|
562
562
|
# 检查task_id的类型并处理
|
563
563
|
if isinstance(task_id, bytes):
|
564
564
|
task_id = task_id.decode('utf-8')
|
565
565
|
assert task_id == "listen_task"
|
566
|
-
|
566
|
+
|
567
567
|
# Get the task
|
568
568
|
task = task_queue.get_task(task_id)
|
569
569
|
assert task is not None
|
570
|
-
|
570
|
+
|
571
571
|
# Execute task locally to verify
|
572
572
|
result = task_queue.execute_task_locally(task)
|
573
573
|
assert result == "result-listen_task"
|
574
|
-
|
574
|
+
|
575
575
|
# Mark queues as listened
|
576
576
|
for name in names:
|
577
577
|
task_queue.set_queue_listened(name)
|
578
|
-
|
578
|
+
|
579
579
|
# Verify that the queue is marked as listened
|
580
580
|
assert redis_client.exists(task_queue._queue_listen_name(names[0])) > 0
|
581
581
|
|
@@ -583,13 +583,13 @@ class TestRedisTaskQueue:
|
|
583
583
|
"""Test the listen method using a separate thread to avoid blocking the test."""
|
584
584
|
# Set up task queue names
|
585
585
|
names = ['test_queue']
|
586
|
-
|
586
|
+
|
587
587
|
# Create a task to be processed
|
588
588
|
task = task_queue.build_task("listen_task", "test_queue", {"param": "value"})
|
589
|
-
|
589
|
+
|
590
590
|
# Patch methods to avoid actual Redis operations
|
591
591
|
mocker.patch.object(task_queue, 'set_task')
|
592
|
-
|
592
|
+
|
593
593
|
# Mock blpop to return our task ID first, then always return None
|
594
594
|
# This avoids StopIteration exception in the thread
|
595
595
|
def mock_blpop_side_effect(*args, **kwargs):
|
@@ -598,51 +598,51 @@ class TestRedisTaskQueue:
|
|
598
598
|
mock_blpop_side_effect.called = True
|
599
599
|
return (task_queue._queue_key('test_queue'), b"listen_task")
|
600
600
|
return None
|
601
|
-
|
601
|
+
|
602
602
|
mocker.patch.object(redis_client, 'blpop', side_effect=mock_blpop_side_effect)
|
603
|
-
|
603
|
+
|
604
604
|
# Mock get_task to return our task
|
605
605
|
mocker.patch.object(task_queue, 'get_task', return_value=task)
|
606
|
-
|
606
|
+
|
607
607
|
# Track when task is executed
|
608
608
|
task_executed = threading.Event()
|
609
|
-
|
609
|
+
|
610
610
|
def mock_execute_task(t):
|
611
611
|
# Mark that the task was executed and return a result
|
612
612
|
task_executed.set()
|
613
613
|
return "result"
|
614
|
-
|
614
|
+
|
615
615
|
mocker.patch.object(task_queue, 'execute_task_locally', side_effect=mock_execute_task)
|
616
|
-
|
616
|
+
|
617
617
|
# Use freezegun to handle the sleep in the listen method
|
618
618
|
current_time = time.time()
|
619
619
|
with freeze_time(datetime.datetime.fromtimestamp(current_time)) as frozen_time:
|
620
620
|
# Replace time.sleep to advance the frozen time
|
621
621
|
original_sleep = time.sleep
|
622
|
-
|
622
|
+
|
623
623
|
def advance_time(seconds):
|
624
624
|
frozen_time.tick(datetime.timedelta(seconds=seconds))
|
625
|
-
|
625
|
+
|
626
626
|
time.sleep = advance_time
|
627
|
-
|
627
|
+
|
628
628
|
try:
|
629
629
|
# Run listen in a separate thread with a stop event
|
630
630
|
stop_event = threading.Event()
|
631
|
-
|
631
|
+
|
632
632
|
# Start a thread that will call listen for a short time
|
633
633
|
listen_thread = threading.Thread(
|
634
634
|
target=lambda: task_queue.listen(names, event=stop_event, workers=1)
|
635
635
|
)
|
636
636
|
listen_thread.daemon = True # Ensure thread doesn't block test exit
|
637
637
|
listen_thread.start()
|
638
|
-
|
638
|
+
|
639
639
|
# Wait for the task to be executed or timeout
|
640
640
|
executed = task_executed.wait(timeout=5)
|
641
|
-
|
641
|
+
|
642
642
|
# Stop the listen thread
|
643
643
|
stop_event.set()
|
644
644
|
listen_thread.join(timeout=1)
|
645
|
-
|
645
|
+
|
646
646
|
# Verify task was processed
|
647
647
|
assert executed, "Task execution timed out"
|
648
648
|
finally:
|
@@ -662,13 +662,13 @@ class TestRedisTaskQueue:
|
|
662
662
|
# Create a valid queue key first
|
663
663
|
name = "test_queue"
|
664
664
|
queue_key = task_queue._queue_key(name)
|
665
|
-
|
665
|
+
|
666
666
|
# Extract the name back
|
667
667
|
extracted_name = task_queue._queue_name(queue_key)
|
668
|
-
|
668
|
+
|
669
669
|
# Verify extraction works
|
670
670
|
assert extracted_name == name
|
671
|
-
|
671
|
+
|
672
672
|
# Test with invalid queue key
|
673
673
|
with pytest.raises(AssertionError):
|
674
674
|
task_queue._queue_name("invalid_key")
|
@@ -677,10 +677,10 @@ class TestRedisTaskQueue:
|
|
677
677
|
"""Test getting a task that doesn't exist."""
|
678
678
|
# Try to get a task with an ID that doesn't exist
|
679
679
|
result = task_queue.get_task("nonexistent_task")
|
680
|
-
|
680
|
+
|
681
681
|
# Verify that None is returned
|
682
682
|
assert result is None
|
683
|
-
|
683
|
+
|
684
684
|
# Try with once=True
|
685
685
|
result = task_queue.get_task("nonexistent_task", once=True)
|
686
686
|
assert result is None
|
@@ -689,13 +689,13 @@ class TestRedisTaskQueue:
|
|
689
689
|
"""Test execute_task_remotely when task has error attribute set."""
|
690
690
|
# Create a task
|
691
691
|
task = task_queue.build_task("error_task", "test", {"param": "value"})
|
692
|
-
|
692
|
+
|
693
693
|
# Create a error for the task
|
694
694
|
task_error = ValueError("Task error")
|
695
|
-
|
695
|
+
|
696
696
|
# Mock time.sleep to avoid actual waiting
|
697
697
|
mocker.patch('time.sleep')
|
698
|
-
|
698
|
+
|
699
699
|
# Mock get_task to return a task with error attribute set
|
700
700
|
def mock_get_task(task_id, once=False):
|
701
701
|
task = RedisTask(
|
@@ -707,9 +707,9 @@ class TestRedisTaskQueue:
|
|
707
707
|
_save=lambda: None
|
708
708
|
)
|
709
709
|
return task
|
710
|
-
|
710
|
+
|
711
711
|
mocker.patch.object(task_queue, 'get_task', side_effect=mock_get_task)
|
712
|
-
|
712
|
+
|
713
713
|
# Execute the task and expect the error to be raised
|
714
714
|
with pytest.raises(ValueError, match="Task error"):
|
715
715
|
task_queue.execute_task_remotely(task)
|
@@ -719,38 +719,38 @@ class TestRedisTaskQueue:
|
|
719
719
|
# Create a task
|
720
720
|
task = task_queue.build_task("timeout_task", "test", {"param": "value"})
|
721
721
|
task_queue.interval = 0.5 # Set interval to 0.5 seconds for testing
|
722
|
-
|
722
|
+
|
723
723
|
# Mock get_task to always return None (simulating no worker processing the task)
|
724
724
|
mocker.patch.object(task_queue, 'get_task', return_value=None)
|
725
|
-
|
725
|
+
|
726
726
|
# Mock time.sleep to count calls and track timeout reduction
|
727
727
|
sleep_call_count = 0
|
728
728
|
interval_sum = 0
|
729
|
-
|
729
|
+
|
730
730
|
def mock_sleep(seconds):
|
731
731
|
nonlocal sleep_call_count, interval_sum
|
732
732
|
sleep_call_count += 1
|
733
733
|
interval_sum += seconds
|
734
734
|
# Don't actually sleep
|
735
|
-
|
735
|
+
|
736
736
|
mocker.patch('time.sleep', side_effect=mock_sleep)
|
737
|
-
|
737
|
+
|
738
738
|
# Set a small timeout to make the test fast
|
739
739
|
timeout = 2.0 # Should allow for exactly 4 iterations with interval=0.5
|
740
|
-
|
740
|
+
|
741
741
|
# Execute the task remotely with timeout and expect TimeoutError
|
742
742
|
with pytest.raises(TimeoutError) as exc_info:
|
743
743
|
task_queue.execute_task_remotely(task, timeout=timeout)
|
744
|
-
|
744
|
+
|
745
745
|
# Verify error message
|
746
746
|
assert f"Task {task.id} in {task.name} has expired" in str(exc_info.value)
|
747
|
-
|
747
|
+
|
748
748
|
# Verify sleep was called
|
749
749
|
# In the implementation, the loop continues while timeout >= 0, so we get 5 iterations
|
750
750
|
# with timeout=2.0 and interval=0.5: [2.0, 1.5, 1.0, 0.5, 0.0]
|
751
751
|
expected_calls = int(timeout / task_queue.interval) + 1 # +1 for the last iteration when timeout=0
|
752
752
|
assert sleep_call_count == expected_calls
|
753
|
-
|
753
|
+
|
754
754
|
# Verify that the total time slept approximately equals the timeout
|
755
755
|
assert abs(interval_sum - timeout) <= task_queue.interval
|
756
756
|
|
@@ -758,21 +758,21 @@ class TestRedisTaskQueue:
|
|
758
758
|
"""Test execute_task_remotely method's timeout logic directly."""
|
759
759
|
# Create a task
|
760
760
|
task = task_queue.build_task("timeout_task2", "test", {"param": "value"})
|
761
|
-
|
761
|
+
|
762
762
|
# Mock redis and get_task to simulate the behavior
|
763
763
|
mocker.patch.object(task_queue, 'get_task', return_value=None)
|
764
|
-
|
764
|
+
|
765
765
|
# Mock time.sleep to avoid actual waiting
|
766
766
|
mocker.patch('time.sleep')
|
767
|
-
|
767
|
+
|
768
768
|
# Force timeout to be exactly 0 after one iteration
|
769
769
|
# This will trigger the exact lines we want to test
|
770
770
|
task_queue.interval = 1.0
|
771
771
|
timeout = 1.0 # Will become 0 after one iteration, then -1 after another
|
772
|
-
|
772
|
+
|
773
773
|
# Execute task and expect timeout
|
774
774
|
with pytest.raises(TimeoutError) as exc_info:
|
775
775
|
task_queue.execute_task_remotely(task, timeout=timeout)
|
776
|
-
|
776
|
+
|
777
777
|
# Verify timeout error
|
778
|
-
assert str(exc_info.value) == f"Task {task.id} in {task.name} has expired"
|
778
|
+
assert str(exc_info.value) == f"Task {task.id} in {task.name} has expired"
|