|
5 | 5 | is attached to the same root rather than to the child. |
6 | 6 | """ |
7 | 7 |
|
| 8 | +import copy |
8 | 9 | import logging |
9 | 10 | from unittest import mock |
10 | 11 |
|
| 12 | +import celery |
11 | 13 | import procrastinate |
12 | 14 | import pytest |
13 | 15 | from procrastinate import testing |
@@ -141,8 +143,15 @@ def test_list_tasks_filters_by_parent(httpx_mock): |
141 | 143 | json={"next": None, "previous": None, "results": [_json_task_response(parent="parent_id")]}, |
142 | 144 | status_code=200, |
143 | 145 | ) |
144 | | - (child,) = list_tasks(parent="parent_id").results |
| 146 | + tasks = list_tasks(parent="parent_id") |
| 147 | + (child,) = tasks.results |
| 148 | + assert isinstance(child, Task) |
145 | 149 | assert child.parent == "parent_id" |
| 150 | + assert list(tasks) == tasks.results |
| 151 | + assert len(tasks) == 1 |
| 152 | + # a TaskList must survive copy / pickle: both probe for dunders that |
| 153 | + # `__getattr__` must not try to delegate |
| 154 | + assert len(copy.deepcopy(tasks)) == 1 |
146 | 155 |
|
147 | 156 |
|
148 | 157 | @pytest.mark.usefixtures("_bind_settings") |
@@ -321,6 +330,106 @@ def test_celery_publish_explicit_parent_wins(): |
321 | 330 | assert create.call_args.kwargs["parent"] == "chosen" |
322 | 331 |
|
323 | 332 |
|
| 333 | +def _celery_app(**conf): |
| 334 | + """A standalone app backed by in-memory transports, so no broker is needed.""" |
| 335 | + app = celery.Celery("test_parents", broker="memory://", backend="cache+memory://", **conf) |
| 336 | + |
| 337 | + @app.task(bind=True, base=taskbadger.celery.Task, name="test_parents.add") |
| 338 | + def add(self, a, b): |
| 339 | + return a + b |
| 340 | + |
| 341 | + return add |
| 342 | + |
| 343 | + |
| 344 | +@pytest.mark.usefixtures("_bind_settings") |
| 345 | +def test_celery_apply_async_parent(): |
| 346 | + """`taskbadger_parent` on `apply_async` reaches the task created at publish time.""" |
| 347 | + add = _celery_app() |
| 348 | + |
| 349 | + with ( |
| 350 | + mock.patch("taskbadger.celery.create_task_safe") as create, |
| 351 | + mock.patch("taskbadger.sdk.get_task"), |
| 352 | + ): |
| 353 | + create.return_value = task_for_test() |
| 354 | + add.apply_async((2, 2), taskbadger_parent="chosen") |
| 355 | + |
| 356 | + assert create.call_args.kwargs["parent"] == "chosen" |
| 357 | + |
| 358 | + |
| 359 | +@pytest.mark.usefixtures("_bind_settings") |
| 360 | +def test_celery_apply_async_parent_beats_the_running_task(): |
| 361 | + add = _celery_app() |
| 362 | + |
| 363 | + with ( |
| 364 | + mock.patch("taskbadger.celery.create_task_safe") as create, |
| 365 | + mock.patch("taskbadger.sdk.get_task"), |
| 366 | + ): |
| 367 | + create.return_value = task_for_test() |
| 368 | + token = enter_task("root_id") |
| 369 | + try: |
| 370 | + add.apply_async((2, 2), taskbadger_parent="chosen") |
| 371 | + finally: |
| 372 | + exit_task(token) |
| 373 | + |
| 374 | + assert create.call_args.kwargs["parent"] == "chosen" |
| 375 | + |
| 376 | + |
| 377 | +@pytest.mark.usefixtures("_bind_settings") |
| 378 | +def test_celery_eager_apply_async_parent(): |
| 379 | + """Eager tasks are created in `task_prerun` rather than at publish time, but |
| 380 | + the explicit parent still has to make it through.""" |
| 381 | + add = _celery_app(task_always_eager=True, task_eager_propagates=True) |
| 382 | + |
| 383 | + with ( |
| 384 | + mock.patch("taskbadger.celery.create_task_safe") as create, |
| 385 | + mock.patch("taskbadger.celery.update_task_safe"), |
| 386 | + mock.patch("taskbadger.sdk.get_task"), |
| 387 | + ): |
| 388 | + create.return_value = task_for_test() |
| 389 | + assert add.apply_async((2, 2), taskbadger_parent="chosen").get() == 4 |
| 390 | + |
| 391 | + assert create.call_args.kwargs["parent"] == "chosen" |
| 392 | + |
| 393 | + |
| 394 | +@pytest.mark.usefixtures("_bind_settings") |
| 395 | +def test_celery_eager_nests_under_the_running_task(): |
| 396 | + add = _celery_app(task_always_eager=True, task_eager_propagates=True) |
| 397 | + |
| 398 | + with ( |
| 399 | + mock.patch("taskbadger.celery.create_task_safe") as create, |
| 400 | + mock.patch("taskbadger.celery.update_task_safe"), |
| 401 | + mock.patch("taskbadger.sdk.get_task"), |
| 402 | + ): |
| 403 | + create.return_value = task_for_test() |
| 404 | + token = enter_task("root_id") |
| 405 | + try: |
| 406 | + add.apply_async((2, 2)) |
| 407 | + finally: |
| 408 | + exit_task(token) |
| 409 | + |
| 410 | + assert create.call_args.kwargs["parent"] == "root_id" |
| 411 | + |
| 412 | + |
| 413 | +@pytest.mark.usefixtures("_bind_settings") |
| 414 | +def test_celery_eager_explicit_none_parent_makes_a_root_task(): |
| 415 | + """`taskbadger_parent=None` asks for a root task, as it does at publish time.""" |
| 416 | + add = _celery_app(task_always_eager=True, task_eager_propagates=True) |
| 417 | + |
| 418 | + with ( |
| 419 | + mock.patch("taskbadger.celery.create_task_safe") as create, |
| 420 | + mock.patch("taskbadger.celery.update_task_safe"), |
| 421 | + mock.patch("taskbadger.sdk.get_task"), |
| 422 | + ): |
| 423 | + create.return_value = task_for_test() |
| 424 | + token = enter_task("root_id") |
| 425 | + try: |
| 426 | + add.apply_async((2, 2), taskbadger_parent=None) |
| 427 | + finally: |
| 428 | + exit_task(token) |
| 429 | + |
| 430 | + assert create.call_args.kwargs["parent"] is None |
| 431 | + |
| 432 | + |
324 | 433 | # --- Procrastinate ------------------------------------------------------------ |
325 | 434 |
|
326 | 435 |
|
|
0 commit comments