├── .flake8 ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── Bug-Report.md │ ├── Documentation-Bug-Report.md │ ├── Feature-Request.md │ └── config.yml ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── deploy.yml │ ├── docker.yml │ ├── examples.yml │ ├── linter.yml │ ├── parallel-support.yml │ ├── python-package.yml │ └── semgrep.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CONTRIBUTING.rst ├── CONTRIBUTORS.txt ├── Changelog.rst ├── LICENSE ├── README.rst ├── docs ├── Makefile ├── _static │ └── .keep ├── _templates │ └── sidebardonations.html ├── build.sh ├── changelog.rst ├── conf.py ├── contributing.rst ├── copyright.rst ├── devguide │ ├── index.rst │ ├── local-development-environment.rst │ ├── release.rst │ ├── sphinx.rst │ └── tox.rst ├── faq.rst ├── getting-started │ ├── first-steps.rst │ ├── help.rst │ ├── index.rst │ ├── introduction.rst │ ├── next-steps.rst │ └── vendors.rst ├── glossary.rst ├── images │ ├── celery_512.png │ └── favicon.ico ├── includes │ ├── installation.txt │ └── worker-breakdown.txt ├── index.rst ├── reference │ ├── index.rst │ ├── pytest_celery.api.rst │ ├── pytest_celery.fixtures.rst │ ├── pytest_celery.rst │ ├── pytest_celery.vendors.localstack.rst │ ├── pytest_celery.vendors.memcached.rst │ ├── pytest_celery.vendors.rabbitmq.rst │ ├── pytest_celery.vendors.redis.backend.rst │ ├── pytest_celery.vendors.redis.broker.rst │ ├── pytest_celery.vendors.redis.rst │ ├── pytest_celery.vendors.rst │ ├── pytest_celery.vendors.worker.content.rst │ └── pytest_celery.vendors.worker.rst └── userguide │ ├── advanced-installation.rst │ ├── app-conf.rst │ ├── celery-bug-report.rst │ ├── default-tasks.rst │ ├── examples │ ├── django.rst │ ├── hybrid_setup.rst │ ├── index.rst │ ├── myutils.rst │ ├── myworker.rst │ ├── rabbitmq_management.rst │ ├── range.rst │ ├── vhost.rst │ └── worker_pool.rst │ ├── index.rst │ ├── resources │ └── index.rst │ ├── setup-matrix.rst │ ├── signals.rst │ ├── tasks.rst │ └── utils-module.rst ├── examples ├── celery_bug_report.py ├── django │ ├── demoapp │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tasks.py │ │ └── views.py │ ├── manage.py │ ├── proj │ │ ├── __init__.py │ │ ├── celery.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── pytest.ini │ ├── requirements.txt │ └── tests │ │ ├── DjangoWorker.Dockerfile │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_tasks.py ├── hybrid_setup │ ├── pytest.ini │ ├── requirements.txt │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_hybrid_setup.py │ │ └── vendors │ │ ├── __init__.py │ │ ├── memcached.py │ │ ├── rabbitmq.py │ │ └── workers │ │ ├── __init__.py │ │ ├── gevent.Dockerfile │ │ ├── gevent.py │ │ ├── legacy.Dockerfile │ │ ├── legacy.py │ │ ├── signals.py │ │ └── tasks.py ├── myutils │ ├── pytest.ini │ ├── requirements.txt │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── myutils.py │ │ └── test_myutils.py ├── myworker │ ├── pytest.ini │ ├── requirements.txt │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── myworker │ │ ├── Dockerfile │ │ ├── __init__.py │ │ └── myworker.py │ │ └── test_myworker.py ├── rabbitmq_management │ ├── pytest.ini │ ├── requirements.txt │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_management_broker.py ├── range │ ├── pytest.ini │ ├── requirements.txt │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_range.py │ │ └── test_range_cluster.py ├── vhost │ ├── pytest.ini │ ├── requirements.txt │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_vhost.py └── worker_pool │ ├── Dockerfile │ ├── pytest.ini │ ├── requirements.txt │ ├── tasks.py │ └── tests │ ├── __init__.py │ ├── test_gevent_pool.py │ └── test_solo_pool.py ├── poetry.lock ├── pyproject.toml ├── src └── pytest_celery │ ├── __init__.py │ ├── api │ ├── __init__.py │ ├── backend.py │ ├── base.py │ ├── broker.py │ ├── container.py │ ├── setup.py │ └── worker.py │ ├── defaults.py │ ├── fixtures │ ├── __init__.py │ ├── backend.py │ ├── broker.py │ ├── setup.py │ └── worker.py │ ├── plugin.py │ └── vendors │ ├── __init__.py │ ├── localstack │ ├── __init__.py │ ├── api.py │ ├── container.py │ ├── defaults.py │ └── fixtures.py │ ├── memcached │ ├── __init__.py │ ├── api.py │ ├── container.py │ ├── defaults.py │ └── fixtures.py │ ├── rabbitmq │ ├── __init__.py │ ├── api.py │ ├── container.py │ ├── defaults.py │ └── fixtures.py │ ├── redis │ ├── __init__.py │ ├── backend │ │ ├── __init__.py │ │ ├── api.py │ │ ├── defaults.py │ │ └── fixtures.py │ ├── broker │ │ ├── __init__.py │ │ ├── api.py │ │ ├── defaults.py │ │ └── fixtures.py │ ├── container.py │ └── defaults.py │ └── worker │ ├── Dockerfile │ ├── __init__.py │ ├── container.py │ ├── content │ ├── __init__.py │ ├── app.py │ └── utils.py │ ├── defaults.py │ ├── fixtures.py │ ├── tasks.py │ └── volume.py ├── tests ├── __init__.py ├── conftest.py ├── defaults.py ├── integration │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── custom_setup │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ └── test_custom_setup.py │ │ ├── test_backend.py │ │ ├── test_base.py │ │ ├── test_broker.py │ │ ├── test_container.py │ │ ├── test_setup.py │ │ └── test_worker.py │ ├── conftest.py │ └── vendors │ │ ├── __init__.py │ │ ├── test_default_tasks.py │ │ ├── test_localstack.py │ │ ├── test_memcached.py │ │ ├── test_rabbitmq.py │ │ ├── test_redis.py │ │ └── test_worker.py ├── smoke │ ├── __init__.py │ ├── conftest.py │ ├── signals.py │ ├── test_canvas.py │ ├── test_control.py │ ├── test_failover.py │ ├── test_signals.py │ ├── test_task.py │ └── test_worker.py ├── tasks.py ├── test_pytest_celery.py └── unit │ ├── __init__.py │ ├── api │ ├── __init__.py │ ├── test_backend.py │ ├── test_base.py │ ├── test_broker.py │ ├── test_container.py │ ├── test_setup.py │ └── test_worker.py │ ├── conftest.py │ └── vendors │ ├── __init__.py │ ├── test_localstack.py │ ├── test_memcached.py │ ├── test_rabbitmq.py │ ├── test_redis.py │ └── test_worker │ ├── __init__.py │ ├── test_default_tasks.py │ ├── test_volume.py │ └── test_worker.py └── tox.ini /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # GitHub automatically requests reviews from the specified code owners when a pull request changes any owned files. 2 | 3 | * @Nusnus 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: nusnus 4 | open_collective: celery 5 | patreon: # Replace with a single Patreon username 6 | ko_fi: # Replace with a single Ko-fi username 7 | custom: # Replace with a single custom sponsorship URL 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Documentation-Bug-Report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation Bug Report 3 | about: Is something wrong with our documentation? 4 | title: '' 5 | labels: 'documentation' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | # Checklist 16 | 19 | 20 | - [ ] I have checked the [issues list](https://github.com/celery/pytest-celery/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22documentation%22+) 21 | for similar or identical bug reports. 22 | - [ ] I have checked the [pull requests list](https://github.com/celery/pytest-celery/pulls?q=is%3Apr+label%3A%22documentation%22) 23 | for existing proposed fixes. 24 | - [ ] I have checked the [commit log](https://github.com/celery/pytest-celery/commits/main) 25 | to find out if the bug was already fixed in the main branch. 26 | - [ ] I have included all related issues and possible duplicate issues in this issue 27 | (If there are none, check this box anyway). 28 | 29 | ## Related Issues and Possible Duplicates 30 | 40 | 41 | #### Related Issues 42 | 43 | - None 44 | 45 | #### Possible Duplicates 46 | 47 | - None 48 | 49 | # Description 50 | 54 | 55 | # Suggestions 56 | 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature-Request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Do you need a new feature? 4 | title: '' 5 | labels: 'feature' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | # Checklist 16 | 19 | 20 | - [ ] I have checked the [issues list](https://github.com/celery/pytest-celery/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22feature%22+) 21 | for similar or identical feature requests. 22 | - [ ] I have checked the [pull requests list](https://github.com/celery/pytest-celery/pulls?utf8=%E2%9C%93&q=is%3Apr+label%3A%22feature%22+) 23 | for existing proposed implementations of this feature. 24 | - [ ] I have checked the [commit log](https://github.com/celery/pytest-celery/commits/main) 25 | to find out if the same feature was already implemented in the 26 | main branch. 27 | - [ ] I have included all related issues and possible duplicate issues in this issue 28 | (If there are none, check this box anyway). 29 | 30 | ## Related Issues and Possible Duplicates 31 | 41 | 42 | #### Related Issues 43 | 44 | - None 45 | 46 | #### Possible Duplicates 47 | 48 | - None 49 | 50 | # Brief Summary 51 | 55 | 56 | # Design 57 | 58 | ## Proposed Behavior 59 | 63 | 64 | ## Proposed UI/UX 65 | 69 | 70 | ## Diagrams 71 | 79 | N/A 80 | 81 | ## Alternatives 82 | 86 | None 87 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Celery Issue Tracker 4 | url: https://github.com/celery/celery/issues/ 5 | about: If this issue only involves Celery, please open a new issue there. 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "pip" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [main] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | # branches: [ main ] 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: blacksmith-4vcpu-ubuntu-2204 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: ["python"] 34 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 35 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v4 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v3 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 50 | 51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 52 | # If this step fails, then you should remove it and run the build manually (see below) 53 | - name: Autobuild 54 | uses: github/codeql-action/autobuild@v3 55 | 56 | # ℹ️ Command-line programs to run using the OS shell. 57 | # 📚 https://git.io/JvXDl 58 | 59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 60 | # and modify them (or add more) to build your code if your project 61 | # uses a compiled language 62 | 63 | #- run: | 64 | # make bootstrap 65 | # make release 66 | 67 | - name: Perform CodeQL Analysis 68 | uses: github/codeql-action/analyze@v3 69 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to PyPI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: blacksmith-4vcpu-ubuntu-2204 10 | env: 11 | POETRY_VIRTUALENVS_CREATE: "false" 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Install poetry 16 | run: | 17 | pipx install poetry 18 | pipx inject poetry poetry-bumpversion 19 | 20 | - name: Build 21 | run: | 22 | poetry version ${{ github.ref_name }} 23 | poetry build 24 | 25 | - name: Publish 26 | run: | 27 | poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }} 28 | poetry publish 29 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | pull_request: 5 | branches: [ 'main'] 6 | paths: 7 | - '.github/workflows/docker.yml' 8 | - 'src/pytest_celery/vendors/worker/**' 9 | - "**.py" 10 | - "**.txt" 11 | - "**.toml" 12 | - "tox.ini" 13 | - 'Dockerfile' 14 | - "poetry.lock" 15 | push: 16 | branches: [ 'main'] 17 | paths: 18 | - '.github/workflows/docker.yml' 19 | - 'src/pytest_celery/vendors/worker/**' 20 | - "**.py" 21 | - "**.txt" 22 | - "**.toml" 23 | - "tox.ini" 24 | - 'Dockerfile' 25 | - "poetry.lock" 26 | 27 | 28 | jobs: 29 | build-worker: 30 | runs-on: blacksmith-4vcpu-ubuntu-2204 31 | timeout-minutes: 5 32 | steps: 33 | - uses: actions/checkout@v4 34 | - name: Build Celery Worker 35 | run: cd src/pytest_celery/vendors/worker && docker build -t pytest-celery-worker . 36 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | check: 7 | name: ${{ matrix.check }} check 8 | runs-on: blacksmith-4vcpu-ubuntu-2204 9 | strategy: 10 | matrix: 11 | check: [lint, mypy] 12 | steps: 13 | - name: Checkout branch 14 | uses: actions/checkout@v4 15 | 16 | - name: Install apt packages 17 | run: | 18 | sudo apt update 19 | 20 | - name: Set up Python 3.13 21 | uses: useblacksmith/setup-python@v6 22 | with: 23 | python-version: '3.13' 24 | cache: 'pip' 25 | cache-dependency-path: '**/setup.py' 26 | 27 | - name: Install Poetry 28 | uses: snok/install-poetry@v1.4.1 29 | with: 30 | version: 1.8.4 31 | 32 | - name: Install CI dependencies 33 | run: | 34 | poetry config virtualenvs.create false 35 | poetry install --only ci 36 | 37 | - name: Run check 38 | run: tox -e ${{ matrix.check }} 39 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: {} 3 | pull_request: {} 4 | push: 5 | branches: 6 | - main 7 | - master 8 | paths: 9 | - .github/workflows/semgrep.yml 10 | schedule: 11 | # random HH:MM to avoid a load spike on GitHub Actions at 00:00 12 | - cron: 36 4 * * * 13 | name: Semgrep 14 | jobs: 15 | semgrep: 16 | name: Scan 17 | runs-on: blacksmith-4vcpu-ubuntu-2204 18 | env: 19 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 20 | container: 21 | image: returntocorp/semgrep 22 | steps: 23 | - uses: actions/checkout@v4 24 | - run: semgrep ci 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE stuff 2 | 3 | .idea/* 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | docs/_draft.rst 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | .env 114 | .venv 115 | env/ 116 | venv/ 117 | ENV/ 118 | env.bak/ 119 | venv.bak/ 120 | 121 | # Spyder project settings 122 | .spyderproject 123 | .spyproject 124 | 125 | # Rope project settings 126 | .ropeproject 127 | 128 | # mkdocs documentation 129 | /site 130 | 131 | # mypy 132 | .mypy_cache/ 133 | .dmypy.json 134 | dmypy.json 135 | 136 | # Pyre type checker 137 | .pyre/ 138 | 139 | # pytype static type analyzer 140 | .pytype/ 141 | 142 | # Cython debug symbols 143 | cython_debug/ 144 | 145 | # PyCharm 146 | .idea/ 147 | 148 | # VSCode 149 | .devcontainer 150 | .vscode/ 151 | 152 | # YAPF 153 | .style.yapf 154 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/python-poetry/poetry 3 | rev: 1.8.0 4 | hooks: 5 | - id: poetry-check 6 | 7 | - repo: https://github.com/asottile/yesqa 8 | rev: v1.5.0 9 | hooks: 10 | - id: yesqa 11 | 12 | - repo: https://github.com/asottile/pyupgrade 13 | rev: v3.19.1 14 | hooks: 15 | - id: pyupgrade 16 | args: ["--py38-plus"] 17 | 18 | - repo: https://github.com/PyCQA/autoflake 19 | rev: v2.3.1 20 | hooks: 21 | - id: autoflake 22 | args: ["--in-place", "--remove-unused-variables"] 23 | 24 | - repo: https://github.com/pycqa/isort 25 | rev: 5.13.2 26 | hooks: 27 | - id: isort 28 | args: 29 | [ 30 | "--multi-line", 31 | "7", 32 | "--force-single-line-imports", 33 | "--profile", 34 | "black", 35 | ] 36 | 37 | - repo: https://github.com/codespell-project/codespell 38 | rev: v2.3.0 39 | hooks: 40 | - id: codespell 41 | args: [--write-changes] 42 | additional_dependencies: 43 | - tomli 44 | 45 | # Disabled until fixed support for Pre-commit v4.x.x 46 | # - repo: https://github.com/PyCQA/docformatter 47 | # rev: v1.7.5 48 | # hooks: 49 | # - id: docformatter 50 | # args: [--in-place] 51 | # language: python 52 | 53 | - repo: https://github.com/pre-commit/pre-commit-hooks 54 | rev: v5.0.0 55 | hooks: 56 | - id: check-ast 57 | - id: check-case-conflict 58 | - id: check-docstring-first 59 | exclude: "examples/django/proj/settings.py" 60 | - id: check-json 61 | - id: check-merge-conflict 62 | - id: check-shebang-scripts-are-executable 63 | - id: check-symlinks 64 | - id: check-toml 65 | - id: check-yaml 66 | - id: debug-statements 67 | - id: destroyed-symlinks 68 | - id: detect-private-key 69 | - id: end-of-file-fixer 70 | - id: forbid-submodules 71 | - id: mixed-line-ending 72 | - id: pretty-format-json 73 | args: ["--autofix"] 74 | - id: trailing-whitespace 75 | 76 | - repo: https://github.com/psf/black 77 | rev: 24.10.0 78 | hooks: 79 | - id: black 80 | args: ["--line-length", "120"] 81 | 82 | - repo: https://github.com/PyCQA/flake8 83 | rev: 7.1.1 84 | hooks: 85 | - id: flake8 86 | args: ["--max-line-length", "120"] 87 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | jobs: 13 | post_create_environment: 14 | # Install poetry 15 | # https://python-poetry.org/docs/#installing-manually 16 | - pip install poetry==1.8.4 17 | # Tell poetry to not use a virtual environment 18 | - poetry config virtualenvs.create false 19 | - poetry export --with docs -E all -f requirements.txt --output docs/requirements.txt 20 | 21 | # Build documentation in the "docs/" directory with Sphinx 22 | sphinx: 23 | configuration: docs/conf.py 24 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 25 | # builder: "dirhtml" 26 | # Fail on all warnings to avoid broken references 27 | # fail_on_warning: true 28 | # Optionally build your docs in additional formats such as PDF and ePub 29 | # formats: 30 | # - pdf 31 | # - epub 32 | 33 | # Optional but recommended, declare the Python requirements required 34 | # to build your documentation 35 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 36 | python: 37 | install: 38 | - method: pip 39 | path: . 40 | - requirements: docs/requirements.txt 41 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | The contributor proposes to grant a license for specific software (referred to as a "Contribution" or multiple "Contributions") to pytest-celery. In turn, pytest-celery agrees to accept these Contributions under the conditions outlined in the BSD open source license. 2 | The contributor acknowledges and consents to pytest-celery having the irreversible and enduring right to produce and share copies of any Contribution. 3 | Additionally, pytest-celery has the authority to generate and distribute collective works and derivative works based on any Contribution, all within the framework of the BSD License. 4 | 5 | Contributors 6 | ------------ 7 | 8 | Tomer Nosrati, 2022/17/07 9 | Aviv Rahamim, 2025/02/23 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Tomer Nosrati. All rights reserved. 2 | 3 | pytest-celery is licensed under The BSD License (3 Clause, also known as 4 | the new BSD license). The license is an OSI approved Open Source 5 | license and is GPL-compatible(1). 6 | 7 | The license text can also be found here: 8 | https://www.opensource.org/license/BSD-3-Clause 9 | 10 | 11 | License 12 | ======= 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions are met: 16 | * Redistributions of source code must retain the above copyright 17 | notice, this list of conditions and the following disclaimer. 18 | * Redistributions in binary form must reproduce the above copyright 19 | notice, this list of conditions and the following disclaimer in the 20 | documentation and/or other materials provided with the distribution. 21 | * Neither the name of Tomer Nosrati, nor the 22 | names of its contributors may be used to endorse or promote products 23 | derived from this software without specific prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 26 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 27 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Tomer Nosrati OR CONTRIBUTORS 29 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 | POSSIBILITY OF SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /docs/_static/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/docs/_static/.keep -------------------------------------------------------------------------------- /docs/_templates/sidebardonations.html: -------------------------------------------------------------------------------- 1 | 3 | 10 | -------------------------------------------------------------------------------- /docs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Used to build the documentation locally for testing/debug purposes. 4 | # Nothing uses this script automatically! 5 | 6 | python -m sphinx -T -E -b html -d _build/doctrees -D language=en . _build/html 7 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../Changelog.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from sphinx_celery import conf 2 | 3 | config = conf.build_config( 4 | "pytest_celery", 5 | __file__, 6 | project="pytest_celery", 7 | version_dev="1.2", 8 | version_stable="1.1", 9 | canonical_url="https://pytest-celery.readthedocs.io/", 10 | webdomain="pytest-celery.readthedocs.io", 11 | github_project="celery/pytest-celery", 12 | author="Tomer Nosrati", 13 | author_name="Tomer Nosrati", 14 | copyright="2025", 15 | publisher="Celery Project", 16 | html_logo="images/celery_512.png", 17 | html_favicon="images/favicon.ico", 18 | html_prepend_sidebars=["sidebardonations.html"], 19 | extra_extensions=[ 20 | "sphinx_click", 21 | "sphinx.ext.napoleon", 22 | "celery.contrib.sphinx", 23 | "sphinxcontrib.mermaid", 24 | ], 25 | apicheck_ignore_modules=[ 26 | r"celery.contrib.*", 27 | ], 28 | linkcheck_ignore=[ 29 | r"^http://localhost", 30 | r"^http://0.0.0.0", 31 | r"https://github\.com/Jc2k/pytest-docker-tools\?tab=readme-ov-file#images", 32 | r"https://github\.com/Jc2k/pytest-docker-tools\?tab=readme-ov-file#containers", 33 | r"https://github\.com/Jc2k/pytest-docker-tools\?tab=readme-ov-file#fixture-wrappers", 34 | r"https://github\.com/celery/celery/blob/main/requirements/test\.txt#L2", 35 | r"https://github\.com/celery/celery/blob/main/tox\.ini#L30", 36 | r"https://www\.opensource\.org/license/BSD-3-Clause", 37 | r"https://pypi\.org/project/pytest-celery/#history", 38 | ], 39 | autodoc_mock_imports=[], 40 | ) 41 | 42 | del config["intersphinx_mapping"]["eventlet"] 43 | 44 | globals().update(config) 45 | 46 | settings = {} 47 | ignored_settings = {} 48 | 49 | 50 | def configcheck_project_settings(): 51 | return set(settings) 52 | 53 | 54 | def configcheck_should_ignore(setting): 55 | return setting in ignored_settings 56 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/copyright.rst: -------------------------------------------------------------------------------- 1 | .. _copyright: 2 | 3 | Copyright 4 | ========= 5 | 6 | *pytest-celery User Manual* 7 | 8 | by Tomer Nosrati 9 | 10 | .. |copy| unicode:: U+000A9 .. COPYRIGHT SIGN 11 | 12 | Copyright |copy| 2025, Tomer Nosrati. 13 | 14 | All rights reserved. This material may be copied or distributed only 15 | subject to the terms and conditions set forth in the `Creative Commons 16 | Attribution-ShareAlike 4.0 International 17 | `_ license. 18 | 19 | You may share and adapt the material, even for commercial purposes, but 20 | you must give the original author credit. If you alter, transform, or build upon this 21 | work, you may distribute the resulting work only under the same or a 22 | compatible license to this one. 23 | 24 | .. note:: 25 | 26 | While the *pytest-celery* documentation is offered under the 27 | Creative Commons *Attribution-ShareAlike 4.0 International* license, 28 | the pytest-celery *software* is offered under the 29 | `BSD License (3 Clause) `_. 30 | -------------------------------------------------------------------------------- /docs/devguide/index.rst: -------------------------------------------------------------------------------- 1 | .. _devguide: 2 | 3 | ========================== 4 | Plugin Development Guide 5 | ========================== 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | The purpose of this guide is to explain the plugin's software development lifecycle (SDLC), 11 | and to provide a set of guidelines and best practices for developing the plugin itself using all of 12 | the available tools and resources. 13 | 14 | This guide is intended for developers who want to contribute to the plugin. It explains which tools 15 | are used and how to set up a local development environment. 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | 20 | local-development-environment 21 | tox 22 | sphinx 23 | release 24 | -------------------------------------------------------------------------------- /docs/devguide/sphinx.rst: -------------------------------------------------------------------------------- 1 | .. _sphinx: 2 | 3 | ====================== 4 | Shpinx Documentation 5 | ====================== 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | The plugin uses the :pypi:`sphinx_celery ` engine to generate the documentation. 11 | The documentation is written in reStructuredText format and is located in the ``docs`` directory of the repository. 12 | 13 | .. contents:: 14 | :local: 15 | :depth: 2 16 | 17 | Building the documentation 18 | ========================== 19 | 20 | To build the documentation, use the :ref:`tox_docs` tox environment. 21 | 22 | Live Documentation 23 | ================== 24 | 25 | To serve the documentation locally, use the :ref:`tox_docs-livehtml` tox environment. 26 | 27 | Generate API Documentation 28 | ========================== 29 | 30 | To generate the API documentation, use the :ref:`tox_docs-apidoc` tox environment. 31 | 32 | Linting the documentation 33 | ========================= 34 | 35 | To lint the documentation, use the :ref:`tox_lint` tox environment, or these Makefile commands:: 36 | 37 | make -C ./docs apicheck 38 | make -C ./docs linkcheck 39 | make -C ./docs configcheck 40 | 41 | Makefile 42 | ======== 43 | 44 | The docs are managed using the following Makefile: 45 | 46 | .. literalinclude:: ../../docs/Makefile 47 | :language: make 48 | :caption: docs.Makefile 49 | :start-after: .PHONY: help 50 | :end-before: .PHONY: clean 51 | -------------------------------------------------------------------------------- /docs/getting-started/help.rst: -------------------------------------------------------------------------------- 1 | .. _help: 2 | 3 | ====== 4 | Help 5 | ====== 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | .. contents:: 11 | :local: 12 | :depth: 2 13 | 14 | Bug tracker 15 | =========== 16 | 17 | If you have any suggestions, bug reports, or annoyances please report them 18 | to our `issue tracker `_. 19 | 20 | Community support 21 | ================= 22 | 23 | Please use the GitHub discussion forum for general questions, discussions and 24 | community support at https://github.com/celery/pytest-celery/discussions 25 | 26 | Contributing 27 | ============ 28 | 29 | Development of pytest-celery happens at GitHub: https://github.com/celery/pytest-celery 30 | 31 | You're highly encouraged to participate in the development of pytest-celery. 32 | Be sure to also read the :ref:`contributing` section in the documentation. 33 | 34 | For development setup instructions, see :ref:`devguide`. 35 | 36 | License 37 | ======= 38 | 39 | .. literalinclude:: ../../LICENSE 40 | :language: text 41 | :caption: pytest-celery/LICENSE 42 | -------------------------------------------------------------------------------- /docs/getting-started/index.rst: -------------------------------------------------------------------------------- 1 | .. _getting-started: 2 | 3 | ================= 4 | Getting Started 5 | ================= 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | introduction 14 | first-steps 15 | next-steps 16 | vendors 17 | help 18 | -------------------------------------------------------------------------------- /docs/glossary.rst: -------------------------------------------------------------------------------- 1 | .. _glossary: 2 | 3 | Glossary 4 | ======== 5 | 6 | .. glossary:: 7 | :sorted: 8 | 9 | tbd 10 | To be defined. 11 | 12 | container 13 | A container in the context of pytest-celery is a low level implementation of a Celery architecture 14 | component. 15 | 16 | node 17 | A node in the context of pytest-celery is a high level implementation of a Celery architecture 18 | component. 19 | 20 | component 21 | Celery architecture component that is defined using a container, a node, APIs & pytest fixtures. 22 | It is a collective name for all of the parts that compose a component, according to the pytest-celery 23 | design. 24 | 25 | vendor 26 | Independent built-in components provided by the plugin. Vendors can be used as-is, 27 | reconfigured, extended or overridden completely by the user. 28 | -------------------------------------------------------------------------------- /docs/images/celery_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/docs/images/celery_512.png -------------------------------------------------------------------------------- /docs/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/docs/images/favicon.ico -------------------------------------------------------------------------------- /docs/includes/installation.txt: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | The **pytest-celery** plugin can be easily installed via the Python Package Index (PyPI) using :command:`pip`. 5 | 6 | Installing the pytest-celery package 7 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | To install the latest version of **pytest-celery**, run the following command: 10 | 11 | .. code-block:: console 12 | 13 | pip install -U pytest-celery 14 | 15 | This command installs **pytest-celery** along with its required dependencies. 16 | 17 | This will include: 18 | 19 | - Latest version of :pypi:`celery `. 20 | - RabbitMQ broker via :pypi:`kombu `, installed as a dependency of Celery. 21 | 22 | Installing pytest-celery vendors 23 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | 25 | The plugin detects which vendor dependencies are installed in the test environment to configure 26 | the default configurations automatically. This means that by just installing the matching dependencies, 27 | the plugin will allow extending the default configurations, up to the supported built-in :ref:`vendors`. 28 | 29 | .. warning:: 30 | 31 | If you don't install any vendor (e.g. no extras and no manual installation), the plugin will result in an 32 | empty :ref:`setup-matrix` and might not be fully functional. 33 | 34 | To install the vendors, you may either install all of the dependencies manually, or use the following extras: 35 | 36 | - ``all``: Installs all vendors. 37 | - ``redis``: Installs Redis vendor, providing **broker** and **result backend** components. 38 | - ``memcached``: Installs Memcached vendor, providing a **result backend** component. 39 | - ``sqs``: Installs Localstack vendor, providing an **SQS broker** component. 40 | 41 | The following extra is installed by default: 42 | 43 | - ``rabbitmq``: Installs RabbitMQ vendor, providing a **broker** component. 44 | 45 | To install **pytest-celery** with the built-in :ref:`vendors`, replace ```` with the name of the vendor. 46 | 47 | .. code-block:: console 48 | 49 | pip install -U "pytest-celery[]" 50 | 51 | RabbitMQ & Redis combo 52 | ---------------------- 53 | 54 | .. code-block:: console 55 | 56 | pip install -U "pytest-celery[redis]" 57 | 58 | This will configure the plugin to generate all possible setups using only RabbitMQ and Redis vendors. 59 | 60 | SQS & Redis combo 61 | ----------------- 62 | 63 | .. code-block:: console 64 | 65 | pip install -U "pytest-celery[redis,sqs]" 66 | 67 | This will configure the plugin to generate all possible setups using only Localstack and Redis vendors. 68 | 69 | All vendors 70 | ----------- 71 | 72 | .. code-block:: console 73 | 74 | pip install -U "pytest-celery[all]" 75 | 76 | This will configure the plugin to generate all possible setups. 77 | 78 | This approach allows you to tailor the installation to your project's specific needs by including only the necessary optional vendors. 79 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Official pytest plugin for Celery 3 | =================================== 4 | 5 | Welcome to :pypi:`pytest-celery `, the official pytest plugin for Celery. 6 | 7 | The pytest-celery plugin introduces significant enhancements with the introduction of 8 | version >= 1.0.0, shifting towards a Docker-based approach for smoke and production-like testing. 9 | While the `celery.contrib.pytest` API continues to support detailed integration 10 | and unit testing, the new Docker-based methodology is tailored for testing in 11 | environments that closely mirror production settings. 12 | 13 | Adopting version >= 1.0.0 enriches your testing suite with these new capabilities 14 | without affecting your existing tests, allowing for a smooth upgrade path. 15 | The documentation here will navigate you through utilizing the Docker-based approach. 16 | For information on the `celery.contrib.pytest` API for integration and unit testing, 17 | please refer to the `official documentation`_. 18 | 19 | .. _`official documentation`: https://docs.celeryproject.org/en/latest/userguide/testing.html 20 | 21 | The pytest-celery plugin is Open Source and licensed under the `BSD License`_. 22 | 23 | .. _`BSD License`: https://www.opensource.org/license/BSD-3-Clause 24 | 25 | .. image:: https://opencollective.com/static/images/opencollectivelogo-footer-n.svg 26 | :target: https://opencollective.com/celery 27 | :alt: Open Collective logo 28 | :width: 240px 29 | 30 | `Open Collective `_ is our community-powered funding platform that fuels Celery's 31 | ongoing development. Your sponsorship directly supports improvements, maintenance, and innovative features that keep 32 | Celery robust and reliable. 33 | 34 | Getting Started 35 | =============== 36 | 37 | - If you're new to pytest-celery you can get started by following the :ref:`getting-started` tutorial. 38 | - You can also check out the :ref:`FAQ `. 39 | 40 | Contents 41 | ======== 42 | 43 | .. toctree:: 44 | :maxdepth: 1 45 | 46 | copyright 47 | 48 | .. toctree:: 49 | :maxdepth: 2 50 | 51 | getting-started/index 52 | userguide/index 53 | devguide/index 54 | 55 | .. toctree:: 56 | :maxdepth: 1 57 | 58 | reference/index 59 | contributing 60 | faq 61 | changelog 62 | glossary 63 | 64 | Indices and tables 65 | ================== 66 | 67 | * :ref:`genindex` 68 | * :ref:`modindex` 69 | * :ref:`search` 70 | -------------------------------------------------------------------------------- /docs/reference/index.rst: -------------------------------------------------------------------------------- 1 | .. _apiref: 2 | 3 | API Documentation 4 | ================= 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | 9 | pytest_celery 10 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.api.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.api package 2 | ========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pytest\_celery.api.backend module 8 | --------------------------------- 9 | 10 | .. automodule:: pytest_celery.api.backend 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pytest\_celery.api.base module 16 | ------------------------------ 17 | 18 | .. automodule:: pytest_celery.api.base 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pytest\_celery.api.broker module 24 | -------------------------------- 25 | 26 | .. automodule:: pytest_celery.api.broker 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pytest\_celery.api.container module 32 | ----------------------------------- 33 | 34 | .. automodule:: pytest_celery.api.container 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pytest\_celery.api.setup module 40 | ------------------------------- 41 | 42 | .. automodule:: pytest_celery.api.setup 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | pytest\_celery.api.worker module 48 | -------------------------------- 49 | 50 | .. automodule:: pytest_celery.api.worker 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | Module contents 56 | --------------- 57 | 58 | .. automodule:: pytest_celery.api 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.fixtures.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.fixtures package 2 | =============================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pytest\_celery.fixtures.backend module 8 | -------------------------------------- 9 | 10 | .. automodule:: pytest_celery.fixtures.backend 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pytest\_celery.fixtures.broker module 16 | ------------------------------------- 17 | 18 | .. automodule:: pytest_celery.fixtures.broker 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pytest\_celery.fixtures.setup module 24 | ------------------------------------ 25 | 26 | .. automodule:: pytest_celery.fixtures.setup 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pytest\_celery.fixtures.worker module 32 | ------------------------------------- 33 | 34 | .. automodule:: pytest_celery.fixtures.worker 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: pytest_celery.fixtures 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery package 2 | ====================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | pytest_celery.api 11 | pytest_celery.fixtures 12 | pytest_celery.vendors 13 | 14 | Submodules 15 | ---------- 16 | 17 | pytest\_celery.defaults module 18 | ------------------------------ 19 | 20 | .. automodule:: pytest_celery.defaults 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | 25 | pytest\_celery.plugin module 26 | ---------------------------- 27 | 28 | .. automodule:: pytest_celery.plugin 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | Module contents 34 | --------------- 35 | 36 | .. automodule:: pytest_celery 37 | :members: 38 | :undoc-members: 39 | :show-inheritance: 40 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.localstack.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors.localstack package 2 | ========================================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | pytest\_celery.vendors.localstack.api module 8 | -------------------------------------------- 9 | 10 | .. automodule:: pytest_celery.vendors.localstack.api 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pytest\_celery.vendors.localstack.container module 16 | -------------------------------------------------- 17 | 18 | .. automodule:: pytest_celery.vendors.localstack.container 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pytest\_celery.vendors.localstack.defaults module 24 | ------------------------------------------------- 25 | 26 | .. automodule:: pytest_celery.vendors.localstack.defaults 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pytest\_celery.vendors.localstack.fixtures module 32 | ------------------------------------------------- 33 | 34 | .. automodule:: pytest_celery.vendors.localstack.fixtures 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: pytest_celery.vendors.localstack 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.memcached.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors.memcached package 2 | ======================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pytest\_celery.vendors.memcached.api module 8 | ------------------------------------------- 9 | 10 | .. automodule:: pytest_celery.vendors.memcached.api 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pytest\_celery.vendors.memcached.container module 16 | ------------------------------------------------- 17 | 18 | .. automodule:: pytest_celery.vendors.memcached.container 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pytest\_celery.vendors.memcached.defaults module 24 | ------------------------------------------------ 25 | 26 | .. automodule:: pytest_celery.vendors.memcached.defaults 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pytest\_celery.vendors.memcached.fixtures module 32 | ------------------------------------------------ 33 | 34 | .. automodule:: pytest_celery.vendors.memcached.fixtures 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: pytest_celery.vendors.memcached 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.rabbitmq.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors.rabbitmq package 2 | ======================================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | pytest\_celery.vendors.rabbitmq.api module 8 | ------------------------------------------ 9 | 10 | .. automodule:: pytest_celery.vendors.rabbitmq.api 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pytest\_celery.vendors.rabbitmq.container module 16 | ------------------------------------------------ 17 | 18 | .. automodule:: pytest_celery.vendors.rabbitmq.container 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pytest\_celery.vendors.rabbitmq.defaults module 24 | ----------------------------------------------- 25 | 26 | .. automodule:: pytest_celery.vendors.rabbitmq.defaults 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pytest\_celery.vendors.rabbitmq.fixtures module 32 | ----------------------------------------------- 33 | 34 | .. automodule:: pytest_celery.vendors.rabbitmq.fixtures 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: pytest_celery.vendors.rabbitmq 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.redis.backend.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors.redis.backend package 2 | ============================================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | pytest\_celery.vendors.redis.backend.api module 8 | ----------------------------------------------- 9 | 10 | .. automodule:: pytest_celery.vendors.redis.backend.api 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pytest\_celery.vendors.redis.backend.defaults module 16 | ---------------------------------------------------- 17 | 18 | .. automodule:: pytest_celery.vendors.redis.backend.defaults 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pytest\_celery.vendors.redis.backend.fixtures module 24 | ---------------------------------------------------- 25 | 26 | .. automodule:: pytest_celery.vendors.redis.backend.fixtures 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Module contents 32 | --------------- 33 | 34 | .. automodule:: pytest_celery.vendors.redis.backend 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.redis.broker.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors.redis.broker package 2 | =========================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pytest\_celery.vendors.redis.broker.api module 8 | ---------------------------------------------- 9 | 10 | .. automodule:: pytest_celery.vendors.redis.broker.api 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pytest\_celery.vendors.redis.broker.defaults module 16 | --------------------------------------------------- 17 | 18 | .. automodule:: pytest_celery.vendors.redis.broker.defaults 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pytest\_celery.vendors.redis.broker.fixtures module 24 | --------------------------------------------------- 25 | 26 | .. automodule:: pytest_celery.vendors.redis.broker.fixtures 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Module contents 32 | --------------- 33 | 34 | .. automodule:: pytest_celery.vendors.redis.broker 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.redis.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors.redis package 2 | ==================================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | pytest_celery.vendors.redis.backend 11 | pytest_celery.vendors.redis.broker 12 | 13 | Submodules 14 | ---------- 15 | 16 | pytest\_celery.vendors.redis.container module 17 | --------------------------------------------- 18 | 19 | .. automodule:: pytest_celery.vendors.redis.container 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | pytest\_celery.vendors.redis.defaults module 25 | -------------------------------------------- 26 | 27 | .. automodule:: pytest_celery.vendors.redis.defaults 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: pytest_celery.vendors.redis 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors package 2 | ============================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | pytest_celery.vendors.localstack 11 | pytest_celery.vendors.memcached 12 | pytest_celery.vendors.rabbitmq 13 | pytest_celery.vendors.redis 14 | pytest_celery.vendors.worker 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: pytest_celery.vendors 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.worker.content.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors.worker.content package 2 | ============================================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | pytest\_celery.vendors.worker.content.app module 8 | ------------------------------------------------ 9 | 10 | .. automodule:: pytest_celery.vendors.worker.content.app 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pytest\_celery.vendors.worker.content.utils module 16 | -------------------------------------------------- 17 | 18 | .. automodule:: pytest_celery.vendors.worker.content.utils 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: pytest_celery.vendors.worker.content 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/reference/pytest_celery.vendors.worker.rst: -------------------------------------------------------------------------------- 1 | pytest\_celery.vendors.worker package 2 | ===================================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | pytest_celery.vendors.worker.content 11 | 12 | Submodules 13 | ---------- 14 | 15 | pytest\_celery.vendors.worker.container module 16 | ---------------------------------------------- 17 | 18 | .. automodule:: pytest_celery.vendors.worker.container 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pytest\_celery.vendors.worker.defaults module 24 | --------------------------------------------- 25 | 26 | .. automodule:: pytest_celery.vendors.worker.defaults 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pytest\_celery.vendors.worker.fixtures module 32 | --------------------------------------------- 33 | 34 | .. automodule:: pytest_celery.vendors.worker.fixtures 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pytest\_celery.vendors.worker.tasks module 40 | ------------------------------------------ 41 | 42 | .. automodule:: pytest_celery.vendors.worker.tasks 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | pytest\_celery.vendors.worker.volume module 48 | ------------------------------------------- 49 | 50 | .. automodule:: pytest_celery.vendors.worker.volume 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | Module contents 56 | --------------- 57 | 58 | .. automodule:: pytest_celery.vendors.worker 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | -------------------------------------------------------------------------------- /docs/userguide/app-conf.rst: -------------------------------------------------------------------------------- 1 | .. _app-conf: 2 | 3 | ======================================= 4 | How to prepare the Celery application 5 | ======================================= 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | The plugin is designed to allow preparing a Celery_ app object that will be applied 11 | to the worker container for each test case. It is useful for configuring the worker 12 | using the standard Celery_ API and uses the pytest fixtures mechanism to allow controlling 13 | the worker configuration pipeline. 14 | 15 | This guide will teach you how to utilize this mechanism to control the worker configuration 16 | for your test cases. 17 | 18 | .. _Celery: https://docs.celeryq.dev/en/stable/reference/celery.html#celery.Celery 19 | 20 | .. contents:: 21 | :local: 22 | :depth: 2 23 | 24 | .. note:: 25 | 26 | If you already understand how the initialization pipeline works, you can skip to the 27 | :ref:`worker-app-configuration` section. 28 | 29 | .. include:: ../includes/worker-breakdown.txt 30 | 31 | .. _worker-app-configuration: 32 | 33 | Worker App Configuration 34 | ======================== 35 | 36 | .. versionadded:: 1.0.0 37 | 38 | To configure the worker app, use the :func:`default_worker_app ` fixture. 39 | 40 | .. code-block:: python 41 | 42 | @pytest.fixture 43 | def default_worker_app(default_worker_app: Celery) -> Celery: 44 | app = default_worker_app 45 | # configure the app here 46 | return app 47 | 48 | Modular Configuration 49 | ~~~~~~~~~~~~~~~~~~~~~ 50 | 51 | The worker app instance can be configured differently for each test case using the 52 | `Fixture availability `_ feature of pytest. 53 | 54 | For example, 55 | 56 | .. code-block:: python 57 | 58 | @pytest.fixture 59 | def default_worker_app(default_worker_app: Celery) -> Celery: 60 | app = default_worker_app 61 | app.conf.A = X 62 | return app 63 | 64 | 65 | class test_example: 66 | @pytest.fixture 67 | def default_worker_app(self, default_worker_app: Celery) -> Celery: 68 | app = default_worker_app 69 | # app.conf.A is already set to X 70 | app.conf.B = Y 71 | return app 72 | 73 | def test_worker_app(self, celery_setup: CeleryTestSetup): 74 | assert celery_setup.app.conf.A == X 75 | assert celery_setup.app.conf.B == Y 76 | 77 | .. warning:: 78 | 79 | The ``default_worker_app`` fixture is called before the worker container 80 | is created so using it in a test case will not change the worker's initialization 81 | pipeline as it is already completed by the time the test case is executed. 82 | -------------------------------------------------------------------------------- /docs/userguide/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | ========== 4 | Examples 5 | ========== 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | :Release: |version| 11 | :Date: |today| 12 | 13 | Every example is an independent project and is tested via the 14 | `CI system `_. 15 | 16 | .. toctree:: 17 | :maxdepth: 1 18 | 19 | myworker 20 | worker_pool 21 | rabbitmq_management 22 | range 23 | myutils 24 | vhost 25 | django 26 | hybrid_setup 27 | -------------------------------------------------------------------------------- /docs/userguide/examples/worker_pool.rst: -------------------------------------------------------------------------------- 1 | .. _examples_worker_pool: 2 | 3 | ============= 4 | worker_pool 5 | ============= 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | .. contents:: 11 | :local: 12 | :depth: 2 13 | 14 | Description 15 | =========== 16 | 17 | This example project demonstrates how to use a different `worker pool `_. 18 | The example uses two different methods to run the Celery worker with different pools. 19 | 20 | The following guide will explain each method and how they are used. 21 | 22 | .. tip:: 23 | 24 | See first the :ref:`examples_myworker` example before continuing with this one. 25 | 26 | Breakdown 27 | ========= 28 | 29 | File Structure 30 | ~~~~~~~~~~~~~~ 31 | 32 | The following diagram lists the relevant files in the project. 33 | 34 | .. code-block:: text 35 | 36 | rabbitmq_management/ 37 | ├── tests/ 38 | │ ├── __init__.py 39 | │ └── test_gevent_pool.py 40 | │ └── test_solo_pool.py 41 | └── Dockerfile 42 | └── tasks.py 43 | └── requirements.txt 44 | 45 | Dockerfile 46 | ~~~~~~~~~~ 47 | 48 | To use the gevent pool, we create our own image using a similar Dockerfile to the one in the :ref:`examples_myworker` example. 49 | The purpose of this worker is to ensure the gevent dependency is installed. 50 | 51 | .. literalinclude:: ../../../examples/worker_pool/Dockerfile 52 | :language: docker 53 | :caption: examples.worker_pool.Dockerfile 54 | 55 | .. literalinclude:: ../../../examples/worker_pool/requirements.txt 56 | :language: text 57 | :caption: examples.worker_pool.requirements.txt 58 | 59 | tasks.py 60 | ~~~~~~~~ 61 | 62 | Our tasks module is using the example task from the `Celery gevent example `_. 63 | 64 | .. literalinclude:: ../../../examples/worker_pool/tasks.py 65 | :language: python 66 | :caption: examples.worker_pool.tasks.py 67 | 68 | .. _test_gevent_pool: 69 | 70 | test_gevent_pool.py 71 | ~~~~~~~~~~~~~~~~~~~ 72 | 73 | To add a new gevent worker, we create a new :class:`CeleryWorkerContainer ` to 74 | configure the worker with the gevent pool. 75 | 76 | .. literalinclude:: ../../../examples/worker_pool/tests/test_gevent_pool.py 77 | :language: python 78 | :caption: examples.worker_pool.tests.test_gevent_pool.py 79 | :end-before: # ---------------------------- 80 | 81 | And then we can just use it in our tests. 82 | 83 | .. literalinclude:: ../../../examples/worker_pool/tests/test_gevent_pool.py 84 | :language: python 85 | :caption: examples.worker_pool.tests.test_gevent_pool.py 86 | :start-after: # ---------------------------- 87 | 88 | test_solo_pool.py 89 | ~~~~~~~~~~~~~~~~~ 90 | 91 | The solo pool example on the other hand, reconfigures the default :ref:`built-in-worker` 92 | as it does not require any additional dependencies. 93 | 94 | .. literalinclude:: ../../../examples/worker_pool/tests/test_solo_pool.py 95 | :language: python 96 | :caption: examples.worker_pool.tests.test_solo_pool.py 97 | -------------------------------------------------------------------------------- /docs/userguide/index.rst: -------------------------------------------------------------------------------- 1 | .. _userguide: 2 | 3 | ============ 4 | User Guide 5 | ============ 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | The following sections will enhance your understanding of the pytest-celery plugin and help you to use it effectively. 11 | It is recommended to first review the :ref:`first-steps` and :ref:`next-steps` sections to get comfortable with the basics 12 | of the plugin before diving into the advanced features. 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | 17 | advanced-installation 18 | setup-matrix 19 | app-conf 20 | utils-module 21 | tasks 22 | default-tasks 23 | signals 24 | celery-bug-report 25 | examples/index 26 | resources/index 27 | -------------------------------------------------------------------------------- /docs/userguide/resources/index.rst: -------------------------------------------------------------------------------- 1 | .. _resources: 2 | 3 | ================== 4 | Useful Resources 5 | ================== 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | This section contains additional resources that may be useful when developing tests with :pypi:`pytest `, 11 | :pypi:`pytest-celery `, and general :pypi:`celery ` related testing articles, guides and tutorials. 12 | 13 | **You're welcome to contribute to this list!** 14 | 15 | .. contents:: 16 | :local: 17 | :depth: 2 18 | 19 | Other Plugins 20 | ~~~~~~~~~~~~~ 21 | 22 | - :pypi:`pytest-rerunfailures `: Do to the natural sensitivity of simulating Celery environments over Docker, it's 23 | common to have flaky tests due to failing docker resources. This plugin provide useful features to rerun failed tests, skipping tests that 24 | failed due to actual assertion errors. 25 | -------------------------------------------------------------------------------- /docs/userguide/signals.rst: -------------------------------------------------------------------------------- 1 | .. _injecting-signals-handlers: 2 | 3 | ================================ 4 | How to connect signal handlers 5 | ================================ 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | Signal handlers may be defined in the publisher or the consumer side or both. When done 11 | on the publisher side, they can be connected inside the scope of the test function using the 12 | `standard Celery API `_. When done on the 13 | consumer side, they can be connected using injected signal handlers modules, which we'll cover in this guide. 14 | 15 | The plugin uses its :ref:`code-generation` mechanism to inject signal handlers modules into the worker 16 | container. The available signal handlers can be configured differently for each test case using the 17 | `Fixture availability `_ feature of pytest. 18 | 19 | This guide will teach you how to utilize this mechanism to connect signal handlers to your Celery workers in your test cases. 20 | 21 | .. contents:: 22 | :local: 23 | :depth: 2 24 | 25 | .. note:: 26 | 27 | If you already understand how the initialization pipeline works, you can skip to the 28 | :ref:`signal-handlers-modules-injection` section. 29 | 30 | .. include:: ../includes/worker-breakdown.txt 31 | 32 | .. _signal-handlers-modules-injection: 33 | 34 | Signal handlers modules injection 35 | ================================= 36 | 37 | .. versionadded:: 1.0.0 38 | 39 | To add your own signal handlers, use the :func:`default_worker_signals ` fixture. 40 | 41 | .. code-block:: python 42 | 43 | @pytest.fixture 44 | def default_worker_signals(default_worker_signals: set) -> set: 45 | from tests import signals 46 | 47 | default_worker_signals.add(signals) 48 | return default_worker_signals 49 | 50 | For example, we can review the plugin's tests to see how the signal handlers are connected. 51 | 52 | signals.py 53 | ~~~~~~~~~~ 54 | 55 | This module contain our signal handlers which we want to connect on the consumer side. 56 | 57 | .. literalinclude:: ../../tests/smoke/signals.py 58 | :language: python 59 | :caption: tests.smoke.signals 60 | :start-after: from __future__ import annotations 61 | 62 | test_signals.py 63 | ~~~~~~~~~~~~~~~ 64 | 65 | These tests demonstrate how to query the output of the signal handlers that were 66 | injected into the worker container alongside inline signal handlers connected on the publisher side. 67 | 68 | .. literalinclude:: ../../tests/smoke/test_signals.py 69 | :language: python 70 | :caption: tests.smoke.test_signals 71 | :start-after: class test_signals 72 | -------------------------------------------------------------------------------- /docs/userguide/tasks.rst: -------------------------------------------------------------------------------- 1 | .. _injecting-tasks: 2 | 3 | ================== 4 | How to add tasks 5 | ================== 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | The plugin uses its :ref:`code-generation` mechanism to inject tasks modules into the worker 11 | container. The available tasks can be configured differently for each test case using the 12 | `Fixture availability `_ feature of pytest. 13 | 14 | This guide will teach you how to utilize this mechanism to add tasks for your Celery workers in your test cases. 15 | 16 | .. contents:: 17 | :local: 18 | :depth: 2 19 | 20 | .. note:: 21 | 22 | If you already understand how the initialization pipeline works, you can skip to the 23 | :ref:`tasks-modules-injection` section. 24 | 25 | .. include:: ../includes/worker-breakdown.txt 26 | 27 | .. _tasks-modules-injection: 28 | 29 | Tasks modules injection 30 | ======================= 31 | 32 | .. versionadded:: 1.0.0 33 | 34 | To add you own tasks, use the :func:`default_worker_tasks ` fixture. 35 | 36 | .. code-block:: python 37 | 38 | @pytest.fixture 39 | def default_worker_tasks(default_worker_tasks: set) -> set: 40 | from tests import tasks 41 | 42 | default_worker_tasks.add(tasks) 43 | return default_worker_tasks 44 | -------------------------------------------------------------------------------- /docs/userguide/utils-module.rst: -------------------------------------------------------------------------------- 1 | .. _utils-module: 2 | 3 | ========================================== 4 | How to inject your own utility functions 5 | ========================================== 6 | 7 | :Release: |version| 8 | :Date: |today| 9 | 10 | The plugin injects a special ``utils.py`` module into the worker component to provide 11 | enhanced testing capabilities over the Celery worker component. The module contains API that is accessible using the 12 | :class:`CeleryTestWorker API `. 13 | 14 | This guide will teach you how to inject your own utility functions into the worker component 15 | using this mechanism. 16 | 17 | .. contents:: 18 | :local: 19 | :depth: 2 20 | 21 | .. note:: 22 | 23 | If you already understand how the initialization pipeline works, you can skip to the 24 | :ref:`custom-utility-functions` section. 25 | 26 | .. include:: ../includes/worker-breakdown.txt 27 | 28 | .. _custom-utility-functions: 29 | 30 | Custom Utility Functions 31 | ======================== 32 | 33 | .. versionadded:: 1.0.0 34 | 35 | To configure your own module, use the :func:`default_worker_utils_module ` fixture. 36 | 37 | .. code-block:: python 38 | 39 | @pytest.fixture 40 | def default_worker_utils_module() -> ModuleType: 41 | from tests import myutils 42 | 43 | return myutils 44 | 45 | This will inject the ``myutils`` module into the worker component, **instead of the default module**, 46 | allowing you to access your own utility functions. 47 | 48 | .. warning:: 49 | 50 | The module must provide all of the existing API in the ``utils.py`` module, otherwise 51 | the worker component will not function correctly (when based off of 52 | :class:`CeleryTestWorker `). 53 | 54 | For reference, the default ``utils.py`` module is defined as follows: 55 | 56 | .. literalinclude:: ../../src/pytest_celery/vendors/worker/content/utils.py 57 | :language: python 58 | :caption: pytest_celery.vendors.worker.content.utils.py 59 | 60 | .. tip:: 61 | 62 | Check out the :ref:`examples_myutils` example for a demonstration of how to use this feature. 63 | -------------------------------------------------------------------------------- /examples/django/demoapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/django/demoapp/__init__.py -------------------------------------------------------------------------------- /examples/django/demoapp/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-24 21:37 2 | 3 | from django.db import migrations 4 | from django.db import models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Widget", 15 | fields=[ 16 | ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), 17 | ("name", models.CharField(max_length=140)), 18 | ], 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /examples/django/demoapp/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/django/demoapp/migrations/__init__.py -------------------------------------------------------------------------------- /examples/django/demoapp/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Widget(models.Model): 5 | name = models.CharField(max_length=140) 6 | -------------------------------------------------------------------------------- /examples/django/demoapp/tasks.py: -------------------------------------------------------------------------------- 1 | # Create your tasks here 2 | 3 | from celery import shared_task 4 | 5 | from .models import Widget 6 | 7 | 8 | @shared_task 9 | def add(x, y): 10 | return x + y 11 | 12 | 13 | @shared_task 14 | def mul(x, y): 15 | return x * y 16 | 17 | 18 | @shared_task 19 | def xsum(numbers): 20 | return sum(numbers) 21 | 22 | 23 | @shared_task 24 | def count_widgets(): 25 | return Widget.objects.count() 26 | 27 | 28 | @shared_task 29 | def rename_widget(widget_id, name): 30 | w = Widget.objects.get(id=widget_id) 31 | w.name = name 32 | w.save() 33 | -------------------------------------------------------------------------------- /examples/django/demoapp/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /examples/django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | if __name__ == "__main__": 7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "proj.settings") 8 | 9 | from django.core.management import execute_from_command_line 10 | 11 | execute_from_command_line(sys.argv) 12 | -------------------------------------------------------------------------------- /examples/django/proj/__init__.py: -------------------------------------------------------------------------------- 1 | # This will make sure the app is always imported when 2 | # Django starts so that shared_task will use this app. 3 | from .celery import app as celery_app 4 | 5 | __all__ = ("celery_app",) 6 | -------------------------------------------------------------------------------- /examples/django/proj/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | 5 | # Set the default Django settings module for the 'celery' program. 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "proj.settings") 7 | 8 | app = Celery("proj") 9 | 10 | # Using a string here means the worker doesn't have to serialize 11 | # the configuration object to child processes. 12 | # - namespace='CELERY' means all celery-related configuration keys 13 | # should have a `CELERY_` prefix. 14 | app.config_from_object("django.conf:settings", namespace="CELERY") 15 | 16 | # Load task modules from all registered Django apps. 17 | app.autodiscover_tasks() 18 | 19 | 20 | @app.task(bind=True, ignore_result=True) 21 | def debug_task(self): 22 | print(f"Request: {self.request!r}") 23 | -------------------------------------------------------------------------------- /examples/django/proj/urls.py: -------------------------------------------------------------------------------- 1 | # Uncomment the next two lines to enable the admin: 2 | # from django.contrib import admin 3 | # admin.autodiscover() 4 | 5 | urlpatterns = [ 6 | # Examples: 7 | # url(r'^$', 'proj.views.home', name='home'), 8 | # url(r'^proj/', include('proj.foo.urls')), 9 | # Uncomment the admin/doc line below to enable admin documentation: 10 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 11 | # Uncomment the next line to enable the admin: 12 | # url(r'^admin/', include(admin.site.urls)), 13 | ] 14 | -------------------------------------------------------------------------------- /examples/django/proj/wsgi.py: -------------------------------------------------------------------------------- 1 | """WSGI config for proj project. 2 | 3 | This module contains the WSGI application used by Django's development server 4 | and any production WSGI deployments. It should expose a module-level variable 5 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 6 | this application via the ``WSGI_APPLICATION`` setting. 7 | 8 | Usually you will have the standard Django WSGI application here, but it also 9 | might make sense to replace the whole Django WSGI application with a custom one 10 | that later delegates to the Django one. For example, you could introduce WSGI 11 | middleware here, or combine a Django application with an application of another 12 | framework. 13 | """ 14 | 15 | import os 16 | 17 | # This application object is used by any WSGI server configured to use this 18 | # file. This includes Django's development server, if the WSGI_APPLICATION 19 | # setting points here. 20 | from django.core.wsgi import get_wsgi_application 21 | 22 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "proj.settings") 23 | 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /examples/django/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = 'proj.settings' 3 | 4 | log_cli = true 5 | log_cli_level = INFO 6 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) 7 | log_cli_date_format = %Y-%m-%d %H:%M:%S 8 | -------------------------------------------------------------------------------- /examples/django/requirements.txt: -------------------------------------------------------------------------------- 1 | sqlalchemy>=1.2.18 2 | django>=2.2.1 3 | pytest-django>=4.7.0 4 | pytest-xdist>=3.5.0 5 | pytest-rerunfailures>=14.0 6 | pytest-celery[all]@git+https://github.com/celery/pytest-celery.git 7 | -------------------------------------------------------------------------------- /examples/django/tests/DjangoWorker.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-bookworm 2 | 3 | # Create a user to run the worker 4 | RUN adduser --disabled-password --gecos "" test_user 5 | 6 | # Install system dependencies 7 | RUN apt-get update && apt-get install -y build-essential git 8 | 9 | # Set arguments 10 | ARG CELERY_LOG_LEVEL=INFO 11 | ARG CELERY_WORKER_NAME=celery_dev_worker 12 | ARG CELERY_WORKER_QUEUE=celery 13 | ENV LOG_LEVEL=$CELERY_LOG_LEVEL 14 | ENV WORKER_NAME=$CELERY_WORKER_NAME 15 | ENV WORKER_QUEUE=$CELERY_WORKER_QUEUE 16 | 17 | EXPOSE 5678 18 | 19 | # Install packages 20 | WORKDIR /src 21 | 22 | COPY --chown=test_user:test_user requirements.txt . 23 | RUN pip install --no-cache-dir --upgrade pip 24 | RUN pip install -r ./requirements.txt 25 | 26 | # Switch to the test_user 27 | USER test_user 28 | 29 | # Start the celery worker 30 | CMD celery -A proj worker --loglevel=$LOG_LEVEL -n $WORKER_NAME@%h -Q $WORKER_QUEUE 31 | -------------------------------------------------------------------------------- /examples/django/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/django/tests/__init__.py -------------------------------------------------------------------------------- /examples/django/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import Any 5 | 6 | import celery 7 | import pytest 8 | from pytest_docker_tools import build 9 | from pytest_docker_tools import container 10 | from pytest_docker_tools import fxtr 11 | 12 | from pytest_celery import CeleryWorkerContainer 13 | from pytest_celery import defaults 14 | 15 | 16 | class DjangoWorkerContainer(CeleryWorkerContainer): 17 | @property 18 | def client(self) -> Any: 19 | return self 20 | 21 | @classmethod 22 | def version(cls) -> str: 23 | return celery.__version__ 24 | 25 | @classmethod 26 | def log_level(cls) -> str: 27 | return "INFO" 28 | 29 | @classmethod 30 | def worker_name(cls) -> str: 31 | return "django_tests_worker" 32 | 33 | @classmethod 34 | def worker_queue(cls) -> str: 35 | return "celery" 36 | 37 | 38 | worker_image = build( 39 | path=".", 40 | dockerfile="tests/DjangoWorker.Dockerfile", 41 | tag="pytest-celery/examples/django:example", 42 | buildargs=DjangoWorkerContainer.buildargs(), 43 | ) 44 | 45 | 46 | default_worker_container = container( 47 | image="{worker_image.id}", 48 | ports=fxtr("default_worker_ports"), 49 | environment=fxtr("default_worker_env"), 50 | network="{default_pytest_celery_network.name}", 51 | volumes={ 52 | # Volume: Worker /app 53 | "{default_worker_volume.name}": defaults.DEFAULT_WORKER_VOLUME, 54 | # Mount: source 55 | os.path.abspath(os.getcwd()): { 56 | "bind": "/src", 57 | "mode": "rw", 58 | }, 59 | }, 60 | wrapper_class=DjangoWorkerContainer, 61 | timeout=defaults.DEFAULT_WORKER_CONTAINER_TIMEOUT, 62 | ) 63 | 64 | 65 | @pytest.fixture 66 | def default_worker_container_cls() -> type[CeleryWorkerContainer]: 67 | return DjangoWorkerContainer 68 | 69 | 70 | @pytest.fixture(scope="session") 71 | def default_worker_container_session_cls() -> type[CeleryWorkerContainer]: 72 | return DjangoWorkerContainer 73 | -------------------------------------------------------------------------------- /examples/django/tests/test_tasks.py: -------------------------------------------------------------------------------- 1 | from demoapp.tasks import add 2 | from demoapp.tasks import count_widgets 3 | 4 | 5 | def test_add(celery_setup): 6 | assert add.s(1, 2).delay().get() == 3 7 | 8 | 9 | def test_count_widgets(celery_setup): 10 | assert count_widgets.s().delay().get() == 0 11 | -------------------------------------------------------------------------------- /examples/hybrid_setup/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = true 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format = %Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /examples/hybrid_setup/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=7.4.4 2 | pytest-xdist>=3.5.0 3 | pytest-subtests>=0.11.0 4 | pytest-rerunfailures>=14.0 5 | celery[gevent] 6 | pytest-celery[all]@git+https://github.com/celery/pytest-celery.git 7 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/hybrid_setup/tests/__init__.py -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | import pytest 4 | from pytest_docker_tools import network 5 | 6 | from pytest_celery import CeleryBackendCluster 7 | from pytest_celery import CeleryBrokerCluster 8 | from pytest_celery import CeleryTestWorker 9 | from pytest_celery import CeleryWorkerCluster 10 | from pytest_celery import MemcachedTestBackend 11 | from tests.vendors.memcached import * 12 | from tests.vendors.rabbitmq import * 13 | from tests.vendors.workers.gevent import * 14 | from tests.vendors.workers.legacy import * 15 | 16 | # ---------------------------- 17 | 18 | hybrid_setup_example_network = network(scope="session") 19 | 20 | 21 | @pytest.fixture 22 | def celery_broker_cluster( 23 | session_rabbitmq_broker: RabbitMQTestBroker, 24 | session_failover_broker: RabbitMQTestBroker, 25 | ) -> CeleryBrokerCluster: 26 | """This is like setting broker_url to 27 | "session_rabbitmq_broker;session_failover_broker".""" 28 | cluster = CeleryBrokerCluster( 29 | session_rabbitmq_broker, 30 | session_failover_broker, 31 | ) 32 | yield cluster 33 | cluster.teardown() 34 | 35 | 36 | @pytest.fixture 37 | def celery_backend_cluster(session_memcached_backend: MemcachedTestBackend) -> CeleryBackendCluster: 38 | cluster = CeleryBackendCluster(session_memcached_backend) 39 | yield cluster 40 | cluster.teardown() 41 | 42 | 43 | @pytest.fixture 44 | def celery_worker_cluster( 45 | gevent_worker: CeleryTestWorker, 46 | legacy_worker: CeleryTestWorker, 47 | ) -> CeleryWorkerCluster: 48 | cluster = CeleryWorkerCluster(gevent_worker, legacy_worker) 49 | yield cluster 50 | cluster.teardown() 51 | 52 | 53 | @pytest.fixture 54 | def default_worker_tasks(default_worker_tasks: set) -> set: 55 | from tests.vendors.workers import tasks 56 | 57 | default_worker_tasks.add(tasks) 58 | return default_worker_tasks 59 | 60 | 61 | @pytest.fixture 62 | def default_worker_signals(default_worker_signals: set) -> set: 63 | from tests.vendors.workers import signals 64 | 65 | default_worker_signals.add(signals) 66 | return default_worker_signals 67 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/test_hybrid_setup.py: -------------------------------------------------------------------------------- 1 | from pytest_subtests import SubTests 2 | 3 | from pytest_celery import RESULT_TIMEOUT 4 | from pytest_celery import CeleryTestSetup 5 | from pytest_celery import CeleryTestWorker 6 | from pytest_celery import RabbitMQTestBroker 7 | from pytest_celery import ping 8 | from tests.vendors.workers.tasks import job 9 | 10 | 11 | class TestHybridSetupExample: 12 | def test_ping(self, celery_setup: CeleryTestSetup): 13 | assert ping.s().delay().get(timeout=RESULT_TIMEOUT) == "pong" 14 | 15 | def test_job(self, celery_setup: CeleryTestSetup): 16 | assert job.s().delay().get(timeout=RESULT_TIMEOUT) == "Done!" 17 | 18 | def test_signal(self, celery_setup: CeleryTestSetup): 19 | celery_setup.worker.assert_log_exists("Worker init handler called!") 20 | 21 | def test_failover( 22 | self, 23 | celery_setup: CeleryTestSetup, 24 | gevent_worker: CeleryTestWorker, 25 | legacy_worker: CeleryTestWorker, 26 | session_failover_broker: RabbitMQTestBroker, 27 | subtests: SubTests, 28 | ): 29 | with subtests.test(msg="Kill the main broker"): 30 | celery_setup.broker.kill() 31 | 32 | with subtests.test(msg="Manually assert the workers"): 33 | gevent_worker.assert_log_exists("Will retry using next failover.") 34 | legacy_worker.assert_log_exists("Will retry using next failover.") 35 | 36 | with subtests.test(msg="Use the celery setup to assert the workers"): 37 | worker: CeleryTestWorker 38 | for worker in celery_setup.worker_cluster: 39 | log = f"Connected to amqp://guest:**@{session_failover_broker.hostname()}:5672//" 40 | worker.assert_log_exists(log) 41 | 42 | with subtests.test(msg="Verify that the workers are still working (publish tasks)"): 43 | assert job.s().delay().get(timeout=RESULT_TIMEOUT) == "Done!" 44 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/hybrid_setup/tests/vendors/__init__.py -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/memcached.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pytest_docker_tools import container 3 | from pytest_docker_tools import fetch 4 | 5 | from pytest_celery import MEMCACHED_CONTAINER_TIMEOUT 6 | from pytest_celery import MEMCACHED_ENV 7 | from pytest_celery import MEMCACHED_IMAGE 8 | from pytest_celery import MEMCACHED_PORTS 9 | from pytest_celery import MemcachedContainer 10 | from pytest_celery import MemcachedTestBackend 11 | 12 | memcached_image = fetch(repository=MEMCACHED_IMAGE) 13 | memcached_test_container = container( 14 | # name="Memcached-Session-Backend", # Optional | Incompatible with parallel execution 15 | image="{memcached_image.id}", 16 | scope="session", 17 | ports=MEMCACHED_PORTS, 18 | environment=MEMCACHED_ENV, 19 | network="{hybrid_setup_example_network.name}", 20 | wrapper_class=MemcachedContainer, 21 | timeout=MEMCACHED_CONTAINER_TIMEOUT, 22 | ) 23 | 24 | 25 | @pytest.fixture 26 | def session_memcached_backend(memcached_test_container: MemcachedContainer) -> MemcachedTestBackend: 27 | backend = MemcachedTestBackend(memcached_test_container) 28 | yield backend 29 | backend.teardown() 30 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/rabbitmq.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pytest_docker_tools import container 3 | from pytest_docker_tools import fetch 4 | 5 | from pytest_celery import RABBITMQ_CONTAINER_TIMEOUT 6 | from pytest_celery import RABBITMQ_ENV 7 | from pytest_celery import RABBITMQ_IMAGE 8 | from pytest_celery import RABBITMQ_PORTS 9 | from pytest_celery import RabbitMQContainer 10 | from pytest_celery import RabbitMQTestBroker 11 | 12 | rabbitmq_image = fetch(repository=RABBITMQ_IMAGE) 13 | 14 | rabbitmq_test_container = container( 15 | # name="Main RabbitMQ Broker (session)", # Optional | Incompatible with parallel execution 16 | image="{rabbitmq_image.id}", 17 | scope="session", 18 | ports=RABBITMQ_PORTS, 19 | environment=RABBITMQ_ENV, 20 | network="{hybrid_setup_example_network.name}", 21 | wrapper_class=RabbitMQContainer, 22 | timeout=RABBITMQ_CONTAINER_TIMEOUT, 23 | ) 24 | 25 | 26 | @pytest.fixture 27 | def session_rabbitmq_broker(rabbitmq_test_container: RabbitMQContainer) -> RabbitMQTestBroker: 28 | broker = RabbitMQTestBroker(rabbitmq_test_container) 29 | yield broker 30 | broker.teardown() 31 | 32 | 33 | failover_test_container = container( 34 | # name="Failover RabbitMQ Broker (session)", # Optional | Incompatible with parallel execution 35 | image="{rabbitmq_image.id}", 36 | scope="session", 37 | ports=RABBITMQ_PORTS, 38 | environment=RABBITMQ_ENV, 39 | network="{hybrid_setup_example_network.name}", 40 | wrapper_class=RabbitMQContainer, 41 | timeout=RABBITMQ_CONTAINER_TIMEOUT, 42 | ) 43 | 44 | 45 | @pytest.fixture 46 | def session_failover_broker(failover_test_container: RabbitMQContainer) -> RabbitMQTestBroker: 47 | broker = RabbitMQTestBroker(failover_test_container) 48 | yield broker 49 | broker.teardown() 50 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/workers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/hybrid_setup/tests/vendors/workers/__init__.py -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/workers/gevent.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-bookworm 2 | 3 | # Create a user to run the worker 4 | RUN adduser --disabled-password --gecos "" test_user 5 | 6 | # Install system dependencies 7 | RUN apt-get update && apt-get install -y build-essential git libevent-dev 8 | 9 | # Set arguments 10 | ARG CELERY_LOG_LEVEL=INFO 11 | ARG CELERY_WORKER_NAME=my_worker 12 | ARG CELERY_WORKER_QUEUE=celery 13 | ENV LOG_LEVEL=$CELERY_LOG_LEVEL 14 | ENV WORKER_NAME=$CELERY_WORKER_NAME 15 | ENV WORKER_QUEUE=$CELERY_WORKER_QUEUE 16 | 17 | # Install packages 18 | RUN pip install --no-cache-dir --upgrade pip 19 | RUN pip install "celery[gevent]" "pytest-celery[all]==1.0.0" 20 | 21 | # The workdir must be /app 22 | WORKDIR /app 23 | 24 | # Switch to the test_user 25 | USER test_user 26 | 27 | # Start the celery worker 28 | CMD celery -A app worker --loglevel=$LOG_LEVEL -n $WORKER_NAME@%h -Q $WORKER_QUEUE 29 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/workers/gevent.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from celery import Celery 3 | from pytest_docker_tools import build 4 | from pytest_docker_tools import container 5 | from pytest_docker_tools import fxtr 6 | 7 | from pytest_celery import CeleryTestWorker 8 | from pytest_celery import CeleryWorkerContainer 9 | from pytest_celery import defaults 10 | 11 | 12 | class GeventWorkerContainer(CeleryWorkerContainer): 13 | @classmethod 14 | def command(cls, *args: str) -> list[str]: 15 | return super().command("-P", "gevent", "-c", "1000") 16 | 17 | 18 | gevent_worker_image = build( 19 | path=".", 20 | dockerfile="tests/vendors/workers/gevent.Dockerfile", 21 | tag="pytest-celery/examples/hybrid_setup:gevent", 22 | buildargs=GeventWorkerContainer.buildargs(), 23 | ) 24 | 25 | 26 | gevent_worker_container = container( 27 | image="{gevent_worker_image.id}", 28 | environment=fxtr("default_worker_env"), 29 | network="{hybrid_setup_example_network.name}", 30 | volumes={"{default_worker_volume.name}": defaults.DEFAULT_WORKER_VOLUME}, 31 | wrapper_class=GeventWorkerContainer, 32 | timeout=defaults.DEFAULT_WORKER_CONTAINER_TIMEOUT, 33 | command=GeventWorkerContainer.command(), 34 | ) 35 | 36 | 37 | @pytest.fixture 38 | def gevent_worker(gevent_worker_container: GeventWorkerContainer, celery_setup_app: Celery) -> CeleryTestWorker: 39 | worker = CeleryTestWorker(gevent_worker_container, app=celery_setup_app) 40 | yield worker 41 | worker.teardown() 42 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/workers/legacy.Dockerfile: -------------------------------------------------------------------------------- 1 | # Celery 4 does not support >3.10-bookworm 2 | FROM python:3.10-bookworm 3 | 4 | # Create a user to run the worker 5 | RUN adduser --disabled-password --gecos "" test_user 6 | 7 | # Install system dependencies 8 | RUN apt-get update && apt-get install -y build-essential git 9 | 10 | # Set arguments 11 | ARG CELERY_LOG_LEVEL=INFO 12 | ARG CELERY_WORKER_NAME=my_worker 13 | ARG CELERY_WORKER_QUEUE=celery 14 | ENV LOG_LEVEL=$CELERY_LOG_LEVEL 15 | ENV WORKER_NAME=$CELERY_WORKER_NAME 16 | ENV WORKER_QUEUE=$CELERY_WORKER_QUEUE 17 | 18 | # Install packages 19 | RUN pip install --no-cache-dir --upgrade pip \ 20 | celery==4.4.7 "pytest-celery[all]==1.0.0" 21 | 22 | # The workdir must be /app 23 | WORKDIR /app 24 | 25 | # Switch to the test_user 26 | USER test_user 27 | 28 | # Start the celery worker 29 | CMD celery -A app worker --loglevel=$LOG_LEVEL -n $WORKER_NAME@%h -Q $WORKER_QUEUE 30 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/workers/legacy.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from celery import Celery 3 | from pytest_docker_tools import build 4 | from pytest_docker_tools import container 5 | from pytest_docker_tools import fxtr 6 | 7 | from pytest_celery import CeleryTestWorker 8 | from pytest_celery import CeleryWorkerContainer 9 | from pytest_celery import defaults 10 | 11 | 12 | class LegacyWorkerContainer(CeleryWorkerContainer): 13 | @classmethod 14 | def version(cls) -> str: 15 | return "4.4.7" 16 | 17 | @classmethod 18 | def worker_queue(cls) -> str: 19 | return "legacy" 20 | 21 | 22 | legacy_worker_image = build( 23 | path=".", 24 | dockerfile="tests/vendors/workers/legacy.Dockerfile", 25 | tag="pytest-celery/examples/hybrid_setup:legacy", 26 | buildargs=LegacyWorkerContainer.buildargs(), 27 | ) 28 | 29 | 30 | legacy_worker_container = container( 31 | image="{legacy_worker_image.id}", 32 | environment=fxtr("default_worker_env"), 33 | network="{hybrid_setup_example_network.name}", 34 | volumes={"{default_worker_volume.name}": defaults.DEFAULT_WORKER_VOLUME}, 35 | wrapper_class=LegacyWorkerContainer, 36 | timeout=defaults.DEFAULT_WORKER_CONTAINER_TIMEOUT, 37 | command=LegacyWorkerContainer.command(), 38 | ) 39 | 40 | 41 | @pytest.fixture 42 | def legacy_worker(legacy_worker_container: LegacyWorkerContainer, celery_setup_app: Celery) -> CeleryTestWorker: 43 | worker = CeleryTestWorker(legacy_worker_container, app=celery_setup_app) 44 | yield worker 45 | worker.teardown() 46 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/workers/signals.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from celery.signals import worker_init 4 | 5 | 6 | @worker_init.connect 7 | def worker_init_handler(sender, **kwargs): 8 | print("Worker init handler called!") 9 | -------------------------------------------------------------------------------- /examples/hybrid_setup/tests/vendors/workers/tasks.py: -------------------------------------------------------------------------------- 1 | import celery.utils 2 | from celery import shared_task 3 | from celery.canvas import group 4 | 5 | from pytest_celery import RESULT_TIMEOUT 6 | 7 | 8 | @shared_task 9 | def noop(*args, **kwargs) -> None: 10 | return celery.utils.noop(*args, **kwargs) 11 | 12 | 13 | @shared_task 14 | def identity(x): 15 | return x 16 | 17 | 18 | @shared_task 19 | def job() -> str: 20 | canvas = ( 21 | group( 22 | identity.si("Hello, "), 23 | identity.si("world!"), 24 | ) 25 | | noop.s().set(queue="legacy") 26 | | identity.si("Done!") 27 | ) 28 | return canvas.delay().get(timeout=RESULT_TIMEOUT) 29 | -------------------------------------------------------------------------------- /examples/myutils/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = true 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format = %Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /examples/myutils/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=7.4.4 2 | pytest-xdist>=3.5.0 3 | pytest-rerunfailures>=14.0 4 | pytest-celery[all]@git+https://github.com/celery/pytest-celery.git 5 | -------------------------------------------------------------------------------- /examples/myutils/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/myutils/tests/__init__.py -------------------------------------------------------------------------------- /examples/myutils/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from types import ModuleType 2 | 3 | import pytest 4 | 5 | from pytest_celery import CeleryTestWorker 6 | 7 | 8 | @pytest.fixture 9 | def default_worker_utils_module() -> ModuleType: 10 | from tests import myutils 11 | 12 | return myutils 13 | 14 | 15 | class MyWorker(CeleryTestWorker): 16 | 17 | def myfunc(self) -> bool: 18 | exit_code, output = self.container.exec_run( 19 | 'python -c "from utils import myfunc; print(myfunc())"', 20 | ) 21 | if exit_code != 0: 22 | raise RuntimeError(f"Error: {output}") 23 | output = output.decode("utf-8") 24 | return output.strip() 25 | 26 | 27 | @pytest.fixture 28 | def default_worker_cls() -> type[CeleryTestWorker]: 29 | return MyWorker 30 | -------------------------------------------------------------------------------- /examples/myutils/tests/myutils.py: -------------------------------------------------------------------------------- 1 | from pytest_celery.vendors.worker.content.utils import get_running_processes_info # noqa 2 | 3 | 4 | def myfunc(): 5 | return "foo" 6 | -------------------------------------------------------------------------------- /examples/myutils/tests/test_myutils.py: -------------------------------------------------------------------------------- 1 | from pytest_celery import CeleryTestSetup 2 | from tests.conftest import MyWorker 3 | from tests.myutils import myfunc 4 | 5 | 6 | def test_myfunc(): 7 | assert myfunc() == "foo" 8 | 9 | 10 | def test_myfunc_in_worker(celery_worker: MyWorker): 11 | assert celery_worker.myfunc() == "foo" 12 | assert celery_worker.get_running_processes_info() 13 | 14 | 15 | def test_myfunc_in_setup_worker(celery_setup: CeleryTestSetup): 16 | celery_worker: MyWorker = celery_setup.worker 17 | assert celery_worker.myfunc() == "foo" 18 | assert celery_worker.get_running_processes_info() 19 | -------------------------------------------------------------------------------- /examples/myworker/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = true 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format = %Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /examples/myworker/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=7.4.4 2 | pytest-xdist>=3.5.0 3 | pytest-rerunfailures>=14.0 4 | pytest-celery[all]@git+https://github.com/celery/pytest-celery.git 5 | -------------------------------------------------------------------------------- /examples/myworker/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/myworker/tests/__init__.py -------------------------------------------------------------------------------- /examples/myworker/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from tests.myworker.myworker import myworker_container # noqa 2 | from tests.myworker.myworker import myworker_image # noqa 3 | from tests.myworker.myworker import myworker_worker # noqa 4 | -------------------------------------------------------------------------------- /examples/myworker/tests/myworker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-bookworm 2 | 3 | # Create a user to run the worker 4 | RUN adduser --disabled-password --gecos "" test_user 5 | 6 | # Install system dependencies 7 | RUN apt-get update && apt-get install -y build-essential git 8 | 9 | # Set arguments 10 | ARG CELERY_LOG_LEVEL=INFO 11 | ARG CELERY_WORKER_NAME=my_worker 12 | ARG CELERY_WORKER_QUEUE=celery 13 | ENV LOG_LEVEL=$CELERY_LOG_LEVEL 14 | ENV WORKER_NAME=$CELERY_WORKER_NAME 15 | ENV WORKER_QUEUE=$CELERY_WORKER_QUEUE 16 | 17 | EXPOSE 5678 18 | 19 | # Install packages 20 | WORKDIR /src 21 | 22 | COPY --chown=test_user:test_user requirements.txt . 23 | RUN pip install --no-cache-dir --upgrade pip 24 | RUN pip install -r ./requirements.txt 25 | RUN git clone https://github.com/celery/celery.git 26 | 27 | WORKDIR /src/celery 28 | 29 | RUN pip install -e . 30 | 31 | # The workdir must be /app 32 | WORKDIR /app 33 | 34 | # Switch to the test_user 35 | USER test_user 36 | 37 | # Start the celery worker 38 | CMD celery -A app worker --loglevel=$LOG_LEVEL -n $WORKER_NAME@%h -Q $WORKER_QUEUE 39 | -------------------------------------------------------------------------------- /examples/myworker/tests/myworker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/myworker/tests/myworker/__init__.py -------------------------------------------------------------------------------- /examples/myworker/tests/myworker/myworker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | 5 | import pytest 6 | from celery import Celery 7 | from pytest_docker_tools import build 8 | from pytest_docker_tools import container 9 | from pytest_docker_tools import fxtr 10 | 11 | from pytest_celery import CeleryTestWorker 12 | from pytest_celery import CeleryWorkerContainer 13 | from pytest_celery import defaults 14 | 15 | 16 | class MyWorkerContainer(CeleryWorkerContainer): 17 | @property 18 | def client(self) -> Any: 19 | return self 20 | 21 | @classmethod 22 | def version(cls) -> str: 23 | return "Celery main branch" 24 | 25 | @classmethod 26 | def log_level(cls) -> str: 27 | return "INFO" 28 | 29 | @classmethod 30 | def worker_name(cls) -> str: 31 | return "my_worker" 32 | 33 | @classmethod 34 | def worker_queue(cls) -> str: 35 | return "myworker" 36 | 37 | 38 | myworker_image = build( 39 | path=".", 40 | dockerfile="tests/myworker/Dockerfile", 41 | tag="pytest-celery/myworker:example", 42 | buildargs=MyWorkerContainer.buildargs(), 43 | ) 44 | 45 | 46 | myworker_container = container( 47 | image="{myworker_image.id}", 48 | ports=MyWorkerContainer.ports(), 49 | environment=fxtr("default_worker_env"), 50 | network="{default_pytest_celery_network.name}", 51 | volumes={"{default_worker_volume.name}": defaults.DEFAULT_WORKER_VOLUME}, 52 | wrapper_class=MyWorkerContainer, 53 | timeout=defaults.DEFAULT_WORKER_CONTAINER_TIMEOUT, 54 | command=MyWorkerContainer.command(), 55 | ) 56 | 57 | 58 | @pytest.fixture 59 | def myworker_worker(myworker_container: MyWorkerContainer, celery_setup_app: Celery) -> CeleryTestWorker: 60 | worker = CeleryTestWorker(myworker_container, app=celery_setup_app) 61 | yield worker 62 | worker.teardown() 63 | -------------------------------------------------------------------------------- /examples/myworker/tests/test_myworker.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from celery.canvas import Signature 3 | from celery.result import AsyncResult 4 | 5 | from pytest_celery import RESULT_TIMEOUT 6 | from pytest_celery import CeleryTestSetup 7 | from pytest_celery import CeleryTestWorker 8 | from pytest_celery import CeleryWorkerCluster 9 | from pytest_celery import ping 10 | 11 | 12 | @pytest.fixture 13 | def celery_worker_cluster( 14 | celery_worker: CeleryTestWorker, 15 | myworker_worker: CeleryTestWorker, 16 | ) -> CeleryWorkerCluster: 17 | """Add myworker worker to the workers cluster alongside the parametrize 18 | plugin worker.""" 19 | cluster = CeleryWorkerCluster(celery_worker, myworker_worker) # type: ignore 20 | yield cluster 21 | cluster.teardown() 22 | 23 | 24 | def test_ping(celery_setup: CeleryTestSetup): 25 | """Test ping task for each worker node.""" 26 | worker: CeleryTestWorker 27 | for worker in celery_setup.worker_cluster: 28 | sig: Signature = ping.s() 29 | res: AsyncResult = sig.apply_async(queue=worker.worker_queue) 30 | assert res.get(timeout=RESULT_TIMEOUT) == "pong" 31 | -------------------------------------------------------------------------------- /examples/rabbitmq_management/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = true 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format = %Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /examples/rabbitmq_management/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=7.4.4 2 | pytest-xdist>=3.5.0 3 | pytest-rerunfailures>=14.0 4 | pytest-celery[all]@git+https://github.com/celery/pytest-celery.git 5 | -------------------------------------------------------------------------------- /examples/rabbitmq_management/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/rabbitmq_management/tests/__init__.py -------------------------------------------------------------------------------- /examples/rabbitmq_management/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pytest_celery import RABBITMQ_PORTS 4 | from pytest_celery import CeleryBrokerCluster 5 | from pytest_celery import RabbitMQContainer 6 | from pytest_celery import RabbitMQTestBroker 7 | 8 | 9 | @pytest.fixture 10 | def default_rabbitmq_broker_image() -> str: 11 | return "rabbitmq:management" 12 | 13 | 14 | @pytest.fixture 15 | def default_rabbitmq_broker_ports() -> dict: 16 | # Expose the management UI port 17 | ports = RABBITMQ_PORTS.copy() 18 | ports.update({"15672/tcp": None}) 19 | return ports 20 | 21 | 22 | class RabbitMQManagementTestBroker(RabbitMQTestBroker): 23 | def get_management_url(self) -> str: 24 | ip = self.container.attrs["NetworkSettings"]["Ports"]["15672/tcp"][0]["HostIp"] 25 | port = self.container.attrs["NetworkSettings"]["Ports"]["15672/tcp"][0]["HostPort"] 26 | # Opening this link during debugging allows you to see the RabbitMQ management UI 27 | # in your browser 28 | return f"http://{ip}:{port}" 29 | 30 | 31 | @pytest.fixture 32 | def celery_rabbitmq_broker(default_rabbitmq_broker: RabbitMQContainer) -> RabbitMQTestBroker: 33 | broker = RabbitMQManagementTestBroker(default_rabbitmq_broker) 34 | yield broker 35 | broker.teardown() 36 | 37 | 38 | @pytest.fixture 39 | def celery_broker_cluster(celery_rabbitmq_broker: RabbitMQTestBroker) -> CeleryBrokerCluster: # type: ignore 40 | cluster = CeleryBrokerCluster(celery_rabbitmq_broker) # type: ignore 41 | yield cluster 42 | cluster.teardown() 43 | -------------------------------------------------------------------------------- /examples/rabbitmq_management/tests/test_management_broker.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests.auth import HTTPBasicAuth 3 | 4 | from pytest_celery import CeleryTestSetup 5 | from tests.conftest import RabbitMQManagementTestBroker 6 | 7 | 8 | def test_login_to_broker_alone(celery_rabbitmq_broker: RabbitMQManagementTestBroker): 9 | api = celery_rabbitmq_broker.get_management_url() + "/api/whoami" 10 | response = requests.get(api, auth=HTTPBasicAuth("guest", "guest")) 11 | assert response.status_code == 200 12 | assert response.json()["name"] == "guest" 13 | assert response.json()["tags"] == ["administrator"] 14 | 15 | 16 | def test_broker_in_setup(celery_setup: CeleryTestSetup): 17 | assert isinstance(celery_setup.broker, RabbitMQManagementTestBroker) 18 | api = celery_setup.broker.get_management_url() + "/api/queues" 19 | response = requests.get(api, auth=HTTPBasicAuth("guest", "guest")) 20 | assert response.status_code == 200 21 | res = response.json() 22 | assert isinstance(res, list) 23 | assert len(list(filter(lambda queues: celery_setup.worker.hostname() in queues["name"], res))) == 1 24 | -------------------------------------------------------------------------------- /examples/range/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = true 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format = %Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /examples/range/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=7.4.4 2 | pytest-xdist>=3.5.0 3 | pytest-subtests>=0.11.0 4 | pytest-rerunfailures>=14.0 5 | pytest-celery[all]@git+https://github.com/celery/pytest-celery.git 6 | -------------------------------------------------------------------------------- /examples/range/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/range/tests/__init__.py -------------------------------------------------------------------------------- /examples/range/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import requests 4 | from packaging.version import parse as parse_version 5 | 6 | 7 | def get_celery_versions(start_version: str, end_version: str) -> list[str]: 8 | url = "https://pypi.org/pypi/celery/json" 9 | response = requests.get(url) 10 | data = response.json() 11 | all_versions = data["releases"].keys() 12 | 13 | filtered_versions = [ 14 | v 15 | for v in all_versions 16 | if ( 17 | parse_version(start_version) <= parse_version(v) <= parse_version(end_version) 18 | and not parse_version(v).is_prerelease 19 | ) 20 | ] 21 | 22 | return sorted(filtered_versions, key=parse_version) 23 | -------------------------------------------------------------------------------- /examples/range/tests/test_range.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from celery.canvas import Signature 3 | from celery.result import AsyncResult 4 | 5 | from pytest_celery import RESULT_TIMEOUT 6 | from pytest_celery import CeleryTestSetup 7 | from pytest_celery import ping 8 | from tests.conftest import get_celery_versions 9 | 10 | 11 | class TestRange: 12 | @pytest.fixture(scope="session", params=get_celery_versions("v4.4.7", "v5.0.0")) 13 | def default_worker_celery_version(self, request: pytest.FixtureRequest) -> str: 14 | return request.param 15 | 16 | def test_ping(self, celery_setup: CeleryTestSetup, default_worker_celery_version: str): 17 | sig: Signature = ping.s() 18 | res: AsyncResult = sig.apply_async() 19 | assert res.get(timeout=RESULT_TIMEOUT) == "pong" 20 | -------------------------------------------------------------------------------- /examples/vhost/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = true 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format = %Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /examples/vhost/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=7.4.4 2 | pytest-xdist>=3.5.0 3 | pytest-rerunfailures>=14.0 4 | pytest-celery[all]@git+https://github.com/celery/pytest-celery.git 5 | -------------------------------------------------------------------------------- /examples/vhost/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/vhost/tests/__init__.py -------------------------------------------------------------------------------- /examples/vhost/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pytest_docker_tools import container 3 | from pytest_docker_tools import fetch 4 | 5 | from pytest_celery import REDIS_CONTAINER_TIMEOUT 6 | from pytest_celery import REDIS_ENV 7 | from pytest_celery import REDIS_IMAGE 8 | from pytest_celery import REDIS_PORTS 9 | from pytest_celery import CeleryBackendCluster 10 | from pytest_celery import CeleryBrokerCluster 11 | from pytest_celery import RedisContainer 12 | from pytest_celery import RedisTestBackend 13 | from pytest_celery import RedisTestBroker 14 | 15 | redis_image = fetch(repository=REDIS_IMAGE) 16 | redis_test_container: RedisContainer = container( 17 | image="{redis_image.id}", 18 | ports=REDIS_PORTS, 19 | environment=REDIS_ENV, 20 | network="{default_pytest_celery_network.name}", 21 | wrapper_class=RedisContainer, 22 | timeout=REDIS_CONTAINER_TIMEOUT, 23 | ) 24 | 25 | 26 | @pytest.fixture 27 | def redis_broker(redis_test_container: RedisContainer) -> RedisTestBroker: 28 | broker = RedisTestBroker(redis_test_container) 29 | yield broker 30 | broker.teardown() 31 | 32 | 33 | @pytest.fixture 34 | def celery_broker_cluster(redis_broker: RedisTestBroker) -> CeleryBrokerCluster: 35 | cluster = CeleryBrokerCluster(redis_broker) 36 | yield cluster 37 | cluster.teardown() 38 | 39 | 40 | class MyRedisTestBackend(RedisTestBackend): 41 | def config(self, *args: tuple, **kwargs: dict) -> dict: 42 | return super().config(vhost=1, *args, **kwargs) 43 | 44 | 45 | @pytest.fixture 46 | def redis_backend(redis_test_container: RedisContainer) -> MyRedisTestBackend: 47 | backend = MyRedisTestBackend(redis_test_container) 48 | yield backend 49 | backend.teardown() 50 | 51 | 52 | @pytest.fixture 53 | def celery_backend_cluster(redis_backend: MyRedisTestBackend) -> CeleryBackendCluster: 54 | cluster = CeleryBackendCluster(redis_backend) 55 | yield cluster 56 | cluster.teardown() 57 | -------------------------------------------------------------------------------- /examples/vhost/tests/test_vhost.py: -------------------------------------------------------------------------------- 1 | from pytest_celery import RESULT_TIMEOUT 2 | from pytest_celery import CeleryTestSetup 3 | from pytest_celery import ping 4 | 5 | 6 | class TestVhost: 7 | def test_ping(self, celery_setup: CeleryTestSetup): 8 | assert ping.s().delay().get(timeout=RESULT_TIMEOUT) == "pong" 9 | 10 | def test_vhost(self, celery_setup: CeleryTestSetup): 11 | assert celery_setup.app.conf.broker_url[:-1] == celery_setup.app.conf.result_backend[:-1] 12 | assert celery_setup.app.conf.broker_url.endswith("/0") 13 | assert celery_setup.app.conf.result_backend.endswith("/1") 14 | 15 | def test_single_redis(self, celery_setup: CeleryTestSetup): 16 | assert celery_setup.broker.container.id == celery_setup.backend.container.id 17 | -------------------------------------------------------------------------------- /examples/worker_pool/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-bookworm 2 | 3 | # Create a user to run the worker 4 | RUN adduser --disabled-password --gecos "" test_user 5 | 6 | # Install system dependencies 7 | RUN apt-get update && apt-get install -y build-essential git libevent-dev 8 | 9 | # Set arguments 10 | ARG CELERY_LOG_LEVEL=INFO 11 | ARG CELERY_WORKER_NAME=my_worker 12 | ARG CELERY_WORKER_QUEUE=celery 13 | ENV LOG_LEVEL=$CELERY_LOG_LEVEL 14 | ENV WORKER_NAME=$CELERY_WORKER_NAME 15 | ENV WORKER_QUEUE=$CELERY_WORKER_QUEUE 16 | 17 | EXPOSE 5678 18 | 19 | # Install packages 20 | COPY --chown=test_user:test_user requirements.txt . 21 | RUN pip install --no-cache-dir --upgrade pip 22 | RUN pip install -r ./requirements.txt 23 | 24 | # The workdir must be /app 25 | WORKDIR /app 26 | 27 | # Switch to the test_user 28 | USER test_user 29 | 30 | # Start the celery worker 31 | CMD celery -A app worker --loglevel=$LOG_LEVEL -n $WORKER_NAME@%h -Q $WORKER_QUEUE 32 | -------------------------------------------------------------------------------- /examples/worker_pool/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = true 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format = %Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /examples/worker_pool/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=7.4.4 2 | pytest-xdist>=3.5.0 3 | pytest-subtests>=0.11.0 4 | pytest-rerunfailures>=14.0 5 | celery[gevent] 6 | pytest-celery[all]@git+https://github.com/celery/pytest-celery.git 7 | -------------------------------------------------------------------------------- /examples/worker_pool/tasks.py: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/celery/celery/blob/main/examples/gevent/tasks.py 2 | 3 | import requests 4 | from celery import shared_task 5 | 6 | 7 | @shared_task(ignore_result=True) 8 | def urlopen(url): 9 | print(f"Opening: {url}") 10 | try: 11 | requests.get(url) 12 | except requests.exceptions.RequestException as exc: 13 | print(f"Exception for {url}: {exc!r}") 14 | return url, 0 15 | print(f"Done with: {url}") 16 | return url, 1 17 | -------------------------------------------------------------------------------- /examples/worker_pool/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/examples/worker_pool/tests/__init__.py -------------------------------------------------------------------------------- /examples/worker_pool/tests/test_gevent_pool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | import tasks 5 | from celery import Celery 6 | from celery.canvas import Signature 7 | from celery.canvas import group 8 | from celery.result import AsyncResult 9 | from pytest_docker_tools import build 10 | from pytest_docker_tools import container 11 | from pytest_docker_tools import fxtr 12 | 13 | from pytest_celery import RESULT_TIMEOUT 14 | from pytest_celery import CeleryTestSetup 15 | from pytest_celery import CeleryTestWorker 16 | from pytest_celery import CeleryWorkerCluster 17 | from pytest_celery import CeleryWorkerContainer 18 | from pytest_celery import defaults 19 | from pytest_celery import ping 20 | 21 | 22 | class GeventWorkerContainer(CeleryWorkerContainer): 23 | @classmethod 24 | def command(cls, *args: str) -> list[str]: 25 | return super().command("-P", "gevent", "-c", "1000") 26 | 27 | 28 | gevent_worker_image = build( 29 | path=".", 30 | dockerfile="Dockerfile", 31 | tag="pytest-celery/examples/worker_pool:gevent", 32 | buildargs=GeventWorkerContainer.buildargs(), 33 | ) 34 | 35 | 36 | gevent_worker_container = container( 37 | image="{gevent_worker_image.id}", 38 | ports=fxtr("default_worker_ports"), 39 | environment=fxtr("default_worker_env"), 40 | network="{default_pytest_celery_network.name}", 41 | volumes={"{default_worker_volume.name}": defaults.DEFAULT_WORKER_VOLUME}, 42 | wrapper_class=GeventWorkerContainer, 43 | timeout=defaults.DEFAULT_WORKER_CONTAINER_TIMEOUT, 44 | command=GeventWorkerContainer.command(), 45 | ) 46 | 47 | 48 | @pytest.fixture 49 | def gevent_worker(gevent_worker_container: GeventWorkerContainer, celery_setup_app: Celery) -> CeleryTestWorker: 50 | worker = CeleryTestWorker(gevent_worker_container, app=celery_setup_app) 51 | yield worker 52 | worker.teardown() 53 | 54 | 55 | @pytest.fixture 56 | def celery_worker_cluster(gevent_worker: CeleryTestWorker) -> CeleryWorkerCluster: 57 | cluster = CeleryWorkerCluster(gevent_worker) 58 | yield cluster 59 | cluster.teardown() 60 | 61 | 62 | @pytest.fixture 63 | def default_worker_tasks(default_worker_tasks: set) -> set: 64 | default_worker_tasks.add(tasks) 65 | return default_worker_tasks 66 | 67 | 68 | # ---------------------------- 69 | 70 | 71 | class TestGeventPool: 72 | def test_celery_banner(self, gevent_worker: CeleryTestWorker): 73 | gevent_worker.assert_log_exists("concurrency: 1000 (gevent)") 74 | 75 | def test_ping(self, celery_setup: CeleryTestSetup): 76 | sig: Signature = ping.s() 77 | res: AsyncResult = sig.apply_async() 78 | assert res.get(timeout=RESULT_TIMEOUT) == "pong" 79 | 80 | def test_celery_gevent_example(self, celery_setup: CeleryTestSetup): 81 | """Based on https://github.com/celery/celery/tree/main/examples/gevent""" 82 | LIST_OF_URLS = [ 83 | "https://github.com/celery", 84 | "https://github.com/celery/celery", 85 | "https://github.com/celery/pytest-celery", 86 | ] 87 | group(tasks.urlopen.s(url) for url in LIST_OF_URLS).apply_async() 88 | celery_setup.worker.assert_log_does_not_exist("Exception for") 89 | -------------------------------------------------------------------------------- /examples/worker_pool/tests/test_solo_pool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | from celery.canvas import Signature 5 | from celery.result import AsyncResult 6 | 7 | from pytest_celery import RESULT_TIMEOUT 8 | from pytest_celery import CeleryTestSetup 9 | from pytest_celery import CeleryTestWorker 10 | from pytest_celery import CeleryWorkerContainer 11 | from pytest_celery import ping 12 | 13 | 14 | class SoloPoolWorker(CeleryWorkerContainer): 15 | @classmethod 16 | def command(cls, *args: str) -> list[str]: 17 | return super().command("-P", "solo") 18 | 19 | 20 | @pytest.fixture 21 | def default_worker_container_cls() -> type[CeleryWorkerContainer]: 22 | return SoloPoolWorker 23 | 24 | 25 | @pytest.fixture(scope="session") 26 | def default_worker_container_session_cls() -> type[CeleryWorkerContainer]: 27 | return SoloPoolWorker 28 | 29 | 30 | class TestSoloPool: 31 | def test_celery_banner(self, celery_worker: CeleryTestWorker): 32 | celery_worker.assert_log_exists("solo") 33 | 34 | def test_ping(self, celery_setup: CeleryTestSetup): 35 | sig: Signature = ping.s() 36 | res: AsyncResult = sig.apply_async() 37 | assert res.get(timeout=RESULT_TIMEOUT) == "pong" 38 | -------------------------------------------------------------------------------- /src/pytest_celery/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/src/pytest_celery/api/__init__.py -------------------------------------------------------------------------------- /src/pytest_celery/api/backend.py: -------------------------------------------------------------------------------- 1 | """Backend components represents Celery's result backend instances. 2 | 3 | This module provides the base API for creating new backend components by 4 | defining the base classes for backend nodes and clusters. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from pytest_celery.api.base import CeleryTestCluster 10 | from pytest_celery.api.base import CeleryTestNode 11 | from pytest_celery.defaults import DEFAULT_WORKER_ENV 12 | 13 | 14 | class CeleryTestBackend(CeleryTestNode): 15 | """This is specialized node type for handling celery backends nodes. It is 16 | used to encapsulate a backend instance. 17 | 18 | Responsibility Scope: 19 | Handling backend specific requirements and configuration. 20 | """ 21 | 22 | @classmethod 23 | def default_config(cls) -> dict: 24 | """Default node configurations if not overridden by the user.""" 25 | return { 26 | "url": DEFAULT_WORKER_ENV["CELERY_RESULT_BACKEND"], 27 | "host_url": DEFAULT_WORKER_ENV["CELERY_RESULT_BACKEND"], 28 | } 29 | 30 | def restart(self, reload_container: bool = True, force: bool = False) -> None: 31 | """Override restart method to update the app result backend with new 32 | container values.""" 33 | super().restart(reload_container, force) 34 | if self.app: 35 | self.app.conf.update( 36 | result_backend=self.config()["host_url"], 37 | ) 38 | 39 | 40 | class CeleryBackendCluster(CeleryTestCluster): 41 | """This is a specialized cluster type for handling celery backends. It is 42 | used to define which backend instances are available for the test. 43 | 44 | Responsibility Scope: 45 | Provude useful methods for managing a cluster of celery backends. 46 | """ 47 | 48 | @classmethod 49 | def default_config(cls) -> dict: 50 | """Default cluster configurations if not overridden by the user.""" 51 | return { 52 | "urls": [DEFAULT_WORKER_ENV["CELERY_RESULT_BACKEND"]], 53 | "host_urls": [DEFAULT_WORKER_ENV["CELERY_RESULT_BACKEND"]], 54 | } 55 | -------------------------------------------------------------------------------- /src/pytest_celery/api/broker.py: -------------------------------------------------------------------------------- 1 | """Broker components represents Celery's broker instances. 2 | 3 | This module provides the base API for creating new broker components by 4 | defining the base classes for broker nodes and clusters. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from pytest_celery.api.base import CeleryTestCluster 10 | from pytest_celery.api.base import CeleryTestNode 11 | from pytest_celery.defaults import DEFAULT_WORKER_ENV 12 | 13 | 14 | class CeleryTestBroker(CeleryTestNode): 15 | """This is specialized node type for handling celery brokers nodes. It is 16 | used to encapsulate a broker instance. 17 | 18 | Responsibility Scope: 19 | Handling broker specific requirements and configuration. 20 | """ 21 | 22 | @classmethod 23 | def default_config(cls) -> dict: 24 | """Default node configurations if not overridden by the user.""" 25 | return { 26 | "url": DEFAULT_WORKER_ENV["CELERY_BROKER_URL"], 27 | "host_url": DEFAULT_WORKER_ENV["CELERY_BROKER_URL"], 28 | } 29 | 30 | def restart(self, reload_container: bool = True, force: bool = False) -> None: 31 | """Override restart method to update the app broker url with new 32 | container values.""" 33 | super().restart(reload_container, force) 34 | if self.app: 35 | self.app.conf.update( 36 | broker_url=self.config()["host_url"], 37 | ) 38 | 39 | 40 | class CeleryBrokerCluster(CeleryTestCluster): 41 | """This is a specialized cluster type for handling celery brokers. It is 42 | used to define which broker instances are available for the test. 43 | 44 | Responsibility Scope: 45 | Provude useful methods for managing a cluster of celery brokers. 46 | """ 47 | 48 | @classmethod 49 | def default_config(cls) -> dict: 50 | """Default cluster configurations if not overridden by the user.""" 51 | return { 52 | "urls": [DEFAULT_WORKER_ENV["CELERY_BROKER_URL"]], 53 | "host_urls": [DEFAULT_WORKER_ENV["CELERY_BROKER_URL"]], 54 | } 55 | -------------------------------------------------------------------------------- /src/pytest_celery/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/src/pytest_celery/fixtures/__init__.py -------------------------------------------------------------------------------- /src/pytest_celery/fixtures/backend.py: -------------------------------------------------------------------------------- 1 | """Every backend component is added to the test matrix using the fixtures of 2 | this module. 3 | 4 | These fixtures will configure the test setup for all supported celery 5 | backends by default. Every backend will be executed as a separate test 6 | case, and the test will be executed for each supported celery backend. 7 | 8 | You may override these fixtures to customize the test setup for your 9 | specific needs. 10 | """ 11 | 12 | # mypy: disable-error-code="misc" 13 | 14 | from __future__ import annotations 15 | 16 | import pytest 17 | 18 | from pytest_celery.api.backend import CeleryBackendCluster 19 | from pytest_celery.api.backend import CeleryTestBackend 20 | from pytest_celery.defaults import ALL_CELERY_BACKENDS 21 | from pytest_celery.defaults import CELERY_BACKEND_CLUSTER 22 | 23 | 24 | @pytest.fixture(params=ALL_CELERY_BACKENDS) 25 | def celery_backend(request: pytest.FixtureRequest) -> CeleryTestBackend: # type: ignore 26 | """Parameterized fixture for all supported celery backends. Responsible for 27 | tearing down the node. 28 | 29 | This fixture will add parametrization to the test function, so that 30 | the test will be executed for each supported celery backend. 31 | """ 32 | backend: CeleryTestBackend = request.getfixturevalue(request.param) 33 | yield backend 34 | backend.teardown() 35 | 36 | 37 | @pytest.fixture 38 | def celery_backend_cluster(celery_backend: CeleryTestBackend) -> CeleryBackendCluster: # type: ignore 39 | """Defines the cluster of backend nodes for the test. Responsible for 40 | tearing down the cluster. 41 | 42 | To disable the cluster, override this fixture and return None. 43 | 44 | Args: 45 | celery_backend (CeleryTestBackend): Parameterized fixture for all supported celery backends. 46 | 47 | Returns: 48 | CeleryBackendCluster: Single node cluster for all supported celery backends. 49 | """ 50 | cluster = CeleryBackendCluster(celery_backend) # type: ignore 51 | yield cluster 52 | cluster.teardown() 53 | 54 | 55 | @pytest.fixture 56 | def celery_backend_cluster_config(request: pytest.FixtureRequest) -> dict | None: 57 | """Attempts to compile the celery configuration from the cluster.""" 58 | try: 59 | use_default_config = pytest.fail.Exception 60 | cluster: CeleryBackendCluster = request.getfixturevalue(CELERY_BACKEND_CLUSTER) 61 | return cluster.config() if cluster else None 62 | except use_default_config: 63 | return CeleryBackendCluster.default_config() 64 | -------------------------------------------------------------------------------- /src/pytest_celery/fixtures/broker.py: -------------------------------------------------------------------------------- 1 | """Every broker component is added to the test matrix using the fixtures of 2 | this module. 3 | 4 | These fixtures will configure the test setup for all supported celery 5 | brokers by default. Every broker will be executed as a separate test 6 | case, and the test will be executed for each supported celery broker. 7 | 8 | You may override these fixtures to customize the test setup for your 9 | specific needs. 10 | """ 11 | 12 | # mypy: disable-error-code="misc" 13 | 14 | from __future__ import annotations 15 | 16 | import pytest 17 | 18 | from pytest_celery.api.broker import CeleryBrokerCluster 19 | from pytest_celery.api.broker import CeleryTestBroker 20 | from pytest_celery.defaults import ALL_CELERY_BROKERS 21 | from pytest_celery.defaults import CELERY_BROKER_CLUSTER 22 | 23 | 24 | @pytest.fixture(params=ALL_CELERY_BROKERS) 25 | def celery_broker(request: pytest.FixtureRequest) -> CeleryTestBroker: # type: ignore 26 | """Parameterized fixture for all supported celery brokers. Responsible for 27 | tearing down the node. 28 | 29 | This fixture will add parametrization to the test function, so that 30 | the test will be executed for each supported celery broker. 31 | """ 32 | broker: CeleryTestBroker = request.getfixturevalue(request.param) 33 | yield broker 34 | broker.teardown() 35 | 36 | 37 | @pytest.fixture 38 | def celery_broker_cluster(celery_broker: CeleryTestBroker) -> CeleryBrokerCluster: # type: ignore 39 | """Defines the cluster of broker nodes for the test. Responsible for 40 | tearing down the cluster. 41 | 42 | It is not recommended to disable the broker cluster, but it can be done by 43 | overriding this fixture and returning None. 44 | 45 | Args: 46 | celery_broker (CeleryTestBroker): Parameterized fixture for all supported celery brokers. 47 | 48 | Returns: 49 | CeleryBrokerCluster: Single node cluster for all supported celery brokers. 50 | """ 51 | cluster = CeleryBrokerCluster(celery_broker) # type: ignore 52 | yield cluster 53 | cluster.teardown() 54 | 55 | 56 | @pytest.fixture 57 | def celery_broker_cluster_config(request: pytest.FixtureRequest) -> dict | None: 58 | """Attempts to compile the celery configuration from the cluster.""" 59 | try: 60 | use_default_config = pytest.fail.Exception 61 | cluster: CeleryBrokerCluster = request.getfixturevalue(CELERY_BROKER_CLUSTER) 62 | return cluster.config() if cluster else None 63 | except use_default_config: 64 | return CeleryBrokerCluster.default_config() 65 | -------------------------------------------------------------------------------- /src/pytest_celery/fixtures/worker.py: -------------------------------------------------------------------------------- 1 | """The :ref:`built-in-worker` is added to the test matrix using the fixtures of 2 | this module. 3 | 4 | These fixtures will configure the test setup for the built-in Celery 5 | worker by default. 6 | 7 | You may override these fixtures to customize the test setup for your 8 | specific needs. 9 | """ 10 | 11 | # mypy: disable-error-code="misc" 12 | 13 | from __future__ import annotations 14 | 15 | import pytest 16 | 17 | from pytest_celery.api.worker import CeleryTestWorker 18 | from pytest_celery.api.worker import CeleryWorkerCluster 19 | from pytest_celery.defaults import ALL_CELERY_WORKERS 20 | 21 | 22 | @pytest.fixture(params=ALL_CELERY_WORKERS) 23 | def celery_worker(request: pytest.FixtureRequest) -> CeleryTestWorker: # type: ignore 24 | """Parameterized fixture for all supported celery workers. Responsible for 25 | tearing down the node. 26 | 27 | This fixture will add parametrization to the test function, so that 28 | the test will be executed for each supported celery worker. 29 | """ 30 | 31 | worker: CeleryTestWorker = request.getfixturevalue(request.param) 32 | yield worker 33 | worker.teardown() 34 | 35 | 36 | @pytest.fixture 37 | def celery_worker_cluster(celery_worker: CeleryTestWorker) -> CeleryWorkerCluster: # type: ignore 38 | """Defines the cluster of worker nodes for the test. Responsible for 39 | tearing down the cluster. 40 | 41 | To disable the cluster, override this fixture and return None. 42 | 43 | Args: 44 | celery_worker (CeleryTestWorker): Parameterized fixture for all supported celery workers. 45 | 46 | Returns: 47 | CeleryWorkerCluster: Single node cluster for all supported celery workers. 48 | """ 49 | cluster = CeleryWorkerCluster(celery_worker) # type: ignore 50 | yield cluster 51 | cluster.teardown() 52 | 53 | 54 | @pytest.fixture 55 | def celery_worker_cluster_config(celery_broker_cluster_config: dict, celery_backend_cluster_config: dict) -> dict: 56 | """Combine the broker and backend cluster configurations. 57 | 58 | Additional configuration can be added. 59 | """ 60 | return { 61 | "celery_broker_cluster_config": celery_broker_cluster_config, 62 | "celery_backend_cluster_config": celery_backend_cluster_config, 63 | } 64 | -------------------------------------------------------------------------------- /src/pytest_celery/plugin.py: -------------------------------------------------------------------------------- 1 | """Pytest-celery entry point.""" 2 | 3 | # Backward compatibility with pytest-celery < 1.0.0 4 | from celery.contrib.pytest import * # noqa 5 | 6 | # pytest-celery >= 1.0.0 infrastructure 7 | from pytest_celery import * # noqa 8 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/__init__.py: -------------------------------------------------------------------------------- 1 | """See :ref:`vendors`.""" 2 | 3 | 4 | def _is_vendor_installed(vendor_name: str) -> bool: 5 | """Check if a vendor is installed. 6 | 7 | Args: 8 | vendor_name (str): Vendor package name. 9 | 10 | Returns: 11 | bool: True if the vendor is installed, False otherwise. 12 | """ 13 | 14 | try: 15 | container_module = f"pytest_celery.vendors.{vendor_name}.container" 16 | __import__(container_module) 17 | return True 18 | except ImportError: 19 | return False 20 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/localstack/__init__.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Localstack Broker vendor. 5 | """ 6 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/localstack/api.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Localstack Broker vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from pytest_celery.api.broker import CeleryTestBroker 10 | 11 | 12 | class LocalstackTestBroker(CeleryTestBroker): 13 | pass 14 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/localstack/container.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Localstack Broker vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from kombu import Connection 10 | 11 | from pytest_celery.api.container import CeleryTestContainer 12 | from pytest_celery.vendors.localstack.defaults import LOCALSTACK_ENV 13 | from pytest_celery.vendors.localstack.defaults import LOCALSTACK_IMAGE 14 | from pytest_celery.vendors.localstack.defaults import LOCALSTACK_PORTS 15 | from pytest_celery.vendors.localstack.defaults import LOCALSTACK_PREFIX 16 | 17 | 18 | class LocalstackContainer(CeleryTestContainer): 19 | """This class manages the lifecycle of a Localstack container.""" 20 | 21 | @property 22 | def client(self) -> Connection: 23 | client = Connection( 24 | self.celeryconfig["host_url"], 25 | port=self.celeryconfig["port"], 26 | ) 27 | return client 28 | 29 | @property 30 | def celeryconfig(self) -> dict: 31 | return { 32 | "url": self.url, 33 | "host_url": self.host_url, 34 | "hostname": self.hostname, 35 | "port": self.port, 36 | } 37 | 38 | @property 39 | def url(self) -> str: 40 | return f"{self.prefix()}{self.hostname}:4566" 41 | 42 | @property 43 | def host_url(self) -> str: 44 | return f"{self.prefix()}localhost:{self.port}" 45 | 46 | @property 47 | def hostname(self) -> str: 48 | return self.attrs["Config"]["Hostname"] 49 | 50 | @property 51 | def port(self) -> int: 52 | return self._wait_port("4566/tcp") 53 | 54 | @classmethod 55 | def version(cls) -> str: 56 | return cls.image().split("/")[-1] 57 | 58 | @classmethod 59 | def initial_env(cls) -> dict: 60 | return LOCALSTACK_ENV 61 | 62 | @classmethod 63 | def image(cls) -> str: 64 | return LOCALSTACK_IMAGE 65 | 66 | @classmethod 67 | def ports(cls) -> dict: 68 | return LOCALSTACK_PORTS 69 | 70 | @classmethod 71 | def prefix(cls) -> str: 72 | return LOCALSTACK_PREFIX 73 | 74 | @property 75 | def ready_prompt(self) -> str | None: 76 | return "Ready." 77 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/localstack/defaults.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Localstack Broker vendor. 5 | """ 6 | 7 | CELERY_LOCALSTACK_BROKER = "celery_localstack_broker" 8 | DEFAULT_LOCALSTACK_BROKER = "default_localstack_broker" 9 | LOCALSTACK_IMAGE = "localstack/localstack" 10 | LOCALSTACK_PORTS = {"4566/tcp": None} 11 | LOCALSTACK_CREDS: dict = { 12 | "AWS_DEFAULT_REGION": "us-east-1", 13 | "AWS_ACCESS_KEY_ID": "test", 14 | "AWS_SECRET_ACCESS_KEY": "test", 15 | } 16 | LOCALSTACK_ENV: dict = { 17 | **LOCALSTACK_CREDS, 18 | } 19 | LOCALSTACK_CONTAINER_TIMEOUT = 60 20 | LOCALSTACK_PREFIX = "sqs://" 21 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/localstack/fixtures.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Localstack Broker vendor. 5 | """ 6 | 7 | # mypy: disable-error-code="misc" 8 | 9 | from __future__ import annotations 10 | 11 | import pytest 12 | from pytest_docker_tools import container 13 | from pytest_docker_tools import fxtr 14 | 15 | from pytest_celery.vendors.localstack.api import LocalstackTestBroker 16 | from pytest_celery.vendors.localstack.container import LocalstackContainer 17 | from pytest_celery.vendors.localstack.defaults import LOCALSTACK_CONTAINER_TIMEOUT 18 | 19 | 20 | @pytest.fixture 21 | def celery_localstack_broker(default_localstack_broker: LocalstackContainer) -> LocalstackTestBroker: 22 | """Creates a LocalstackTestBroker instance. Responsible for tearing down 23 | the node. 24 | 25 | Args: 26 | default_localstack_broker (LocalstackContainer): Instantiated LocalstackContainer. 27 | """ 28 | broker = LocalstackTestBroker(default_localstack_broker) 29 | yield broker 30 | broker.teardown() 31 | 32 | 33 | @pytest.fixture 34 | def default_localstack_broker_cls() -> type[LocalstackContainer]: 35 | """Default Localstack broker container class. Override to apply custom 36 | configuration globally. 37 | 38 | See also: :ref:`vendor-class`. 39 | 40 | Returns: 41 | type[LocalstackContainer]: API for managing the vendor's container. 42 | """ 43 | return LocalstackContainer 44 | 45 | 46 | default_localstack_broker = container( 47 | image="{default_localstack_broker_image}", 48 | ports=fxtr("default_localstack_broker_ports"), 49 | environment=fxtr("default_localstack_broker_env"), 50 | network="{default_pytest_celery_network.name}", 51 | wrapper_class=LocalstackContainer, 52 | timeout=LOCALSTACK_CONTAINER_TIMEOUT, 53 | ) 54 | 55 | 56 | @pytest.fixture 57 | def default_localstack_broker_env(default_localstack_broker_cls: type[LocalstackContainer]) -> dict: 58 | """Environment variables for this vendor. 59 | 60 | Args: 61 | default_localstack_broker_cls (type[LocalstackContainer]): See also: :ref:`vendor-class`. 62 | 63 | Returns: 64 | dict: Items to pass to the container's environment. 65 | """ 66 | return default_localstack_broker_cls.initial_env() 67 | 68 | 69 | @pytest.fixture 70 | def default_localstack_broker_image(default_localstack_broker_cls: type[LocalstackContainer]) -> str: 71 | """Sets the image name for this vendor. 72 | 73 | Args: 74 | default_localstack_broker_cls (type[LocalstackContainer]): See also: :ref:`vendor-class`. 75 | 76 | Returns: 77 | str: Docker image name. 78 | """ 79 | return default_localstack_broker_cls.image() 80 | 81 | 82 | @pytest.fixture 83 | def default_localstack_broker_ports(default_localstack_broker_cls: type[LocalstackContainer]) -> dict: 84 | """Port bindings for this vendor. 85 | 86 | Args: 87 | default_localstack_broker_cls (type[LocalstackContainer]): See also: :ref:`vendor-class`. 88 | 89 | Returns: 90 | dict: Port bindings. 91 | """ 92 | return default_localstack_broker_cls.ports() 93 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/memcached/__init__.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Memcached Backend vendor. 5 | """ 6 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/memcached/api.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Memcached Backend vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from pytest_celery.api.backend import CeleryTestBackend 10 | 11 | 12 | class MemcachedTestBackend(CeleryTestBackend): 13 | pass 14 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/memcached/container.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Memcached Backend vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | import memcache 10 | 11 | from pytest_celery.api.container import CeleryTestContainer 12 | from pytest_celery.vendors.memcached.defaults import MEMCACHED_ENV 13 | from pytest_celery.vendors.memcached.defaults import MEMCACHED_IMAGE 14 | from pytest_celery.vendors.memcached.defaults import MEMCACHED_PORTS 15 | from pytest_celery.vendors.memcached.defaults import MEMCACHED_PREFIX 16 | 17 | 18 | class MemcachedContainer(CeleryTestContainer): 19 | """This class manages the lifecycle of a Memcached container.""" 20 | 21 | @property 22 | def client(self) -> memcache.Client: 23 | conf = self.celeryconfig 24 | servers = [f"{conf['host_url'][:-1].split('://')[-1]}"] 25 | client = memcache.Client(servers) 26 | return client 27 | 28 | @property 29 | def celeryconfig(self) -> dict: 30 | return { 31 | "url": self.url, 32 | "host_url": self.host_url, 33 | "hostname": self.hostname, 34 | "port": self.port, 35 | } 36 | 37 | @property 38 | def url(self) -> str: 39 | return f"{self.prefix()}{self.hostname}/" 40 | 41 | @property 42 | def host_url(self) -> str: 43 | return f"{self.prefix()}localhost:{self.port}/" 44 | 45 | @property 46 | def hostname(self) -> str: 47 | return self.attrs["Config"]["Hostname"] 48 | 49 | @property 50 | def port(self) -> int: 51 | return self._wait_port("11211/tcp") 52 | 53 | @classmethod 54 | def version(cls) -> str: 55 | return cls.image().split(":")[-1] 56 | 57 | @classmethod 58 | def initial_env(cls) -> dict: 59 | return MEMCACHED_ENV 60 | 61 | @classmethod 62 | def image(cls) -> str: 63 | return MEMCACHED_IMAGE 64 | 65 | @classmethod 66 | def ports(cls) -> dict: 67 | return MEMCACHED_PORTS 68 | 69 | @classmethod 70 | def prefix(cls) -> str: 71 | return MEMCACHED_PREFIX 72 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/memcached/defaults.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Memcached Backend vendor. 5 | """ 6 | 7 | CELERY_MEMCACHED_BACKEND = "celery_memcached_backend" 8 | DEFAULT_MEMCACHED_BACKEND = "default_memcached_backend" 9 | MEMCACHED_IMAGE = "memcached:latest" 10 | MEMCACHED_PORTS = {"11211/tcp": None} 11 | MEMCACHED_ENV: dict = {} 12 | MEMCACHED_CONTAINER_TIMEOUT = 60 13 | MEMCACHED_PREFIX = "cache+memcached://" 14 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/memcached/fixtures.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Memcached Backend vendor. 5 | """ 6 | 7 | # mypy: disable-error-code="misc" 8 | 9 | from __future__ import annotations 10 | 11 | import pytest 12 | from pytest_docker_tools import container 13 | from pytest_docker_tools import fxtr 14 | 15 | from pytest_celery.vendors.memcached.api import MemcachedTestBackend 16 | from pytest_celery.vendors.memcached.container import MemcachedContainer 17 | from pytest_celery.vendors.memcached.defaults import MEMCACHED_CONTAINER_TIMEOUT 18 | 19 | 20 | @pytest.fixture 21 | def celery_memcached_backend(default_memcached_backend: MemcachedContainer) -> MemcachedTestBackend: 22 | """Creates a MemcachedTestBackend instance. Responsible for tearing down 23 | the node. 24 | 25 | Args: 26 | default_memcached_backend (MemcachedContainer): Instantiated MemcachedContainer. 27 | """ 28 | backend = MemcachedTestBackend(default_memcached_backend) 29 | yield backend 30 | backend.teardown() 31 | 32 | 33 | @pytest.fixture 34 | def default_memcached_backend_cls() -> type[MemcachedContainer]: 35 | """Default Memcached backend container class. Override to apply custom 36 | configuration globally. 37 | 38 | See also: :ref:`vendor-class`. 39 | 40 | Returns: 41 | type[MemcachedContainer]: API for managing the vendor's container. 42 | """ 43 | return MemcachedContainer 44 | 45 | 46 | default_memcached_backend = container( 47 | image="{default_memcached_backend_image}", 48 | ports=fxtr("default_memcached_backend_ports"), 49 | environment=fxtr("default_memcached_backend_env"), 50 | network="{default_pytest_celery_network.name}", 51 | wrapper_class=MemcachedContainer, 52 | timeout=MEMCACHED_CONTAINER_TIMEOUT, 53 | ) 54 | 55 | 56 | @pytest.fixture 57 | def default_memcached_backend_env(default_memcached_backend_cls: type[MemcachedContainer]) -> dict: 58 | """Environment variables for this vendor. 59 | 60 | Args: 61 | default_memcached_backend_cls (type[MemcachedContainer]): See also: :ref:`vendor-class`. 62 | 63 | Returns: 64 | dict: Items to pass to the container's environment. 65 | """ 66 | return default_memcached_backend_cls.initial_env() 67 | 68 | 69 | @pytest.fixture 70 | def default_memcached_backend_image(default_memcached_backend_cls: type[MemcachedContainer]) -> str: 71 | """Docker image for this vendor. 72 | 73 | Args: 74 | default_memcached_backend_cls (type[MemcachedContainer]): See also: :ref:`vendor-class`. 75 | 76 | Returns: 77 | str: Docker image name. 78 | """ 79 | return default_memcached_backend_cls.image() 80 | 81 | 82 | @pytest.fixture 83 | def default_memcached_backend_ports(default_memcached_backend_cls: type[MemcachedContainer]) -> dict: 84 | """Port bindings for this vendor. 85 | 86 | Args: 87 | default_memcached_backend_cls (type[MemcachedContainer]): See also: :ref:`vendor-class`. 88 | 89 | Returns: 90 | dict: Port bindings. 91 | """ 92 | return default_memcached_backend_cls.ports() 93 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/rabbitmq/__init__.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the RabbitMQ Broker vendor. 5 | """ 6 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/rabbitmq/api.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the RabbitMQ Broker vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from pytest_celery.api.broker import CeleryTestBroker 10 | 11 | 12 | class RabbitMQTestBroker(CeleryTestBroker): 13 | pass 14 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/rabbitmq/container.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the RabbitMQ Broker vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from kombu import Connection 10 | 11 | from pytest_celery.api.container import CeleryTestContainer 12 | from pytest_celery.vendors.rabbitmq.defaults import RABBITMQ_ENV 13 | from pytest_celery.vendors.rabbitmq.defaults import RABBITMQ_IMAGE 14 | from pytest_celery.vendors.rabbitmq.defaults import RABBITMQ_PORTS 15 | from pytest_celery.vendors.rabbitmq.defaults import RABBITMQ_PREFIX 16 | 17 | 18 | class RabbitMQContainer(CeleryTestContainer): 19 | """This class manages the lifecycle of a RabbitMQ container.""" 20 | 21 | @property 22 | def client(self) -> Connection: 23 | client = Connection( 24 | self.celeryconfig["host_url"], 25 | port=self.celeryconfig["port"], 26 | ) 27 | return client 28 | 29 | @property 30 | def celeryconfig(self) -> dict: 31 | return { 32 | "url": self.url, 33 | "host_url": self.host_url, 34 | "hostname": self.hostname, 35 | "port": self.port, 36 | "vhost": self.vhost, 37 | } 38 | 39 | @property 40 | def url(self) -> str: 41 | return f"{self.prefix()}{self.hostname}/{self.vhost}" 42 | 43 | @property 44 | def host_url(self) -> str: 45 | return f"{self.prefix()}localhost:{self.port}/{self.vhost}" 46 | 47 | @property 48 | def hostname(self) -> str: 49 | return self.attrs["Config"]["Hostname"] 50 | 51 | @property 52 | def port(self) -> int: 53 | return self._wait_port("5672/tcp") 54 | 55 | @property 56 | def vhost(self) -> str: 57 | return "/" 58 | 59 | @classmethod 60 | def version(cls) -> str: 61 | return cls.image().split(":")[-1] 62 | 63 | @classmethod 64 | def initial_env(cls) -> dict: 65 | return RABBITMQ_ENV 66 | 67 | @classmethod 68 | def image(cls) -> str: 69 | return RABBITMQ_IMAGE 70 | 71 | @classmethod 72 | def ports(cls) -> dict: 73 | return RABBITMQ_PORTS 74 | 75 | @classmethod 76 | def prefix(cls) -> str: 77 | return RABBITMQ_PREFIX 78 | 79 | @property 80 | def ready_prompt(self) -> str | None: 81 | return "Server startup complete" 82 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/rabbitmq/defaults.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the RabbitMQ Broker vendor. 5 | """ 6 | 7 | CELERY_RABBITMQ_BROKER = "celery_rabbitmq_broker" 8 | DEFAULT_RABBITMQ_BROKER = "default_rabbitmq_broker" 9 | RABBITMQ_IMAGE = "rabbitmq:latest" 10 | RABBITMQ_PORTS = {"5672/tcp": None} 11 | RABBITMQ_ENV: dict = {} 12 | RABBITMQ_CONTAINER_TIMEOUT = 120 13 | RABBITMQ_PREFIX = "amqp://" 14 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/rabbitmq/fixtures.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the RabbitMQ Broker vendor. 5 | """ 6 | 7 | # mypy: disable-error-code="misc" 8 | 9 | from __future__ import annotations 10 | 11 | import pytest 12 | from pytest_docker_tools import container 13 | from pytest_docker_tools import fxtr 14 | 15 | from pytest_celery.vendors.rabbitmq.api import RabbitMQTestBroker 16 | from pytest_celery.vendors.rabbitmq.container import RabbitMQContainer 17 | from pytest_celery.vendors.rabbitmq.defaults import RABBITMQ_CONTAINER_TIMEOUT 18 | 19 | 20 | @pytest.fixture 21 | def celery_rabbitmq_broker(default_rabbitmq_broker: RabbitMQContainer) -> RabbitMQTestBroker: 22 | """Creates a RabbitMQTestBroker instance. Responsible for tearing down the 23 | node. 24 | 25 | Args: 26 | default_rabbitmq_broker (RabbitMQContainer): Instantiated RabbitMQContainer. 27 | """ 28 | broker = RabbitMQTestBroker(default_rabbitmq_broker) 29 | yield broker 30 | broker.teardown() 31 | 32 | 33 | @pytest.fixture 34 | def default_rabbitmq_broker_cls() -> type[RabbitMQContainer]: 35 | """Default RabbitMQ broker container class. Override to apply custom 36 | configuration globally. 37 | 38 | See also: :ref:`vendor-class`. 39 | 40 | Returns: 41 | type[RabbitMQContainer]: API for managing the vendor's container. 42 | """ 43 | return RabbitMQContainer 44 | 45 | 46 | default_rabbitmq_broker = container( 47 | image="{default_rabbitmq_broker_image}", 48 | ports=fxtr("default_rabbitmq_broker_ports"), 49 | environment=fxtr("default_rabbitmq_broker_env"), 50 | network="{default_pytest_celery_network.name}", 51 | wrapper_class=RabbitMQContainer, 52 | timeout=RABBITMQ_CONTAINER_TIMEOUT, 53 | ) 54 | 55 | 56 | @pytest.fixture 57 | def default_rabbitmq_broker_env(default_rabbitmq_broker_cls: type[RabbitMQContainer]) -> dict: 58 | """Environment variables for this vendor. 59 | 60 | Args: 61 | default_rabbitmq_broker_cls (type[RabbitMQContainer]): See also: :ref:`vendor-class`. 62 | 63 | Returns: 64 | dict: Items to pass to the container's environment. 65 | """ 66 | return default_rabbitmq_broker_cls.initial_env() 67 | 68 | 69 | @pytest.fixture 70 | def default_rabbitmq_broker_image(default_rabbitmq_broker_cls: type[RabbitMQContainer]) -> str: 71 | """Sets the image name for this vendor. 72 | 73 | Args: 74 | default_rabbitmq_broker_cls (type[RabbitMQContainer]): See also: :ref:`vendor-class`. 75 | 76 | Returns: 77 | str: Docker image name. 78 | """ 79 | return default_rabbitmq_broker_cls.image() 80 | 81 | 82 | @pytest.fixture 83 | def default_rabbitmq_broker_ports(default_rabbitmq_broker_cls: type[RabbitMQContainer]) -> dict: 84 | """Port bindings for this vendor. 85 | 86 | Args: 87 | default_rabbitmq_broker_cls (type[RabbitMQContainer]): See also: :ref:`vendor-class`. 88 | 89 | Returns: 90 | dict: Port bindings. 91 | """ 92 | return default_rabbitmq_broker_cls.ports() 93 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/__init__.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis vendor. 5 | """ 6 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/backend/__init__.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis Backend vendor. 5 | """ 6 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/backend/api.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis Backend vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | import gc 10 | 11 | from celery.result import AsyncResult 12 | 13 | from pytest_celery.api.backend import CeleryTestBackend 14 | 15 | 16 | class RedisTestBackend(CeleryTestBackend): 17 | def teardown(self) -> None: 18 | """When a test that has a AsyncResult object is finished there's a race 19 | condition between the AsyncResult object and the Redis container. 20 | 21 | The AsyncResult object tries to release the connection but the 22 | Redis container has already exited. 23 | """ 24 | # First, force a garbage collection to clean up unreachable objects 25 | gc.collect() 26 | 27 | # Next, find all live AsyncResult objects and clean them up 28 | async_results = [obj for obj in gc.get_objects() if isinstance(obj, AsyncResult)] 29 | 30 | for async_result in async_results: 31 | try: 32 | # Remove the backend reference to prevent interaction with Redis 33 | async_result.backend = None 34 | except Exception: 35 | pass 36 | 37 | super().teardown() 38 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/backend/defaults.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis Backend vendor. 5 | """ 6 | 7 | CELERY_REDIS_BACKEND = "celery_redis_backend" 8 | DEFAULT_REDIS_BACKEND = "default_redis_backend" 9 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/broker/__init__.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis Broker vendor. 5 | """ 6 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/broker/api.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis Broker vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from pytest_celery.api.broker import CeleryTestBroker 10 | 11 | 12 | class RedisTestBroker(CeleryTestBroker): 13 | pass 14 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/broker/defaults.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis Broker vendor. 5 | """ 6 | 7 | CELERY_REDIS_BROKER = "celery_redis_broker" 8 | DEFAULT_REDIS_BROKER = "default_redis_broker" 9 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/container.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | from redis import StrictRedis as Redis 10 | 11 | from pytest_celery.api.container import CeleryTestContainer 12 | from pytest_celery.vendors.redis.defaults import REDIS_ENV 13 | from pytest_celery.vendors.redis.defaults import REDIS_IMAGE 14 | from pytest_celery.vendors.redis.defaults import REDIS_PORTS 15 | from pytest_celery.vendors.redis.defaults import REDIS_PREFIX 16 | 17 | 18 | class RedisContainer(CeleryTestContainer): 19 | """This class manages the lifecycle of a Redis container.""" 20 | 21 | @property 22 | def client(self) -> Redis | None: 23 | client = Redis.from_url( 24 | self.celeryconfig["host_url"], 25 | decode_responses=True, 26 | ) 27 | return client 28 | 29 | @property 30 | def celeryconfig(self) -> dict: 31 | return { 32 | "url": self.url, 33 | "host_url": self.host_url, 34 | "hostname": self.hostname, 35 | "port": self.port, 36 | "vhost": self.vhost, 37 | } 38 | 39 | @classmethod 40 | def command( 41 | cls, 42 | *args: str, 43 | debugpy: bool = False, 44 | wait_for_client: bool = True, 45 | **kwargs: dict, 46 | ) -> list[str]: 47 | return [ 48 | "redis-server", 49 | "--save", 50 | "", 51 | "--appendonly", 52 | "no", 53 | "--maxmemory-policy", 54 | "noeviction", 55 | "--protected-mode", 56 | "no", 57 | *args, 58 | ] 59 | 60 | @property 61 | def url(self) -> str: 62 | return f"{self.prefix()}{self.hostname}/{self.vhost}" 63 | 64 | @property 65 | def host_url(self) -> str: 66 | return f"{self.prefix()}localhost:{self.port}/{self.vhost}" 67 | 68 | @property 69 | def hostname(self) -> str: 70 | return self.attrs["Config"]["Hostname"] 71 | 72 | @property 73 | def port(self) -> int: 74 | return self._wait_port("6379/tcp") 75 | 76 | @property 77 | def vhost(self) -> str: 78 | return "0" 79 | 80 | @classmethod 81 | def version(cls) -> str: 82 | return cls.image().split(":")[-1] 83 | 84 | @classmethod 85 | def initial_env(cls) -> dict: 86 | return REDIS_ENV 87 | 88 | @classmethod 89 | def image(cls) -> str: 90 | return REDIS_IMAGE 91 | 92 | @classmethod 93 | def ports(cls) -> dict: 94 | return REDIS_PORTS 95 | 96 | @classmethod 97 | def prefix(cls) -> str: 98 | return REDIS_PREFIX 99 | 100 | @property 101 | def ready_prompt(self) -> str | None: 102 | return "Ready to accept connections" 103 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/redis/defaults.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the Redis vendor. 5 | """ 6 | 7 | REDIS_IMAGE = "redis:latest" 8 | REDIS_PORTS = {"6379/tcp": None} 9 | REDIS_ENV: dict = {} 10 | REDIS_CONTAINER_TIMEOUT = 60 11 | REDIS_PREFIX = "redis://" 12 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/worker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-buster 2 | 3 | # Create a user to run the worker 4 | RUN adduser --disabled-password --gecos "" test_user 5 | 6 | # Install system dependencies 7 | RUN apt-get update && apt-get install -y build-essential \ 8 | git \ 9 | wget \ 10 | make \ 11 | curl \ 12 | apt-utils \ 13 | debconf \ 14 | lsb-release \ 15 | libmemcached-dev \ 16 | libffi-dev \ 17 | ca-certificates \ 18 | pypy3 \ 19 | pypy3-lib \ 20 | sudo 21 | 22 | # Set arguments 23 | ARG CELERY_VERSION="" 24 | ARG CELERY_LOG_LEVEL=INFO 25 | ARG CELERY_WORKER_NAME=celery_test_worker 26 | ARG CELERY_WORKER_QUEUE=celery 27 | ENV WORKER_VERSION=$CELERY_VERSION 28 | ENV LOG_LEVEL=$CELERY_LOG_LEVEL 29 | ENV WORKER_NAME=$CELERY_WORKER_NAME 30 | ENV WORKER_QUEUE=$CELERY_WORKER_QUEUE 31 | 32 | ENV PYTHONUNBUFFERED=1 33 | ENV PYTHONDONTWRITEBYTECODE=1 34 | 35 | EXPOSE 5678 36 | 37 | # Install Python dependencies 38 | RUN pip install --no-cache-dir --upgrade \ 39 | pip \ 40 | celery[redis,pymemcache,gevent]${WORKER_VERSION:+==$WORKER_VERSION} \ 41 | pytest-celery[sqs]@git+https://github.com/celery/pytest-celery.git 42 | 43 | # The workdir must be /app 44 | WORKDIR /app 45 | 46 | COPY content/ . 47 | 48 | # Switch to the test_user 49 | USER test_user 50 | 51 | # Start the celery worker 52 | CMD celery -A app worker --loglevel=$LOG_LEVEL -n $WORKER_NAME@%h -Q $WORKER_QUEUE 53 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/worker/__init__.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the :ref:`built-in-worker` vendor. 5 | """ 6 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/worker/content/__init__.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the :ref:`built-in-worker` vendor. 5 | """ 6 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/worker/content/app.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. This module is part of the :ref:`built-in-worker` vendor. 3 | 4 | Template for Celery worker application. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | import json 10 | 11 | from celery import Celery 12 | 13 | imports = None 14 | 15 | app = Celery("celery_test_app") 16 | config = None 17 | 18 | if config: 19 | app.config_from_object(config) 20 | print(f"Changed worker configuration: {json.dumps(config, indent=4)}") 21 | 22 | 23 | if __name__ == "__main__": 24 | app.start() 25 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/worker/content/utils.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the :ref:`built-in-worker` vendor. 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | import json 10 | 11 | import psutil 12 | 13 | 14 | def get_running_processes_info(columns: list[str] | None = None) -> str: 15 | """Get information about running processes using psutil.""" 16 | if not columns: 17 | columns = [ 18 | "pid", 19 | "name", 20 | "username", 21 | "cmdline", 22 | "cpu_percent", 23 | "memory_percent", 24 | "create_time", 25 | ] 26 | processes = [proc.info for proc in psutil.process_iter(columns)] 27 | return json.dumps(processes) 28 | -------------------------------------------------------------------------------- /src/pytest_celery/vendors/worker/defaults.py: -------------------------------------------------------------------------------- 1 | """The pytest-celery plugin provides a set of built-in components called 2 | :ref:`vendors`. 3 | 4 | This module is part of the :ref:`built-in-worker` vendor. 5 | """ 6 | 7 | import os 8 | 9 | CELERY_SETUP_WORKER = "celery_setup_worker" 10 | DEFAULT_WORKER = "default_worker_container" 11 | WORKER_DOCKERFILE_ROOTDIR = os.path.dirname(__file__) 12 | WORKER_CELERY_APP_NAME = "celery_test_app" 13 | WORKER_CELERY_VERSION = "" # latest from pypi 14 | WORKER_LOG_LEVEL = "INFO" 15 | WORKER_NAME = "celery_test_worker" 16 | WORKER_QUEUE = "celery" 17 | WORKER_ENV = { 18 | "CELERY_BROKER_URL": "memory://", 19 | "CELERY_RESULT_BACKEND": "cache+memory://", 20 | "PYTHONUNBUFFERED": "1", 21 | "PYTHONDONTWRITEBYTECODE": "1", 22 | "PYTHONPATH": "/app", 23 | } 24 | WORKER_DEBUGPY_PORTS = { 25 | "5678/tcp": "5678", 26 | } 27 | WORKER_VOLUME = { 28 | "bind": "/app", 29 | "mode": "rw", 30 | } 31 | DEFAULT_WORKER_APP_NAME = WORKER_CELERY_APP_NAME 32 | DEFAULT_WORKER_VERSION = WORKER_CELERY_VERSION 33 | DEFAULT_WORKER_LOG_LEVEL = WORKER_LOG_LEVEL 34 | DEFAULT_WORKER_NAME = WORKER_NAME 35 | DEFAULT_WORKER_ENV = WORKER_ENV 36 | DEFAULT_WORKER_PORTS = None 37 | DEFAULT_WORKER_QUEUE = WORKER_QUEUE 38 | DEFAULT_WORKER_CONTAINER_TIMEOUT = 60 39 | DEFAULT_WORKER_VOLUME = WORKER_VOLUME 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from celery import Celery 5 | 6 | from pytest_celery import LOCALSTACK_CREDS 7 | 8 | 9 | @pytest.fixture 10 | def default_worker_tasks(default_worker_tasks: set) -> set: 11 | from tests import tasks 12 | 13 | default_worker_tasks.add(tasks) 14 | return default_worker_tasks 15 | 16 | 17 | @pytest.fixture 18 | def default_worker_env(default_worker_env: dict) -> dict: 19 | default_worker_env.update(LOCALSTACK_CREDS) 20 | return default_worker_env 21 | 22 | 23 | @pytest.fixture(scope="session", autouse=True) 24 | def set_aws_credentials(): 25 | os.environ.update(LOCALSTACK_CREDS) 26 | 27 | 28 | @pytest.fixture 29 | def default_worker_app(default_worker_app: Celery) -> Celery: 30 | app = default_worker_app 31 | if app.conf.broker_url and app.conf.broker_url.startswith("sqs"): 32 | app.conf.broker_transport_options["region"] = LOCALSTACK_CREDS["AWS_DEFAULT_REGION"] 33 | return app 34 | -------------------------------------------------------------------------------- /tests/defaults.py: -------------------------------------------------------------------------------- 1 | from pytest_celery import CELERY_BACKEND 2 | from pytest_celery import CELERY_BACKEND_CLUSTER 3 | from pytest_celery import CELERY_BROKER 4 | from pytest_celery import CELERY_BROKER_CLUSTER 5 | from pytest_celery import CELERY_WORKER 6 | from pytest_celery import CELERY_WORKER_CLUSTER 7 | from pytest_celery import DEFAULT_LOCALSTACK_BROKER 8 | from pytest_celery import DEFAULT_MEMCACHED_BACKEND 9 | from pytest_celery import DEFAULT_RABBITMQ_BROKER 10 | from pytest_celery import DEFAULT_REDIS_BACKEND 11 | from pytest_celery import DEFAULT_REDIS_BROKER 12 | from pytest_celery import DEFAULT_WORKER 13 | 14 | DEFAULT_WORKERS = (DEFAULT_WORKER,) 15 | DEFAULT_BACKENDS = ( 16 | DEFAULT_REDIS_BACKEND, 17 | DEFAULT_MEMCACHED_BACKEND, 18 | ) 19 | DEFAULT_BROKERS = ( 20 | DEFAULT_LOCALSTACK_BROKER, 21 | DEFAULT_RABBITMQ_BROKER, 22 | DEFAULT_REDIS_BROKER, 23 | ) 24 | 25 | ALL_REDIS_FIXTURES = ( 26 | DEFAULT_REDIS_BACKEND, 27 | DEFAULT_REDIS_BROKER, 28 | ) 29 | ALL_LOCALSTACK_FIXTURES = (DEFAULT_LOCALSTACK_BROKER,) 30 | ALL_RABBITMQ_FIXTURES = (DEFAULT_RABBITMQ_BROKER,) 31 | ALL_MEMCACHED_FIXTURES = (DEFAULT_MEMCACHED_BACKEND,) 32 | ALL_WORKERS_FIXTURES = (*DEFAULT_WORKERS,) 33 | ALL_BACKENDS_FIXTURES = (*DEFAULT_BACKENDS,) 34 | ALL_BROKERS_FIXTURES = (*DEFAULT_BROKERS,) 35 | ALL_COMPONENTS_FIXTURES = ( 36 | *ALL_WORKERS_FIXTURES, 37 | *ALL_BACKENDS_FIXTURES, 38 | *ALL_BROKERS_FIXTURES, 39 | ) 40 | ALL_NODES_FIXTURES = ( 41 | CELERY_WORKER, 42 | CELERY_BACKEND, 43 | CELERY_BROKER, 44 | ) 45 | ALL_CLUSTERS_FIXTURES = ( 46 | CELERY_WORKER_CLUSTER, 47 | CELERY_BACKEND_CLUSTER, 48 | CELERY_BROKER_CLUSTER, 49 | ) 50 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/integration/api/__init__.py -------------------------------------------------------------------------------- /tests/integration/api/custom_setup/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/integration/api/custom_setup/__init__.py -------------------------------------------------------------------------------- /tests/integration/api/custom_setup/test_custom_setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | from celery import Celery 5 | 6 | from pytest_celery import RESULT_TIMEOUT 7 | from pytest_celery import CeleryTestSetup 8 | from pytest_celery import CeleryTestWorker 9 | from pytest_celery import LocalstackTestBroker 10 | from tests.integration.api.custom_setup.conftest import Celery4WorkerContainer 11 | from tests.integration.api.custom_setup.conftest import Celery5WorkerContainer 12 | from tests.tasks import identity 13 | 14 | 15 | class test_custom_setup: 16 | def test_ready(self, celery_setup: CeleryTestSetup): 17 | assert celery_setup.ready() 18 | 19 | def test_worker_is_connected_to_backend(self, celery_setup: CeleryTestSetup): 20 | backend_urls = [ 21 | backend.container.celeryconfig["host_url"].replace("cache+", "") for backend in celery_setup.backend_cluster 22 | ] 23 | worker: CeleryTestWorker 24 | for worker in celery_setup.worker_cluster: 25 | app: Celery = worker.app 26 | assert app.backend.as_uri() in backend_urls 27 | 28 | def test_worker_is_connected_to_broker(self, celery_setup: CeleryTestSetup): 29 | def strip_url(url: str) -> str: 30 | while url.endswith("/"): 31 | url = url.rstrip("/") 32 | return url 33 | 34 | broker_urls = [strip_url(broker.container.celeryconfig["host_url"]) for broker in celery_setup.broker_cluster] 35 | worker: CeleryTestWorker 36 | for worker in celery_setup.worker_cluster: 37 | app: Celery = worker.app 38 | as_uri = app.connection().as_uri().replace("guest:**@", "") 39 | as_uri = strip_url(as_uri) 40 | assert as_uri in broker_urls 41 | 42 | def test_log_level(self, celery_setup: CeleryTestSetup): 43 | worker: CeleryTestWorker 44 | for worker in celery_setup.worker_cluster: 45 | if worker.logs(): 46 | worker.assert_log_exists(worker.log_level) 47 | 48 | def test_apply_async(self, celery_setup: CeleryTestSetup): 49 | assert celery_setup.app 50 | worker: CeleryTestWorker 51 | for worker in celery_setup.worker_cluster: 52 | if worker.version == Celery4WorkerContainer.version(): 53 | if isinstance(celery_setup.broker, LocalstackTestBroker): 54 | pytest.xfail("Probably bug with the test environment") 55 | expected = "test_apply_async" 56 | queue = worker.worker_queue 57 | sig = identity.s(expected) 58 | res = sig.apply_async(queue=queue) 59 | assert res.get(timeout=RESULT_TIMEOUT) == expected 60 | 61 | def test_custom_cluster_version(self, celery_setup: CeleryTestSetup): 62 | assert len(celery_setup.worker_cluster) == 2 63 | assert celery_setup.worker_cluster.versions == { 64 | Celery5WorkerContainer.version(), 65 | Celery4WorkerContainer.version(), 66 | } 67 | -------------------------------------------------------------------------------- /tests/integration/api/test_backend.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import CeleryBackendCluster 6 | from pytest_celery import CeleryTestBackend 7 | from pytest_celery import CeleryTestCluster 8 | from pytest_celery import CeleryTestNode 9 | from tests.integration.api.test_base import BaseCluster 10 | from tests.integration.api.test_base import BaseNodes 11 | 12 | 13 | class test_celey_test_backend(BaseNodes): 14 | @pytest.fixture 15 | def node(self, celery_backend: CeleryTestBackend) -> CeleryTestNode: 16 | return celery_backend 17 | 18 | 19 | class test_celery_backend_cluster(BaseCluster): 20 | @pytest.fixture 21 | def cluster(self, celery_backend_cluster: CeleryBackendCluster) -> CeleryTestCluster: 22 | return celery_backend_cluster 23 | -------------------------------------------------------------------------------- /tests/integration/api/test_base.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import CeleryTestCluster 6 | from pytest_celery import CeleryTestNode 7 | from pytest_celery import RedisTestBackend 8 | 9 | 10 | class BaseNodes: 11 | def test_ready(self, node: CeleryTestNode): 12 | assert node.ready() 13 | 14 | def test_app(self, node: CeleryTestNode): 15 | assert node.app is None 16 | 17 | def test_logs(self, node: CeleryTestNode): 18 | node.logs() 19 | 20 | def test_name(self, node: CeleryTestNode): 21 | assert isinstance(node.name(), str) 22 | 23 | def test_hostname(self, node: CeleryTestNode): 24 | hostname = node.hostname() 25 | assert isinstance(hostname, str) 26 | assert node.container.id[:12] in hostname 27 | 28 | @pytest.mark.parametrize("signal", [None, "SIGKILL"]) 29 | def test_kill(self, node: CeleryTestNode, signal: str | int): 30 | node.kill(signal) 31 | assert node.container.status == "exited" 32 | 33 | def test_kill_no_reload(self, node: CeleryTestNode): 34 | node.kill(reload_container=False) 35 | assert node.container.status != "exited" 36 | 37 | @pytest.mark.parametrize("force", [True, False]) 38 | def test_restart(self, node: CeleryTestNode, force: bool): 39 | node.restart(force=force) 40 | assert node.container.status == "running" 41 | 42 | def test_restart_no_reload(self, node: CeleryTestNode): 43 | node.restart(reload_container=False) 44 | assert node.container.status == "running" 45 | 46 | def test_teardown(self, node: CeleryTestNode): 47 | if isinstance(node, RedisTestBackend): 48 | pytest.skip("RedisTestBackend.teardown() breaks the testing environment") 49 | node.teardown() 50 | 51 | @pytest.mark.skip(reason="TODO") 52 | def test_wait_for_logs(self, node: CeleryTestNode): 53 | pass 54 | 55 | @pytest.mark.skip(reason="TODO") 56 | def test_assert_log_exists(self, node: CeleryTestNode): 57 | pass 58 | 59 | @pytest.mark.skip(reason="TODO") 60 | def test_assert_log_does_not_exist(self, node: CeleryTestNode): 61 | pass 62 | 63 | 64 | class BaseCluster: 65 | def test_ready(self, cluster: CeleryTestCluster): 66 | assert cluster.ready() 67 | 68 | def test_teardown(self, cluster: CeleryTestCluster): 69 | cluster.teardown() 70 | 71 | def test_app(self, cluster: CeleryTestCluster): 72 | node: CeleryTestNode 73 | for node in cluster: 74 | assert node.app is None 75 | 76 | def test_config(self, cluster: CeleryTestCluster): 77 | expected_keys = {"urls", "host_urls"} 78 | assert set(cluster.config().keys()) == expected_keys 79 | -------------------------------------------------------------------------------- /tests/integration/api/test_broker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import CeleryBrokerCluster 6 | from pytest_celery import CeleryTestBroker 7 | from pytest_celery import CeleryTestCluster 8 | from pytest_celery import CeleryTestNode 9 | from tests.integration.api.test_base import BaseCluster 10 | from tests.integration.api.test_base import BaseNodes 11 | 12 | 13 | class test_celery_test_broker(BaseNodes): 14 | @pytest.fixture 15 | def node(self, celery_broker: CeleryTestBroker) -> CeleryTestNode: 16 | return celery_broker 17 | 18 | 19 | class test_celery_broker_cluster(BaseCluster): 20 | @pytest.fixture 21 | def cluster(self, celery_broker_cluster: CeleryBrokerCluster) -> CeleryTestCluster: 22 | return celery_broker_cluster 23 | -------------------------------------------------------------------------------- /tests/integration/api/test_container.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import CeleryTestContainer 6 | from tests.defaults import ALL_COMPONENTS_FIXTURES 7 | 8 | 9 | @pytest.fixture 10 | def container(request): 11 | return request.getfixturevalue(request.param) 12 | 13 | 14 | @pytest.mark.parametrize("container", ALL_COMPONENTS_FIXTURES, indirect=["container"]) 15 | class test_celery_test_container: 16 | def test_client(self, container: CeleryTestContainer): 17 | assert container.client 18 | 19 | def test_ready(self, container: CeleryTestContainer): 20 | assert container.ready() 21 | -------------------------------------------------------------------------------- /tests/integration/api/test_worker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | from celery import Celery 5 | 6 | from pytest_celery import CeleryTestCluster 7 | from pytest_celery import CeleryTestNode 8 | from pytest_celery import CeleryTestWorker 9 | from pytest_celery import CeleryWorkerCluster 10 | from pytest_celery import CeleryWorkerContainer 11 | from tests.integration.api.test_base import BaseCluster 12 | from tests.integration.api.test_base import BaseNodes 13 | 14 | 15 | class test_celey_test_worker(BaseNodes): 16 | @pytest.fixture 17 | def node(self, celery_worker: CeleryTestWorker) -> CeleryTestNode: 18 | return celery_worker 19 | 20 | def test_app(self, celery_worker: CeleryTestWorker, celery_setup_app: Celery): 21 | assert celery_worker.app is celery_setup_app 22 | 23 | def test_version(self, celery_worker: CeleryTestWorker): 24 | assert celery_worker.version == CeleryWorkerContainer.version() 25 | 26 | def test_hostname(self, celery_worker: CeleryTestWorker): 27 | hostname = celery_worker.hostname() 28 | assert "@" in hostname 29 | assert celery_worker.worker_name in hostname.split("@")[0] 30 | assert celery_worker.container.id[:12] in hostname.split("@")[1] 31 | 32 | def test_wait_for_log(self, celery_worker: CeleryTestWorker): 33 | log = f"{celery_worker.hostname()} v{celery_worker.version}" 34 | celery_worker.wait_for_log(log, "test_celey_test_worker.test_wait_for_log") 35 | 36 | def test_assert_log_exists(self, celery_worker: CeleryTestWorker): 37 | log = f"{celery_worker.hostname()} v{celery_worker.version}" 38 | celery_worker.assert_log_exists(log, "test_celey_test_worker.test_assert_log_exists") 39 | 40 | 41 | class test_celery_worker_cluster(BaseCluster): 42 | @pytest.fixture 43 | def cluster(self, celery_worker_cluster: CeleryWorkerCluster) -> CeleryTestCluster: 44 | return celery_worker_cluster 45 | 46 | def test_app(self, celery_worker_cluster: CeleryWorkerCluster, celery_setup_app: Celery): 47 | worker: CeleryTestWorker 48 | for worker in celery_worker_cluster: 49 | assert worker.app is celery_setup_app 50 | 51 | def test_config(self, celery_worker_cluster: CeleryWorkerCluster): 52 | with pytest.raises(NotImplementedError): 53 | celery_worker_cluster.config() 54 | 55 | def test_versions(self, celery_worker_cluster: CeleryWorkerCluster): 56 | assert celery_worker_cluster.versions == {CeleryWorkerContainer.version()} 57 | -------------------------------------------------------------------------------- /tests/integration/conftest.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | 5 | import pytest 6 | from pytest_docker_tools import build 7 | from pytest_docker_tools import container 8 | from pytest_docker_tools import fxtr 9 | 10 | from pytest_celery import DEFAULT_WORKER_CONTAINER_TIMEOUT 11 | from pytest_celery import DEFAULT_WORKER_VOLUME 12 | from pytest_celery import WORKER_DOCKERFILE_ROOTDIR 13 | from pytest_celery import CeleryWorkerContainer 14 | 15 | 16 | class IntegrationWorkerContainer(CeleryWorkerContainer): 17 | @property 18 | def client(self) -> Any: 19 | # Overriding the worker container until we have a proper client class 20 | return self 21 | 22 | @classmethod 23 | def log_level(cls) -> str: 24 | return "INFO" 25 | 26 | @classmethod 27 | def worker_name(cls) -> str: 28 | return CeleryWorkerContainer.worker_name() + "-integration-worker" 29 | 30 | @classmethod 31 | def worker_queue(cls) -> str: 32 | return CeleryWorkerContainer.worker_queue() + "-integration-tests-queue" 33 | 34 | 35 | @pytest.fixture 36 | def default_worker_container_cls() -> type[CeleryWorkerContainer]: 37 | return IntegrationWorkerContainer 38 | 39 | 40 | @pytest.fixture(scope="session") 41 | def default_worker_container_session_cls() -> type[CeleryWorkerContainer]: 42 | return IntegrationWorkerContainer 43 | 44 | 45 | integration_tests_worker_image = build( 46 | path=WORKER_DOCKERFILE_ROOTDIR, 47 | tag="pytest-celery/components/worker:integration", 48 | buildargs=IntegrationWorkerContainer.buildargs(), 49 | ) 50 | 51 | 52 | default_worker_container = container( 53 | image="{integration_tests_worker_image.id}", 54 | ports=fxtr("default_worker_ports"), 55 | environment=fxtr("default_worker_env"), 56 | network="{default_pytest_celery_network.name}", 57 | volumes={"{default_worker_volume.name}": DEFAULT_WORKER_VOLUME}, 58 | wrapper_class=IntegrationWorkerContainer, 59 | timeout=DEFAULT_WORKER_CONTAINER_TIMEOUT, 60 | command=fxtr("default_worker_command"), 61 | ) 62 | -------------------------------------------------------------------------------- /tests/integration/vendors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/integration/vendors/__init__.py -------------------------------------------------------------------------------- /tests/integration/vendors/test_default_tasks.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pytest_celery import RESULT_TIMEOUT 4 | from pytest_celery import CeleryTestSetup 5 | from pytest_celery import add 6 | from pytest_celery import add_replaced 7 | from pytest_celery import fail 8 | from pytest_celery import identity 9 | from pytest_celery import noop 10 | from pytest_celery import ping 11 | from pytest_celery import sleep 12 | from pytest_celery import xsum 13 | 14 | 15 | class test_default_tasks: 16 | def test_add(self, celery_setup: CeleryTestSetup): 17 | assert add.s(1, 2).apply_async(queue=celery_setup.worker.worker_queue).get(timeout=RESULT_TIMEOUT) == 3 18 | 19 | def test_add_replaced(self, celery_setup: CeleryTestSetup): 20 | queue = celery_setup.worker.worker_queue 21 | add_replaced.s(1, 2, queue=queue).apply_async(queue=queue) 22 | celery_setup.worker.assert_log_exists("ignored") 23 | 24 | def test_fail(self, celery_setup: CeleryTestSetup): 25 | with pytest.raises(RuntimeError): 26 | fail.s().apply_async(queue=celery_setup.worker.worker_queue).get(timeout=RESULT_TIMEOUT) 27 | 28 | def test_identity(self, celery_setup: CeleryTestSetup): 29 | assert identity.s(1).apply_async(queue=celery_setup.worker.worker_queue).get(timeout=RESULT_TIMEOUT) == 1 30 | 31 | def test_noop(self, celery_setup: CeleryTestSetup): 32 | assert noop.s().apply_async(queue=celery_setup.worker.worker_queue).get(timeout=RESULT_TIMEOUT) is None 33 | 34 | def test_ping(self, celery_setup: CeleryTestSetup): 35 | assert ping.s().apply_async(queue=celery_setup.worker.worker_queue).get(timeout=RESULT_TIMEOUT) == "pong" 36 | 37 | def test_sleep(self, celery_setup: CeleryTestSetup): 38 | assert sleep.s().apply_async(queue=celery_setup.worker.worker_queue).get(timeout=RESULT_TIMEOUT) is True 39 | 40 | def test_xsum(self, celery_setup: CeleryTestSetup): 41 | assert xsum.s([1, 2, 3]).apply_async(queue=celery_setup.worker.worker_queue).get(timeout=RESULT_TIMEOUT) == 6 42 | 43 | def test_xsum_nested_list(self, celery_setup: CeleryTestSetup): 44 | assert ( 45 | xsum.s([[1, 2], [3, 4], [5, 6]]) 46 | .apply_async(queue=celery_setup.worker.worker_queue) 47 | .get(timeout=RESULT_TIMEOUT) 48 | == 21 49 | ) 50 | -------------------------------------------------------------------------------- /tests/integration/vendors/test_localstack.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | from kombu import Connection 5 | 6 | from pytest_celery import LocalstackContainer 7 | from pytest_celery import LocalstackTestBroker 8 | from tests.defaults import ALL_LOCALSTACK_FIXTURES 9 | 10 | 11 | @pytest.fixture 12 | def container(request): 13 | return request.getfixturevalue(request.param) 14 | 15 | 16 | @pytest.mark.parametrize("container", ALL_LOCALSTACK_FIXTURES, indirect=["container"]) 17 | class test_localstack_container: 18 | def test_client(self, container: LocalstackContainer): 19 | c: Connection = container.client 20 | assert c 21 | try: 22 | assert c.connect() 23 | finally: 24 | c.release() 25 | 26 | def test_celeryconfig(self, container: LocalstackContainer): 27 | expected_keys = {"url", "host_url", "hostname", "port"} 28 | config = container.celeryconfig 29 | assert set(config.keys()) == expected_keys 30 | assert container.prefix() in config["url"] 31 | assert container.prefix() in config["host_url"] 32 | 33 | 34 | class test_localstack_test_broker: 35 | def test_config(self, celery_localstack_broker: LocalstackTestBroker): 36 | expected_keys = {"url", "host_url", "hostname", "port"} 37 | assert set(celery_localstack_broker.config().keys()) == expected_keys 38 | assert celery_localstack_broker.container.prefix() in celery_localstack_broker.config()["url"] 39 | assert celery_localstack_broker.container.prefix() in celery_localstack_broker.config()["host_url"] 40 | -------------------------------------------------------------------------------- /tests/integration/vendors/test_memcached.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import MemcachedContainer 6 | from pytest_celery import MemcachedTestBackend 7 | from tests.defaults import ALL_MEMCACHED_FIXTURES 8 | 9 | 10 | @pytest.fixture 11 | def container(request): 12 | return request.getfixturevalue(request.param) 13 | 14 | 15 | @pytest.mark.parametrize("container", ALL_MEMCACHED_FIXTURES, indirect=["container"]) 16 | class test_memcached_container: 17 | def test_client(self, container: MemcachedContainer): 18 | assert container.client 19 | assert not container.client.touch("ready", 1) 20 | assert container.client.set("ready", "1") 21 | assert container.client.get("ready") == "1" 22 | assert container.client.delete("ready") 23 | 24 | def test_celeryconfig(self, container: MemcachedContainer): 25 | expected_keys = {"url", "host_url", "hostname", "port"} 26 | config = container.celeryconfig 27 | assert set(config.keys()) == expected_keys 28 | assert container.prefix() in config["url"] 29 | assert container.prefix() in config["host_url"] 30 | 31 | 32 | class test_memcached_test_backend: 33 | def test_config(self, celery_memcached_backend: MemcachedTestBackend): 34 | expected_keys = {"url", "host_url", "hostname", "port"} 35 | assert set(celery_memcached_backend.config().keys()) == expected_keys 36 | assert celery_memcached_backend.container.prefix() in celery_memcached_backend.config()["url"] 37 | assert celery_memcached_backend.container.prefix() in celery_memcached_backend.config()["host_url"] 38 | -------------------------------------------------------------------------------- /tests/integration/vendors/test_rabbitmq.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | from kombu import Connection 5 | 6 | from pytest_celery import RabbitMQContainer 7 | from pytest_celery import RabbitMQTestBroker 8 | from tests.defaults import ALL_RABBITMQ_FIXTURES 9 | 10 | 11 | @pytest.fixture 12 | def container(request): 13 | return request.getfixturevalue(request.param) 14 | 15 | 16 | @pytest.mark.parametrize("container", ALL_RABBITMQ_FIXTURES, indirect=["container"]) 17 | class test_rabbitmq_container: 18 | def test_client(self, container: RabbitMQContainer): 19 | c: Connection = container.client 20 | assert c 21 | try: 22 | assert c.connect() 23 | finally: 24 | c.release() 25 | 26 | def test_celeryconfig(self, container: RabbitMQContainer): 27 | expected_keys = {"url", "host_url", "hostname", "port", "vhost"} 28 | config = container.celeryconfig 29 | assert set(config.keys()) == expected_keys 30 | assert container.prefix() in config["url"] 31 | assert container.prefix() in config["host_url"] 32 | 33 | 34 | class test_rabbitmq_test_broker: 35 | def test_config(self, celery_rabbitmq_broker: RabbitMQTestBroker): 36 | expected_keys = {"url", "host_url", "hostname", "port", "vhost"} 37 | assert set(celery_rabbitmq_broker.config().keys()) == expected_keys 38 | assert celery_rabbitmq_broker.container.prefix() in celery_rabbitmq_broker.config()["url"] 39 | assert celery_rabbitmq_broker.container.prefix() in celery_rabbitmq_broker.config()["host_url"] 40 | -------------------------------------------------------------------------------- /tests/integration/vendors/test_redis.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import RedisContainer 6 | from pytest_celery import RedisTestBackend 7 | from pytest_celery import RedisTestBroker 8 | from tests.defaults import ALL_REDIS_FIXTURES 9 | 10 | 11 | @pytest.fixture 12 | def container(request): 13 | return request.getfixturevalue(request.param) 14 | 15 | 16 | @pytest.mark.parametrize("container", ALL_REDIS_FIXTURES, indirect=["container"]) 17 | class test_redis_container: 18 | def test_client(self, container: RedisContainer): 19 | assert container.client 20 | assert container.client.ping() 21 | assert container.client.set("ready", "1") 22 | assert container.client.get("ready") == "1" 23 | assert container.client.delete("ready") 24 | 25 | def test_celeryconfig(self, container: RedisContainer): 26 | expected_keys = {"url", "host_url", "hostname", "port", "vhost"} 27 | config = container.celeryconfig 28 | assert set(config.keys()) == expected_keys 29 | assert container.prefix() in config["url"] 30 | assert container.prefix() in config["host_url"] 31 | 32 | 33 | class test_redis_test_backend: 34 | def test_config(self, celery_redis_backend: RedisTestBackend): 35 | expected_keys = {"url", "host_url", "hostname", "port", "vhost"} 36 | assert set(celery_redis_backend.config().keys()) == expected_keys 37 | assert celery_redis_backend.container.prefix() in celery_redis_backend.config()["url"] 38 | assert celery_redis_backend.container.prefix() in celery_redis_backend.config()["host_url"] 39 | 40 | 41 | class test_redis_test_broker: 42 | def test_config(self, celery_redis_broker: RedisTestBroker): 43 | expected_keys = {"url", "host_url", "hostname", "port", "vhost"} 44 | assert set(celery_redis_broker.config().keys()) == expected_keys 45 | assert celery_redis_broker.container.prefix() in celery_redis_broker.config()["url"] 46 | assert celery_redis_broker.container.prefix() in celery_redis_broker.config()["host_url"] 47 | -------------------------------------------------------------------------------- /tests/integration/vendors/test_worker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from types import ModuleType 4 | 5 | import pytest 6 | 7 | from pytest_celery import DEFAULT_WORKER_ENV 8 | from pytest_celery import CeleryBackendCluster 9 | from pytest_celery import CeleryTestWorker 10 | from pytest_celery import CeleryWorkerContainer 11 | from tests.defaults import ALL_WORKERS_FIXTURES 12 | 13 | 14 | @pytest.fixture 15 | def container(request): 16 | return request.getfixturevalue(request.param) 17 | 18 | 19 | @pytest.mark.parametrize("container", ALL_WORKERS_FIXTURES, indirect=["container"]) 20 | class test_celery_worker_container: 21 | def test_client(self, container: CeleryWorkerContainer): 22 | assert container.client 23 | assert container.client == container 24 | 25 | def test_celeryconfig(self, container: CeleryWorkerContainer): 26 | with pytest.raises(NotImplementedError): 27 | container.celeryconfig 28 | 29 | class test_disabling_cluster: 30 | @pytest.fixture 31 | def celery_backend_cluster(self) -> CeleryBackendCluster: 32 | return None 33 | 34 | def test_disabling_backend_cluster(self, container: CeleryWorkerContainer): 35 | assert container.logs().count("results: disabled://") 36 | 37 | results = DEFAULT_WORKER_ENV["CELERY_BROKER_URL"] 38 | assert container.logs().count(f"transport: {results}") 39 | 40 | class test_replacing_app_module: 41 | @pytest.fixture(params=["Default", "Custom"]) 42 | def default_worker_app_module(self, request: pytest.FixtureRequest) -> ModuleType: 43 | if request.param == "Default": 44 | return request.getfixturevalue("default_worker_app_module") 45 | else: 46 | from pytest_celery.vendors.worker.content import app 47 | 48 | return app 49 | 50 | def test_replacing_app_module(self, container: CeleryWorkerContainer, default_worker_app_module: ModuleType): 51 | assert container.app_module() == default_worker_app_module 52 | 53 | class test_replacing_command: 54 | @pytest.fixture 55 | def default_worker_command(self, default_worker_command: list[str]) -> list[str]: 56 | return [*default_worker_command, "--pool", "solo"] 57 | 58 | def test_replacing_command(self, container: CeleryWorkerContainer): 59 | assert container.logs().count("solo") == 1 60 | 61 | 62 | class test_base_test_worker: 63 | def test_config(self, celery_setup_worker: CeleryTestWorker): 64 | with pytest.raises(NotImplementedError): 65 | celery_setup_worker.config() 66 | -------------------------------------------------------------------------------- /tests/smoke/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/smoke/__init__.py -------------------------------------------------------------------------------- /tests/smoke/signals.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from celery.signals import worker_init 4 | from celery.signals import worker_process_init 5 | from celery.signals import worker_process_shutdown 6 | from celery.signals import worker_ready 7 | from celery.signals import worker_shutdown 8 | 9 | 10 | @worker_init.connect 11 | def worker_init_handler(sender, **kwargs): # type: ignore 12 | print("worker_init_handler") 13 | 14 | 15 | @worker_process_init.connect 16 | def worker_process_init_handler(sender, **kwargs): # type: ignore 17 | print("worker_process_init_handler") 18 | 19 | 20 | @worker_process_shutdown.connect 21 | def worker_process_shutdown_handler(sender, pid, exitcode, **kwargs): # type: ignore 22 | print("worker_process_shutdown_handler") 23 | 24 | 25 | @worker_ready.connect 26 | def worker_ready_handler(sender, **kwargs): # type: ignore 27 | print("worker_ready_handler") 28 | 29 | 30 | @worker_shutdown.connect 31 | def worker_shutdown_handler(sender, **kwargs): # type: ignore 32 | print("worker_shutdown_handler") 33 | -------------------------------------------------------------------------------- /tests/smoke/test_control.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pytest_celery import CeleryTestSetup 4 | 5 | 6 | class test_control: 7 | def test_control_ping(self, celery_setup: CeleryTestSetup): 8 | r = celery_setup.app.control.ping() 9 | assert all([all([res["ok"] == "pong" for _, res in response.items()]) for response in r]) 10 | -------------------------------------------------------------------------------- /tests/smoke/test_failover.py: -------------------------------------------------------------------------------- 1 | # mypy: disable-error-code="misc" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | from pytest_docker_tools import container 7 | from pytest_docker_tools import fxtr 8 | 9 | from pytest_celery import RABBITMQ_CONTAINER_TIMEOUT 10 | from pytest_celery import RESULT_TIMEOUT 11 | from pytest_celery import CeleryBrokerCluster 12 | from pytest_celery import CeleryTestSetup 13 | from pytest_celery import CeleryTestWorker 14 | from pytest_celery import RabbitMQContainer 15 | from pytest_celery import RabbitMQTestBroker 16 | from tests.tasks import identity 17 | 18 | failover_broker = container( 19 | image="{default_rabbitmq_broker_image}", 20 | ports=fxtr("default_rabbitmq_broker_ports"), 21 | environment=fxtr("default_rabbitmq_broker_env"), 22 | network="{default_pytest_celery_network.name}", 23 | wrapper_class=RabbitMQContainer, 24 | timeout=RABBITMQ_CONTAINER_TIMEOUT, 25 | ) 26 | 27 | 28 | @pytest.fixture 29 | def failover_rabbitmq_broker(failover_broker: RabbitMQContainer) -> RabbitMQTestBroker: 30 | broker = RabbitMQTestBroker(failover_broker) 31 | yield broker 32 | broker.teardown() 33 | 34 | 35 | @pytest.fixture 36 | def celery_broker_cluster( 37 | celery_rabbitmq_broker: RabbitMQTestBroker, 38 | failover_rabbitmq_broker: RabbitMQTestBroker, 39 | ) -> CeleryBrokerCluster: 40 | cluster = CeleryBrokerCluster(celery_rabbitmq_broker, failover_rabbitmq_broker) 41 | yield cluster 42 | cluster.teardown() 43 | 44 | 45 | class test_failover: 46 | def test_broker_failover(self, celery_setup: CeleryTestSetup): 47 | worker: CeleryTestWorker 48 | assert len(celery_setup.broker_cluster) > 1 49 | celery_setup.broker.kill() 50 | for worker in celery_setup.worker_cluster: 51 | expected = "test_broker_failover" 52 | res = identity.s(expected).apply_async(queue=worker.worker_queue) 53 | assert res.get(timeout=RESULT_TIMEOUT) == expected 54 | -------------------------------------------------------------------------------- /tests/smoke/test_signals.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | from celery.signals import after_task_publish 5 | from celery.signals import before_task_publish 6 | 7 | from pytest_celery import CeleryTestSetup 8 | from pytest_celery import LocalstackTestBroker 9 | from tests.tasks import noop 10 | 11 | 12 | @pytest.fixture 13 | def default_worker_signals(default_worker_signals: set) -> set: 14 | from tests.smoke import signals 15 | 16 | default_worker_signals.add(signals) 17 | return default_worker_signals 18 | 19 | 20 | class test_signals: 21 | @pytest.mark.parametrize( 22 | "log, control", 23 | [ 24 | ("worker_init_handler", None), 25 | ("worker_process_init_handler", None), 26 | ("worker_ready_handler", None), 27 | ("worker_process_shutdown_handler", "shutdown"), 28 | ("worker_shutdown_handler", "shutdown"), 29 | ], 30 | ) 31 | def test_sanity(self, celery_setup: CeleryTestSetup, log: str, control: str): 32 | if isinstance(celery_setup.broker, LocalstackTestBroker) and control == "shutdown": 33 | pytest.xfail("Potential real bug where shutdown signal isn't called with SQS broker") 34 | 35 | if control: 36 | celery_setup.app.control.broadcast(control) 37 | celery_setup.worker.assert_log_exists(log) 38 | 39 | def test_before_task_publish(self, celery_setup: CeleryTestSetup): 40 | @before_task_publish.connect 41 | def before_task_publish_handler(*args, **kwargs): 42 | nonlocal signal_was_called 43 | signal_was_called = True 44 | 45 | signal_was_called = False 46 | noop.s().apply_async(queue=celery_setup.worker.worker_queue) 47 | assert signal_was_called is True 48 | 49 | def test_after_task_publish(self, celery_setup: CeleryTestSetup): 50 | @after_task_publish.connect 51 | def after_task_publish_handler(*args, **kwargs): 52 | nonlocal signal_was_called 53 | signal_was_called = True 54 | 55 | signal_was_called = False 56 | noop.s().apply_async(queue=celery_setup.worker.worker_queue) 57 | assert signal_was_called is True 58 | -------------------------------------------------------------------------------- /tests/smoke/test_task.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from celery import signature 4 | 5 | from pytest_celery import RESULT_TIMEOUT 6 | from pytest_celery import CeleryTestSetup 7 | from tests.tasks import add 8 | from tests.tasks import identity 9 | from tests.tasks import replace_with_task 10 | 11 | 12 | class test_replace: 13 | def test_sanity(self, celery_setup: CeleryTestSetup): 14 | queues = [w.worker_queue for w in celery_setup.worker_cluster] 15 | if len(queues) < 2: 16 | queues.append(queues[0]) 17 | replace_with = signature(identity, args=(40,), queue=queues[1]) 18 | sig1 = replace_with_task.s(replace_with) 19 | sig2 = add.s(2).set(queue=queues[1]) 20 | c = sig1 | sig2 21 | r = c.apply_async(queue=queues[0]) 22 | assert r.get(timeout=RESULT_TIMEOUT) == 42 23 | -------------------------------------------------------------------------------- /tests/smoke/test_worker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import CeleryTestSetup 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "pool", 10 | [ 11 | "solo", 12 | "prefork", 13 | "threads", 14 | ], 15 | ) 16 | class test_replacing_pool: 17 | @pytest.fixture 18 | def default_worker_command(self, default_worker_command: list[str], pool: str) -> list[str]: 19 | return [*default_worker_command, "--pool", pool] 20 | 21 | def test_pool_from_celery_banner(self, celery_setup: CeleryTestSetup, pool: str): 22 | if pool == "threads": 23 | pool = "thread" 24 | celery_setup.worker.assert_log_exists(pool) 25 | -------------------------------------------------------------------------------- /tests/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import Task 2 | from celery import shared_task 3 | from celery import signature 4 | from celery.canvas import Signature 5 | 6 | from pytest_celery.vendors.worker.tasks import * # noqa 7 | 8 | 9 | @shared_task 10 | def replaced_with_me(): 11 | return True 12 | 13 | 14 | @shared_task(bind=True) 15 | def replace_with_task(self: Task, replace_with: Signature = None): 16 | if replace_with is None: 17 | replace_with = replaced_with_me.s() 18 | self.replace(signature(replace_with)) 19 | -------------------------------------------------------------------------------- /tests/test_pytest_celery.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import pytest_celery 4 | 5 | 6 | def test_version(): 7 | assert pytest_celery.VERSION 8 | assert len(pytest_celery.VERSION) >= 3 9 | pytest_celery.VERSION = (0, 3, 0) 10 | assert pytest_celery.__version__.count(".") >= 2 11 | 12 | 13 | @pytest.mark.parametrize( 14 | "attr", 15 | [ 16 | "__author__", 17 | "__contact__", 18 | "__homepage__", 19 | "__docformat__", 20 | ], 21 | ) 22 | def test_meta(attr): 23 | assert getattr(pytest_celery, attr, None) 24 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/unit/api/__init__.py -------------------------------------------------------------------------------- /tests/unit/api/test_backend.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | from celery import Celery 5 | 6 | from pytest_celery import DEFAULT_WORKER_ENV 7 | from pytest_celery import CeleryBackendCluster 8 | from pytest_celery import CeleryTestBackend 9 | 10 | 11 | class test_celey_test_backend: 12 | def test_default_config_format(self, celery_backend: CeleryTestBackend): 13 | assert celery_backend.default_config()["url"] == DEFAULT_WORKER_ENV["CELERY_RESULT_BACKEND"] 14 | assert celery_backend.default_config()["host_url"] == DEFAULT_WORKER_ENV["CELERY_RESULT_BACKEND"] 15 | 16 | def test_restart_no_app(self, celery_backend: CeleryTestBackend): 17 | assert celery_backend.app is None 18 | celery_backend.restart() 19 | 20 | def test_restart_with_app(self, celery_backend: CeleryTestBackend, celery_setup_app: Celery): 21 | celery_backend._app = celery_setup_app 22 | assert "result_backend" not in celery_setup_app.conf.changes 23 | celery_backend.restart() 24 | assert "result_backend" in celery_setup_app.conf.changes 25 | 26 | 27 | class test_celery_backend_cluster: 28 | def test_default_config_format(self, celery_backend_cluster: CeleryBackendCluster): 29 | assert celery_backend_cluster.default_config()["urls"] == [DEFAULT_WORKER_ENV["CELERY_RESULT_BACKEND"]] 30 | assert celery_backend_cluster.default_config()["host_urls"] == [DEFAULT_WORKER_ENV["CELERY_RESULT_BACKEND"]] 31 | 32 | class test_disabling_cluster: 33 | @pytest.fixture 34 | def celery_backend_cluster(self) -> CeleryBackendCluster: 35 | return None 36 | 37 | def test_disabling_backend_cluster( 38 | self, 39 | celery_backend_cluster: CeleryBackendCluster, 40 | celery_backend_cluster_config: dict, 41 | ): 42 | assert celery_backend_cluster is None 43 | assert celery_backend_cluster_config is None 44 | -------------------------------------------------------------------------------- /tests/unit/api/test_broker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | from celery import Celery 5 | 6 | from pytest_celery import DEFAULT_WORKER_ENV 7 | from pytest_celery import CeleryBrokerCluster 8 | from pytest_celery import CeleryTestBroker 9 | 10 | 11 | class test_celey_test_broker: 12 | def test_default_config_format(self, celery_broker: CeleryTestBroker): 13 | assert celery_broker.default_config()["url"] == DEFAULT_WORKER_ENV["CELERY_BROKER_URL"] 14 | assert celery_broker.default_config()["host_url"] == DEFAULT_WORKER_ENV["CELERY_BROKER_URL"] 15 | 16 | def test_restart_no_app(self, celery_broker: CeleryTestBroker): 17 | assert celery_broker.app is None 18 | celery_broker.restart() 19 | 20 | def test_restart_with_app(self, celery_broker: CeleryTestBroker, celery_setup_app: Celery): 21 | celery_broker._app = celery_setup_app 22 | assert "broker_url" not in celery_setup_app.conf.changes 23 | celery_broker.restart() 24 | assert "broker_url" in celery_setup_app.conf.changes 25 | 26 | 27 | class test_celery_broker_cluster: 28 | def test_default_config_format(self, celery_broker_cluster: CeleryBrokerCluster): 29 | assert celery_broker_cluster.default_config()["urls"] == [DEFAULT_WORKER_ENV["CELERY_BROKER_URL"]] 30 | assert celery_broker_cluster.default_config()["host_urls"] == [DEFAULT_WORKER_ENV["CELERY_BROKER_URL"]] 31 | 32 | class test_disabling_cluster: 33 | @pytest.fixture 34 | def celery_broker_cluster(self) -> CeleryBrokerCluster: 35 | return None 36 | 37 | def test_disabling_broker_cluster( 38 | self, celery_broker_cluster: CeleryBrokerCluster, celery_broker_cluster_config: dict 39 | ): 40 | assert celery_broker_cluster is None 41 | assert celery_broker_cluster_config is None 42 | -------------------------------------------------------------------------------- /tests/unit/api/test_container.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from unittest.mock import Mock 4 | 5 | import pytest 6 | 7 | from pytest_celery import CeleryTestContainer 8 | 9 | 10 | @pytest.fixture 11 | def mocked_container() -> CeleryTestContainer: 12 | docker_container_mock = Mock() 13 | return CeleryTestContainer(container=docker_container_mock) 14 | 15 | 16 | class test_celery_test_container: 17 | def test_client(self, mocked_container: CeleryTestContainer): 18 | with pytest.raises(NotImplementedError): 19 | mocked_container.client 20 | 21 | def test_celeryconfig(self, mocked_container: CeleryTestContainer): 22 | with pytest.raises(NotImplementedError): 23 | mocked_container.celeryconfig 24 | 25 | def test_command(self, mocked_container: CeleryTestContainer): 26 | with pytest.raises(NotImplementedError): 27 | mocked_container.command() 28 | 29 | def test_teardown(self, mocked_container: CeleryTestContainer): 30 | mocked_container.teardown() 31 | 32 | def test_ready_prompt(self, mocked_container: CeleryTestContainer): 33 | assert mocked_container.ready_prompt is None 34 | 35 | def test_wait_port(self, mocked_container: CeleryTestContainer): 36 | with pytest.raises(ValueError): 37 | mocked_container._wait_port(None) 38 | 39 | def test_wait_ready(self, mocked_container: CeleryTestContainer): 40 | assert mocked_container._wait_ready() 41 | -------------------------------------------------------------------------------- /tests/unit/api/test_worker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from celery import Celery 4 | 5 | from pytest_celery import CeleryTestWorker 6 | from pytest_celery import CeleryWorkerCluster 7 | 8 | 9 | class test_celey_test_worker: 10 | def test_app(self, celery_worker: CeleryTestWorker, celery_setup_app: Celery): 11 | assert celery_worker.app is celery_setup_app 12 | 13 | def test_version(self, celery_worker: CeleryTestWorker): 14 | celery_worker.version 15 | celery_worker.container.version.assert_called_once() 16 | 17 | def test_log_level(self, celery_worker: CeleryTestWorker): 18 | celery_worker.log_level 19 | celery_worker.container.log_level.assert_called_once() 20 | 21 | def test_worker_name(self, celery_worker: CeleryTestWorker): 22 | celery_worker.worker_name 23 | celery_worker.container.worker_name.assert_called_once() 24 | 25 | def test_worker_queue(self, celery_worker: CeleryTestWorker): 26 | celery_worker.worker_queue 27 | celery_worker.container.worker_queue.assert_called_once() 28 | 29 | 30 | class test_celery_worker_cluster: 31 | def test_app(self, celery_worker_cluster: CeleryWorkerCluster, celery_setup_app: Celery): 32 | for node in celery_worker_cluster: 33 | assert node.app is celery_setup_app 34 | 35 | def test_versions(self, celery_worker_cluster: CeleryWorkerCluster): 36 | celery_worker_cluster.versions 37 | for node in celery_worker_cluster: 38 | node.container.version.assert_called_once() 39 | -------------------------------------------------------------------------------- /tests/unit/conftest.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | 6 | from pytest_celery import CeleryWorkerContainer 7 | from pytest_celery import LocalstackContainer 8 | from pytest_celery import MemcachedContainer 9 | from pytest_celery import RabbitMQContainer 10 | from pytest_celery import RedisContainer 11 | 12 | 13 | @pytest.fixture(autouse=True) 14 | def mock_wait_for_callable(): 15 | with patch("pytest_celery.api.base.wait_for_callable", new=Mock()): 16 | with patch("pytest_celery.api.container.wait_for_callable", new=Mock()): 17 | yield 18 | 19 | 20 | def mocked_container(spec: type) -> Mock: 21 | mocked_container = Mock(spec=spec) 22 | mocked_container.id = "mocked_test_container_id" 23 | mocked_container.celeryconfig = { 24 | "url": "mocked_url/", 25 | "host_url": "mocked_host_url/", 26 | } 27 | return mocked_container 28 | 29 | 30 | @pytest.fixture 31 | def default_localstack_broker() -> LocalstackContainer: 32 | return mocked_container(LocalstackContainer) 33 | 34 | 35 | @pytest.fixture 36 | def default_memcached_backend() -> MemcachedContainer: 37 | return mocked_container(MemcachedContainer) 38 | 39 | 40 | @pytest.fixture 41 | def default_rabbitmq_broker() -> RabbitMQContainer: 42 | return mocked_container(RabbitMQContainer) 43 | 44 | 45 | @pytest.fixture 46 | def default_redis_backend() -> RedisContainer: 47 | return mocked_container(RedisContainer) 48 | 49 | 50 | @pytest.fixture 51 | def default_redis_broker() -> RedisContainer: 52 | return mocked_container(RedisContainer) 53 | 54 | 55 | @pytest.fixture 56 | def default_worker_container() -> CeleryWorkerContainer: 57 | return mocked_container(CeleryWorkerContainer) 58 | -------------------------------------------------------------------------------- /tests/unit/vendors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/unit/vendors/__init__.py -------------------------------------------------------------------------------- /tests/unit/vendors/test_localstack.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import LOCALSTACK_ENV 6 | from pytest_celery import LOCALSTACK_IMAGE 7 | from pytest_celery import LOCALSTACK_PORTS 8 | from pytest_celery import LOCALSTACK_PREFIX 9 | from pytest_celery import LocalstackContainer 10 | from pytest_celery import LocalstackTestBroker 11 | 12 | 13 | class test_localstack_container: 14 | def test_version(self): 15 | assert LocalstackContainer.version() == "localstack" 16 | 17 | def test_env(self): 18 | assert LocalstackContainer.initial_env() == LOCALSTACK_ENV 19 | 20 | def test_image(self): 21 | assert LocalstackContainer.image() == LOCALSTACK_IMAGE 22 | 23 | def test_ports(self): 24 | assert LocalstackContainer.ports() == LOCALSTACK_PORTS 25 | 26 | def test_prefix(self): 27 | assert LocalstackContainer.prefix() == LOCALSTACK_PREFIX 28 | 29 | 30 | class test_localstack_broker_api: 31 | @pytest.mark.skip(reason="Placeholder") 32 | def test_placeholder(self, celery_localstack_broker: LocalstackTestBroker): 33 | # The class LocalstackTestBroker is currently a placeholder 34 | # so we don't have any specific tests for it yet. 35 | # This test suite is pre-configured to test the LocalstackTestBroker 36 | # and ready to be used once the class is implemented. 37 | pass 38 | -------------------------------------------------------------------------------- /tests/unit/vendors/test_memcached.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import MEMCACHED_ENV 6 | from pytest_celery import MEMCACHED_IMAGE 7 | from pytest_celery import MEMCACHED_PORTS 8 | from pytest_celery import MEMCACHED_PREFIX 9 | from pytest_celery import MemcachedContainer 10 | from pytest_celery import MemcachedTestBackend 11 | 12 | 13 | class test_memcached_container: 14 | def test_version(self): 15 | assert MemcachedContainer.version() == "latest" 16 | 17 | def test_env(self): 18 | assert MemcachedContainer.initial_env() == MEMCACHED_ENV 19 | 20 | def test_image(self): 21 | assert MemcachedContainer.image() == MEMCACHED_IMAGE 22 | 23 | def test_ports(self): 24 | assert MemcachedContainer.ports() == MEMCACHED_PORTS 25 | 26 | def test_prefix(self): 27 | assert MemcachedContainer.prefix() == MEMCACHED_PREFIX 28 | 29 | 30 | class test_memcached_backend_api: 31 | @pytest.mark.skip(reason="Placeholder") 32 | def test_placeholder(self, celery_memcached_backend: MemcachedTestBackend): 33 | # The class MemcachedTestBackend is currently a placeholder 34 | # so we don't have any specific tests for it yet. 35 | # This test suite is pre-configured to test the MemcachedTestBackend 36 | # and ready to be used once the class is implemented. 37 | pass 38 | -------------------------------------------------------------------------------- /tests/unit/vendors/test_rabbitmq.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import RABBITMQ_ENV 6 | from pytest_celery import RABBITMQ_IMAGE 7 | from pytest_celery import RABBITMQ_PORTS 8 | from pytest_celery import RABBITMQ_PREFIX 9 | from pytest_celery import RabbitMQContainer 10 | from pytest_celery import RabbitMQTestBroker 11 | 12 | 13 | class test_rabbitmq_container: 14 | def test_version(self): 15 | assert RabbitMQContainer.version() == "latest" 16 | 17 | def test_env(self): 18 | assert RabbitMQContainer.initial_env() == RABBITMQ_ENV 19 | 20 | def test_image(self): 21 | assert RabbitMQContainer.image() == RABBITMQ_IMAGE 22 | 23 | def test_ports(self): 24 | assert RabbitMQContainer.ports() == RABBITMQ_PORTS 25 | 26 | def test_prefix(self): 27 | assert RabbitMQContainer.prefix() == RABBITMQ_PREFIX 28 | 29 | 30 | class test_rabbitmq_broker_api: 31 | @pytest.mark.skip(reason="Placeholder") 32 | def test_placeholder(self, celery_rabbitmq_broker: RabbitMQTestBroker): 33 | # The class RabbitMQTestBroker is currently a placeholder 34 | # so we don't have any specific tests for it yet. 35 | # This test suite is pre-configured to test the RabbitMQTestBroker 36 | # and ready to be used once the class is implemented. 37 | pass 38 | -------------------------------------------------------------------------------- /tests/unit/vendors/test_redis.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from pytest_celery import REDIS_ENV 6 | from pytest_celery import REDIS_IMAGE 7 | from pytest_celery import REDIS_PORTS 8 | from pytest_celery import REDIS_PREFIX 9 | from pytest_celery import RedisContainer 10 | from pytest_celery import RedisTestBackend 11 | from pytest_celery import RedisTestBroker 12 | 13 | 14 | class test_redis_container: 15 | def test_version(self): 16 | assert RedisContainer.version() == "latest" 17 | 18 | def test_env(self): 19 | assert RedisContainer.initial_env() == REDIS_ENV 20 | 21 | def test_image(self): 22 | assert RedisContainer.image() == REDIS_IMAGE 23 | 24 | def test_ports(self): 25 | assert RedisContainer.ports() == REDIS_PORTS 26 | 27 | def test_prefix(self): 28 | assert RedisContainer.prefix() == REDIS_PREFIX 29 | 30 | 31 | class test_redis_backend_api: 32 | @pytest.mark.skip(reason="RedisTestBackend.teardown() breaks the testing environment") 33 | def test_teardown(self, celery_redis_backend: RedisTestBackend): 34 | celery_redis_backend.teardown() 35 | celery_redis_backend.container.teardown.assert_called_once() 36 | 37 | 38 | class test_redis_broker_api: 39 | @pytest.mark.skip(reason="Placeholder") 40 | def test_placeholder(self, celery_redis_broker: RedisTestBroker): 41 | # The class RedisTestBroker is currently a placeholder 42 | # so we don't have any specific tests for it yet. 43 | # This test suite is pre-configured to test the RedisTestBroker 44 | # and ready to be used once the class is implemented. 45 | pass 46 | -------------------------------------------------------------------------------- /tests/unit/vendors/test_worker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celery/pytest-celery/4d1a1dbcf340abab43d773b928f824a5a4ce7f08/tests/unit/vendors/test_worker/__init__.py -------------------------------------------------------------------------------- /tests/unit/vendors/test_worker/test_default_tasks.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | 3 | import pytest 4 | from celery.exceptions import Ignore 5 | 6 | from pytest_celery import add 7 | from pytest_celery import add_replaced 8 | from pytest_celery import fail 9 | from pytest_celery import identity 10 | from pytest_celery import noop 11 | from pytest_celery import ping 12 | from pytest_celery import sleep 13 | from pytest_celery import xsum 14 | 15 | 16 | class test_default_tasks: 17 | def test_add(self): 18 | assert add(1, 2) == 3 19 | 20 | def test_add_replaced(self): 21 | with patch("pytest_celery.add_replaced.replace", side_effect=Ignore): 22 | with pytest.raises(Ignore): 23 | add_replaced(1, 2) 24 | 25 | def test_fail(self): 26 | with pytest.raises(RuntimeError): 27 | fail() 28 | 29 | def test_identity(self): 30 | assert identity(1) == 1 31 | 32 | def test_noop(self): 33 | assert noop() is None 34 | 35 | def test_ping(self): 36 | assert ping() == "pong" 37 | 38 | def test_sleep(self): 39 | assert sleep() is True 40 | 41 | def test_xsum(self): 42 | assert xsum([1, 2, 3]) == 6 43 | 44 | def test_xsum_nested_list(self): 45 | assert xsum([[1, 2], [3, 4], [5, 6]]) == 21 46 | --------------------------------------------------------------------------------