pax_global_header00006660000000000000000000000064147354164020014520gustar00rootroot0000000000000052 comment=623ab74b80b6f748e88254714f9b21a16eb7911e pytest-asyncio-0.25.1/000077500000000000000000000000001473541640200146005ustar00rootroot00000000000000pytest-asyncio-0.25.1/.github/000077500000000000000000000000001473541640200161405ustar00rootroot00000000000000pytest-asyncio-0.25.1/.github/CODEOWNERS000066400000000000000000000000361473541640200175320ustar00rootroot00000000000000* @asvetlov @seifertm @Tinche pytest-asyncio-0.25.1/.github/actionlint-matcher.json000066400000000000000000000006631473541640200226250ustar00rootroot00000000000000{ "problemMatcher": [ { "owner": "actionlint", "pattern": [ { "code": 5, "column": 3, "file": 1, "line": 2, "message": 4, "regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$" } ] } ] } pytest-asyncio-0.25.1/.github/dependabot.yml000066400000000000000000000006531473541640200207740ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: pip directory: /dependencies/default schedule: interval: weekly open-pull-requests-limit: 10 target-branch: main - package-ecosystem: pip directory: /dependencies/docs schedule: interval: weekly open-pull-requests-limit: 10 target-branch: main - package-ecosystem: github-actions directory: / schedule: interval: daily open-pull-requests-limit: 10 pytest-asyncio-0.25.1/.github/workflows/000077500000000000000000000000001473541640200201755ustar00rootroot00000000000000pytest-asyncio-0.25.1/.github/workflows/main.yml000066400000000000000000000111731473541640200216470ustar00rootroot00000000000000--- name: CI on: push: branches: [main] tags: [v*] pull_request: branches: [main] merge_group: workflow_dispatch: env: PYTHON_LATEST: 3.13 jobs: lint: name: Run linters runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} prerelease: ${{ steps.version.outputs.prerelease }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_LATEST }} - name: Install GitHub matcher for ActionLint checker run: | echo "::add-matcher::.github/actionlint-matcher.json" - name: Install pre-commit run: python -m pip install pre-commit - name: Run pre-commit checks run: pre-commit run --all-files --show-diff-on-failure - name: Install check-wheel-content, and twine run: python -m pip install build check-wheel-contents twine - name: Build package run: python -m build - name: List result run: ls -l dist - name: Check wheel contents run: check-wheel-contents dist/*.whl - name: Check long_description run: python -m twine check dist/* - name: Install pytest-asyncio run: pip install . - name: Get version info id: version run: python ./tools/get-version.py >> $GITHUB_OUTPUT - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: dist path: dist test: name: ${{ matrix.os }} - Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }}-latest strategy: matrix: os: [ubuntu, windows] python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 if: "!endsWith(matrix.python-version, '-dev')" with: python-version: ${{ matrix.python-version }} - uses: deadsnakes/action@v3.2.0 if: endsWith(matrix.python-version, '-dev') with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -VV python -m site python -m pip install --upgrade pip python -m pip install --upgrade coverage[toml] virtualenv tox tox-gh-actions - name: Run tox targets for ${{ matrix.python-version }} run: python -m tox - name: Store coverage data uses: actions/upload-artifact@v4 if: "!endsWith(matrix.os, 'windows')" with: name: coverage-python-${{ matrix.python-version }} path: coverage/coverage.* if-no-files-found: error check: name: Check if: always() needs: [lint, test] runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_LATEST }} - name: Install Coverage.py run: | set -xe python -m pip install --upgrade coverage[toml] - name: Download coverage data for all test runs uses: actions/download-artifact@v4 with: pattern: coverage-* path: coverage merge-multiple: true - name: Combine coverage data and create report run: | coverage combine coverage xml - name: Upload coverage report uses: codecov/codecov-action@v5 with: files: coverage.xml fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} deploy: name: Deploy environment: release # Run only on pushing a tag if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') needs: [lint, check] runs-on: ubuntu-latest steps: - name: Install pandoc run: | sudo apt-get install -y pandoc - name: Checkout uses: actions/checkout@v4 - name: Download distributions uses: actions/download-artifact@v4 with: name: dist path: dist - name: Collected dists run: | tree dist - name: Convert README.rst to Markdown run: | pandoc -s -o README.md README.rst - name: PyPI upload uses: pypa/gh-action-pypi-publish@v1.12.3 with: attestations: true packages-dir: dist password: ${{ secrets.PYPI_API_TOKEN }} - name: GitHub Release uses: ncipollo/release-action@v1 with: name: pytest-asyncio ${{ needs.lint.outputs.version }} artifacts: dist/* bodyFile: README.md prerelease: ${{ needs.lint.outputs.prerelease }} token: ${{ secrets.GITHUB_TOKEN }} pytest-asyncio-0.25.1/.gitignore000066400000000000000000000014601473541640200165710ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg .hypothesis/ # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ coverage/ .pytest_cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ .venv* .idea .vscode # pyenv .python-version # generated by setuptools_scm pytest_asyncio/_version.py pytest-asyncio-0.25.1/.pre-commit-config.yaml000066400000000000000000000034211473541640200210610ustar00rootroot00000000000000--- repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-merge-conflict exclude: rst$ - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.8.4 hooks: - id: ruff args: [--fix] - repo: https://github.com/asottile/yesqa rev: v1.5.0 hooks: - id: yesqa - repo: https://github.com/Zac-HD/shed rev: 2024.10.1 hooks: - id: shed args: - --refactor types_or: - python - markdown - rst - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt rev: 0.2.3 hooks: - id: yamlfmt args: [--mapping, '2', --sequence, '2', --offset, '0'] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: fix-encoding-pragma args: [--remove] - id: check-case-conflict - id: check-json - id: check-xml - id: check-yaml - id: debug-statements - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.14.0 hooks: - id: mypy exclude: ^(docs|tests)/.* additional_dependencies: - pytest - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: - id: python-use-type-annotations - repo: https://github.com/rhysd/actionlint rev: v1.7.5 hooks: - id: actionlint-docker args: - -ignore - 'SC2155:' - -ignore - 'SC2086:' - -ignore - 'SC1004:' stages: [manual] - repo: https://github.com/sirosen/check-jsonschema rev: 0.30.0 hooks: - id: check-github-actions - repo: https://github.com/tox-dev/pyproject-fmt rev: v2.5.0 hooks: - id: pyproject-fmt # https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version additional_dependencies: [tox>=4.9] ci: skip: - actionlint-docker - check-github-actions pytest-asyncio-0.25.1/.readthedocs.yaml000066400000000000000000000011151473541640200200250ustar00rootroot00000000000000--- version: 2 build: os: ubuntu-24.04 tools: python: >- 3.12 commands: - >- PYTHONWARNINGS=error python3 -Im venv "${READTHEDOCS_VIRTUALENV_PATH}" - >- PYTHONWARNINGS=error "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im pip install tox - >- PYTHONWARNINGS=error "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im tox -e docs --notest -vvvvv - >- PYTHONWARNINGS=error "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im tox -e docs --skip-pkg-install -q -- "${READTHEDOCS_OUTPUT}"/html -b html -D language=en pytest-asyncio-0.25.1/LICENSE000066400000000000000000000260741473541640200156160ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. pytest-asyncio-0.25.1/MANIFEST.in000066400000000000000000000001171473541640200163350ustar00rootroot00000000000000recursive-exclude .github * exclude .gitignore exclude .pre-commit-config.yaml pytest-asyncio-0.25.1/Makefile000066400000000000000000000013131473541640200162360ustar00rootroot00000000000000.PHONY: clean clean-build clean-pyc clean-test lint test clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts clean-build: ## remove build artifacts rm -fr build/ rm -fr dist/ rm -fr .eggs/ find . -name '*.egg-info' -exec rm -fr {} + find . -name '*.egg' -exec rm -f {} + clean-pyc: ## remove Python file artifacts find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + clean-test: ## remove test and coverage artifacts rm -fr .tox/ rm -fr coverage/ rm -fr htmlcov/ test: coverage run -m pytest install: pip install -U pre-commit pre-commit install pytest-asyncio-0.25.1/README.rst000066400000000000000000000045251473541640200162750ustar00rootroot00000000000000pytest-asyncio ============== .. image:: https://img.shields.io/pypi/v/pytest-asyncio.svg :target: https://pypi.python.org/pypi/pytest-asyncio .. image:: https://github.com/pytest-dev/pytest-asyncio/workflows/CI/badge.svg :target: https://github.com/pytest-dev/pytest-asyncio/actions?workflow=CI .. image:: https://codecov.io/gh/pytest-dev/pytest-asyncio/branch/main/graph/badge.svg :target: https://codecov.io/gh/pytest-dev/pytest-asyncio .. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg :target: https://github.com/pytest-dev/pytest-asyncio :alt: Supported Python versions .. image:: https://img.shields.io/badge/Matrix-%23pytest--asyncio-brightgreen :alt: Matrix chat room: #pytest-asyncio :target: https://matrix.to/#/#pytest-asyncio:matrix.org `pytest-asyncio `_ is a `pytest `_ plugin. It facilitates testing of code that uses the `asyncio `_ library. Specifically, pytest-asyncio provides support for coroutines as test functions. This allows users to *await* code inside their tests. For example, the following code is executed as a test item by pytest: .. code-block:: python @pytest.mark.asyncio async def test_some_asyncio_code(): res = await library.do_something() assert b"expected result" == res More details can be found in the `documentation `_. Note that test classes subclassing the standard `unittest `__ library are not supported. Users are advised to use `unittest.IsolatedAsyncioTestCase `__ or an async framework such as `asynctest `__. pytest-asyncio is available under the `Apache License 2.0 `_. Installation ------------ To install pytest-asyncio, simply: .. code-block:: bash $ pip install pytest-asyncio This is enough for pytest to pick up pytest-asyncio. Contributing ------------ Contributions are very welcome. Tests can be run with ``tox``, please ensure the coverage at least stays the same before you submit a pull request. pytest-asyncio-0.25.1/SECURITY.rst000066400000000000000000000003361473541640200166030ustar00rootroot00000000000000Security contact information ============================ To report a security vulnerability, please use the `Tidelift security contact. `__ Tidelift will coordinate the fix and disclosure. pytest-asyncio-0.25.1/dependencies/000077500000000000000000000000001473541640200172265ustar00rootroot00000000000000pytest-asyncio-0.25.1/dependencies/default/000077500000000000000000000000001473541640200206525ustar00rootroot00000000000000pytest-asyncio-0.25.1/dependencies/default/constraints.txt000066400000000000000000000003051473541640200237600ustar00rootroot00000000000000attrs==24.3.0 coverage==7.6.10 exceptiongroup==1.2.2 hypothesis==6.123.2 iniconfig==2.0.0 packaging==24.2 pluggy==1.5.0 pytest==8.3.4 sortedcontainers==2.4.0 tomli==2.2.1 typing_extensions==4.12.2 pytest-asyncio-0.25.1/dependencies/default/requirements.txt000066400000000000000000000002041473541640200241320ustar00rootroot00000000000000# Always adjust install_requires in setup.cfg and pytest-min-requirements.txt # when changing runtime dependencies pytest >= 8.2,<9 pytest-asyncio-0.25.1/dependencies/docs/000077500000000000000000000000001473541640200201565ustar00rootroot00000000000000pytest-asyncio-0.25.1/dependencies/docs/constraints.txt000066400000000000000000000007521473541640200232720ustar00rootroot00000000000000alabaster==0.7.16 Babel==2.16.0 certifi==2024.12.14 charset-normalizer==3.4.1 docutils==0.21.2 idna==3.10 imagesize==1.4.1 Jinja2==3.1.5 MarkupSafe==3.0.2 packaging==24.2 Pygments==2.18.0 requests==2.32.3 snowballstemmer==2.2.0 Sphinx==8.0.2 sphinx-rtd-theme==3.0.2 sphinxcontrib-applehelp==2.0.0 sphinxcontrib-devhelp==2.0.0 sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jquery==4.1 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 urllib3==2.3.0 pytest-asyncio-0.25.1/dependencies/docs/requirements.txt000066400000000000000000000000461473541640200234420ustar00rootroot00000000000000sphinx >= 5.3 sphinx-rtd-theme >= 1.0 pytest-asyncio-0.25.1/dependencies/pytest-min/000077500000000000000000000000001473541640200213375ustar00rootroot00000000000000pytest-asyncio-0.25.1/dependencies/pytest-min/constraints.txt000066400000000000000000000005531473541640200244520ustar00rootroot00000000000000argcomplete==3.1.2 attrs==23.1.0 certifi==2023.7.22 charset-normalizer==3.3.1 coverage==7.3.2 elementpath==4.1.5 exceptiongroup==1.1.3 hypothesis==6.88.3 idna==3.4 iniconfig==2.0.0 mock==5.1.0 nose==1.3.7 packaging==23.2 pluggy==1.5.0 py==1.11.0 Pygments==2.16.1 pytest==8.2.0 requests==2.31.0 sortedcontainers==2.4.0 tomli==2.0.1 urllib3==2.0.7 xmlschema==2.5.0 pytest-asyncio-0.25.1/dependencies/pytest-min/requirements.txt000066400000000000000000000002111473541640200246150ustar00rootroot00000000000000# Always adjust install_requires in setup.cfg and requirements.txt # when changing minimum version dependencies pytest[testing] == 8.2.0 pytest-asyncio-0.25.1/docs/000077500000000000000000000000001473541640200155305ustar00rootroot00000000000000pytest-asyncio-0.25.1/docs/concepts.rst000066400000000000000000000076311473541640200201070ustar00rootroot00000000000000======== Concepts ======== .. _concepts/event_loops: asyncio event loops =================== In order to understand how pytest-asyncio works, it helps to understand how pytest collectors work. If you already know about pytest collectors, please :ref:`skip ahead `. Otherwise, continue reading. Let's assume we have a test suite with a file named *test_all_the_things.py* holding a single test, async or not: .. include:: concepts_function_scope_example.py :code: python The file *test_all_the_things.py* is a Python module with a Python test function. When we run pytest, the test runner descends into Python packages, modules, and classes, in order to find all tests, regardless whether the tests will run or not. This process is referred to as *test collection* by pytest. In our particular example, pytest will find our test module and the test function. We can visualize the collection result by running ``pytest --collect-only``:: The example illustrates that the code of our test suite is hierarchical. Pytest uses so called *collectors* for each level of the hierarchy. Our contrived example test suite uses the *Module* and *Function* collectors, but real world test code may contain additional hierarchy levels via the *Package* or *Class* collectors. There's also a special *Session* collector at the root of the hierarchy. You may notice that the individual levels resemble the possible `scopes of a pytest fixture. `__ .. _pytest-asyncio-event-loops: Pytest-asyncio provides one asyncio event loop for each pytest collector. By default, each test runs in the event loop provided by the *Function* collector, i.e. tests use the loop with the narrowest scope. This gives the highest level of isolation between tests. If two or more tests share a common ancestor collector, the tests can be configured to run in their ancestor's loop by passing the appropriate *loop_scope* keyword argument to the *asyncio* mark. For example, the following two tests use the asyncio event loop provided by the *Module* collector: .. include:: concepts_module_scope_example.py :code: python It's highly recommended for neighboring tests to use the same event loop scope. For example, all tests in a class or module should use the same scope. Assigning neighboring tests to different event loop scopes is discouraged as it can make test code hard to follow. Test discovery modes ==================== Pytest-asyncio provides two modes for test discovery, *strict* and *auto*. Strict mode ----------- In strict mode pytest-asyncio will only run tests that have the *asyncio* marker and will only evaluate async fixtures decorated with ``@pytest_asyncio.fixture``. Test functions and fixtures without these markers and decorators will not be handled by pytest-asyncio. This mode is intended for projects that want so support multiple asynchronous programming libraries as it allows pytest-asyncio to coexist with other async testing plugins in the same codebase. Pytest automatically enables installed plugins. As a result pytest plugins need to coexist peacefully in their default configuration. This is why strict mode is the default mode. Auto mode --------- In *auto* mode pytest-asyncio automatically adds the *asyncio* marker to all asynchronous test functions. It will also take ownership of all async fixtures, regardless of whether they are decorated with ``@pytest.fixture`` or ``@pytest_asyncio.fixture``. This mode is intended for projects that use *asyncio* as their only asynchronous programming library. Auto mode makes for the simplest test and fixture configuration and is the recommended default. If you intend to support multiple asynchronous programming libraries, e.g. *asyncio* and *trio*, strict mode will be the preferred option. pytest-asyncio-0.25.1/docs/concepts_function_scope_example.py000066400000000000000000000001741473541640200245330ustar00rootroot00000000000000import asyncio import pytest @pytest.mark.asyncio async def test_runs_in_a_loop(): assert asyncio.get_running_loop() pytest-asyncio-0.25.1/docs/concepts_module_scope_example.py000066400000000000000000000005141473541640200241710ustar00rootroot00000000000000import asyncio import pytest loop: asyncio.AbstractEventLoop @pytest.mark.asyncio(loop_scope="module") async def test_remember_loop(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="module") async def test_runs_in_a_loop(): global loop assert asyncio.get_running_loop() is loop pytest-asyncio-0.25.1/docs/conf.py000066400000000000000000000017531473541640200170350ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information import importlib.metadata project = "pytest-asyncio" copyright = "2023, pytest-asyncio contributors" author = "Tin Tvrtković" release = importlib.metadata.version(project) # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [] templates_path = ["_templates"] exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinx_rtd_theme" html_static_path = [] pytest-asyncio-0.25.1/docs/how-to-guides/000077500000000000000000000000001473541640200202235ustar00rootroot00000000000000pytest-asyncio-0.25.1/docs/how-to-guides/change_default_fixture_loop.rst000066400000000000000000000015421473541640200265070ustar00rootroot00000000000000========================================================== How to change the default event loop scope of all fixtures ========================================================== The :ref:`configuration/asyncio_default_fixture_loop_scope` configuration option sets the default event loop scope for asynchronous fixtures. The following code snippets configure all fixtures to run in a session-scoped loop by default: .. code-block:: ini :caption: pytest.ini [pytest] asyncio_default_fixture_loop_scope = session .. code-block:: toml :caption: pyproject.toml [tool.pytest.ini_options] asyncio_default_fixture_loop_scope = "session" .. code-block:: ini :caption: setup.cfg [tool:pytest] asyncio_default_fixture_loop_scope = session Please refer to :ref:`configuration/asyncio_default_fixture_loop_scope` for other valid scopes. pytest-asyncio-0.25.1/docs/how-to-guides/change_fixture_loop.rst000066400000000000000000000006641473541640200250070ustar00rootroot00000000000000=============================================== How to change the event loop scope of a fixture =============================================== The event loop scope of an asynchronous fixture is specified via the *loop_scope* keyword argument to :ref:`pytest_asyncio.fixture `. The following fixture runs in the module-scoped event loop: .. include:: change_fixture_loop_example.py :code: python pytest-asyncio-0.25.1/docs/how-to-guides/change_fixture_loop_example.py000066400000000000000000000004701473541640200263350ustar00rootroot00000000000000import asyncio import pytest import pytest_asyncio @pytest_asyncio.fixture(loop_scope="module") async def current_loop(): return asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="module") async def test_runs_in_module_loop(current_loop): assert current_loop is asyncio.get_running_loop() pytest-asyncio-0.25.1/docs/how-to-guides/class_scoped_loop_example.py000066400000000000000000000005661473541640200260120ustar00rootroot00000000000000import asyncio import pytest @pytest.mark.asyncio(loop_scope="class") class TestInOneEventLoopPerClass: loop: asyncio.AbstractEventLoop async def test_remember_loop(self): TestInOneEventLoopPerClass.loop = asyncio.get_running_loop() async def test_assert_same_loop(self): assert asyncio.get_running_loop() is TestInOneEventLoopPerClass.loop pytest-asyncio-0.25.1/docs/how-to-guides/index.rst000066400000000000000000000007101473541640200220620ustar00rootroot00000000000000============= How-To Guides ============= .. toctree:: :hidden: migrate_from_0_21 migrate_from_0_23 change_fixture_loop change_default_fixture_loop run_class_tests_in_same_loop run_module_tests_in_same_loop run_package_tests_in_same_loop run_session_tests_in_same_loop multiple_loops uvloop test_item_is_async This section of the documentation provides code snippets and recipes to accomplish specific tasks with pytest-asyncio. pytest-asyncio-0.25.1/docs/how-to-guides/migrate_from_0_21.rst000066400000000000000000000037441473541640200241610ustar00rootroot00000000000000.. _how_to_guides/migrate_from_0_21: ======================================== How to migrate from pytest-asyncio v0.21 ======================================== 1. If your test suite re-implements the *event_loop* fixture, make sure the fixture implementations don't do anything besides creating a new asyncio event loop, yielding it, and closing it. 2. Convert all synchronous test cases requesting the *event_loop* fixture to asynchronous test cases. 3. Convert all synchronous fixtures requesting the *event_loop* fixture to asynchronous fixtures. 4. Remove the *event_loop* argument from all asynchronous test cases in favor of ``event_loop = asyncio.get_running_loop()``. 5. Remove the *event_loop* argument from all asynchronous fixtures in favor of ``event_loop = asyncio.get_running_loop()``. Go through all re-implemented *event_loop* fixtures in your test suite one by one, starting with the the fixture with the deepest nesting level and take note of the fixture scope: 1. For all tests and fixtures affected by the re-implemented *event_loop* fixture, configure the *loop_scope* for async tests and fixtures to match the *event_loop* fixture scope. This can be done for each test and fixture individually using either the ``pytest.mark.asyncio(loop_scope="…")`` marker for async tests or ``@pytest_asyncio.fixture(loop_scope="…")`` for async fixtures. Alternatively, you can set the default loop scope for fixtures using the :ref:`asyncio_default_fixture_loop_scope ` configuration option. Snippets to mark all tests with the same *asyncio* marker, thus sharing the same loop scope, are present in the how-to section of the documentation. Depending on the homogeneity of your test suite, you may want a mixture of explicit decorators and default settings. 2. Remove the re-implemented *event_loop* fixture. If you haven't set the *asyncio_default_fixture_loop_scope* configuration option, yet, set it to *function* to silence the deprecation warning. pytest-asyncio-0.25.1/docs/how-to-guides/migrate_from_0_23.rst000066400000000000000000000023701473541640200241550ustar00rootroot00000000000000======================================== How to migrate from pytest-asyncio v0.23 ======================================== The following steps assume that your test suite has no re-implementations of the *event_loop* fixture, nor explicit fixtures requests for it. If this isn't the case, please follow the :ref:`migration guide for pytest-asyncio v0.21. ` 1. Explicitly set the *loop_scope* of async fixtures by replacing occurrences of ``@pytest.fixture(scope="…")`` and ``@pytest_asyncio.fixture(scope="…")`` with ``@pytest_asyncio.fixture(loop_scope="…", scope="…")`` such that *loop_scope* and *scope* are the same. If you use auto mode, resolve all import errors from missing imports of *pytest_asyncio*. If your async fixtures all use the same *loop_scope*, you may choose to set the *asyncio_default_fixture_loop_scope* configuration option to that loop scope, instead. 2. If you haven't set *asyncio_default_fixture_loop_scope*, set it to *function* to address the deprecation warning about the unset configuration option. 3. Change all occurrences of ``pytest.mark.asyncio(scope="…")`` to ``pytest.mark.asyncio(loop_scope="…")`` to address the deprecation warning about the *scope* argument to the *asyncio* marker. pytest-asyncio-0.25.1/docs/how-to-guides/module_scoped_loop_example.py000066400000000000000000000004611473541640200261640ustar00rootroot00000000000000import asyncio import pytest pytestmark = pytest.mark.asyncio(loop_scope="module") loop: asyncio.AbstractEventLoop async def test_remember_loop(): global loop loop = asyncio.get_running_loop() async def test_assert_same_loop(): global loop assert asyncio.get_running_loop() is loop pytest-asyncio-0.25.1/docs/how-to-guides/multiple_loops.rst000066400000000000000000000010251473541640200240220ustar00rootroot00000000000000====================================== How to test with different event loops ====================================== Parametrizing the *event_loop_policy* fixture parametrizes all async tests. The following example causes all async tests to run multiple times, once for each event loop in the fixture parameters: .. include:: multiple_loops_example.py :code: python You may choose to limit the scope of the fixture to *package,* *module,* or *class,* if you only want a subset of your tests to run with different event loops. pytest-asyncio-0.25.1/docs/how-to-guides/multiple_loops_example.py000066400000000000000000000007271473541640200253650ustar00rootroot00000000000000import asyncio from asyncio import DefaultEventLoopPolicy import pytest class CustomEventLoopPolicy(DefaultEventLoopPolicy): pass @pytest.fixture( scope="session", params=( CustomEventLoopPolicy(), CustomEventLoopPolicy(), ), ) def event_loop_policy(request): return request.param @pytest.mark.asyncio async def test_uses_custom_event_loop_policy(): assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy) pytest-asyncio-0.25.1/docs/how-to-guides/package_scoped_loop_example.py000066400000000000000000000001061473541640200262660ustar00rootroot00000000000000import pytest pytestmark = pytest.mark.asyncio(loop_scope="package") pytest-asyncio-0.25.1/docs/how-to-guides/run_class_tests_in_same_loop.rst000066400000000000000000000006401473541640200267140ustar00rootroot00000000000000====================================================== How to run all tests in a class in the same event loop ====================================================== All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="class")``. This is easily achieved by using the *asyncio* marker as a class decorator. .. include:: class_scoped_loop_example.py :code: python pytest-asyncio-0.25.1/docs/how-to-guides/run_module_tests_in_same_loop.rst000066400000000000000000000006441473541640200271000ustar00rootroot00000000000000======================================================= How to run all tests in a module in the same event loop ======================================================= All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="module")``. This is easily achieved by adding a `pytestmark` statement to your module. .. include:: module_scoped_loop_example.py :code: python pytest-asyncio-0.25.1/docs/how-to-guides/run_package_tests_in_same_loop.rst000066400000000000000000000010301473541640200271740ustar00rootroot00000000000000======================================================== How to run all tests in a package in the same event loop ======================================================== All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="package")``. Add the following code to the ``__init__.py`` of the test package: .. include:: package_scoped_loop_example.py :code: python Note that this marker is not passed down to tests in subpackages. Subpackages constitute their own, separate package. pytest-asyncio-0.25.1/docs/how-to-guides/run_session_tests_in_same_loop.rst000066400000000000000000000011061473541640200272700ustar00rootroot00000000000000========================================================== How to run all tests in the session in the same event loop ========================================================== All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="session")``. The easiest way to mark all tests is via a ``pytest_collection_modifyitems`` hook in the ``conftest.py`` at the root folder of your test suite. .. include:: session_scoped_loop_example.py :code: python Note that this will also override *all* manually applied marks in *strict* mode. pytest-asyncio-0.25.1/docs/how-to-guides/session_scoped_loop_example.py000066400000000000000000000005421473541640200263620ustar00rootroot00000000000000import pytest from pytest_asyncio import is_async_test def pytest_collection_modifyitems(items): pytest_asyncio_tests = (item for item in items if is_async_test(item)) session_scope_marker = pytest.mark.asyncio(loop_scope="session") for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) pytest-asyncio-0.25.1/docs/how-to-guides/test_item_is_async.rst000066400000000000000000000004461473541640200246460ustar00rootroot00000000000000======================================= How to tell if a test function is async ======================================= Use ``pytest_asyncio.is_async_item`` to determine if a test item is asynchronous and managed by pytest-asyncio. .. include:: test_item_is_async_example.py :code: python pytest-asyncio-0.25.1/docs/how-to-guides/test_item_is_async_example.py000066400000000000000000000002351473541640200261750ustar00rootroot00000000000000from pytest_asyncio import is_async_test def pytest_collection_modifyitems(items): for item in items: if is_async_test(item): pass pytest-asyncio-0.25.1/docs/how-to-guides/test_session_scoped_loop_example.py000066400000000000000000000034141473541640200274220ustar00rootroot00000000000000from pathlib import Path from textwrap import dedent from pytest import Pytester def test_session_scoped_loop_configuration_works_in_auto_mode( pytester: Pytester, ): session_wide_mark_conftest = ( Path(__file__).parent / "session_scoped_loop_example.py" ) pytester.makeconftest(session_wide_mark_conftest.read_text()) pytester.makepyfile( dedent( """\ import asyncio session_loop = None async def test_store_loop(request): global session_loop session_loop = asyncio.get_running_loop() async def test_compare_loop(request): global session_loop assert asyncio.get_running_loop() is session_loop """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=auto") result.assert_outcomes(passed=2) def test_session_scoped_loop_configuration_works_in_strict_mode( pytester: Pytester, ): session_wide_mark_conftest = ( Path(__file__).parent / "session_scoped_loop_example.py" ) pytester.makeconftest(session_wide_mark_conftest.read_text()) pytester.makepyfile( dedent( """\ import asyncio import pytest session_loop = None @pytest.mark.asyncio async def test_store_loop(request): global session_loop session_loop = asyncio.get_running_loop() @pytest.mark.asyncio async def test_compare_loop(request): global session_loop assert asyncio.get_running_loop() is session_loop """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2) pytest-asyncio-0.25.1/docs/how-to-guides/uvloop.rst000066400000000000000000000012051473541640200222770ustar00rootroot00000000000000======================= How to test with uvloop ======================= Redefinig the *event_loop_policy* fixture will parametrize all async tests. The following example causes all async tests to run multiple times, once for each event loop in the fixture parameters: Replace the default event loop policy in your *conftest.py:* .. code-block:: python import pytest import uvloop @pytest.fixture(scope="session") def event_loop_policy(): return uvloop.EventLoopPolicy() You may choose to limit the scope of the fixture to *package,* *module,* or *class,* if you only want a subset of your tests to run with uvloop. pytest-asyncio-0.25.1/docs/index.rst000066400000000000000000000023641473541640200173760ustar00rootroot00000000000000========================== Welcome to pytest-asyncio! ========================== .. toctree:: :maxdepth: 1 :hidden: concepts how-to-guides/index reference/index support pytest-asyncio is a `pytest `_ plugin. It facilitates testing of code that uses the `asyncio `_ library. Specifically, pytest-asyncio provides support for coroutines as test functions. This allows users to *await* code inside their tests. For example, the following code is executed as a test item by pytest: .. code-block:: python @pytest.mark.asyncio async def test_some_asyncio_code(): res = await library.do_something() assert b"expected result" == res Note that test classes subclassing the standard `unittest `__ library are not supported. Users are advised to use `unittest.IsolatedAsyncioTestCase `__ or an async framework such as `asynctest `__. pytest-asyncio is available under the `Apache License 2.0 `_. pytest-asyncio-0.25.1/docs/reference/000077500000000000000000000000001473541640200174665ustar00rootroot00000000000000pytest-asyncio-0.25.1/docs/reference/changelog.rst000066400000000000000000000524271473541640200221610ustar00rootroot00000000000000========= Changelog ========= 0.25.1 (2025-01-02) =================== - Fixes an issue that caused a broken event loop when a function-scoped test was executed in between two tests with wider loop scope `#950 `_ - Improves test collection speed in auto mode `#1020 `_ - Corrects the warning that is emitted upon redefining the event_loop fixture 0.25.0 (2024-12-13) =================== - Deprecated: Added warning when asyncio test requests async ``@pytest.fixture`` in strict mode. This will become an error in a future version of flake8-asyncio. `#979 `_ - Updates the error message about `pytest.mark.asyncio`'s `scope` keyword argument to say `loop_scope` instead. `#1004 `_ - Verbose log displays correct parameter name: asyncio_default_fixture_loop_scope `#990 `_ - Propagates `contextvars` set in async fixtures to other fixtures and tests on Python 3.11 and above. `#1008 `_ 0.24.0 (2024-08-22) =================== - BREAKING: Updated minimum supported pytest version to v8.2.0 - Adds an optional `loop_scope` keyword argument to `pytest.mark.asyncio`. This argument controls which event loop is used to run the marked async test. `#706`_, `#871 `_ - Deprecates the optional `scope` keyword argument to `pytest.mark.asyncio` for API consistency with ``pytest_asyncio.fixture``. Users are encouraged to use the `loop_scope` keyword argument, which does exactly the same. - Raises an error when passing `scope` or `loop_scope` as a positional argument to ``@pytest.mark.asyncio``. `#812 `_ - Fixes a bug that caused module-scoped async fixtures to fail when reused in other modules `#862 `_ `#668 `_ - Added the ``asyncio_default_fixture_loop_scope`` configuration option `c74d1c3 `_ 0.23.8 (2024-07-17) =================== - Fixes a bug that caused duplicate markers in async tests `#813 `_ - Declare support for Python 3.13 Known issues ------------ As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved. 0.23.7 (2024-05-19) =================== - Silence deprecation warnings about unclosed event loops that occurred with certain CPython patch releases `#817 `_ Known issues ------------ As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved. 0.23.6 (2024-03-19) =================== - Fix compatibility with pytest 8.2 `#800 `_ Known issues ------------ As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved. 0.23.5 (2024-02-09) =================== - Declare compatibility with pytest 8 `#737 `_ - Fix typing errors with recent versions of mypy `#769 `_ - Prevent DeprecationWarning about internal use of `asyncio.get_event_loop()` from affecting test cases `#757 `_ Known issues ------------ As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved. 0.23.4 (2024-01-28) =================== - pytest-asyncio no longer imports additional, unrelated packages during test collection `#729 `_ - Addresses further issues that caused an internal pytest error during test collection - Declares incompatibility with pytest 8 `#737 `_ Known issues ------------ As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved. 0.23.3 (2024-01-01) =================== - Fixes a bug that caused event loops to be closed prematurely when using async generator fixtures with class scope or wider in a function-scoped test `#706 `_ - Fixes various bugs that caused an internal pytest error during test collection `#711 `_ `#713 `_ `#719 `_ Known issues ------------ As of v0.23, pytest-asyncio attaches an asyncio event loop to each item of the test suite (i.e. session, packages, modules, classes, functions) and allows tests to be run in those loops when marked accordingly. Pytest-asyncio currently assumes that async fixture scope is correlated with the new event loop scope. This prevents fixtures from being evaluated independently from the event loop scope and breaks some existing test suites (see `#706`_). For example, a test suite may require all fixtures and tests to run in the same event loop, but have async fixtures that are set up and torn down for each module. If you're affected by this issue, please continue using the v0.21 release, until it is resolved. 0.23.2 (2023-12-04) =================== - Fixes a bug that caused an internal pytest error when collecting .txt files `#703 `_ 0.23.1 (2023-12-03) =================== - Fixes a bug that caused an internal pytest error when using module-level skips `#701 `_ 0.23.0 (2023-12-03) =================== This release is backwards-compatible with v0.21. Changes are non-breaking, unless you upgrade from v0.22. - BREAKING: The *asyncio_event_loop* mark has been removed. Event loops with class, module, package, and session scopes can be requested via the *scope* keyword argument to the _asyncio_ mark. - Introduces the *event_loop_policy* fixture which allows testing with non-default or multiple event loops `#662 `_ - Introduces ``pytest_asyncio.is_async_test`` which returns whether a test item is managed by pytest-asyncio `#376 `_ - Removes and *pytest-trio,* *mypy,* and *flaky* from the test dependencies `#620 `_, `#674 `_, `#678 `_, 0.22.0 (2023-10-31) =================== This release has been yanked from PyPI due to fundamental issues with the _asyncio_event_loop_ mark. - Class-scoped and module-scoped event loops can be requested via the _asyncio_event_loop_ mark. `#620 `_ - Deprecate redefinition of the `event_loop` fixture. `#587 `_ Users requiring a class-scoped or module-scoped asyncio event loop for their tests should mark the corresponding class or module with `asyncio_event_loop`. - Test items based on asynchronous generators always exit with *xfail* status and emit a warning during the collection phase. This behavior is consistent with synchronous yield tests. `#642 `__ - Remove support for Python 3.7 - Declare support for Python 3.12 0.21.2 (2024-04-29) =================== - Fix compatibility with pytest 8.2. Backport of `#800 `_ to pytest-asyncio v0.21 for users who are unable to upgrade to a more recent version (see `#706`_) 0.21.1 (2023-07-12) =================== - Output a proper error message when an invalid ``asyncio_mode`` is selected. - Extend warning message about unclosed event loops with additional possible cause. `#531 `_ - Previously, some tests reported "skipped" or "xfailed" as a result. Now all tests report a "success" result. 0.21.0 (2023-03-19) =================== - Drop compatibility with pytest 6.1. Pytest-asyncio now depends on pytest 7.0 or newer. - pytest-asyncio cleans up any stale event loops when setting up and tearing down the event_loop fixture. This behavior has been deprecated and pytest-asyncio emits a DeprecationWarning when tearing down the event_loop fixture and current event loop has not been closed. 0.20.3 (2022-12-08) =================== - Prevent DeprecationWarning to bubble up on CPython 3.10.9 and 3.11.1. `#460 `_ 0.20.2 (2022-11-11) =================== - Fixes an issue with async fixtures that are defined as methods on a test class not being rebound to the actual test instance. `#197 `_ - Replaced usage of deprecated ``@pytest.mark.tryfirst`` with ``@pytest.hookimpl(tryfirst=True)`` `#438 `_ 0.20.1 (2022-10-21) =================== - Fixes an issue that warned about using an old version of pytest, even though the most recent version was installed. `#430 `_ 0.20.0 (2022-10-21) =================== - BREAKING: Removed *legacy* mode. If you're upgrading from v0.19 and you haven't configured ``asyncio_mode = legacy``, you can upgrade without taking any additional action. If you're upgrading from an earlier version or you have explicitly enabled *legacy* mode, you need to switch to *auto* or *strict* mode before upgrading to this version. - Deprecate use of pytest v6. - Fixed an issue which prevented fixture setup from being cached. `#404 `_ 0.19.0 (2022-07-13) =================== - BREAKING: The default ``asyncio_mode`` is now *strict*. `#293 `_ - Removes `setup.py` since all relevant configuration is present `setup.cfg`. Users requiring an editable installation of pytest-asyncio need to use pip v21.1 or newer. `#283 `_ - Declare support for Python 3.11. 0.18.3 (2022-03-25) =================== - Adds `pytest-trio `_ to the test dependencies - Fixes a bug that caused pytest-asyncio to try to set up async pytest_trio fixtures in strict mode. `#298 `_ 0.18.2 (2022-03-03) =================== - Fix asyncio auto mode not marking static methods. `#295 `_ - Fix a compatibility issue with Hypothesis 6.39.0. `#302 `_ 0.18.1 (2022-02-10) =================== - Fixes a regression that prevented async fixtures from working in synchronous tests. `#286 `_ 0.18.0 (2022-02-07) =================== - Raise a warning if @pytest.mark.asyncio is applied to non-async function. `#275 `_ - Support parametrized ``event_loop`` fixture. `#278 `_ 0.17.2 (2022-01-17) =================== - Require ``typing-extensions`` on Python<3.8 only. `#269 `_ - Fix a regression in tests collection introduced by 0.17.1, the plugin works fine with non-python tests again. `#267 `_ 0.17.1 (2022-01-16) =================== - Fixes a bug that prevents async Hypothesis tests from working without explicit ``asyncio`` marker when ``--asyncio-mode=auto`` is set. `#258 `_ - Fixed a bug that closes the default event loop if the loop doesn't exist `#257 `_ - Added type annotations. `#198 `_ - Show asyncio mode in pytest report headers. `#266 `_ - Relax ``asyncio_mode`` type definition; it allows to support pytest 6.1+. `#262 `_ 0.17.0 (2022-01-13) =================== - `pytest-asyncio` no longer alters existing event loop policies. `#168 `_, `#188 `_ - Drop support for Python 3.6 - Fixed an issue when pytest-asyncio was used in combination with `flaky` or inherited asynchronous Hypothesis tests. `#178 `_ `#231 `_ - Added `flaky `_ to test dependencies - Added ``unused_udp_port`` and ``unused_udp_port_factory`` fixtures (similar to ``unused_tcp_port`` and ``unused_tcp_port_factory`` counterparts. `#99 `_ - Added the plugin modes: *strict*, *auto*, and *legacy*. See `documentation `_ for details. `#125 `_ - Correctly process ``KeyboardInterrupt`` during async fixture setup phase `#219 `_ 0.16.0 (2021-10-16) =================== - Add support for Python 3.10 0.15.1 (2021-04-22) =================== - Hotfix for errors while closing event loops while replacing them. `#209 `_ `#210 `_ 0.15.0 (2021-04-19) =================== - Add support for Python 3.9 - Abandon support for Python 3.5. If you still require support for Python 3.5, please use pytest-asyncio v0.14 or earlier. - Set ``unused_tcp_port_factory`` fixture scope to 'session'. `#163 `_ - Properly close event loops when replacing them. `#208 `_ 0.14.0 (2020-06-24) =================== - Fix `#162 `_, and ``event_loop`` fixture behavior now is coherent on all scopes. `#164 `_ 0.12.0 (2020-05-04) =================== - Run the event loop fixture as soon as possible. This helps with fixtures that have an implicit dependency on the event loop. `#156 `_ 0.11.0 (2020-04-20) =================== - Test on 3.8, drop 3.3 and 3.4. Stick to 0.10 for these versions. `#152 `_ - Use the new Pytest 5.4.0 Function API. We therefore depend on pytest >= 5.4.0. `#142 `_ - Better ``pytest.skip`` support. `#126 `_ 0.10.0 (2019-01-08) ==================== - ``pytest-asyncio`` integrates with `Hypothesis `_ to support ``@given`` on async test functions using ``asyncio``. `#102 `_ - Pytest 4.1 support. `#105 `_ 0.9.0 (2018-07-28) ================== - Python 3.7 support. - Remove ``event_loop_process_pool`` fixture and ``pytest.mark.asyncio_process_pool`` marker (see https://bugs.python.org/issue34075 for deprecation and removal details) 0.8.0 (2017-09-23) ================== - Improve integration with other packages (like aiohttp) with more careful event loop handling. `#64 `_ 0.7.0 (2017-09-08) ================== - Python versions pre-3.6 can use the async_generator library for async fixtures. `#62 ` 0.6.0 (2017-05-28) ================== - Support for Python versions pre-3.5 has been dropped. - ``pytestmark`` now works on both module and class level. - The ``forbid_global_loop`` parameter has been removed. - Support for async and async gen fixtures has been added. `#45 `_ - The deprecation warning regarding ``asyncio.async()`` has been fixed. `#51 `_ 0.5.0 (2016-09-07) ================== - Introduced a changelog. `#31 `_ - The ``event_loop`` fixture is again responsible for closing itself. This makes the fixture slightly harder to correctly override, but enables other fixtures to depend on it correctly. `#30 `_ - Deal with the event loop policy by wrapping a special pytest hook, ``pytest_fixture_setup``. This allows setting the policy before fixtures dependent on the ``event_loop`` fixture run, thus allowing them to take advantage of the ``forbid_global_loop`` parameter. As a consequence of this, we now depend on pytest 3.0. `#29 `_ 0.4.1 (2016-06-01) ================== - Fix a bug preventing the propagation of exceptions from the plugin. `#25 `_ 0.4.0 (2016-05-30) ================== - Make ``event_loop`` fixtures simpler to override by closing them in the plugin, instead of directly in the fixture. `#21 `_ - Introduce the ``forbid_global_loop`` parameter. `#21 `_ 0.3.0 (2015-12-19) ================== - Support for Python 3.5 ``async``/``await`` syntax. `#17 `_ 0.2.0 (2015-08-01) ================== - ``unused_tcp_port_factory`` fixture. `#10 `_ 0.1.1 (2015-04-23) ================== Initial release. pytest-asyncio-0.25.1/docs/reference/configuration.rst000066400000000000000000000021041473541640200230640ustar00rootroot00000000000000============= Configuration ============= .. _configuration/asyncio_default_fixture_loop_scope: asyncio_default_fixture_loop_scope ================================== Determines the default event loop scope of asynchronous fixtures. When this configuration option is unset, it defaults to the fixture scope. In future versions of pytest-asyncio, the value will default to ``function`` when unset. Possible values are: ``function``, ``class``, ``module``, ``package``, ``session`` asyncio_mode ============ The pytest-asyncio mode can be set by the ``asyncio_mode`` configuration option in the `configuration file `_: .. code-block:: ini # pytest.ini [pytest] asyncio_mode = auto The value can also be set via the ``--asyncio-mode`` command-line option: .. code-block:: bash $ pytest tests --asyncio-mode=strict If the asyncio mode is set in both the pytest configuration file and the command-line option, the command-line option takes precedence. If no asyncio mode is specified, the mode defaults to `strict`. pytest-asyncio-0.25.1/docs/reference/decorators/000077500000000000000000000000001473541640200216335ustar00rootroot00000000000000pytest-asyncio-0.25.1/docs/reference/decorators/index.rst000066400000000000000000000022201473541640200234700ustar00rootroot00000000000000.. _decorators/pytest_asyncio_fixture: ========== Decorators ========== The ``@pytest_asyncio.fixture`` decorator allows coroutines and async generator functions to be used as pytest fixtures. The decorator takes all arguments supported by `@pytest.fixture`. Additionally, ``@pytest_asyncio.fixture`` supports the *loop_scope* keyword argument, which selects the event loop in which the fixture is run (see :ref:`concepts/event_loops`). The default event loop scope is *function* scope. Possible loop scopes are *session,* *package,* *module,* *class,* and *function*. The *loop_scope* of a fixture can be chosen independently from its caching *scope*. However, the event loop scope must be larger or the same as the fixture's caching scope. In other words, it's possible to reevaluate an async fixture multiple times within the same event loop, but it's not possible to switch out the running event loop in an async fixture. Examples: .. include:: pytest_asyncio_fixture_example.py :code: python *auto* mode automatically converts coroutines and async generator functions declared with the standard ``@pytest.fixture`` decorator to pytest-asyncio fixtures. pytest-asyncio-0.25.1/docs/reference/decorators/pytest_asyncio_fixture_example.py000066400000000000000000000007271473541640200305510ustar00rootroot00000000000000import pytest_asyncio @pytest_asyncio.fixture async def fixture_runs_in_fresh_loop_for_every_function(): ... @pytest_asyncio.fixture(loop_scope="session", scope="module") async def fixture_runs_in_session_loop_once_per_module(): ... @pytest_asyncio.fixture(loop_scope="module", scope="module") async def fixture_runs_in_module_loop_once_per_module(): ... @pytest_asyncio.fixture(loop_scope="module") async def fixture_runs_in_module_loop_once_per_function(): ... pytest-asyncio-0.25.1/docs/reference/fixtures/000077500000000000000000000000001473541640200213375ustar00rootroot00000000000000pytest-asyncio-0.25.1/docs/reference/fixtures/event_loop_example.py000066400000000000000000000001561473541640200256000ustar00rootroot00000000000000import asyncio def test_event_loop_fixture(event_loop): event_loop.run_until_complete(asyncio.sleep(0)) pytest-asyncio-0.25.1/docs/reference/fixtures/event_loop_policy_example.py000066400000000000000000000005651473541640200271630ustar00rootroot00000000000000import asyncio import pytest class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): pass @pytest.fixture(scope="module") def event_loop_policy(request): return CustomEventLoopPolicy() @pytest.mark.asyncio(loop_scope="module") async def test_uses_custom_event_loop_policy(): assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy) pytest-asyncio-0.25.1/docs/reference/fixtures/event_loop_policy_parametrized_example.py000066400000000000000000000007141473541640200317260ustar00rootroot00000000000000import asyncio from asyncio import DefaultEventLoopPolicy import pytest class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): pass @pytest.fixture( params=( DefaultEventLoopPolicy(), CustomEventLoopPolicy(), ), ) def event_loop_policy(request): return request.param @pytest.mark.asyncio async def test_uses_custom_event_loop_policy(): assert isinstance(asyncio.get_event_loop_policy(), DefaultEventLoopPolicy) pytest-asyncio-0.25.1/docs/reference/fixtures/index.rst000066400000000000000000000054561473541640200232120ustar00rootroot00000000000000======== Fixtures ======== event_loop ========== *This fixture is deprecated.* *If you want to request an asyncio event loop with a scope other than function scope, use the "loop_scope" argument to* :ref:`reference/markers/asyncio` *when marking the tests. If you want to return different types of event loops, use the* :ref:`reference/fixtures/event_loop_policy` *fixture.* Creates a new asyncio event loop based on the current event loop policy. The new loop is available as the return value of this fixture for synchronous functions, or via `asyncio.get_running_loop `__ for asynchronous functions. The event loop is closed when the fixture scope ends. The fixture scope defaults to ``function`` scope. .. include:: event_loop_example.py :code: python Note that, when using the ``event_loop`` fixture, you need to interact with the event loop using methods like ``event_loop.run_until_complete``. If you want to *await* code inside your test function, you need to write a coroutine and use it as a test function. The :ref:`asyncio ` marker is used to mark coroutines that should be treated as test functions. If you need to change the type of the event loop, prefer setting a custom event loop policy over redefining the ``event_loop`` fixture. If the ``pytest.mark.asyncio`` decorator is applied to a test function, the ``event_loop`` fixture will be requested automatically by the test function. .. _reference/fixtures/event_loop_policy: event_loop_policy ================= Returns the event loop policy used to create asyncio event loops. The default return value is *asyncio.get_event_loop_policy().* This fixture can be overridden when a different event loop policy should be used. .. include:: event_loop_policy_example.py :code: python Multiple policies can be provided via fixture parameters. The fixture is automatically applied to all pytest-asyncio tests. Therefore, all tests managed by pytest-asyncio are run once for each fixture parameter. The following example runs the test with different event loop policies. .. include:: event_loop_policy_parametrized_example.py :code: python unused_tcp_port =============== Finds and yields a single unused TCP port on the localhost interface. Useful for binding temporary test servers. unused_tcp_port_factory ======================= A callable which returns a different unused TCP port each invocation. Useful when several unused TCP ports are required in a test. .. code-block:: python def a_test(unused_tcp_port_factory): _port1, _port2 = unused_tcp_port_factory(), unused_tcp_port_factory() unused_udp_port and unused_udp_port_factory =========================================== Works just like their TCP counterparts but returns unused UDP ports. pytest-asyncio-0.25.1/docs/reference/functions.rst000066400000000000000000000003701473541640200222300ustar00rootroot00000000000000========= Functions ========= is_async_test ============= Returns whether a specific pytest Item is an asynchronous test managed by pytest-asyncio. This function is intended to be used in pytest hooks or by plugins that depend on pytest-asyncio. pytest-asyncio-0.25.1/docs/reference/index.rst000066400000000000000000000005271473541640200213330ustar00rootroot00000000000000========= Reference ========= .. toctree:: :hidden: configuration fixtures/index functions markers/index decorators/index changelog This section of the documentation provides descriptions of the individual parts provided by pytest-asyncio. The reference section also provides a chronological list of changes for each release. pytest-asyncio-0.25.1/docs/reference/markers/000077500000000000000000000000001473541640200211325ustar00rootroot00000000000000class_scoped_loop_custom_policies_strict_mode_example.py000066400000000000000000000005141473541640200345100ustar00rootroot00000000000000pytest-asyncio-0.25.1/docs/reference/markersimport asyncio import pytest @pytest.fixture( params=[ asyncio.DefaultEventLoopPolicy(), asyncio.DefaultEventLoopPolicy(), ] ) def event_loop_policy(request): return request.param class TestWithDifferentLoopPolicies: @pytest.mark.asyncio async def test_parametrized_loop(self): pass pytest-asyncio-0.25.1/docs/reference/markers/class_scoped_loop_strict_mode_example.py000066400000000000000000000005471473541640200313140ustar00rootroot00000000000000import asyncio import pytest @pytest.mark.asyncio(loop_scope="class") class TestClassScopedLoop: loop: asyncio.AbstractEventLoop async def test_remember_loop(self): TestClassScopedLoop.loop = asyncio.get_running_loop() async def test_this_runs_in_same_loop(self): assert asyncio.get_running_loop() is TestClassScopedLoop.loop pytest-asyncio-0.25.1/docs/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py000066400000000000000000000006701473541640200341120ustar00rootroot00000000000000import asyncio import pytest import pytest_asyncio @pytest.mark.asyncio(loop_scope="class") class TestClassScopedLoop: loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="class") async def my_fixture(self): TestClassScopedLoop.loop = asyncio.get_running_loop() async def test_runs_is_same_loop_as_fixture(self, my_fixture): assert asyncio.get_running_loop() is TestClassScopedLoop.loop pytest-asyncio-0.25.1/docs/reference/markers/function_scoped_loop_pytestmark_strict_mode_example.py000066400000000000000000000003001473541640200343020ustar00rootroot00000000000000import asyncio import pytest # Marks all test coroutines in this module pytestmark = pytest.mark.asyncio async def test_runs_in_asyncio_event_loop(): assert asyncio.get_running_loop() pytest-asyncio-0.25.1/docs/reference/markers/function_scoped_loop_strict_mode_example.py000066400000000000000000000002101473541640200320170ustar00rootroot00000000000000import asyncio import pytest @pytest.mark.asyncio async def test_runs_in_asyncio_event_loop(): assert asyncio.get_running_loop() pytest-asyncio-0.25.1/docs/reference/markers/index.rst000066400000000000000000000040501473541640200227720ustar00rootroot00000000000000======= Markers ======= .. _reference/markers/asyncio: ``pytest.mark.asyncio`` ======================= A coroutine or async generator with this marker is treated as a test function by pytest. The marked function is executed as an asyncio task in the event loop provided by pytest-asyncio. .. include:: function_scoped_loop_strict_mode_example.py :code: python Multiple async tests in a single class or module can be marked using |pytestmark|_. .. include:: function_scoped_loop_pytestmark_strict_mode_example.py :code: python The ``pytest.mark.asyncio`` marker can be omitted entirely in |auto mode|_ where the *asyncio* marker is added automatically to *async* test functions. By default, each test runs in it's own asyncio event loop. Multiple tests can share the same event loop by providing a *loop_scope* keyword argument to the *asyncio* mark. The supported scopes are *class,* and *module,* and *package*. The following code example provides a shared event loop for all tests in `TestClassScopedLoop`: .. include:: class_scoped_loop_strict_mode_example.py :code: python If you request class scope for a test that is not part of a class, it will result in a *UsageError*. Similar to class-scoped event loops, a module-scoped loop is provided when setting mark's scope to *module:* .. include:: module_scoped_loop_strict_mode_example.py :code: python Package-scoped loops only work with `regular Python packages. `__ That means they require an *__init__.py* to be present. Package-scoped loops do not work in `namespace packages. `__ Subpackages do not share the loop with their parent package. Tests marked with *session* scope share the same event loop, even if the tests exist in different packages. .. |auto mode| replace:: *auto mode* .. _auto mode: ../../concepts.html#auto-mode .. |pytestmark| replace:: ``pytestmark`` .. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules pytest-asyncio-0.25.1/docs/reference/markers/module_scoped_loop_strict_mode_example.py000066400000000000000000000007021473541640200314650ustar00rootroot00000000000000import asyncio import pytest pytestmark = pytest.mark.asyncio(loop_scope="module") loop: asyncio.AbstractEventLoop async def test_remember_loop(): global loop loop = asyncio.get_running_loop() async def test_this_runs_in_same_loop(): global loop assert asyncio.get_running_loop() is loop class TestClassA: async def test_this_runs_in_same_loop(self): global loop assert asyncio.get_running_loop() is loop pytest-asyncio-0.25.1/docs/support.rst000066400000000000000000000024541473541640200200030ustar00rootroot00000000000000=============== Getting support =============== Enterprise support ================== `Tidelift `_ works with maintainers of numerous open source projects to ensure enterprise-grade support for your software supply chain. The Tidelift subscription includes security updates, verified license compliance, continuous software maintenance, and more. As a result, you get the guarantees provided by commercial software for the open source packages you use. Consider `signing up for the Tidelift subscription `__. Direct maintainer support ========================= If you require commercial support outside of the Tidelift subscription, reach out to `Michael Seifert, `__ one of the project's maintainers. Community support ================= The GitHub page of pytest-asyncio offers free community support on a best-effort basis. Please use the `issue tracker `__ to report bugs and the Matrix chat room `#pytest-asyncio:matrix.org `__ or `GitHub discussions `__ to ask questions. pytest-asyncio-0.25.1/pyproject.toml000066400000000000000000000076331473541640200175250ustar00rootroot00000000000000[build-system] build-backend = "setuptools.build_meta" requires = [ "setuptools>=51", "setuptools-scm[toml]>=6.2", "wheel>=0.36", ] [project] name = "pytest-asyncio" description = "Pytest support for asyncio" readme.content-type = "text/x-rst" readme.file = "README.rst" license.text = "Apache 2.0" authors = [ { name = "Tin Tvrtković ", email = "tinchester@gmail.com" }, ] requires-python = ">=3.9" classifiers = [ "Development Status :: 4 - Beta", "Framework :: AsyncIO", "Framework :: Pytest", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Testing", "Typing :: Typed", ] dynamic = [ "version", ] dependencies = [ "pytest>=8.2,<9", ] optional-dependencies.docs = [ "sphinx>=5.3", "sphinx-rtd-theme>=1", ] optional-dependencies.testing = [ "coverage>=6.2", "hypothesis>=5.7.1", ] urls."Bug Tracker" = "https://github.com/pytest-dev/pytest-asyncio/issues" urls.Changelog = "https://pytest-asyncio.readthedocs.io/en/latest/reference/changelog.html" urls.Documentation = "https://pytest-asyncio.readthedocs.io" urls.Homepage = "https://github.com/pytest-dev/pytest-asyncio" urls."Source Code" = "https://github.com/pytest-dev/pytest-asyncio" entry-points.pytest11.asyncio = "pytest_asyncio.plugin" [tool.setuptools] packages = [ "pytest_asyncio", ] include-package-data = true license-files = [ "LICENSE", ] [tool.setuptools_scm] write_to = "pytest_asyncio/_version.py" [tool.ruff] line-length = 88 format.docstring-code-format = true lint.select = [ "B", # bugbear "D", # pydocstyle "E", # pycodestyle "F", # pyflakes "FA100", # add future annotations "PGH004", # pygrep-hooks - Use specific rule codes when using noqa "PIE", # flake8-pie "PLE", # pylint error "PYI", # flake8-pyi "RUF", # ruff "T100", # flake8-debugger "UP", # pyupgrade "W", # pycodestyle ] lint.ignore = [ # bugbear ignore "B028", # No explicit `stacklevel` keyword argument found # pydocstyle ignore "D100", # Missing docstring in public module "D101", # Missing docstring in public class "D102", # Missing docstring in public method "D103", # Missing docstring in public function "D104", # Missing docstring in public package "D105", # Missing docstring in magic method "D106", # Missing docstring in public nested class "D107", # Missing docstring in `__init__` "D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible "D205", # 1 blank line required between summary line and description "D209", # [*] Multi-line docstring closing quotes should be on a separate line "D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. "D400", # First line should end with a period "D401", # First line of docstring should be in imperative mood "D402", # First line should not be the function's signature "D404", # First word of the docstring should not be "This" "D415", # First line should end with a period, question mark, or exclamation point ] [tool.pytest.ini_options] python_files = [ "test_*.py", "*_example.py", ] addopts = "-rsx --tb=short" testpaths = [ "docs", "tests", ] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" junit_family = "xunit2" filterwarnings = [ "error", "ignore:The event_loop fixture provided by pytest-asyncio has been redefined.*:DeprecationWarning", ] [tool.coverage.run] source = [ "pytest_asyncio", ] branch = true data_file = "coverage/coverage" omit = [ "*/_version.py", ] parallel = true [tool.coverage.report] show_missing = true pytest-asyncio-0.25.1/pytest_asyncio/000077500000000000000000000000001473541640200176555ustar00rootroot00000000000000pytest-asyncio-0.25.1/pytest_asyncio/__init__.py000066400000000000000000000003541473541640200217700ustar00rootroot00000000000000"""The main point for importing pytest-asyncio items.""" from __future__ import annotations from ._version import version as __version__ # noqa: F401 from .plugin import fixture, is_async_test __all__ = ("fixture", "is_async_test") pytest-asyncio-0.25.1/pytest_asyncio/plugin.py000066400000000000000000001306101473541640200215260ustar00rootroot00000000000000"""pytest-asyncio implementation.""" from __future__ import annotations import asyncio import contextlib import contextvars import enum import functools import inspect import socket import warnings from asyncio import AbstractEventLoop, AbstractEventLoopPolicy from collections.abc import ( AsyncIterator, Awaitable, Coroutine as AbstractCoroutine, Generator, Iterable, Iterator, Mapping, Sequence, ) from textwrap import dedent from typing import ( Any, Callable, Literal, TypeVar, Union, overload, ) import pluggy import pytest from pytest import ( Class, Collector, Config, FixtureDef, FixtureRequest, Function, Item, Mark, Metafunc, Module, Package, Parser, PytestCollectionWarning, PytestDeprecationWarning, PytestPluginManager, Session, StashKey, ) _ScopeName = Literal["session", "package", "module", "class", "function"] _T = TypeVar("_T") SimpleFixtureFunction = TypeVar( "SimpleFixtureFunction", bound=Callable[..., Awaitable[object]] ) FactoryFixtureFunction = TypeVar( "FactoryFixtureFunction", bound=Callable[..., AsyncIterator[object]] ) FixtureFunction = Union[SimpleFixtureFunction, FactoryFixtureFunction] FixtureFunctionMarker = Callable[[FixtureFunction], FixtureFunction] class PytestAsyncioError(Exception): """Base class for exceptions raised by pytest-asyncio""" class MultipleEventLoopsRequestedError(PytestAsyncioError): """Raised when a test requests multiple asyncio event loops.""" class Mode(str, enum.Enum): AUTO = "auto" STRICT = "strict" ASYNCIO_MODE_HELP = """\ 'auto' - for automatically handling all async functions by the plugin 'strict' - for autoprocessing disabling (useful if different async frameworks \ should be tested together, e.g. \ both pytest-asyncio and pytest-trio are used in the same project) """ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: group = parser.getgroup("asyncio") group.addoption( "--asyncio-mode", dest="asyncio_mode", default=None, metavar="MODE", help=ASYNCIO_MODE_HELP, ) parser.addini( "asyncio_mode", help="default value for --asyncio-mode", default="strict", ) parser.addini( "asyncio_default_fixture_loop_scope", type="string", help="default scope of the asyncio event loop used to execute async fixtures", default=None, ) @overload def fixture( fixture_function: FixtureFunction, *, scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., loop_scope: _ScopeName | None = ..., params: Iterable[object] | None = ..., autouse: bool = ..., ids: ( Iterable[str | float | int | bool | None] | Callable[[Any], object | None] | None ) = ..., name: str | None = ..., ) -> FixtureFunction: ... @overload def fixture( fixture_function: None = ..., *, scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., loop_scope: _ScopeName | None = ..., params: Iterable[object] | None = ..., autouse: bool = ..., ids: ( Iterable[str | float | int | bool | None] | Callable[[Any], object | None] | None ) = ..., name: str | None = None, ) -> FixtureFunctionMarker: ... def fixture( fixture_function: FixtureFunction | None = None, loop_scope: _ScopeName | None = None, **kwargs: Any, ) -> FixtureFunction | FixtureFunctionMarker: if fixture_function is not None: _make_asyncio_fixture_function(fixture_function, loop_scope) return pytest.fixture(fixture_function, **kwargs) else: @functools.wraps(fixture) def inner(fixture_function: FixtureFunction) -> FixtureFunction: return fixture(fixture_function, loop_scope=loop_scope, **kwargs) return inner def _is_asyncio_fixture_function(obj: Any) -> bool: obj = getattr(obj, "__func__", obj) # instance method maybe? return getattr(obj, "_force_asyncio_fixture", False) def _make_asyncio_fixture_function(obj: Any, loop_scope: _ScopeName | None) -> None: if hasattr(obj, "__func__"): # instance method, check the function object obj = obj.__func__ obj._force_asyncio_fixture = True obj._loop_scope = loop_scope def _is_coroutine_or_asyncgen(obj: Any) -> bool: return inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj) def _get_asyncio_mode(config: Config) -> Mode: val = config.getoption("asyncio_mode") if val is None: val = config.getini("asyncio_mode") try: return Mode(val) except ValueError as e: modes = ", ".join(m.value for m in Mode) raise pytest.UsageError( f"{val!r} is not a valid asyncio_mode. Valid modes: {modes}." ) from e _DEFAULT_FIXTURE_LOOP_SCOPE_UNSET = """\ The configuration option "asyncio_default_fixture_loop_scope" is unset. The event loop scope for asynchronous fixtures will default to the fixture caching \ scope. Future versions of pytest-asyncio will default the loop scope for asynchronous \ fixtures to function scope. Set the default fixture loop scope explicitly in order to \ avoid unexpected behavior in the future. Valid fixture loop scopes are: \ "function", "class", "module", "package", "session" """ def pytest_configure(config: Config) -> None: default_loop_scope = config.getini("asyncio_default_fixture_loop_scope") if not default_loop_scope: warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET)) config.addinivalue_line( "markers", "asyncio: " "mark the test as a coroutine, it will be " "run using an asyncio event loop", ) @pytest.hookimpl(tryfirst=True) def pytest_report_header(config: Config) -> list[str]: """Add asyncio config to pytest header.""" mode = _get_asyncio_mode(config) default_loop_scope = config.getini("asyncio_default_fixture_loop_scope") return [ f"asyncio: mode={mode}, asyncio_default_fixture_loop_scope={default_loop_scope}" ] def _preprocess_async_fixtures( collector: Collector, processed_fixturedefs: set[FixtureDef], ) -> None: config = collector.config default_loop_scope = config.getini("asyncio_default_fixture_loop_scope") asyncio_mode = _get_asyncio_mode(config) fixturemanager = config.pluginmanager.get_plugin("funcmanage") assert fixturemanager is not None for fixtures in fixturemanager._arg2fixturedefs.values(): for fixturedef in fixtures: func = fixturedef.func if fixturedef in processed_fixturedefs or not _is_coroutine_or_asyncgen( func ): continue if asyncio_mode == Mode.STRICT and not _is_asyncio_fixture_function(func): # Ignore async fixtures without explicit asyncio mark in strict mode # This applies to pytest_trio fixtures, for example continue scope = ( getattr(func, "_loop_scope", None) or default_loop_scope or fixturedef.scope ) if scope == "function" and "event_loop" not in fixturedef.argnames: fixturedef.argnames += ("event_loop",) _make_asyncio_fixture_function(func, scope) function_signature = inspect.signature(func) if "event_loop" in function_signature.parameters: warnings.warn( PytestDeprecationWarning( f"{func.__name__} is asynchronous and explicitly " f'requests the "event_loop" fixture. Asynchronous fixtures and ' f'test functions should use "asyncio.get_running_loop()" ' f"instead." ) ) if "request" not in fixturedef.argnames: fixturedef.argnames += ("request",) _synchronize_async_fixture(fixturedef) assert _is_asyncio_fixture_function(fixturedef.func) processed_fixturedefs.add(fixturedef) def _synchronize_async_fixture(fixturedef: FixtureDef) -> None: """Wraps the fixture function of an async fixture in a synchronous function.""" if inspect.isasyncgenfunction(fixturedef.func): _wrap_asyncgen_fixture(fixturedef) elif inspect.iscoroutinefunction(fixturedef.func): _wrap_async_fixture(fixturedef) def _add_kwargs( func: Callable[..., Any], kwargs: dict[str, Any], event_loop: asyncio.AbstractEventLoop, request: FixtureRequest, ) -> dict[str, Any]: sig = inspect.signature(func) ret = kwargs.copy() if "request" in sig.parameters: ret["request"] = request if "event_loop" in sig.parameters: ret["event_loop"] = event_loop return ret def _perhaps_rebind_fixture_func(func: _T, instance: Any | None) -> _T: if instance is not None: # The fixture needs to be bound to the actual request.instance # so it is bound to the same object as the test method. unbound, cls = func, None try: unbound, cls = func.__func__, type(func.__self__) # type: ignore except AttributeError: pass # Only if the fixture was bound before to an instance of # the same type. if cls is not None and isinstance(instance, cls): func = unbound.__get__(instance) # type: ignore return func def _wrap_asyncgen_fixture(fixturedef: FixtureDef) -> None: fixture = fixturedef.func @functools.wraps(fixture) def _asyncgen_fixture_wrapper(request: FixtureRequest, **kwargs: Any): func = _perhaps_rebind_fixture_func(fixture, request.instance) event_loop_fixture_id = _get_event_loop_fixture_id_for_async_fixture( request, func ) event_loop = request.getfixturevalue(event_loop_fixture_id) kwargs.pop(event_loop_fixture_id, None) gen_obj = func(**_add_kwargs(func, kwargs, event_loop, request)) async def setup(): res = await gen_obj.__anext__() # type: ignore[union-attr] return res context = contextvars.copy_context() setup_task = _create_task_in_context(event_loop, setup(), context) result = event_loop.run_until_complete(setup_task) reset_contextvars = _apply_contextvar_changes(context) def finalizer() -> None: """Yield again, to finalize.""" async def async_finalizer() -> None: try: await gen_obj.__anext__() # type: ignore[union-attr] except StopAsyncIteration: pass else: msg = "Async generator fixture didn't stop." msg += "Yield only once." raise ValueError(msg) task = _create_task_in_context(event_loop, async_finalizer(), context) event_loop.run_until_complete(task) if reset_contextvars is not None: reset_contextvars() request.addfinalizer(finalizer) return result fixturedef.func = _asyncgen_fixture_wrapper # type: ignore[misc] def _wrap_async_fixture(fixturedef: FixtureDef) -> None: fixture = fixturedef.func @functools.wraps(fixture) def _async_fixture_wrapper(request: FixtureRequest, **kwargs: Any): func = _perhaps_rebind_fixture_func(fixture, request.instance) event_loop_fixture_id = _get_event_loop_fixture_id_for_async_fixture( request, func ) event_loop = request.getfixturevalue(event_loop_fixture_id) kwargs.pop(event_loop_fixture_id, None) async def setup(): res = await func(**_add_kwargs(func, kwargs, event_loop, request)) return res context = contextvars.copy_context() setup_task = _create_task_in_context(event_loop, setup(), context) result = event_loop.run_until_complete(setup_task) # Copy the context vars modified by the setup task into the current # context, and (if needed) add a finalizer to reset them. # # Note that this is slightly different from the behavior of a non-async # fixture, which would rely on the fixture author to add a finalizer # to reset the variables. In this case, the author of the fixture can't # write such a finalizer because they have no way to capture the Context # in which the setup function was run, so we need to do it for them. reset_contextvars = _apply_contextvar_changes(context) if reset_contextvars is not None: request.addfinalizer(reset_contextvars) return result fixturedef.func = _async_fixture_wrapper # type: ignore[misc] def _get_event_loop_fixture_id_for_async_fixture( request: FixtureRequest, func: Any ) -> str: default_loop_scope = request.config.getini("asyncio_default_fixture_loop_scope") loop_scope = ( getattr(func, "_loop_scope", None) or default_loop_scope or request.scope ) if loop_scope == "function": event_loop_fixture_id = "event_loop" else: event_loop_node = _retrieve_scope_root(request._pyfuncitem, loop_scope) event_loop_fixture_id = event_loop_node.stash.get( # Type ignored because of non-optimal mypy inference. _event_loop_fixture_id, # type: ignore[arg-type] "", ) assert event_loop_fixture_id return event_loop_fixture_id def _create_task_in_context( loop: asyncio.AbstractEventLoop, coro: AbstractCoroutine[Any, Any, _T], context: contextvars.Context, ) -> asyncio.Task[_T]: """ Return an asyncio task that runs the coro in the specified context, if possible. This allows fixture setup and teardown to be run as separate asyncio tasks, while still being able to use context-manager idioms to maintain context variables and make those variables visible to test functions. This is only fully supported on Python 3.11 and newer, as it requires the API added for https://github.com/python/cpython/issues/91150. On earlier versions, the returned task will use the default context instead. """ try: return loop.create_task(coro, context=context) except TypeError: return loop.create_task(coro) def _apply_contextvar_changes( context: contextvars.Context, ) -> Callable[[], None] | None: """ Copy contextvar changes from the given context to the current context. If any contextvars were modified by the fixture, return a finalizer that will restore them. """ context_tokens = [] for var in context: try: if var.get() is context.get(var): # This variable is not modified, so leave it as-is. continue except LookupError: # This variable isn't yet set in the current context at all. pass token = var.set(context.get(var)) context_tokens.append((var, token)) if not context_tokens: return None def restore_contextvars(): while context_tokens: (var, token) = context_tokens.pop() var.reset(token) return restore_contextvars class PytestAsyncioFunction(Function): """Base class for all test functions managed by pytest-asyncio.""" @classmethod def item_subclass_for(cls, item: Function, /) -> type[PytestAsyncioFunction] | None: """ Returns a subclass of PytestAsyncioFunction if there is a specialized subclass for the specified function item. Return None if no specialized subclass exists for the specified item. """ for subclass in cls.__subclasses__(): if subclass._can_substitute(item): return subclass return None @classmethod def _from_function(cls, function: Function, /) -> Function: """ Instantiates this specific PytestAsyncioFunction type from the specified Function item. """ assert function.get_closest_marker("asyncio") subclass_instance = cls.from_parent( function.parent, name=function.name, callspec=getattr(function, "callspec", None), callobj=function.obj, fixtureinfo=function._fixtureinfo, keywords=function.keywords, originalname=function.originalname, ) subclass_instance.own_markers = function.own_markers assert subclass_instance.own_markers == function.own_markers subclassed_function_signature = inspect.signature(subclass_instance.obj) if "event_loop" in subclassed_function_signature.parameters: subclass_instance.warn( PytestDeprecationWarning( f"{subclass_instance.name} is asynchronous and explicitly " f'requests the "event_loop" fixture. Asynchronous fixtures and ' f'test functions should use "asyncio.get_running_loop()" instead.' ) ) return subclass_instance @staticmethod def _can_substitute(item: Function) -> bool: """Returns whether the specified function can be replaced by this class""" raise NotImplementedError() class Coroutine(PytestAsyncioFunction): """Pytest item created by a coroutine""" @staticmethod def _can_substitute(item: Function) -> bool: func = item.obj return inspect.iscoroutinefunction(func) def runtest(self) -> None: self.obj = wrap_in_sync( # https://github.com/pytest-dev/pytest-asyncio/issues/596 self.obj, # type: ignore[has-type] ) super().runtest() class AsyncGenerator(PytestAsyncioFunction): """Pytest item created by an asynchronous generator""" @staticmethod def _can_substitute(item: Function) -> bool: func = item.obj return inspect.isasyncgenfunction(func) @classmethod def _from_function(cls, function: Function, /) -> Function: async_gen_item = super()._from_function(function) unsupported_item_type_message = ( f"Tests based on asynchronous generators are not supported. " f"{function.name} will be ignored." ) async_gen_item.warn(PytestCollectionWarning(unsupported_item_type_message)) async_gen_item.add_marker( pytest.mark.xfail(run=False, reason=unsupported_item_type_message) ) return async_gen_item class AsyncStaticMethod(PytestAsyncioFunction): """ Pytest item that is a coroutine or an asynchronous generator decorated with staticmethod """ @staticmethod def _can_substitute(item: Function) -> bool: func = item.obj return isinstance(func, staticmethod) and _is_coroutine_or_asyncgen( func.__func__ ) def runtest(self) -> None: self.obj = wrap_in_sync( # https://github.com/pytest-dev/pytest-asyncio/issues/596 self.obj, # type: ignore[has-type] ) super().runtest() class AsyncHypothesisTest(PytestAsyncioFunction): """ Pytest item that is coroutine or an asynchronous generator decorated by @hypothesis.given. """ @staticmethod def _can_substitute(item: Function) -> bool: func = item.obj return ( getattr(func, "is_hypothesis_test", False) # type: ignore[return-value] and getattr(func, "hypothesis", None) and inspect.iscoroutinefunction(func.hypothesis.inner_test) ) def runtest(self) -> None: self.obj.hypothesis.inner_test = wrap_in_sync( self.obj.hypothesis.inner_test, ) super().runtest() _HOLDER: set[FixtureDef] = set() # The function name needs to start with "pytest_" # see https://github.com/pytest-dev/pytest/issues/11307 @pytest.hookimpl(specname="pytest_pycollect_makeitem", tryfirst=True) def pytest_pycollect_makeitem_preprocess_async_fixtures( collector: pytest.Module | pytest.Class, name: str, obj: object ) -> pytest.Item | pytest.Collector | list[pytest.Item | pytest.Collector] | None: """A pytest hook to collect asyncio coroutines.""" if not collector.funcnamefilter(name): return None _preprocess_async_fixtures(collector, _HOLDER) return None # The function name needs to start with "pytest_" # see https://github.com/pytest-dev/pytest/issues/11307 @pytest.hookimpl(specname="pytest_pycollect_makeitem", hookwrapper=True) def pytest_pycollect_makeitem_convert_async_functions_to_subclass( collector: pytest.Module | pytest.Class, name: str, obj: object ) -> Generator[None, pluggy.Result, None]: """ Converts coroutines and async generators collected as pytest.Functions to AsyncFunction items. """ hook_result = yield try: node_or_list_of_nodes: ( pytest.Item | pytest.Collector | list[pytest.Item | pytest.Collector] | None ) = hook_result.get_result() except BaseException as e: hook_result.force_exception(e) return if not node_or_list_of_nodes: return if isinstance(node_or_list_of_nodes, Sequence): node_iterator = iter(node_or_list_of_nodes) else: # Treat single node as a single-element iterable node_iterator = iter((node_or_list_of_nodes,)) updated_node_collection = [] for node in node_iterator: updated_item = node if isinstance(node, Function): specialized_item_class = PytestAsyncioFunction.item_subclass_for(node) if specialized_item_class: if _get_asyncio_mode( node.config ) == Mode.AUTO and not node.get_closest_marker("asyncio"): node.add_marker("asyncio") if node.get_closest_marker("asyncio"): updated_item = specialized_item_class._from_function(node) updated_node_collection.append(updated_item) hook_result.force_result(updated_node_collection) _event_loop_fixture_id = StashKey[str]() _fixture_scope_by_collector_type: Mapping[type[pytest.Collector], _ScopeName] = { Class: "class", # Package is a subclass of module and the dict is used in isinstance checks # Therefore, the order matters and Package needs to appear before Module Package: "package", Module: "module", Session: "session", } # A stack used to push package-scoped loops during collection of a package # and pop those loops during collection of a Module __package_loop_stack: list[FixtureFunctionMarker | FixtureFunction] = [] @pytest.hookimpl def pytest_collectstart(collector: pytest.Collector) -> None: try: collector_scope = next( scope for cls, scope in _fixture_scope_by_collector_type.items() if isinstance(collector, cls) ) except StopIteration: return # Session is not a PyCollector type, so it doesn't have a corresponding # "obj" attribute to attach a dynamic fixture function to. # However, there's only one session per pytest run, so there's no need to # create the fixture dynamically. We can simply define a session-scoped # event loop fixture once in the plugin code. if collector_scope == "session": event_loop_fixture_id = _session_event_loop.__name__ collector.stash[_event_loop_fixture_id] = event_loop_fixture_id return # There seem to be issues when a fixture is shadowed by another fixture # and both differ in their params. # https://github.com/pytest-dev/pytest/issues/2043 # https://github.com/pytest-dev/pytest/issues/11350 # As such, we assign a unique name for each event_loop fixture. # The fixture name is stored in the collector's Stash, so it can # be injected when setting up the test event_loop_fixture_id = f"{collector.nodeid}::" collector.stash[_event_loop_fixture_id] = event_loop_fixture_id @pytest.fixture( scope=collector_scope, name=event_loop_fixture_id, ) def scoped_event_loop( *args, # Function needs to accept "cls" when collected by pytest.Class event_loop_policy, ) -> Iterator[asyncio.AbstractEventLoop]: new_loop_policy = event_loop_policy with _temporary_event_loop_policy(new_loop_policy): loop = _make_pytest_asyncio_loop(asyncio.new_event_loop()) asyncio.set_event_loop(loop) yield loop loop.close() # @pytest.fixture does not register the fixture anywhere, so pytest doesn't # know it exists. We work around this by attaching the fixture function to the # collected Python object, where it will be picked up by pytest.Class.collect() # or pytest.Module.collect(), respectively if type(collector) is Package: # Packages do not have a corresponding Python object. Therefore, the fixture # for the package-scoped event loop is added to a stack. When a module inside # the package is collected, the module will attach the fixture to its # Python object. __package_loop_stack.append(scoped_event_loop) elif isinstance(collector, Module): # Accessing Module.obj triggers a module import executing module-level # statements. A module-level pytest.skip statement raises the "Skipped" # OutcomeException or a Collector.CollectError, if the "allow_module_level" # kwargs is missing. These cases are handled correctly when they happen inside # Collector.collect(), but this hook runs before the actual collect call. # Therefore, we monkey patch Module.collect to add the scoped fixture to the # module before it runs the actual collection. def _patched_collect(): # If the collected module is a DoctestTextfile, collector.obj is None module = collector.obj if module is not None: module.__pytest_asyncio_scoped_event_loop = scoped_event_loop try: package_loop = __package_loop_stack.pop() module.__pytest_asyncio_package_scoped_event_loop = package_loop except IndexError: pass return collector.__original_collect() collector.__original_collect = collector.collect # type: ignore[attr-defined] collector.collect = _patched_collect # type: ignore[method-assign] elif isinstance(collector, Class): collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop @contextlib.contextmanager def _temporary_event_loop_policy(policy: AbstractEventLoopPolicy) -> Iterator[None]: old_loop_policy = asyncio.get_event_loop_policy() try: old_loop = _get_event_loop_no_warn() except RuntimeError: old_loop = None asyncio.set_event_loop_policy(policy) try: yield finally: # Try detecting user-created event loops that were left unclosed # at the end of a test. try: current_loop: AbstractEventLoop | None = _get_event_loop_no_warn() except RuntimeError: current_loop = None if current_loop is not None and not current_loop.is_closed(): warnings.warn( _UNCLOSED_EVENT_LOOP_WARNING % current_loop, DeprecationWarning, ) current_loop.close() asyncio.set_event_loop_policy(old_loop_policy) # When a test uses both a scoped event loop and the event_loop fixture, # the "_provide_clean_event_loop" finalizer of the event_loop fixture # will already have installed a fresh event loop, in order to shield # subsequent tests from side-effects. We close this loop before restoring # the old loop to avoid ResourceWarnings. try: _get_event_loop_no_warn().close() except RuntimeError: pass asyncio.set_event_loop(old_loop) _REDEFINED_EVENT_LOOP_FIXTURE_WARNING = dedent( """\ The event_loop fixture provided by pytest-asyncio has been redefined in %s:%d Replacing the event_loop fixture with a custom implementation is deprecated and will lead to errors in the future. If you want to request an asyncio event loop with a scope other than function scope, use the "loop_scope" argument to the asyncio mark when marking the tests. If you want to return different types of event loops, use the event_loop_policy fixture. """ ) @pytest.hookimpl(tryfirst=True) def pytest_generate_tests(metafunc: Metafunc) -> None: marker = metafunc.definition.get_closest_marker("asyncio") if not marker: return scope = _get_marked_loop_scope(marker) if scope == "function": return event_loop_node = _retrieve_scope_root(metafunc.definition, scope) event_loop_fixture_id = event_loop_node.stash.get(_event_loop_fixture_id, None) if event_loop_fixture_id: # This specific fixture name may already be in metafunc.argnames, if this # test indirectly depends on the fixture. For example, this is the case # when the test depends on an async fixture, both of which share the same # event loop fixture mark. if event_loop_fixture_id in metafunc.fixturenames: return fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage") assert fixturemanager is not None if "event_loop" in metafunc.fixturenames: raise MultipleEventLoopsRequestedError( _MULTIPLE_LOOPS_REQUESTED_ERROR.format( test_name=metafunc.definition.nodeid, scope=scope, scoped_loop_node=event_loop_node.nodeid, ), ) # Add the scoped event loop fixture to Metafunc's list of fixture names and # fixturedefs and leave the actual parametrization to pytest # The fixture needs to be appended to avoid messing up the fixture evaluation # order metafunc.fixturenames.append(event_loop_fixture_id) metafunc._arg2fixturedefs[event_loop_fixture_id] = ( fixturemanager._arg2fixturedefs[event_loop_fixture_id] ) @pytest.hookimpl(hookwrapper=True) def pytest_fixture_setup( fixturedef: FixtureDef, ) -> Generator[None, pluggy.Result, None]: """Adjust the event loop policy when an event loop is produced.""" if fixturedef.argname == "event_loop": # The use of a fixture finalizer is preferred over the # pytest_fixture_post_finalizer hook. The fixture finalizer is invoked once # for each fixture, whereas the hook may be invoked multiple times for # any specific fixture. # see https://github.com/pytest-dev/pytest/issues/5848 _add_finalizers( fixturedef, _close_event_loop, _restore_event_loop_policy(asyncio.get_event_loop_policy()), _provide_clean_event_loop, ) outcome = yield loop: asyncio.AbstractEventLoop = outcome.get_result() # Weird behavior was observed when checking for an attribute of FixtureDef.func # Instead, we now check for a special attribute of the returned event loop fixture_filename = inspect.getsourcefile(fixturedef.func) if not _is_pytest_asyncio_loop(loop): _, fixture_line_number = inspect.getsourcelines(fixturedef.func) warnings.warn( _REDEFINED_EVENT_LOOP_FIXTURE_WARNING % (fixture_filename, fixture_line_number), DeprecationWarning, ) policy = asyncio.get_event_loop_policy() try: old_loop = _get_event_loop_no_warn(policy) if old_loop is not loop and not _is_pytest_asyncio_loop(old_loop): old_loop.close() except RuntimeError: # Either the current event loop has been set to None # or the loop policy doesn't specify to create new loops # or we're not in the main thread pass policy.set_event_loop(loop) return yield def _make_pytest_asyncio_loop(loop: AbstractEventLoop) -> AbstractEventLoop: loop.__pytest_asyncio = True # type: ignore[attr-defined] return loop def _is_pytest_asyncio_loop(loop: AbstractEventLoop) -> bool: return getattr(loop, "__pytest_asyncio", False) def _add_finalizers(fixturedef: FixtureDef, *finalizers: Callable[[], object]) -> None: """ Registers the specified fixture finalizers in the fixture. Finalizers need to be specified in the exact order in which they should be invoked. :param fixturedef: Fixture definition which finalizers should be added to :param finalizers: Finalizers to be added """ for finalizer in reversed(finalizers): fixturedef.addfinalizer(finalizer) _UNCLOSED_EVENT_LOOP_WARNING = dedent( """\ pytest-asyncio detected an unclosed event loop when tearing down the event_loop fixture: %r pytest-asyncio will close the event loop for you, but future versions of the library will no longer do so. In order to ensure compatibility with future versions, please make sure that: 1. Any custom "event_loop" fixture properly closes the loop after yielding it 2. The scopes of your custom "event_loop" fixtures do not overlap 3. Your code does not modify the event loop in async fixtures or tests """ ) def _close_event_loop() -> None: policy = asyncio.get_event_loop_policy() try: loop = policy.get_event_loop() except RuntimeError: loop = None if loop is not None and not _is_pytest_asyncio_loop(loop): if not loop.is_closed(): warnings.warn( _UNCLOSED_EVENT_LOOP_WARNING % loop, DeprecationWarning, ) loop.close() def _restore_event_loop_policy(previous_policy) -> Callable[[], None]: def _restore_policy(): # Close any event loop associated with the old loop policy # to avoid ResourceWarnings in the _provide_clean_event_loop finalizer try: loop = _get_event_loop_no_warn(previous_policy) except RuntimeError: loop = None if loop and not _is_pytest_asyncio_loop(loop): loop.close() asyncio.set_event_loop_policy(previous_policy) return _restore_policy def _provide_clean_event_loop() -> None: # At this point, the event loop for the current thread is closed. # When a user calls asyncio.get_event_loop(), they will get a closed loop. # In order to avoid this side effect from pytest-asyncio, we need to replace # the current loop with a fresh one. # Note that we cannot set the loop to None, because get_event_loop only creates # a new loop, when set_event_loop has not been called. policy = asyncio.get_event_loop_policy() try: old_loop = _get_event_loop_no_warn(policy) except RuntimeError: old_loop = None if old_loop is not None and not _is_pytest_asyncio_loop(old_loop): new_loop = policy.new_event_loop() policy.set_event_loop(new_loop) def _get_event_loop_no_warn( policy: AbstractEventLoopPolicy | None = None, ) -> asyncio.AbstractEventLoop: with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) if policy is not None: return policy.get_event_loop() else: return asyncio.get_event_loop() @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: """ Pytest hook called before a test case is run. Wraps marked tests in a synchronous function where the wrapped test coroutine is executed in an event loop. """ if pyfuncitem.get_closest_marker("asyncio") is not None: if isinstance(pyfuncitem, PytestAsyncioFunction): asyncio_mode = _get_asyncio_mode(pyfuncitem.config) for fixname, fixtures in pyfuncitem._fixtureinfo.name2fixturedefs.items(): # name2fixturedefs is a dict between fixture name and a list of matching # fixturedefs. The last entry in the list is closest and the one used. func = fixtures[-1].func if ( asyncio_mode == Mode.STRICT and _is_coroutine_or_asyncgen(func) and not _is_asyncio_fixture_function(func) ): warnings.warn( PytestDeprecationWarning( f"asyncio test {pyfuncitem.name!r} requested async " "@pytest.fixture " f"{fixname!r} in strict mode. " "You might want to use @pytest_asyncio.fixture or switch " "to auto mode. " "This will become an error in future versions of " "flake8-asyncio." ), stacklevel=1, ) # no stacklevel points at the users code, so we set stacklevel=1 # so it at least indicates that it's the plugin complaining. # Pytest gives the test file & name in the warnings summary at least else: pyfuncitem.warn( pytest.PytestWarning( f"The test {pyfuncitem} is marked with '@pytest.mark.asyncio' " "but it is not an async function. " "Please remove the asyncio mark. " "If the test is not marked explicitly, " "check for global marks applied via 'pytestmark'." ) ) yield return None def wrap_in_sync( func: Callable[..., Awaitable[Any]], ): """ Return a sync wrapper around an async function executing it in the current event loop. """ # if the function is already wrapped, we rewrap using the original one # not using __wrapped__ because the original function may already be # a wrapped one raw_func = getattr(func, "_raw_test_func", None) if raw_func is not None: func = raw_func @functools.wraps(func) def inner(*args, **kwargs): coro = func(*args, **kwargs) _loop = _get_event_loop_no_warn() task = asyncio.ensure_future(coro, loop=_loop) try: _loop.run_until_complete(task) except BaseException: # run_until_complete doesn't get the result from exceptions # that are not subclasses of `Exception`. Consume all # exceptions to prevent asyncio's warning from logging. if task.done() and not task.cancelled(): task.exception() raise inner._raw_test_func = func # type: ignore[attr-defined] return inner _MULTIPLE_LOOPS_REQUESTED_ERROR = dedent( """\ Multiple asyncio event loops with different scopes have been requested by {test_name}. The test explicitly requests the event_loop fixture, while another event loop with {scope} scope is provided by {scoped_loop_node}. Remove "event_loop" from the requested fixture in your test to run the test in a {scope}-scoped event loop or remove the scope argument from the "asyncio" mark to run the test in a function-scoped event loop. """ ) def pytest_runtest_setup(item: pytest.Item) -> None: marker = item.get_closest_marker("asyncio") if marker is None: return scope = _get_marked_loop_scope(marker) if scope != "function": parent_node = _retrieve_scope_root(item, scope) event_loop_fixture_id = parent_node.stash[_event_loop_fixture_id] else: event_loop_fixture_id = "event_loop" fixturenames = item.fixturenames # type: ignore[attr-defined] if event_loop_fixture_id not in fixturenames: fixturenames.append(event_loop_fixture_id) obj = getattr(item, "obj", None) if not getattr(obj, "hypothesis", False) and getattr( obj, "is_hypothesis_test", False ): pytest.fail( f"test function `{item!r}` is using Hypothesis, but pytest-asyncio " "only works with Hypothesis 3.64.0 or later." ) _DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR = """\ An asyncio pytest marker defines both "scope" and "loop_scope", \ but it should only use "loop_scope". """ _MARKER_SCOPE_KWARG_DEPRECATION_WARNING = """\ The "scope" keyword argument to the asyncio marker has been deprecated. \ Please use the "loop_scope" argument instead. """ def _get_marked_loop_scope(asyncio_marker: Mark) -> _ScopeName: assert asyncio_marker.name == "asyncio" if asyncio_marker.args or ( asyncio_marker.kwargs and set(asyncio_marker.kwargs) - {"loop_scope", "scope"} ): raise ValueError("mark.asyncio accepts only a keyword argument 'loop_scope'.") if "scope" in asyncio_marker.kwargs: if "loop_scope" in asyncio_marker.kwargs: raise pytest.UsageError(_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR) warnings.warn(PytestDeprecationWarning(_MARKER_SCOPE_KWARG_DEPRECATION_WARNING)) scope = asyncio_marker.kwargs.get("loop_scope") or asyncio_marker.kwargs.get( "scope", "function" ) assert scope in {"function", "class", "module", "package", "session"} return scope def _retrieve_scope_root(item: Collector | Item, scope: str) -> Collector: node_type_by_scope = { "class": Class, "module": Module, "package": Package, "session": Session, } scope_root_type = node_type_by_scope[scope] for node in reversed(item.listchain()): if isinstance(node, scope_root_type): assert isinstance(node, pytest.Collector) return node error_message = ( f"{item.name} is marked to be run in an event loop with scope {scope}, " f"but is not part of any {scope}." ) raise pytest.UsageError(error_message) @pytest.fixture def event_loop(request: FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]: """Create an instance of the default event loop for each test case.""" new_loop_policy = request.getfixturevalue(event_loop_policy.__name__) with _temporary_event_loop_policy(new_loop_policy): loop = asyncio.get_event_loop_policy().new_event_loop() # Add a magic value to the event loop, so pytest-asyncio can determine if the # event_loop fixture was overridden. Other implementations of event_loop don't # set this value. # The magic value must be set as part of the function definition, because pytest # seems to have multiple instances of the same FixtureDef or fixture function loop = _make_pytest_asyncio_loop(loop) yield loop loop.close() @pytest.fixture(scope="session") def _session_event_loop( request: FixtureRequest, event_loop_policy: AbstractEventLoopPolicy ) -> Iterator[asyncio.AbstractEventLoop]: new_loop_policy = event_loop_policy with _temporary_event_loop_policy(new_loop_policy): loop = _make_pytest_asyncio_loop(asyncio.new_event_loop()) asyncio.set_event_loop(loop) yield loop loop.close() @pytest.fixture(scope="session", autouse=True) def event_loop_policy() -> AbstractEventLoopPolicy: """Return an instance of the policy used to create asyncio event loops.""" return asyncio.get_event_loop_policy() def is_async_test(item: Item) -> bool: """Returns whether a test item is a pytest-asyncio test""" return isinstance(item, PytestAsyncioFunction) def _unused_port(socket_type: int) -> int: """Find an unused localhost port from 1024-65535 and return it.""" with contextlib.closing(socket.socket(type=socket_type)) as sock: sock.bind(("127.0.0.1", 0)) return sock.getsockname()[1] @pytest.fixture def unused_tcp_port() -> int: return _unused_port(socket.SOCK_STREAM) @pytest.fixture def unused_udp_port() -> int: return _unused_port(socket.SOCK_DGRAM) @pytest.fixture(scope="session") def unused_tcp_port_factory() -> Callable[[], int]: """A factory function, producing different unused TCP ports.""" produced = set() def factory(): """Return an unused port.""" port = _unused_port(socket.SOCK_STREAM) while port in produced: port = _unused_port(socket.SOCK_STREAM) produced.add(port) return port return factory @pytest.fixture(scope="session") def unused_udp_port_factory() -> Callable[[], int]: """A factory function, producing different unused UDP ports.""" produced = set() def factory(): """Return an unused port.""" port = _unused_port(socket.SOCK_DGRAM) while port in produced: port = _unused_port(socket.SOCK_DGRAM) produced.add(port) return port return factory pytest-asyncio-0.25.1/pytest_asyncio/py.typed000066400000000000000000000000001473541640200213420ustar00rootroot00000000000000pytest-asyncio-0.25.1/setup.cfg000066400000000000000000000016351473541640200164260ustar00rootroot00000000000000[metadata] # Not everything is in in pyproject.toml because of this issue: ; Traceback (most recent call last): ; File "/tmp/build-env-rud8b5r6/lib/python3.12/site-packages/setuptools/config/expand.py", line 69, in __getattr__ ; return next( ; ^^^^^ ;StopIteration ; ;The above exception was the direct cause of the following exception: ; ;Traceback (most recent call last): ; File "/tmp/build-env-rud8b5r6/lib/python3.12/site-packages/setuptools/config/expand.py", line 183, in read_attr ; return getattr(StaticModule(module_name, spec), attr_name) ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ; File "/tmp/build-env-rud8b5r6/lib/python3.12/site-packages/setuptools/config/expand.py", line 75, in __getattr__ ; raise AttributeError(f"{self.name} has no attribute {attr}") from e ;AttributeError: pytest_asyncio has no attribute __version__ version = attr: pytest_asyncio.__version__ pytest-asyncio-0.25.1/tests/000077500000000000000000000000001473541640200157425ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/__init__.py000066400000000000000000000000001473541640200200410ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/async_fixtures/000077500000000000000000000000001473541640200210105ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/async_fixtures/__init__.py000066400000000000000000000000001473541640200231070ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/async_fixtures/test_async_fixtures.py000066400000000000000000000014561473541640200254750ustar00rootroot00000000000000from __future__ import annotations import asyncio import unittest.mock import pytest START = object() END = object() RETVAL = object() @pytest.fixture def mock(): return unittest.mock.Mock(return_value=RETVAL) @pytest.fixture async def async_fixture(mock): return await asyncio.sleep(0.1, result=mock(START)) @pytest.mark.asyncio async def test_async_fixture(async_fixture, mock): assert mock.call_count == 1 assert mock.call_args_list[-1] == unittest.mock.call(START) assert async_fixture is RETVAL class TestAsyncFixtureMethod: is_same_instance = False @pytest.fixture(autouse=True) async def async_fixture_method(self): self.is_same_instance = True @pytest.mark.asyncio async def test_async_fixture_method(self): assert self.is_same_instance pytest-asyncio-0.25.1/tests/async_fixtures/test_async_fixtures_contextvars.py000066400000000000000000000155311473541640200301340ustar00rootroot00000000000000""" Regression test for https://github.com/pytest-dev/pytest-asyncio/issues/127: contextvars were not properly maintained among fixtures and tests. """ from __future__ import annotations import sys from textwrap import dedent import pytest from pytest import Pytester _prelude = dedent( """ import pytest import pytest_asyncio from contextlib import contextmanager from contextvars import ContextVar _context_var = ContextVar("context_var") @contextmanager def context_var_manager(value): token = _context_var.set(value) try: yield finally: _context_var.reset(token) """ ) def test_var_from_sync_generator_propagates_to_async(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( _prelude + dedent( """ @pytest.fixture def var_fixture(): with context_var_manager("value"): yield @pytest_asyncio.fixture async def check_var_fixture(var_fixture): assert _context_var.get() == "value" @pytest.mark.asyncio async def test(check_var_fixture): assert _context_var.get() == "value" """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) @pytest.mark.xfail( sys.version_info < (3, 11), reason="requires asyncio Task context support", strict=True, ) def test_var_from_async_generator_propagates_to_sync(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( _prelude + dedent( """ @pytest_asyncio.fixture async def var_fixture(): with context_var_manager("value"): yield @pytest.fixture def check_var_fixture(var_fixture): assert _context_var.get() == "value" @pytest.mark.asyncio async def test(check_var_fixture): assert _context_var.get() == "value" """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) @pytest.mark.xfail( sys.version_info < (3, 11), reason="requires asyncio Task context support", strict=True, ) def test_var_from_async_fixture_propagates_to_sync(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( _prelude + dedent( """ @pytest_asyncio.fixture async def var_fixture(): _context_var.set("value") # Rely on async fixture teardown to reset the context var. @pytest.fixture def check_var_fixture(var_fixture): assert _context_var.get() == "value" def test(check_var_fixture): assert _context_var.get() == "value" """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) @pytest.mark.xfail( sys.version_info < (3, 11), reason="requires asyncio Task context support", strict=True, ) def test_var_from_generator_reset_before_previous_fixture_cleanup(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( _prelude + dedent( """ @pytest_asyncio.fixture async def no_var_fixture(): with pytest.raises(LookupError): _context_var.get() yield with pytest.raises(LookupError): _context_var.get() @pytest_asyncio.fixture async def var_fixture(no_var_fixture): with context_var_manager("value"): yield @pytest.mark.asyncio async def test(var_fixture): assert _context_var.get() == "value" """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) @pytest.mark.xfail( sys.version_info < (3, 11), reason="requires asyncio Task context support", strict=True, ) def test_var_from_fixture_reset_before_previous_fixture_cleanup(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( _prelude + dedent( """ @pytest_asyncio.fixture async def no_var_fixture(): with pytest.raises(LookupError): _context_var.get() yield with pytest.raises(LookupError): _context_var.get() @pytest_asyncio.fixture async def var_fixture(no_var_fixture): _context_var.set("value") # Rely on async fixture teardown to reset the context var. @pytest.mark.asyncio async def test(var_fixture): assert _context_var.get() == "value" """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) @pytest.mark.xfail( sys.version_info < (3, 11), reason="requires asyncio Task context support", strict=True, ) def test_var_previous_value_restored_after_fixture(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( _prelude + dedent( """ @pytest_asyncio.fixture async def var_fixture_1(): with context_var_manager("value1"): yield assert _context_var.get() == "value1" @pytest_asyncio.fixture async def var_fixture_2(var_fixture_1): with context_var_manager("value2"): yield assert _context_var.get() == "value2" @pytest.mark.asyncio async def test(var_fixture_2): assert _context_var.get() == "value2" """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) @pytest.mark.xfail( sys.version_info < (3, 11), reason="requires asyncio Task context support", strict=True, ) def test_var_set_to_existing_value_ok(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( _prelude + dedent( """ @pytest_asyncio.fixture async def var_fixture(): with context_var_manager("value"): yield @pytest_asyncio.fixture async def same_var_fixture(var_fixture): with context_var_manager(_context_var.get()): yield @pytest.mark.asyncio async def test(same_var_fixture): assert _context_var.get() == "value" """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/async_fixtures/test_async_fixtures_scope.py000066400000000000000000000010571473541640200266630ustar00rootroot00000000000000""" We support module-scoped async fixtures, but only if the event loop is module-scoped too. """ from __future__ import annotations import asyncio import pytest @pytest.fixture(scope="module") def event_loop(): """A module-scoped event loop.""" loop = asyncio.new_event_loop() yield loop loop.close() @pytest.fixture(scope="module") async def async_fixture(): await asyncio.sleep(0.1) return 1 @pytest.mark.asyncio async def test_async_fixture_scope(async_fixture): assert async_fixture == 1 await asyncio.sleep(0.1) pytest-asyncio-0.25.1/tests/async_fixtures/test_async_fixtures_with_finalizer.py000066400000000000000000000040731473541640200305710ustar00rootroot00000000000000from __future__ import annotations import asyncio import functools import pytest import pytest_asyncio @pytest.mark.asyncio(loop_scope="module") async def test_module_with_event_loop_finalizer(port_with_event_loop_finalizer): await asyncio.sleep(0.01) assert port_with_event_loop_finalizer @pytest.mark.asyncio(loop_scope="module") async def test_module_with_get_event_loop_finalizer(port_with_get_event_loop_finalizer): await asyncio.sleep(0.01) assert port_with_get_event_loop_finalizer @pytest.fixture(scope="module") def event_loop(): """Change event_loop fixture to module level.""" policy = asyncio.get_event_loop_policy() loop = policy.new_event_loop() yield loop loop.close() @pytest_asyncio.fixture(loop_scope="module", scope="module") async def port_with_event_loop_finalizer(request): def port_finalizer(finalizer): async def port_afinalizer(): # await task using loop provided by event_loop fixture # RuntimeError is raised if task is created on a different loop await finalizer asyncio.run(port_afinalizer()) worker = asyncio.ensure_future(asyncio.sleep(0.2)) request.addfinalizer(functools.partial(port_finalizer, worker)) return True @pytest_asyncio.fixture(loop_scope="module", scope="module") async def port_with_get_event_loop_finalizer(request): def port_finalizer(finalizer): async def port_afinalizer(): # await task using current loop retrieved from the event loop policy # RuntimeError is raised if task is created on a different loop. # This can happen when pytest_fixture_setup # does not set up the loop correctly, # for example when policy.set_event_loop() is called with a wrong argument await finalizer current_loop = asyncio.get_event_loop_policy().get_event_loop() current_loop.run_until_complete(port_afinalizer()) worker = asyncio.ensure_future(asyncio.sleep(0.2)) request.addfinalizer(functools.partial(port_finalizer, worker)) return True pytest-asyncio-0.25.1/tests/async_fixtures/test_async_gen_fixtures.py000066400000000000000000000021341473541640200263200ustar00rootroot00000000000000from __future__ import annotations import unittest.mock import pytest START = object() END = object() RETVAL = object() @pytest.fixture(scope="module") def mock(): return unittest.mock.Mock(return_value=RETVAL) @pytest.fixture async def async_gen_fixture(mock): try: yield mock(START) except Exception as e: mock(e) else: mock(END) @pytest.mark.asyncio async def test_async_gen_fixture(async_gen_fixture, mock): assert mock.called assert mock.call_args_list[-1] == unittest.mock.call(START) assert async_gen_fixture is RETVAL @pytest.mark.asyncio async def test_async_gen_fixture_finalized(mock): try: assert mock.called assert mock.call_args_list[-1] == unittest.mock.call(END) finally: mock.reset_mock() class TestAsyncGenFixtureMethod: is_same_instance = False @pytest.fixture(autouse=True) async def async_gen_fixture_method(self): self.is_same_instance = True yield None @pytest.mark.asyncio async def test_async_gen_fixture_method(self): assert self.is_same_instance pytest-asyncio-0.25.1/tests/async_fixtures/test_nested.py000066400000000000000000000010631473541640200237030ustar00rootroot00000000000000from __future__ import annotations import asyncio import pytest @pytest.fixture() async def async_inner_fixture(): await asyncio.sleep(0.01) print("inner start") yield True print("inner stop") @pytest.fixture() async def async_fixture_outer(async_inner_fixture): await asyncio.sleep(0.01) print("outer start") assert async_inner_fixture is True yield True print("outer stop") @pytest.mark.asyncio async def test_async_fixture(async_fixture_outer): assert async_fixture_outer is True print("test_async_fixture") pytest-asyncio-0.25.1/tests/async_fixtures/test_parametrized_loop.py000066400000000000000000000022761473541640200261500ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_event_loop_parametrization(pytester: Pytester): pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio TESTS_COUNT = 0 def teardown_module(): # parametrized 2 * 2 times: 2 for 'event_loop' and 2 for 'fix' assert TESTS_COUNT == 4 @pytest.fixture(scope="module", params=[1, 2]) def event_loop(request): request.param loop = asyncio.new_event_loop() yield loop loop.close() @pytest_asyncio.fixture(params=["a", "b"]) async def fix(request): await asyncio.sleep(0) return request.param @pytest.mark.asyncio async def test_parametrized_loop(fix): await asyncio.sleep(0) global TESTS_COUNT TESTS_COUNT += 1 """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=4) pytest-asyncio-0.25.1/tests/async_fixtures/test_shared_module_fixture.py000066400000000000000000000023041473541640200270010ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_asyncio_mark_provides_package_scoped_loop_strict_mode(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", conftest=dedent( """\ import pytest_asyncio @pytest_asyncio.fixture(loop_scope="module", scope="module") async def async_shared_module_fixture(): return True """ ), test_module_one=dedent( """\ import pytest @pytest.mark.asyncio async def test_shared_module_fixture_use_a(async_shared_module_fixture): assert async_shared_module_fixture is True """ ), test_module_two=dedent( """\ import pytest @pytest.mark.asyncio async def test_shared_module_fixture_use_b(async_shared_module_fixture): assert async_shared_module_fixture is True """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) pytest-asyncio-0.25.1/tests/conftest.py000066400000000000000000000014051473541640200201410ustar00rootroot00000000000000from __future__ import annotations import asyncio import pytest pytest_plugins = "pytester" @pytest.fixture def dependent_fixture(event_loop): """A fixture dependent on the event_loop fixture, doing some cleanup.""" counter = 0 async def just_a_sleep(): """Just sleep a little while.""" nonlocal event_loop await asyncio.sleep(0.1) nonlocal counter counter += 1 event_loop.run_until_complete(just_a_sleep()) yield event_loop.run_until_complete(just_a_sleep()) assert counter == 2 @pytest.fixture(scope="session", name="factory_involving_factories") def factory_involving_factories_fixture(unused_tcp_port_factory): def factory(): return unused_tcp_port_factory() return factory pytest-asyncio-0.25.1/tests/hypothesis/000077500000000000000000000000001473541640200201415ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/hypothesis/__init__.py000066400000000000000000000000001473541640200222400ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/hypothesis/test_base.py000066400000000000000000000073241473541640200224720ustar00rootroot00000000000000""" Tests for the Hypothesis integration, which wraps async functions in a sync shim for Hypothesis. """ from __future__ import annotations from textwrap import dedent import pytest from hypothesis import given, strategies as st from pytest import Pytester def test_hypothesis_given_decorator_before_asyncio_mark(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest from hypothesis import given, strategies as st @given(st.integers()) @pytest.mark.asyncio async def test_mark_inner(n): assert isinstance(n, int) """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1) @pytest.mark.asyncio @given(st.integers()) async def test_mark_outer(n): assert isinstance(n, int) @pytest.mark.parametrize("y", [1, 2]) @given(x=st.none()) @pytest.mark.asyncio async def test_mark_and_parametrize(x, y): assert x is None assert y in (1, 2) def test_can_use_explicit_event_loop_fixture(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = module") pytester.makepyfile( dedent( """\ import asyncio import pytest from hypothesis import given import hypothesis.strategies as st pytest_plugins = 'pytest_asyncio' @pytest.fixture(scope="module") def event_loop(): loop = asyncio.get_event_loop_policy().new_event_loop() yield loop loop.close() @given(st.integers()) @pytest.mark.asyncio async def test_explicit_fixture_request(event_loop, n): semaphore = asyncio.Semaphore(value=0) event_loop.call_soon(semaphore.release) await semaphore.acquire() """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=2) result.stdout.fnmatch_lines( [ '*is asynchronous and explicitly requests the "event_loop" fixture*', "*event_loop fixture provided by pytest-asyncio has been redefined*", ] ) def test_async_auto_marked(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest from hypothesis import given import hypothesis.strategies as st pytest_plugins = 'pytest_asyncio' @given(n=st.integers()) async def test_hypothesis(n: int): assert isinstance(n, int) """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) def test_sync_not_auto_marked(pytester: Pytester): """Assert that synchronous Hypothesis functions are not marked with asyncio""" pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest from hypothesis import given import hypothesis.strategies as st pytest_plugins = 'pytest_asyncio' @given(n=st.integers()) def test_hypothesis(request, n: int): markers = [marker.name for marker in request.node.own_markers] assert "asyncio" not in markers assert isinstance(n, int) """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/loop_fixture_scope/000077500000000000000000000000001473541640200216525ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/loop_fixture_scope/__init__.py000066400000000000000000000000001473541640200237510ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/loop_fixture_scope/conftest.py000066400000000000000000000005731473541640200240560ustar00rootroot00000000000000from __future__ import annotations import asyncio import pytest class CustomSelectorLoop(asyncio.SelectorEventLoop): """A subclass with no overrides, just to test for presence.""" @pytest.fixture(scope="module") def event_loop(): """Create an instance of the default event loop for each test case.""" loop = CustomSelectorLoop() yield loop loop.close() pytest-asyncio-0.25.1/tests/loop_fixture_scope/test_loop_fixture_scope.py000066400000000000000000000007251473541640200271770ustar00rootroot00000000000000"""Unit tests for overriding the event loop with a larger scoped one.""" from __future__ import annotations import asyncio import pytest @pytest.mark.asyncio async def test_for_custom_loop(): """This test should be executed using the custom loop.""" await asyncio.sleep(0.01) assert type(asyncio.get_event_loop()).__name__ == "CustomSelectorLoop" @pytest.mark.asyncio async def test_dependent_fixture(dependent_fixture): await asyncio.sleep(0.1) pytest-asyncio-0.25.1/tests/markers/000077500000000000000000000000001473541640200174065ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/markers/__init__.py000066400000000000000000000000001473541640200215050ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/markers/test_class_scope.py000066400000000000000000000234221473541640200233200ustar00rootroot00000000000000"""Test if pytestmark works when defined on a class.""" from __future__ import annotations import asyncio from textwrap import dedent import pytest class TestPyTestMark: pytestmark = pytest.mark.asyncio async def test_is_asyncio(self, sample_fixture): assert asyncio.get_event_loop() counter = 1 async def inc(): nonlocal counter counter += 1 await asyncio.sleep(0) await asyncio.ensure_future(inc()) assert counter == 2 @pytest.fixture def sample_fixture(): return None def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_functions( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest class TestClassScopedLoop: loop: asyncio.AbstractEventLoop @pytest.mark.asyncio(loop_scope="class") async def test_remember_loop(self): TestClassScopedLoop.loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="class") async def test_this_runs_in_same_loop(self): assert asyncio.get_running_loop() is TestClassScopedLoop.loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_class( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.mark.asyncio(loop_scope="class") class TestClassScopedLoop: loop: asyncio.AbstractEventLoop async def test_remember_loop(self): TestClassScopedLoop.loop = asyncio.get_running_loop() async def test_this_runs_in_same_loop(self): assert asyncio.get_running_loop() is TestClassScopedLoop.loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_raises_when_class_scoped_is_request_without_class( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.mark.asyncio(loop_scope="class") async def test_has_no_surrounding_class(): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(errors=1) result.stdout.fnmatch_lines( "*is marked to be run in an event loop with scope*", ) def test_asyncio_mark_is_inherited_to_subclasses(pytester: pytest.Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.mark.asyncio(loop_scope="class") class TestSuperClassWithMark: pass class TestWithoutMark(TestSuperClassWithMark): loop: asyncio.AbstractEventLoop async def test_remember_loop(self): TestWithoutMark.loop = asyncio.get_running_loop() async def test_this_runs_in_same_loop(self): assert asyncio.get_running_loop() is TestWithoutMark.loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_respects_the_loop_policy( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): pass class TestUsesCustomEventLoop: @pytest.fixture(scope="class") def event_loop_policy(self): return CustomEventLoopPolicy() @pytest.mark.asyncio async def test_uses_custom_event_loop_policy(self): assert isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) @pytest.mark.asyncio async def test_does_not_use_custom_event_loop_policy(): assert not isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_respects_parametrized_loop_policies( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.fixture( scope="class", params=[ asyncio.DefaultEventLoopPolicy(), asyncio.DefaultEventLoopPolicy(), ] ) def event_loop_policy(request): return request.param @pytest.mark.asyncio(loop_scope="class") class TestWithDifferentLoopPolicies: async def test_parametrized_loop(self, request): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_provides_class_scoped_loop_to_fixtures( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio @pytest.mark.asyncio(loop_scope="class") class TestClassScopedLoop: loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture async def my_fixture(self): TestClassScopedLoop.loop = asyncio.get_running_loop() @pytest.mark.asyncio async def test_runs_is_same_loop_as_fixture(self, my_fixture): assert asyncio.get_running_loop() is TestClassScopedLoop.loop """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_class_scoped_fixture_with_function_scoped_test( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop class TestMixedScopes: @pytest_asyncio.fixture(loop_scope="class", scope="class") async def async_fixture(self): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="function") async def test_runs_in_different_loop_as_fixture(self, async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import asyncio class TestClass: @pytest.fixture(scope="class") def sets_event_loop_to_none(self): # asyncio.run() creates a new event loop without closing the # existing one. For any test, but the first one, this leads to # a ResourceWarning when the discarded loop is destroyed by the # garbage collector. We close the current loop to avoid this. try: asyncio.get_event_loop().close() except RuntimeError: pass return asyncio.run(asyncio.sleep(0)) # asyncio.run() sets the current event loop to None when finished @pytest.mark.asyncio(loop_scope="class") # parametrization may impact fixture ordering @pytest.mark.parametrize("n", (0, 1)) async def test_does_not_fail(self, sets_event_loop_to_none, n): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set( pytester: pytest.Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio(loop_scope="class") class TestClass: async def test_anything(self): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(warnings=0, passed=1) pytest-asyncio-0.25.1/tests/markers/test_function_scope.py000066400000000000000000000213551473541640200240430ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_asyncio_mark_provides_function_scoped_loop_strict_mode(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio loop: asyncio.AbstractEventLoop async def test_remember_loop(): global loop loop = asyncio.get_running_loop() async def test_does_not_run_in_same_loop(): global loop assert asyncio.get_running_loop() is not loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_loop_scope_function_provides_function_scoped_event_loop(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio(loop_scope="function") loop: asyncio.AbstractEventLoop async def test_remember_loop(): global loop loop = asyncio.get_running_loop() async def test_does_not_run_in_same_loop(): global loop assert asyncio.get_running_loop() is not loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_raises_when_scope_and_loop_scope_arguments_are_present(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio(scope="function", loop_scope="function") async def test_raises(): ... """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(errors=1) def test_warns_when_scope_argument_is_present(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio(scope="function") async def test_warns(): ... """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1, warnings=2) result.stdout.fnmatch_lines("*DeprecationWarning*") def test_function_scope_supports_explicit_event_loop_fixture_request( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest pytestmark = pytest.mark.asyncio async def test_remember_loop(event_loop): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( '*is asynchronous and explicitly requests the "event_loop" fixture*' ) def test_asyncio_mark_respects_the_loop_policy( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): pass @pytest.fixture(scope="function") def event_loop_policy(): return CustomEventLoopPolicy() async def test_uses_custom_event_loop_policy(): assert isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_respects_parametrized_loop_policies( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): pass @pytest.fixture( scope="module", params=[ CustomEventLoopPolicy(), CustomEventLoopPolicy(), ], ) def event_loop_policy(request): return request.param async def test_parametrized_loop(): assert isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_provides_function_scoped_loop_to_fixtures( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio pytestmark = pytest.mark.asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture async def my_fixture(): global loop loop = asyncio.get_running_loop() async def test_runs_is_same_loop_as_fixture(my_fixture): global loop assert asyncio.get_running_loop() is loop """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import asyncio @pytest.fixture def sets_event_loop_to_none(): # asyncio.run() creates a new event loop without closing the existing # one. For any test, but the first one, this leads to a ResourceWarning # when the discarded loop is destroyed by the garbage collector. # We close the current loop to avoid this try: asyncio.get_event_loop().close() except RuntimeError: pass return asyncio.run(asyncio.sleep(0)) # asyncio.run() sets the current event loop to None when finished @pytest.mark.asyncio # parametrization may impact fixture ordering @pytest.mark.parametrize("n", (0, 1)) async def test_does_not_fail(sets_event_loop_to_none, n): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio async def test_anything(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(warnings=0, passed=1) def test_asyncio_mark_does_not_duplicate_other_marks_in_auto_mode( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makeconftest( dedent( """\ def pytest_configure(config): config.addinivalue_line( "markers", "dummy_marker: mark used for testing purposes" ) """ ) ) pytester.makepyfile( dedent( """\ import pytest @pytest.mark.dummy_marker async def test_markers_not_duplicated(request): markers = [] for node, marker in request.node.iter_markers_with_node(): markers.append(marker) assert len(markers) == 2 """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=auto") result.assert_outcomes(warnings=0, passed=1) pytest-asyncio-0.25.1/tests/markers/test_invalid_arguments.py000066400000000000000000000040671473541640200245410ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent import pytest def test_no_error_when_scope_passed_as_sole_keyword_argument( pytester: pytest.Pytester, ): pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio(loop_scope="session") async def test_anything(): pass """ ) ) result = pytester.runpytest_subprocess() result.assert_outcomes(passed=1) result.stdout.no_fnmatch_line("*ValueError*") def test_error_when_scope_passed_as_positional_argument( pytester: pytest.Pytester, ): pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio("session") async def test_anything(): pass """ ) ) result = pytester.runpytest_subprocess() result.assert_outcomes(errors=1) result.stdout.fnmatch_lines( ["*ValueError: mark.asyncio accepts only a keyword argument*"] ) def test_error_when_wrong_keyword_argument_is_passed( pytester: pytest.Pytester, ): pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio(cope="session") async def test_anything(): pass """ ) ) result = pytester.runpytest_subprocess() result.assert_outcomes(errors=1) result.stdout.fnmatch_lines( ["*ValueError: mark.asyncio accepts only a keyword argument 'loop_scope'*"] ) def test_error_when_additional_keyword_arguments_are_passed( pytester: pytest.Pytester, ): pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio(loop_scope="session", more="stuff") async def test_anything(): pass """ ) ) result = pytester.runpytest_subprocess() result.assert_outcomes(errors=1) result.stdout.fnmatch_lines( ["*ValueError: mark.asyncio accepts only a keyword argument*"] ) pytest-asyncio-0.25.1/tests/markers/test_mixed_scope.py000066400000000000000000000020611473541640200233150ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_function_scoped_loop_restores_previous_loop_scope(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest module_loop: asyncio.AbstractEventLoop @pytest.mark.asyncio(loop_scope="module") async def test_remember_loop(): global module_loop module_loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="function") async def test_with_function_scoped_loop(): pass @pytest.mark.asyncio(loop_scope="module") async def test_runs_in_same_loop(): global module_loop assert asyncio.get_running_loop() is module_loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=3) pytest-asyncio-0.25.1/tests/markers/test_module_scope.py000066400000000000000000000261501473541640200235010ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_asyncio_mark_works_on_module_level(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio class TestPyTestMark: async def test_is_asyncio(self, event_loop, sample_fixture): assert asyncio.get_event_loop() counter = 1 async def inc(): nonlocal counter counter += 1 await asyncio.sleep(0) await asyncio.ensure_future(inc()) assert counter == 2 async def test_is_asyncio(event_loop, sample_fixture): assert asyncio.get_event_loop() counter = 1 async def inc(): nonlocal counter counter += 1 await asyncio.sleep(0) await asyncio.ensure_future(inc()) assert counter == 2 @pytest.fixture def sample_fixture(): return None """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=2, warnings=2) result.stdout.fnmatch_lines( '*is asynchronous and explicitly requests the "event_loop" fixture*' ) def test_asyncio_mark_provides_module_scoped_loop_strict_mode(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio(loop_scope="module") loop: asyncio.AbstractEventLoop async def test_remember_loop(): global loop loop = asyncio.get_running_loop() async def test_this_runs_in_same_loop(): global loop assert asyncio.get_running_loop() is loop class TestClassA: async def test_this_runs_in_same_loop(self): global loop assert asyncio.get_running_loop() is loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=3) def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio(loop_scope="module") async def test_remember_loop(event_loop): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(errors=1) result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *") def test_asyncio_mark_respects_the_loop_policy( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", custom_policy=dedent( """\ import asyncio class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): pass """ ), test_uses_custom_policy=dedent( """\ import asyncio import pytest from .custom_policy import CustomEventLoopPolicy pytestmark = pytest.mark.asyncio(loop_scope="module") @pytest.fixture(scope="module") def event_loop_policy(): return CustomEventLoopPolicy() async def test_uses_custom_event_loop_policy(): assert isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ), test_does_not_use_custom_policy=dedent( """\ import asyncio import pytest from .custom_policy import CustomEventLoopPolicy pytestmark = pytest.mark.asyncio(loop_scope="module") async def test_does_not_use_custom_event_loop_policy(): assert not isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_respects_parametrized_loop_policies( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio(loop_scope="module") @pytest.fixture( scope="module", params=[ asyncio.DefaultEventLoopPolicy(), asyncio.DefaultEventLoopPolicy(), ], ) def event_loop_policy(request): return request.param async def test_parametrized_loop(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_provides_module_scoped_loop_to_fixtures( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio pytestmark = pytest.mark.asyncio(loop_scope="module") loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="module", scope="module") async def my_fixture(): global loop loop = asyncio.get_running_loop() async def test_runs_is_same_loop_as_fixture(my_fixture): global loop assert asyncio.get_running_loop() is loop """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_module_scoped_fixture_with_class_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="module", scope="module") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="class") class TestMixedScopes: async def test_runs_in_different_loop_as_fixture(self, async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_module_scoped_fixture_with_function_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="module", scope="module") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="function") async def test_runs_in_different_loop_as_fixture(async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_allows_combining_module_scoped_asyncgen_fixture_with_function_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="module", scope="module") async def async_fixture(): global loop loop = asyncio.get_running_loop() yield @pytest.mark.asyncio(loop_scope="function") async def test_runs_in_different_loop_as_fixture(async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import asyncio @pytest.fixture(scope="module") def sets_event_loop_to_none(): # asyncio.run() creates a new event loop without closing the existing # one. For any test, but the first one, this leads to a ResourceWarning # when the discarded loop is destroyed by the garbage collector. # We close the current loop to avoid this try: asyncio.get_event_loop().close() except RuntimeError: pass return asyncio.run(asyncio.sleep(0)) # asyncio.run() sets the current event loop to None when finished @pytest.mark.asyncio(loop_scope="module") # parametrization may impact fixture ordering @pytest.mark.parametrize("n", (0, 1)) async def test_does_not_fail(sets_event_loop_to_none, n): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio(loop_scope="module") async def test_anything(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(warnings=0, passed=1) pytest-asyncio-0.25.1/tests/markers/test_package_scope.py000066400000000000000000000265131473541640200236120ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_asyncio_mark_provides_package_scoped_loop_strict_mode(pytester: Pytester): package_name = pytester.path.name subpackage_name = "subpkg" pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", shared_module=dedent( """\ import asyncio loop: asyncio.AbstractEventLoop = None """ ), test_module_one=dedent( f"""\ import asyncio import pytest from {package_name} import shared_module @pytest.mark.asyncio(loop_scope="package") async def test_remember_loop(): shared_module.loop = asyncio.get_running_loop() """ ), test_module_two=dedent( f"""\ import asyncio import pytest from {package_name} import shared_module pytestmark = pytest.mark.asyncio(loop_scope="package") async def test_this_runs_in_same_loop(): assert asyncio.get_running_loop() is shared_module.loop class TestClassA: async def test_this_runs_in_same_loop(self): assert asyncio.get_running_loop() is shared_module.loop """ ), ) subpkg = pytester.mkpydir(subpackage_name) subpkg.joinpath("__init__.py").touch() subpkg.joinpath("test_subpkg.py").write_text( dedent( f"""\ import asyncio import pytest from {package_name} import shared_module pytestmark = pytest.mark.asyncio(loop_scope="package") async def test_subpackage_runs_in_different_loop(): assert asyncio.get_running_loop() is not shared_module.loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=4) def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_raises=dedent( """\ import asyncio import pytest @pytest.mark.asyncio(loop_scope="package") async def test_remember_loop(event_loop): pass """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(errors=1) result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *") def test_asyncio_mark_respects_the_loop_policy( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", conftest=dedent( """\ import pytest from .custom_policy import CustomEventLoopPolicy @pytest.fixture(scope="package") def event_loop_policy(): return CustomEventLoopPolicy() """ ), custom_policy=dedent( """\ import asyncio class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): pass """ ), test_uses_custom_policy=dedent( """\ import asyncio import pytest from .custom_policy import CustomEventLoopPolicy pytestmark = pytest.mark.asyncio(loop_scope="package") async def test_uses_custom_event_loop_policy(): assert isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ), test_also_uses_custom_policy=dedent( """\ import asyncio import pytest from .custom_policy import CustomEventLoopPolicy pytestmark = pytest.mark.asyncio(loop_scope="package") async def test_also_uses_custom_event_loop_policy(): assert isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_respects_parametrized_loop_policies( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_parametrization=dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio(loop_scope="package") @pytest.fixture( scope="package", params=[ asyncio.DefaultEventLoopPolicy(), asyncio.DefaultEventLoopPolicy(), ], ) def event_loop_policy(request): return request.param async def test_parametrized_loop(): pass """ ), ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_provides_package_scoped_loop_to_fixtures( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") package_name = pytester.path.name pytester.makepyfile( __init__="", conftest=dedent( f"""\ import asyncio import pytest_asyncio from {package_name} import shared_module @pytest_asyncio.fixture(loop_scope="package", scope="package") async def my_fixture(): shared_module.loop = asyncio.get_running_loop() """ ), shared_module=dedent( """\ import asyncio loop: asyncio.AbstractEventLoop = None """ ), test_fixture_runs_in_scoped_loop=dedent( f"""\ import asyncio import pytest import pytest_asyncio from {package_name} import shared_module pytestmark = pytest.mark.asyncio(loop_scope="package") async def test_runs_in_same_loop_as_fixture(my_fixture): assert asyncio.get_running_loop() is shared_module.loop """ ), ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_package_scoped_fixture_with_module_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="package", scope="package") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="module") async def test_runs_in_different_loop_as_fixture(async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_package_scoped_fixture_with_class_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="package", scope="package") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="class") class TestMixedScopes: async def test_runs_in_different_loop_as_fixture(self, async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_package_scoped_fixture_with_function_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="package", scope="package") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio async def test_runs_in_different_loop_as_fixture(async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_loop_is_none=dedent( """\ import pytest import asyncio @pytest.fixture(scope="package") def sets_event_loop_to_none(): # asyncio.run() creates a new event loop without closing the existing # one. For any test, but the first one, this leads to a ResourceWarning # when the discarded loop is destroyed by the garbage collector. # We close the current loop to avoid this try: asyncio.get_event_loop().close() except RuntimeError: pass return asyncio.run(asyncio.sleep(0)) # asyncio.run() sets the current event loop to None when finished @pytest.mark.asyncio(loop_scope="package") # parametrization may impact fixture ordering @pytest.mark.parametrize("n", (0, 1)) async def test_does_not_fail(sets_event_loop_to_none, n): pass """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_module=dedent( """\ import pytest @pytest.mark.asyncio(loop_scope="package") async def test_anything(): pass """ ), ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(warnings=0, passed=1) pytest-asyncio-0.25.1/tests/markers/test_session_scope.py000066400000000000000000000324151473541640200237000ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_asyncio_mark_provides_session_scoped_loop_strict_mode(pytester: Pytester): package_name = pytester.path.name pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", shared_module=dedent( """\ import asyncio loop: asyncio.AbstractEventLoop = None """ ), test_module_one=dedent( f"""\ import asyncio import pytest from {package_name} import shared_module @pytest.mark.asyncio(loop_scope="session") async def test_remember_loop(): shared_module.loop = asyncio.get_running_loop() """ ), test_module_two=dedent( f"""\ import asyncio import pytest from {package_name} import shared_module pytestmark = pytest.mark.asyncio(loop_scope="session") async def test_this_runs_in_same_loop(): assert asyncio.get_running_loop() is shared_module.loop class TestClassA: async def test_this_runs_in_same_loop(self): assert asyncio.get_running_loop() is shared_module.loop """ ), ) # subpackage_name must alphabetically come after test_module_one.py subpackage_name = "z_subpkg" subpkg = pytester.mkpydir(subpackage_name) subpkg.joinpath("test_subpkg.py").write_text( dedent( f"""\ import asyncio import pytest from {package_name} import shared_module pytestmark = pytest.mark.asyncio(loop_scope="session") async def test_subpackage_runs_in_same_loop(): assert asyncio.get_running_loop() is shared_module.loop """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=4) def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_raises=dedent( """\ import asyncio import pytest @pytest.mark.asyncio(loop_scope="session") async def test_remember_loop(event_loop): pass """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(errors=1) result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *") def test_asyncio_mark_respects_the_loop_policy( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", conftest=dedent( """\ import pytest from .custom_policy import CustomEventLoopPolicy @pytest.fixture(scope="session") def event_loop_policy(): return CustomEventLoopPolicy() """ ), custom_policy=dedent( """\ import asyncio class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): pass """ ), test_uses_custom_policy=dedent( """\ import asyncio import pytest from .custom_policy import CustomEventLoopPolicy pytestmark = pytest.mark.asyncio(loop_scope="session") async def test_uses_custom_event_loop_policy(): assert isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ), test_also_uses_custom_policy=dedent( """\ import asyncio import pytest from .custom_policy import CustomEventLoopPolicy pytestmark = pytest.mark.asyncio(loop_scope="session") async def test_also_uses_custom_event_loop_policy(): assert isinstance( asyncio.get_event_loop_policy(), CustomEventLoopPolicy, ) """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_respects_parametrized_loop_policies( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_parametrization=dedent( """\ import asyncio import pytest pytestmark = pytest.mark.asyncio(loop_scope="session") @pytest.fixture( scope="session", params=[ asyncio.DefaultEventLoopPolicy(), asyncio.DefaultEventLoopPolicy(), ], ) def event_loop_policy(request): return request.param async def test_parametrized_loop(): pass """ ), ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_asyncio_mark_provides_session_scoped_loop_to_fixtures( pytester: Pytester, ): package_name = pytester.path.name pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", conftest=dedent( f"""\ import asyncio import pytest_asyncio from {package_name} import shared_module @pytest_asyncio.fixture(loop_scope="session", scope="session") async def my_fixture(): shared_module.loop = asyncio.get_running_loop() """ ), shared_module=dedent( """\ import asyncio loop: asyncio.AbstractEventLoop = None """ ), ) subpackage_name = "subpkg" subpkg = pytester.mkpydir(subpackage_name) subpkg.joinpath("test_subpkg.py").write_text( dedent( f"""\ import asyncio import pytest import pytest_asyncio from {package_name} import shared_module pytestmark = pytest.mark.asyncio(loop_scope="session") async def test_runs_in_same_loop_as_fixture(my_fixture): assert asyncio.get_running_loop() is shared_module.loop """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_session_scoped_fixture_with_package_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="session", scope="session") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="package") async def test_runs_in_different_loop_as_fixture(async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_session_scoped_fixture_with_module_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="session", scope="session") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="module") async def test_runs_in_different_loop_as_fixture(async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_session_scoped_fixture_with_class_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="session", scope="session") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="class") class TestMixedScopes: async def test_runs_in_different_loop_as_fixture(self, async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_allows_combining_session_scoped_fixture_with_function_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="session", scope="session") async def async_fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio async def test_runs_in_different_loop_as_fixture(async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_allows_combining_session_scoped_asyncgen_fixture_with_function_scoped_test( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_mixed_scopes=dedent( """\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop @pytest_asyncio.fixture(loop_scope="session", scope="session") async def async_fixture(): global loop loop = asyncio.get_running_loop() yield @pytest.mark.asyncio async def test_runs_in_different_loop_as_fixture(async_fixture): global loop assert asyncio.get_running_loop() is not loop """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import asyncio @pytest.fixture(scope="session") def sets_event_loop_to_none(): # asyncio.run() creates a new event loop without closing the existing # one. For any test, but the first one, this leads to a ResourceWarning # when the discarded loop is destroyed by the garbage collector. # We close the current loop to avoid this try: asyncio.get_event_loop().close() except RuntimeError: pass return asyncio.run(asyncio.sleep(0)) # asyncio.run() sets the current event loop to None when finished @pytest.mark.asyncio(loop_scope="session") # parametrization may impact fixture ordering @pytest.mark.parametrize("n", (0, 1)) async def test_does_not_fail(sets_event_loop_to_none, n): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio(loop_scope="session") async def test_anything(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(warnings=0, passed=1) pytest-asyncio-0.25.1/tests/modes/000077500000000000000000000000001473541640200170515ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/modes/__init__.py000066400000000000000000000000001473541640200211500ustar00rootroot00000000000000pytest-asyncio-0.25.1/tests/modes/test_auto_mode.py000066400000000000000000000071201473541640200224360ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_auto_mode_cmdline(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' async def test_a(): await asyncio.sleep(0) """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) def test_auto_mode_cfg(pytester: Pytester): pytester.makeini( dedent( """\ [pytest] asyncio_default_fixture_loop_scope = function asyncio_mode = auto """ ) ) pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' async def test_a(): await asyncio.sleep(0) """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) def test_auto_mode_async_fixture(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' @pytest.fixture async def fixture_a(): await asyncio.sleep(0) return 1 async def test_a(fixture_a): await asyncio.sleep(0) assert fixture_a == 1 """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) def test_auto_mode_method_fixture(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' class TestA: @pytest.fixture async def fixture_a(self): await asyncio.sleep(0) return 1 async def test_a(self, fixture_a): await asyncio.sleep(0) assert fixture_a == 1 """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) def test_auto_mode_static_method(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio pytest_plugins = 'pytest_asyncio' class TestA: @staticmethod async def test_a(): await asyncio.sleep(0) """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) def test_auto_mode_static_method_fixture(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' class TestA: @staticmethod @pytest.fixture async def fixture_a(): await asyncio.sleep(0) return 1 @staticmethod async def test_a(fixture_a): await asyncio.sleep(0) assert fixture_a == 1 """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/modes/test_strict_mode.py000066400000000000000000000140171473541640200230010ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_strict_mode_cmdline(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' @pytest.mark.asyncio async def test_a(): await asyncio.sleep(0) """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_strict_mode_cfg(pytester: Pytester): pytester.makeini( dedent( """\ [pytest] asyncio_default_fixture_loop_scope = function asyncio_mode = strict """ ) ) pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' @pytest.mark.asyncio async def test_a(): await asyncio.sleep(0) """ ) ) result = pytester.runpytest() result.assert_outcomes(passed=1) def test_strict_mode_method_fixture(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio pytest_plugins = 'pytest_asyncio' class TestA: @pytest_asyncio.fixture async def fixture_a(self): await asyncio.sleep(0) return 1 @pytest.mark.asyncio async def test_a(self, fixture_a): await asyncio.sleep(0) assert fixture_a == 1 """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) def test_strict_mode_ignores_unmarked_coroutine(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest async def test_anything(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(skipped=1, warnings=1) result.stdout.fnmatch_lines(["*async def functions are not natively supported*"]) def test_strict_mode_ignores_unmarked_fixture(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest # Not using pytest_asyncio.fixture @pytest.fixture() async def any_fixture(): raise RuntimeError() async def test_anything(any_fixture): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(skipped=1, warnings=2) result.stdout.fnmatch_lines( [ "*async def functions are not natively supported*", "*coroutine 'any_fixture' was never awaited*", ], ) def test_strict_mode_marked_test_unmarked_fixture_warning(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest # Not using pytest_asyncio.fixture @pytest.fixture() async def any_fixture(): pass @pytest.mark.asyncio async def test_anything(any_fixture): # suppress unawaited coroutine warning try: any_fixture.send(None) except StopIteration: pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, failed=0, skipped=0, warnings=1) result.stdout.fnmatch_lines( [ "*warnings summary*", ( "test_strict_mode_marked_test_unmarked_fixture_warning.py::" "test_anything" ), ( "*/pytest_asyncio/plugin.py:*: PytestDeprecationWarning: " "asyncio test 'test_anything' requested async " "@pytest.fixture 'any_fixture' in strict mode. " "You might want to use @pytest_asyncio.fixture or switch to " "auto mode. " "This will become an error in future versions of flake8-asyncio." ), ], ) # autouse is not handled in any special way currently def test_strict_mode_marked_test_unmarked_autouse_fixture_warning(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest # Not using pytest_asyncio.fixture @pytest.fixture(autouse=True) async def any_fixture(): pass @pytest.mark.asyncio async def test_anything(any_fixture): # suppress unawaited coroutine warning try: any_fixture.send(None) except StopIteration: pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( [ "*warnings summary*", ( "test_strict_mode_marked_test_unmarked_autouse_fixture_warning.py::" "test_anything" ), ( "*/pytest_asyncio/plugin.py:*: PytestDeprecationWarning: " "*asyncio test 'test_anything' requested async " "@pytest.fixture 'any_fixture' in strict mode. " "You might want to use @pytest_asyncio.fixture or switch to " "auto mode. " "This will become an error in future versions of flake8-asyncio." ), ], ) pytest-asyncio-0.25.1/tests/test_asyncio_fixture.py000066400000000000000000000030651473541640200225720ustar00rootroot00000000000000from __future__ import annotations import asyncio from textwrap import dedent import pytest from pytest import Pytester import pytest_asyncio @pytest_asyncio.fixture async def fixture_bare(): await asyncio.sleep(0) return 1 @pytest.mark.asyncio async def test_bare_fixture(fixture_bare): await asyncio.sleep(0) assert fixture_bare == 1 @pytest_asyncio.fixture(name="new_fixture_name") async def fixture_with_name(request): await asyncio.sleep(0) return request.fixturename @pytest.mark.asyncio async def test_fixture_with_name(new_fixture_name): await asyncio.sleep(0) assert new_fixture_name == "new_fixture_name" @pytest_asyncio.fixture(params=[2, 4]) async def fixture_with_params(request): await asyncio.sleep(0) return request.param @pytest.mark.asyncio async def test_fixture_with_params(fixture_with_params): await asyncio.sleep(0) assert fixture_with_params % 2 == 0 @pytest.mark.parametrize("mode", ("auto", "strict")) def test_sync_function_uses_async_fixture(pytester: Pytester, mode): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest_asyncio pytest_plugins = 'pytest_asyncio' @pytest_asyncio.fixture async def always_true(): return True def test_sync_function_uses_async_fixture(always_true): assert always_true is True """ ) ) result = pytester.runpytest(f"--asyncio-mode={mode}") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/test_asyncio_mark.py000066400000000000000000000077051473541640200220430ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_asyncio_mark_on_sync_function_emits_warning(pytester: Pytester): pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio def test_a(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1) result.stdout.fnmatch_lines( ["*is marked with '@pytest.mark.asyncio' but it is not an async function.*"] ) def test_asyncio_mark_on_async_generator_function_emits_warning_in_strict_mode( pytester: Pytester, ): pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio async def test_a(): yield """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(xfailed=1, warnings=1) result.stdout.fnmatch_lines( ["*Tests based on asynchronous generators are not supported*"] ) def test_asyncio_mark_on_async_generator_function_emits_warning_in_auto_mode( pytester: Pytester, ): pytester.makepyfile( dedent( """\ async def test_a(): yield """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=auto", "-W default") result.assert_outcomes(xfailed=1, warnings=1) result.stdout.fnmatch_lines( ["*Tests based on asynchronous generators are not supported*"] ) def test_asyncio_mark_on_async_generator_method_emits_warning_in_strict_mode( pytester: Pytester, ): pytester.makepyfile( dedent( """\ import pytest class TestAsyncGenerator: @pytest.mark.asyncio async def test_a(self): yield """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(xfailed=1, warnings=1) result.stdout.fnmatch_lines( ["*Tests based on asynchronous generators are not supported*"] ) def test_asyncio_mark_on_async_generator_method_emits_warning_in_auto_mode( pytester: Pytester, ): pytester.makepyfile( dedent( """\ class TestAsyncGenerator: @staticmethod async def test_a(): yield """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=auto", "-W default") result.assert_outcomes(xfailed=1, warnings=1) result.stdout.fnmatch_lines( ["*Tests based on asynchronous generators are not supported*"] ) def test_asyncio_mark_on_async_generator_staticmethod_emits_warning_in_strict_mode( pytester: Pytester, ): pytester.makepyfile( dedent( """\ import pytest class TestAsyncGenerator: @staticmethod @pytest.mark.asyncio async def test_a(): yield """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(xfailed=1, warnings=1) result.stdout.fnmatch_lines( ["*Tests based on asynchronous generators are not supported*"] ) def test_asyncio_mark_on_async_generator_staticmethod_emits_warning_in_auto_mode( pytester: Pytester, ): pytester.makepyfile( dedent( """\ class TestAsyncGenerator: @staticmethod async def test_a(): yield """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=auto", "-W default") result.assert_outcomes(xfailed=1, warnings=1) result.stdout.fnmatch_lines( ["*Tests based on asynchronous generators are not supported*"] ) pytest-asyncio-0.25.1/tests/test_dependent_fixtures.py000066400000000000000000000005211473541640200232500ustar00rootroot00000000000000from __future__ import annotations import asyncio import pytest @pytest.mark.asyncio async def test_dependent_fixture(dependent_fixture): """Test a dependent fixture.""" await asyncio.sleep(0.1) @pytest.mark.asyncio async def test_factory_involving_factories(factory_involving_factories): factory_involving_factories() pytest-asyncio-0.25.1/tests/test_doctest.py000066400000000000000000000023061473541640200210210ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_plugin_does_not_interfere_with_doctest_collection(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( '''\ def any_function(): """ >>> 42 42 """ ''' ), ) result = pytester.runpytest("--asyncio-mode=strict", "--doctest-modules") result.assert_outcomes(passed=1) def test_plugin_does_not_interfere_with_doctest_textfile_collection(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makefile(".txt", "") # collected as DoctestTextfile pytester.makepyfile( __init__="", test_python_file=dedent( """\ import pytest pytest_plugins = "pytest_asyncio" @pytest.mark.asyncio async def test_anything(): pass """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/test_event_loop_fixture.py000066400000000000000000000033531473541640200232770ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_event_loop_fixture_respects_event_loop_policy(pytester: Pytester): pytester.makeconftest( dedent( """\ '''Defines and sets a custom event loop policy''' import asyncio from asyncio import DefaultEventLoopPolicy, SelectorEventLoop class TestEventLoop(SelectorEventLoop): pass class TestEventLoopPolicy(DefaultEventLoopPolicy): def new_event_loop(self): return TestEventLoop() # This statement represents a code which sets a custom event loop policy asyncio.set_event_loop_policy(TestEventLoopPolicy()) """ ) ) pytester.makepyfile( dedent( """\ '''Tests that any externally provided event loop policy remains unaltered''' import asyncio import pytest @pytest.mark.asyncio async def test_uses_loop_provided_by_custom_policy(): '''Asserts that test cases use the event loop provided by the custom event loop policy''' assert type(asyncio.get_event_loop()).__name__ == "TestEventLoop" @pytest.mark.asyncio async def test_custom_policy_is_not_overwritten(): ''' Asserts that any custom event loop policy stays the same across test cases. ''' assert type(asyncio.get_event_loop()).__name__ == "TestEventLoop" """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2) pytest-asyncio-0.25.1/tests/test_event_loop_fixture_finalizer.py000066400000000000000000000111041473541640200253330ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_event_loop_fixture_finalizer_returns_fresh_loop_after_test(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) @pytest.mark.asyncio async def test_1(): # This async test runs in its own event loop global loop running_loop = asyncio.get_event_loop_policy().get_event_loop() # Make sure this test case received a different loop assert running_loop is not loop def test_2(): # Code outside of pytest-asyncio should not receive a "used" event loop current_loop = asyncio.get_event_loop_policy().get_event_loop() assert not current_loop.is_running() assert not current_loop.is_closed() """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=2) def test_event_loop_fixture_finalizer_handles_loop_set_to_none_sync( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio def test_sync(event_loop): asyncio.get_event_loop_policy().set_event_loop(None) """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_event_loop_fixture_finalizer_handles_loop_set_to_none_async_without_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.mark.asyncio async def test_async_without_explicit_fixture_request(): asyncio.get_event_loop_policy().set_event_loop(None) """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_event_loop_fixture_finalizer_handles_loop_set_to_none_async_with_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.mark.asyncio async def test_async_with_explicit_fixture_request(event_loop): asyncio.get_event_loop_policy().set_event_loop(None) """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( '*is asynchronous and explicitly requests the "event_loop" fixture*' ) def test_event_loop_fixture_finalizer_raises_warning_when_fixture_leaves_loop_unclosed( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' @pytest.fixture def event_loop(): loop = asyncio.get_event_loop_policy().new_event_loop() yield loop @pytest.mark.asyncio async def test_ends_with_unclosed_loop(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default") result.assert_outcomes(passed=1, warnings=2) result.stdout.fnmatch_lines("*unclosed event loop*") def test_event_loop_fixture_finalizer_raises_warning_when_test_leaves_loop_unclosed( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import asyncio import pytest pytest_plugins = 'pytest_asyncio' @pytest.mark.asyncio async def test_ends_with_unclosed_loop(): asyncio.set_event_loop(asyncio.new_event_loop()) """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines("*unclosed event loop*") pytest-asyncio-0.25.1/tests/test_event_loop_fixture_override_deprecation.py000066400000000000000000000057721473541640200275620ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_emit_warning_when_event_loop_fixture_is_redefined(pytester: Pytester): pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.fixture def event_loop(): loop = asyncio.new_event_loop() yield loop loop.close() @pytest.mark.asyncio async def test_emits_warning(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( ["*event_loop fixture provided by pytest-asyncio has been redefined*"] ) def test_emit_warning_when_event_loop_fixture_is_redefined_explicit_request( pytester: Pytester, ): pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.fixture def event_loop(): loop = asyncio.new_event_loop() yield loop loop.close() @pytest.mark.asyncio async def test_emits_warning_when_requested_explicitly(event_loop): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=2) result.stdout.fnmatch_lines( ["*event_loop fixture provided by pytest-asyncio has been redefined*"] ) result.stdout.fnmatch_lines( ['*is asynchronous and explicitly requests the "event_loop" fixture*'] ) def test_does_not_emit_warning_when_no_test_uses_the_event_loop_fixture( pytester: Pytester, ): pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.fixture def event_loop(): loop = asyncio.new_event_loop() yield loop loop.close() def test_emits_no_warning(): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1, warnings=0) def test_emit_warning_when_redefined_event_loop_is_used_by_fixture(pytester: Pytester): pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio @pytest.fixture def event_loop(): loop = asyncio.new_event_loop() yield loop loop.close() @pytest_asyncio.fixture async def uses_event_loop(): pass def test_emits_warning(uses_event_loop): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) pytest-asyncio-0.25.1/tests/test_explicit_event_loop_fixture_request.py000066400000000000000000000114661473541640200267540ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_emit_warning_when_event_loop_is_explicitly_requested_in_coroutine( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest @pytest.mark.asyncio async def test_coroutine_emits_warning(event_loop): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( ['*is asynchronous and explicitly requests the "event_loop" fixture*'] ) def test_emit_warning_when_event_loop_is_explicitly_requested_in_coroutine_method( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest class TestEmitsWarning: @pytest.mark.asyncio async def test_coroutine_emits_warning(self, event_loop): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( ['*is asynchronous and explicitly requests the "event_loop" fixture*'] ) def test_emit_warning_when_event_loop_is_explicitly_requested_in_coroutine_staticmethod( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest class TestEmitsWarning: @staticmethod @pytest.mark.asyncio async def test_coroutine_emits_warning(event_loop): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( ['*is asynchronous and explicitly requests the "event_loop" fixture*'] ) def test_emit_warning_when_event_loop_is_explicitly_requested_in_coroutine_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import pytest_asyncio @pytest_asyncio.fixture async def emits_warning(event_loop): pass @pytest.mark.asyncio async def test_uses_fixture(emits_warning): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( ['*is asynchronous and explicitly requests the "event_loop" fixture*'] ) def test_emit_warning_when_event_loop_is_explicitly_requested_in_async_gen_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import pytest_asyncio @pytest_asyncio.fixture async def emits_warning(event_loop): yield @pytest.mark.asyncio async def test_uses_fixture(emits_warning): pass """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default") result.assert_outcomes(passed=1, warnings=1) result.stdout.fnmatch_lines( ['*is asynchronous and explicitly requests the "event_loop" fixture*'] ) def test_does_not_emit_warning_when_event_loop_is_explicitly_requested_in_sync_function( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest def test_uses_fixture(event_loop): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_does_not_emit_warning_when_event_loop_is_explicitly_requested_in_sync_fixture( pytester: Pytester, ): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest @pytest.fixture def any_fixture(event_loop): pass def test_uses_fixture(any_fixture): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/test_fixture_loop_scopes.py000066400000000000000000000074371473541640200234610ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent import pytest from pytest import Pytester @pytest.mark.parametrize( "fixture_scope", ("session", "package", "module", "class", "function") ) def test_loop_scope_session_is_independent_of_fixture_scope( pytester: Pytester, fixture_scope: str, ): pytester.makepyfile( dedent( f"""\ import asyncio import pytest import pytest_asyncio loop: asyncio.AbstractEventLoop = None @pytest_asyncio.fixture(scope="{fixture_scope}", loop_scope="session") async def fixture(): global loop loop = asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="session") async def test_runs_in_same_loop_as_fixture(fixture): global loop assert loop == asyncio.get_running_loop() """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) @pytest.mark.parametrize("default_loop_scope", ("function", "module", "session")) def test_default_loop_scope_config_option_changes_fixture_loop_scope( pytester: Pytester, default_loop_scope: str, ): pytester.makeini( dedent( f"""\ [pytest] asyncio_default_fixture_loop_scope = {default_loop_scope} """ ) ) pytester.makepyfile( dedent( f"""\ import asyncio import pytest import pytest_asyncio @pytest_asyncio.fixture async def fixture_loop(): return asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="{default_loop_scope}") async def test_runs_in_fixture_loop(fixture_loop): assert asyncio.get_running_loop() is fixture_loop """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_default_class_loop_scope_config_option_changes_fixture_loop_scope( pytester: Pytester, ): pytester.makeini( dedent( """\ [pytest] asyncio_default_fixture_loop_scope = class """ ) ) pytester.makepyfile( dedent( """\ import asyncio import pytest import pytest_asyncio class TestClass: @pytest_asyncio.fixture async def fixture_loop(self): return asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="class") async def test_runs_in_fixture_loop(self, fixture_loop): assert asyncio.get_running_loop() is fixture_loop """ ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_default_package_loop_scope_config_option_changes_fixture_loop_scope( pytester: Pytester, ): pytester.makeini( dedent( """\ [pytest] asyncio_default_fixture_loop_scope = package """ ) ) pytester.makepyfile( __init__="", test_a=dedent( """\ import asyncio import pytest import pytest_asyncio @pytest_asyncio.fixture async def fixture_loop(): return asyncio.get_running_loop() @pytest.mark.asyncio(loop_scope="package") async def test_runs_in_fixture_loop(fixture_loop): assert asyncio.get_running_loop() is fixture_loop """ ), ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/test_import.py000066400000000000000000000032731473541640200206720ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_import_warning_does_not_cause_internal_error(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ raise ImportWarning() async def test_errors_out(): pass """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(errors=1) def test_import_warning_in_package_does_not_cause_internal_error(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__=dedent( """\ raise ImportWarning() """ ), test_a=dedent( """\ async def test_errors_out(): pass """ ), ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(errors=1) def test_does_not_import_unrelated_packages(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pkg_dir = pytester.mkpydir("mypkg") pkg_dir.joinpath("__init__.py").write_text( dedent( """\ raise ImportError() """ ), ) test_dir = pytester.mkdir("tests") test_dir.joinpath("test_a.py").write_text( dedent( """\ async def test_passes(): pass """ ), ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/test_is_async_test.py000066400000000000000000000057471473541640200222370ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_returns_false_for_sync_item(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import pytest_asyncio def test_sync(): pass def pytest_collection_modifyitems(items): async_tests = [ item for item in items if pytest_asyncio.is_async_test(item) ] assert len(async_tests) == 0 """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_returns_true_for_marked_coroutine_item_in_strict_mode(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import pytest_asyncio @pytest.mark.asyncio async def test_coro(): pass def pytest_collection_modifyitems(items): async_tests = [ item for item in items if pytest_asyncio.is_async_test(item) ] assert len(async_tests) == 1 """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) def test_returns_false_for_unmarked_coroutine_item_in_strict_mode(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import pytest_asyncio async def test_coro(): pass def pytest_collection_modifyitems(items): async_tests = [ item for item in items if pytest_asyncio.is_async_test(item) ] assert len(async_tests) == 0 """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(failed=1) def test_returns_true_for_unmarked_coroutine_item_in_auto_mode(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest import pytest_asyncio async def test_coro(): pass def pytest_collection_modifyitems(items): async_tests = [ item for item in items if pytest_asyncio.is_async_test(item) ] assert len(async_tests) == 1 """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) pytest-asyncio-0.25.1/tests/test_multiloop.py000066400000000000000000000040131473541640200213750ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_event_loop_override(pytester: Pytester): pytester.makeconftest( dedent( '''\ import asyncio import pytest @pytest.fixture def dependent_fixture(event_loop): """A fixture dependent on the event_loop fixture, doing some cleanup.""" counter = 0 async def just_a_sleep(): """Just sleep a little while.""" nonlocal event_loop await asyncio.sleep(0.1) nonlocal counter counter += 1 event_loop.run_until_complete(just_a_sleep()) yield event_loop.run_until_complete(just_a_sleep()) assert counter == 2 class CustomSelectorLoop(asyncio.SelectorEventLoop): """A subclass with no overrides, just to test for presence.""" @pytest.fixture def event_loop(): """Create an instance of the default event loop for each test case.""" loop = CustomSelectorLoop() yield loop loop.close() ''' ) ) pytester.makepyfile( dedent( '''\ """Unit tests for overriding the event loop.""" import asyncio import pytest @pytest.mark.asyncio async def test_for_custom_loop(): """This test should be executed using the custom loop.""" await asyncio.sleep(0.01) assert type(asyncio.get_event_loop()).__name__ == "CustomSelectorLoop" @pytest.mark.asyncio async def test_dependent_fixture(dependent_fixture): await asyncio.sleep(0.1) ''' ) ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2, warnings=2) pytest-asyncio-0.25.1/tests/test_port_factories.py000066400000000000000000000142041473541640200223770ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester import pytest_asyncio.plugin def test_unused_tcp_port_selects_unused_port(pytester: Pytester): pytester.makepyfile( dedent( """\ import asyncio import pytest @pytest.mark.asyncio async def test_unused_port_fixture(unused_tcp_port): async def closer(_, writer): writer.close() server1 = await asyncio.start_server( closer, host="localhost", port=unused_tcp_port ) with pytest.raises(IOError): await asyncio.start_server( closer, host="localhost", port=unused_tcp_port ) server1.close() await server1.wait_closed() """ ) ) def test_unused_udp_port_selects_unused_port(pytester: Pytester): pytester.makepyfile( dedent( """\ @pytest.mark.asyncio async def test_unused_udp_port_fixture(unused_udp_port): class Closer: def connection_made(self, transport): pass def connection_lost(self, *arg, **kwd): pass event_loop = asyncio.get_running_loop() transport1, _ = await event_loop.create_datagram_endpoint( Closer, local_addr=("127.0.0.1", unused_udp_port), reuse_port=False, ) with pytest.raises(IOError): await event_loop.create_datagram_endpoint( Closer, local_addr=("127.0.0.1", unused_udp_port), reuse_port=False, ) transport1.abort() """ ) ) def test_unused_tcp_port_factory_selects_unused_port(pytester: Pytester): pytester.makepyfile( dedent( """\ @pytest.mark.asyncio async def test_unused_port_factory_fixture(unused_tcp_port_factory): async def closer(_, writer): writer.close() port1, port2, port3 = ( unused_tcp_port_factory(), unused_tcp_port_factory(), unused_tcp_port_factory(), ) server1 = await asyncio.start_server( closer, host="localhost", port=port1 ) server2 = await asyncio.start_server( closer, host="localhost", port=port2 ) server3 = await asyncio.start_server( closer, host="localhost", port=port3 ) for port in port1, port2, port3: with pytest.raises(IOError): await asyncio.start_server(closer, host="localhost", port=port) server1.close() await server1.wait_closed() server2.close() await server2.wait_closed() server3.close() await server3.wait_closed() """ ) ) def test_unused_udp_port_factory_selects_unused_port(pytester: Pytester): pytester.makepyfile( dedent( """\ @pytest.mark.asyncio async def test_unused_udp_port_factory_fixture(unused_udp_port_factory): class Closer: def connection_made(self, transport): pass def connection_lost(self, *arg, **kwd): pass port1, port2, port3 = ( unused_udp_port_factory(), unused_udp_port_factory(), unused_udp_port_factory(), ) event_loop = asyncio.get_running_loop() transport1, _ = await event_loop.create_datagram_endpoint( Closer, local_addr=("127.0.0.1", port1), reuse_port=False, ) transport2, _ = await event_loop.create_datagram_endpoint( Closer, local_addr=("127.0.0.1", port2), reuse_port=False, ) transport3, _ = await event_loop.create_datagram_endpoint( Closer, local_addr=("127.0.0.1", port3), reuse_port=False, ) for port in port1, port2, port3: with pytest.raises(IOError): await event_loop.create_datagram_endpoint( Closer, local_addr=("127.0.0.1", port), reuse_port=False, ) transport1.abort() transport2.abort() transport3.abort() """ ) ) def test_unused_port_factory_duplicate(unused_tcp_port_factory, monkeypatch): """Test correct avoidance of duplicate ports.""" counter = 0 def mock_unused_tcp_port(_ignored): """Force some duplicate ports.""" nonlocal counter counter += 1 if counter < 5: return 10000 else: return 10000 + counter monkeypatch.setattr(pytest_asyncio.plugin, "_unused_port", mock_unused_tcp_port) assert unused_tcp_port_factory() == 10000 assert unused_tcp_port_factory() > 10000 def test_unused_udp_port_factory_duplicate(unused_udp_port_factory, monkeypatch): """Test correct avoidance of duplicate UDP ports.""" counter = 0 def mock_unused_udp_port(_ignored): """Force some duplicate ports.""" nonlocal counter counter += 1 if counter < 5: return 10000 else: return 10000 + counter monkeypatch.setattr(pytest_asyncio.plugin, "_unused_port", mock_unused_udp_port) assert unused_udp_port_factory() == 10000 assert unused_udp_port_factory() > 10000 pytest-asyncio-0.25.1/tests/test_simple.py000066400000000000000000000063651473541640200206560ustar00rootroot00000000000000"""Quick'n'dirty unit tests for provided fixtures and markers.""" from __future__ import annotations import asyncio from textwrap import dedent import pytest from pytest import Pytester async def async_coro(): await asyncio.sleep(0) return "ok" def test_event_loop_fixture(event_loop): """Test the injection of the event_loop fixture.""" assert event_loop ret = event_loop.run_until_complete(async_coro()) assert ret == "ok" @pytest.mark.asyncio async def test_asyncio_marker(): """Test the asyncio pytest marker.""" await asyncio.sleep(0) def test_asyncio_marker_compatibility_with_xfail(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest pytest_plugins = "pytest_asyncio" @pytest.mark.xfail(reason="need a failure", strict=True) @pytest.mark.asyncio async def test_asyncio_marker_fail(): raise AssertionError """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(xfailed=1) def test_asyncio_auto_mode_compatibility_with_xfail(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest pytest_plugins = "pytest_asyncio" @pytest.mark.xfail(reason="need a failure", strict=True) async def test_asyncio_marker_fail(): raise AssertionError """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(xfailed=1) @pytest.mark.asyncio async def test_asyncio_marker_with_default_param(a_param=None): """Test the asyncio pytest marker.""" await asyncio.sleep(0) class TestMarkerInClassBasedTests: """Test that asyncio marked functions work for methods of test classes.""" @pytest.mark.asyncio async def test_asyncio_marker_with_implicit_loop_fixture(self): """ Test the "asyncio" marker works on a method in a class-based test with implicit loop fixture. """ ret = await async_coro() assert ret == "ok" class TestEventLoopStartedBeforeFixtures: @pytest.fixture async def loop(self): return asyncio.get_event_loop() @staticmethod def foo(): return 1 @pytest.mark.asyncio async def test_no_event_loop(self, loop): assert await loop.run_in_executor(None, self.foo) == 1 @pytest.mark.asyncio async def test_event_loop_after_fixture(self, loop): assert await loop.run_in_executor(None, self.foo) == 1 @pytest.mark.asyncio async def test_event_loop_before_fixture(self, loop): assert await loop.run_in_executor(None, self.foo) == 1 def test_invalid_asyncio_mode(pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") result = pytester.runpytest("-o", "asyncio_mode=True") result.stderr.no_fnmatch_line("INTERNALERROR> *") result.stderr.fnmatch_lines( "ERROR: 'True' is not a valid asyncio_mode. Valid modes: auto, strict." ) pytest-asyncio-0.25.1/tests/test_skips.py000066400000000000000000000076361473541640200205200ustar00rootroot00000000000000from __future__ import annotations from textwrap import dedent from pytest import Pytester def test_asyncio_strict_mode_skip(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest pytest_plugins = "pytest_asyncio" @pytest.mark.asyncio async def test_no_warning_on_skip(): pytest.skip("Test a skip error inside asyncio") """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(skipped=1) def test_asyncio_auto_mode_skip(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest pytest_plugins = "pytest_asyncio" async def test_no_warning_on_skip(): pytest.skip("Test a skip error inside asyncio") """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(skipped=1) def test_asyncio_strict_mode_module_level_skip(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest pytest.skip("Skip all tests", allow_module_level=True) @pytest.mark.asyncio async def test_is_skipped(): pass """ ) ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(skipped=1) def test_asyncio_auto_mode_module_level_skip(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest pytest.skip("Skip all tests", allow_module_level=True) async def test_is_skipped(): pass """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(skipped=1) def test_asyncio_auto_mode_wrong_skip_usage(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ import pytest pytest.skip("Skip all tests") async def test_is_skipped(): pass """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(errors=1) def test_unittest_skiptest_compatibility(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( dedent( """\ from unittest import SkipTest raise SkipTest("Skip all tests") async def test_is_skipped(): pass """ ) ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(skipped=1) def test_skip_in_module_does_not_skip_package(pytester: Pytester): pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") pytester.makepyfile( __init__="", test_skip=dedent( """\ import pytest pytest.skip("Skip all tests", allow_module_level=True) def test_a(): pass def test_b(): pass """ ), test_something=dedent( """\ import pytest @pytest.mark.asyncio async def test_something(): pass """ ), ) result = pytester.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1, skipped=1) pytest-asyncio-0.25.1/tests/test_subprocess.py000066400000000000000000000013361473541640200215460ustar00rootroot00000000000000"""Tests for using subprocesses in tests.""" from __future__ import annotations import asyncio.subprocess import sys import pytest if sys.platform == "win32": # The default asyncio event loop implementation on Windows does not # support subprocesses. Subprocesses are available for Windows if a # ProactorEventLoop is used. @pytest.fixture() def event_loop(): loop = asyncio.ProactorEventLoop() yield loop loop.close() @pytest.mark.asyncio async def test_subprocess(): """Starting a subprocess should be possible.""" proc = await asyncio.subprocess.create_subprocess_exec( sys.executable, "--version", stdout=asyncio.subprocess.PIPE ) await proc.communicate() pytest-asyncio-0.25.1/tools/000077500000000000000000000000001473541640200157405ustar00rootroot00000000000000pytest-asyncio-0.25.1/tools/get-version.py000066400000000000000000000006551473541640200205620ustar00rootroot00000000000000from __future__ import annotations import json import sys from importlib import metadata from packaging.version import parse as parse_version def main(): version_string = metadata.version("pytest-asyncio") version = parse_version(version_string) print(f"version={version}") prerelease = json.dumps(version.is_prerelease) print(f"prerelease={prerelease}") if __name__ == "__main__": sys.exit(main()) pytest-asyncio-0.25.1/tox.ini000066400000000000000000000037421473541640200161210ustar00rootroot00000000000000[tox] minversion = 4.9.0 envlist = py39, py310, py311, py312, py313, pytest-min, docs isolated_build = true passenv = CI [testenv] extras = testing install_command = python -m pip install \ --requirement dependencies/default/requirements.txt \ --constraint dependencies/default/constraints.txt \ {opts} {packages} commands = make test allowlist_externals = make [testenv:pytest-min] extras = testing install_command = python -m pip install \ --requirement dependencies/pytest-min/requirements.txt \ --constraint dependencies/pytest-min/constraints.txt \ {opts} {packages} commands = make test allowlist_externals = make [testenv:docs] allowlist_externals = git extras = docs deps = --requirement dependencies/docs/requirements.txt --constraint dependencies/docs/constraints.txt change_dir = docs description = Build The Docs with {basepython} commands = # Retrieve possibly missing commits: -git fetch --unshallow -git fetch --tags # Build the html docs with Sphinx: {envpython} -Im sphinx \ -j auto \ {tty:--color} \ -a \ -T \ -n \ -W --keep-going \ -d "{temp_dir}{/}.doctrees" \ . \ {posargs:"{envdir}{/}docs_out" -b html} # Print out the output docs dir and a way to serve html: -{envpython} -c\ 'import pathlib;\ docs_dir = pathlib.Path(r"{envdir}") / "docs_out";\ index_file = docs_dir / "index.html";\ print("\n" + "=" * 120 +\ f"\n\nOpen the documentation with:\n\n\ \t$ python3 -Im webbrowser \N\{QUOTATION MARK\}file://\{index_file\}\N\{QUOTATION MARK\}\n\n\ To serve docs, use\n\n\ \t$ python3 -Im http.server --directory \ \N\{QUOTATION MARK\}\{docs_dir\}\N\{QUOTATION MARK\} 0\n\n" +\ "=" * 120)' changedir = {toxinidir}{/}docs isolated_build = true passenv = SSH_AUTH_SOCK skip_install = false [gh-actions] python = 3.9: py39, pytest-min 3.10: py310 3.11: py311 3.12: py312 3.13: py313 pypy3: pypy3