|
5 | 5 | import pytest |
6 | 6 | import pytz |
7 | 7 | from mock import patch |
| 8 | +from requests.exceptions import HTTPError |
8 | 9 |
|
9 | 10 | from kolibri.core.tasks.constants import DEFAULT_QUEUE |
10 | 11 | from kolibri.core.tasks.constants import Priority |
|
26 | 27 | def defaultbackend(): |
27 | 28 | with connection() as c: |
28 | 29 | b = Storage(c) |
| 30 | + b.Base.metadata.create_all(c) |
29 | 31 | b.clear(force=True) |
30 | 32 | yield b |
31 | 33 | b.clear(force=True) |
32 | 34 |
|
33 | 35 |
|
34 | | -@pytest.fixture |
35 | | -def func(): |
36 | | - @register_task |
37 | | - def add(x, y): |
38 | | - return x + y |
| 36 | +@register_task( |
| 37 | + retry_on=[ValueError, TypeError], |
| 38 | +) |
| 39 | +def add(x, y): |
| 40 | + return x + y |
39 | 41 |
|
40 | | - TaskRegistry["kolibri.core.tasks.test.taskrunner.test_storage.add"] = add |
41 | 42 |
|
42 | | - yield add |
43 | | - TaskRegistry.clear() |
| 43 | +@pytest.fixture(autouse=True) |
| 44 | +def register_add_task(): |
| 45 | + # register before tests |
| 46 | + TaskRegistry[callable_to_import_path(add)] = add |
| 47 | + try: |
| 48 | + yield |
| 49 | + finally: |
| 50 | + # clear after tests |
| 51 | + TaskRegistry.clear() |
44 | 52 |
|
45 | 53 |
|
46 | 54 | @pytest.fixture |
47 | | -def simplejob(func): |
48 | | - return Job(func) |
| 55 | +def simplejob(): |
| 56 | + return Job(add) |
49 | 57 |
|
50 | 58 |
|
51 | 59 | class TestBackend: |
52 | | - def test_can_enqueue_single_job(self, defaultbackend, simplejob, func): |
| 60 | + def test_can_enqueue_single_job(self, defaultbackend, simplejob): |
53 | 61 | job_id = defaultbackend.enqueue_job(simplejob, QUEUE) |
54 | 62 |
|
55 | 63 | new_job = defaultbackend.get_job(job_id) |
56 | 64 |
|
57 | 65 | # Does the returned job record the function we set to run? |
58 | | - assert str(new_job.func) == callable_to_import_path(func) |
| 66 | + assert str(new_job.func) == callable_to_import_path(add) |
59 | 67 |
|
60 | 68 | # Does the job have the right state (QUEUED)? |
61 | 69 | assert new_job.state == State.QUEUED |
@@ -310,6 +318,134 @@ def test_can_reschedule_finished_job(self, defaultbackend, simplejob): |
310 | 318 | assert requeued_job.state == State.QUEUED |
311 | 319 | assert requeued_orm_job.scheduled_time > previous_scheduled_time |
312 | 320 |
|
| 321 | + def test_job_retry_on_matching_exception(self, defaultbackend, simplejob): |
| 322 | + exception = ValueError("Error") |
| 323 | + job_id = defaultbackend.enqueue_job( |
| 324 | + simplejob, QUEUE, retry_interval=5, max_retries=3 |
| 325 | + ) |
| 326 | + defaultbackend.mark_job_as_failed(job_id, exception, "Traceback") |
| 327 | + |
| 328 | + orm_job = defaultbackend.get_orm_job(job_id) |
| 329 | + previous_scheduled_time = orm_job.scheduled_time |
| 330 | + |
| 331 | + defaultbackend.reschedule_finished_job_if_needed( |
| 332 | + simplejob.job_id, exception=exception |
| 333 | + ) |
| 334 | + requeued_orm_job = defaultbackend.get_orm_job(job_id) |
| 335 | + requeued_job = defaultbackend.get_job(job_id) |
| 336 | + |
| 337 | + assert requeued_job.state == State.QUEUED |
| 338 | + assert requeued_orm_job.scheduled_time > previous_scheduled_time |
| 339 | + assert requeued_orm_job.retries == 1 |
| 340 | + |
| 341 | + def test_job_retry_on_matching_exception__no_max_retries( |
| 342 | + self, defaultbackend, simplejob |
| 343 | + ): |
| 344 | + exception = ValueError("Error") |
| 345 | + job_id = defaultbackend.enqueue_job(simplejob, QUEUE, retry_interval=5) |
| 346 | + defaultbackend.mark_job_as_failed(job_id, exception, "Traceback") |
| 347 | + |
| 348 | + orm_job = defaultbackend.get_orm_job(job_id) |
| 349 | + previous_scheduled_time = orm_job.scheduled_time |
| 350 | + |
| 351 | + defaultbackend.reschedule_finished_job_if_needed( |
| 352 | + simplejob.job_id, exception=exception |
| 353 | + ) |
| 354 | + requeued_orm_job = defaultbackend.get_orm_job(job_id) |
| 355 | + requeued_job = defaultbackend.get_job(job_id) |
| 356 | + |
| 357 | + assert requeued_job.state == State.QUEUED |
| 358 | + assert requeued_orm_job.scheduled_time > previous_scheduled_time |
| 359 | + assert requeued_orm_job.retries == 1 |
| 360 | + |
| 361 | + def test_job_retry_on_matching_exception__no_retry_interval( |
| 362 | + self, defaultbackend, simplejob |
| 363 | + ): |
| 364 | + exception = TypeError("Error") |
| 365 | + job_id = defaultbackend.enqueue_job(simplejob, QUEUE, max_retries=3) |
| 366 | + defaultbackend.mark_job_as_failed(job_id, exception, "Traceback") |
| 367 | + |
| 368 | + orm_job = defaultbackend.get_orm_job(job_id) |
| 369 | + previous_scheduled_time = orm_job.scheduled_time |
| 370 | + |
| 371 | + defaultbackend.reschedule_finished_job_if_needed( |
| 372 | + simplejob.job_id, exception=exception |
| 373 | + ) |
| 374 | + requeued_orm_job = defaultbackend.get_orm_job(job_id) |
| 375 | + requeued_job = defaultbackend.get_job(job_id) |
| 376 | + |
| 377 | + assert requeued_job.state == State.QUEUED |
| 378 | + assert requeued_orm_job.scheduled_time > previous_scheduled_time |
| 379 | + assert requeued_orm_job.retries == 1 |
| 380 | + |
| 381 | + def test_job_not_retry_on_matching_exception__no_retry_params( |
| 382 | + self, defaultbackend, simplejob |
| 383 | + ): |
| 384 | + # If job has no retry params, it should not retry even if exception matches |
| 385 | + exception = ValueError("Error") |
| 386 | + job_id = defaultbackend.enqueue_job(simplejob, QUEUE) |
| 387 | + defaultbackend.mark_job_as_failed(job_id, exception, "Traceback") |
| 388 | + |
| 389 | + orm_job = defaultbackend.get_orm_job(job_id) |
| 390 | + previous_scheduled_time = orm_job.scheduled_time |
| 391 | + |
| 392 | + defaultbackend.reschedule_finished_job_if_needed( |
| 393 | + simplejob.job_id, exception=exception |
| 394 | + ) |
| 395 | + requeued_orm_job = defaultbackend.get_orm_job(job_id) |
| 396 | + requeued_job = defaultbackend.get_job(job_id) |
| 397 | + |
| 398 | + assert requeued_job.state == State.FAILED |
| 399 | + assert requeued_orm_job.scheduled_time == previous_scheduled_time |
| 400 | + assert requeued_orm_job.retries is None |
| 401 | + |
| 402 | + def test_job_not_retry_on_non_matching_exception(self, defaultbackend, simplejob): |
| 403 | + exception = HTTPError("Error") |
| 404 | + job_id = defaultbackend.enqueue_job( |
| 405 | + simplejob, QUEUE, retry_interval=5, max_retries=3 |
| 406 | + ) |
| 407 | + defaultbackend.mark_job_as_failed(job_id, exception, "Traceback") |
| 408 | + |
| 409 | + orm_job = defaultbackend.get_orm_job(job_id) |
| 410 | + previous_scheduled_time = orm_job.scheduled_time |
| 411 | + |
| 412 | + defaultbackend.reschedule_finished_job_if_needed( |
| 413 | + simplejob.job_id, exception=exception |
| 414 | + ) |
| 415 | + requeued_orm_job = defaultbackend.get_orm_job(job_id) |
| 416 | + requeued_job = defaultbackend.get_job(job_id) |
| 417 | + |
| 418 | + assert requeued_job.state == State.FAILED |
| 419 | + assert requeued_orm_job.scheduled_time == previous_scheduled_time |
| 420 | + assert requeued_orm_job.retries is None |
| 421 | + |
| 422 | + def test_job_not_retry_on_limit_max_retries(self, defaultbackend, simplejob): |
| 423 | + exception = ValueError("Error") |
| 424 | + job_id = defaultbackend.enqueue_job(simplejob, QUEUE, max_retries=1) |
| 425 | + defaultbackend.mark_job_as_failed(job_id, exception, "Traceback") |
| 426 | + |
| 427 | + # Retry first time |
| 428 | + defaultbackend.reschedule_finished_job_if_needed( |
| 429 | + simplejob.job_id, exception=exception |
| 430 | + ) |
| 431 | + defaultbackend.mark_job_as_failed(job_id, exception, "Traceback") |
| 432 | + |
| 433 | + orm_job = defaultbackend.get_orm_job(job_id) |
| 434 | + previous_scheduled_time = orm_job.scheduled_time |
| 435 | + |
| 436 | + # When trying to retry second time, it should not retry as max_retries is reached |
| 437 | + defaultbackend.reschedule_finished_job_if_needed( |
| 438 | + simplejob.job_id, exception=exception |
| 439 | + ) |
| 440 | + |
| 441 | + requeued_orm_job = defaultbackend.get_orm_job(job_id) |
| 442 | + requeued_job = defaultbackend.get_job(job_id) |
| 443 | + |
| 444 | + retries = requeued_orm_job.retries |
| 445 | + assert requeued_job.state == State.FAILED |
| 446 | + assert requeued_orm_job.scheduled_time == previous_scheduled_time |
| 447 | + assert retries == 1 |
| 448 | + |
313 | 449 | def test_reschedule_finished_job_canceled(self, defaultbackend, simplejob): |
314 | 450 | # Test case where the job is canceled. |
315 | 451 | job_id = defaultbackend.enqueue_job(simplejob, QUEUE) |
|
0 commit comments