├── .github
├── CODEOWNERS
├── actionlint-matcher.json
├── dependabot.yml
└── workflows
│ └── main.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── SECURITY.rst
├── changelog.d
└── .gitkeep
├── dependencies
├── default
│ ├── constraints.txt
│ └── requirements.txt
├── docs
│ ├── constraints.txt
│ └── requirements.txt
└── pytest-min
│ ├── constraints.txt
│ └── requirements.txt
├── docs
├── concepts.rst
├── concepts_function_scope_example.py
├── concepts_module_scope_example.py
├── conf.py
├── how-to-guides
│ ├── change_default_fixture_loop.rst
│ ├── change_default_test_loop.rst
│ ├── change_fixture_loop.rst
│ ├── change_fixture_loop_example.py
│ ├── class_scoped_loop_example.py
│ ├── index.rst
│ ├── migrate_from_0_21.rst
│ ├── migrate_from_0_23.rst
│ ├── module_scoped_loop_example.py
│ ├── multiple_loops.rst
│ ├── multiple_loops_example.py
│ ├── package_scoped_loop_example.py
│ ├── run_class_tests_in_same_loop.rst
│ ├── run_module_tests_in_same_loop.rst
│ ├── run_package_tests_in_same_loop.rst
│ ├── test_item_is_async.rst
│ ├── test_item_is_async_example.py
│ └── uvloop.rst
├── index.rst
├── reference
│ ├── changelog.rst
│ ├── configuration.rst
│ ├── decorators
│ │ ├── index.rst
│ │ └── pytest_asyncio_fixture_example.py
│ ├── fixtures
│ │ ├── event_loop_policy_example.py
│ │ ├── event_loop_policy_parametrized_example.py
│ │ └── index.rst
│ ├── functions.rst
│ ├── index.rst
│ └── markers
│ │ ├── class_scoped_loop_custom_policies_strict_mode_example.py
│ │ ├── class_scoped_loop_strict_mode_example.py
│ │ ├── class_scoped_loop_with_fixture_strict_mode_example.py
│ │ ├── function_scoped_loop_pytestmark_strict_mode_example.py
│ │ ├── function_scoped_loop_strict_mode_example.py
│ │ ├── index.rst
│ │ └── module_scoped_loop_strict_mode_example.py
└── support.rst
├── pyproject.toml
├── pytest_asyncio
├── __init__.py
├── plugin.py
└── py.typed
├── setup.cfg
├── tests
├── __init__.py
├── async_fixtures
│ ├── __init__.py
│ ├── test_async_fixtures.py
│ ├── test_async_fixtures_contextvars.py
│ ├── test_nested.py
│ └── test_shared_module_fixture.py
├── conftest.py
├── hypothesis
│ ├── __init__.py
│ └── test_base.py
├── markers
│ ├── __init__.py
│ ├── test_class_scope.py
│ ├── test_function_scope.py
│ ├── test_invalid_arguments.py
│ ├── test_mixed_scope.py
│ ├── test_module_scope.py
│ ├── test_package_scope.py
│ └── test_session_scope.py
├── modes
│ ├── __init__.py
│ ├── test_auto_mode.py
│ └── test_strict_mode.py
├── test_asyncio_fixture.py
├── test_asyncio_mark.py
├── test_doctest.py
├── test_event_loop_fixture.py
├── test_fixture_loop_scopes.py
├── test_import.py
├── test_is_async_test.py
├── test_port_factories.py
├── test_simple.py
├── test_skips.py
└── test_subprocess.py
├── tools
└── get-version.py
└── tox.ini
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @asvetlov @seifertm @Tinche
2 |
--------------------------------------------------------------------------------
/.github/actionlint-matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "problemMatcher": [
3 | {
4 | "owner": "actionlint",
5 | "pattern": [
6 | {
7 | "code": 5,
8 | "column": 3,
9 | "file": 1,
10 | "line": 2,
11 | "message": 4,
12 | "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)* \\[(.+?)\\]$"
13 | }
14 | ]
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | updates:
4 | - package-ecosystem: pip
5 | directory: /dependencies/default
6 | schedule:
7 | interval: weekly
8 | open-pull-requests-limit: 10
9 | target-branch: main
10 | - package-ecosystem: pip
11 | directory: /dependencies/docs
12 | schedule:
13 | interval: weekly
14 | open-pull-requests-limit: 10
15 | target-branch: main
16 | - package-ecosystem: github-actions
17 | directory: /
18 | schedule:
19 | interval: daily
20 | open-pull-requests-limit: 10
21 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: CI
3 |
4 | on:
5 | push:
6 | branches: [main]
7 | tags: [v*]
8 | pull_request:
9 | branches: [main]
10 | merge_group:
11 | workflow_dispatch:
12 |
13 | env:
14 | PYTHON_LATEST: 3.13
15 |
16 | jobs:
17 | lint:
18 | name: Run linters
19 | runs-on: ubuntu-latest
20 | outputs:
21 | version: ${{ steps.version.outputs.version }}
22 | prerelease: ${{ steps.version.outputs.prerelease }}
23 | steps:
24 | - uses: actions/checkout@v4
25 | with:
26 | fetch-depth: 0
27 | - uses: actions/setup-python@v5
28 | with:
29 | python-version: ${{ env.PYTHON_LATEST }}
30 | - name: Install GitHub matcher for ActionLint checker
31 | run: |
32 | echo "::add-matcher::.github/actionlint-matcher.json"
33 | - name: Install pre-commit
34 | run: python -m pip install pre-commit
35 | - name: Run pre-commit checks
36 | run: pre-commit run --all-files --show-diff-on-failure
37 | - name: Install check-wheel-content, and twine
38 | run: python -m pip install build check-wheel-contents twine
39 | - name: Build package
40 | run: python -m build
41 | - name: List result
42 | run: ls -l dist
43 | - name: Check wheel contents
44 | run: check-wheel-contents dist/*.whl
45 | - name: Check long_description
46 | run: python -m twine check dist/*
47 | - name: Install pytest-asyncio
48 | run: pip install .
49 | - name: Get version info
50 | id: version
51 | run: python ./tools/get-version.py >> $GITHUB_OUTPUT
52 | - name: Upload artifacts
53 | uses: actions/upload-artifact@v4
54 | with:
55 | name: dist
56 | path: dist
57 |
58 | test:
59 | name: ${{ matrix.os }} - Python ${{ matrix.python-version }}
60 | runs-on: ${{ matrix.os }}-latest
61 | continue-on-error: ${{ !matrix.required }}
62 | strategy:
63 | matrix:
64 | os: [ubuntu, windows]
65 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
66 | required: [true]
67 | include:
68 | - os: ubuntu
69 | python-version: 3.14-dev
70 | required: false
71 | - os: windows
72 | python-version: 3.14-dev
73 | required: false
74 |
75 |
76 | steps:
77 | - uses: actions/checkout@v4
78 | - uses: actions/setup-python@v5
79 | with:
80 | python-version: ${{ matrix.python-version }}
81 | - name: Install dependencies
82 | run: |
83 | python -VV
84 | python -m site
85 | python -m pip install --upgrade pip
86 | python -m pip install --upgrade coverage[toml] virtualenv tox tox-gh-actions
87 | - name: Run tox targets for ${{ matrix.python-version }}
88 | run: python -m tox
89 |
90 | - name: Store coverage data
91 | uses: actions/upload-artifact@v4
92 | if: "!endsWith(matrix.os, 'windows')"
93 | with:
94 | name: coverage-python-${{ matrix.python-version }}
95 | path: coverage/coverage.*
96 | if-no-files-found: error
97 |
98 | check:
99 | name: Check
100 | if: always()
101 | needs: [lint, test]
102 | runs-on: ubuntu-latest
103 | steps:
104 | - name: Decide whether the needed jobs succeeded or failed
105 | uses: re-actors/alls-green@release/v1
106 | with:
107 | jobs: ${{ toJSON(needs) }}
108 | - uses: actions/checkout@v4
109 | - uses: actions/setup-python@v5
110 | with:
111 | python-version: ${{ env.PYTHON_LATEST }}
112 | - name: Install Coverage.py
113 | run: |
114 | set -xe
115 | python -m pip install --upgrade coverage[toml]
116 | - name: Download coverage data for all test runs
117 | uses: actions/download-artifact@v4
118 | with:
119 | pattern: coverage-*
120 | path: coverage
121 | merge-multiple: true
122 | - name: Combine coverage data and create report
123 | run: |
124 | coverage combine
125 | coverage xml
126 | - name: Upload coverage report
127 | uses: codecov/codecov-action@v5
128 | with:
129 | files: coverage.xml
130 | fail_ci_if_error: true
131 | token: ${{ secrets.CODECOV_TOKEN }}
132 |
133 | prepare-release-notes:
134 | name: Prepare Release Notes
135 | needs: [lint]
136 | runs-on: ubuntu-latest
137 | steps:
138 | - name: Checkout
139 | uses: actions/checkout@v4
140 | with:
141 | fetch-depth: 0
142 | - name: Install Python
143 | uses: actions/setup-python@v5
144 | - name: Install towncrier
145 | run: pip install towncrier==24.8.0
146 | - name: Install pandoc
147 | run: |
148 | sudo apt-get install -y pandoc
149 | - name: Install pytest-asyncio
150 | run: pip install .
151 | - name: Compile Release Notes Draft
152 | if: ${{ !contains(github.ref, 'refs/tags/') }}
153 | run: towncrier build --draft --version "${{ needs.lint.outputs.version }}" > release-notes.rst
154 | - name: Extract release notes from Git tag
155 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
156 | run: |
157 | set -e
158 | git for-each-ref "${GITHUB_REF}" --format='%(contents)' > release-notes.rst
159 | # Strip PGP signature from signed tags
160 | sed -i "/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----\n/d" release-notes.rst
161 | - name: Convert Release Notes to Markdown
162 | run: |
163 | pandoc -s -o release-notes.md release-notes.rst
164 | - name: Upload artifacts
165 | uses: actions/upload-artifact@v4
166 | with:
167 | name: release-notes.md
168 | path: release-notes.md
169 |
170 | deploy:
171 | name: Deploy
172 | environment: release
173 | # Run only on pushing a tag
174 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
175 | needs: [lint, check, prepare-release-notes]
176 | runs-on: ubuntu-latest
177 | steps:
178 | - name: Download distributions
179 | uses: actions/download-artifact@v4
180 | with:
181 | name: dist
182 | path: dist
183 | - name: Collected dists
184 | run: |
185 | tree dist
186 | - name: PyPI upload
187 | uses: pypa/gh-action-pypi-publish@v1.12.4
188 | with:
189 | attestations: true
190 | packages-dir: dist
191 | password: ${{ secrets.PYPI_API_TOKEN }}
192 | - name: Download Release Notes
193 | uses: actions/download-artifact@v4
194 | with:
195 | name: release-notes.md
196 | path: release-notes.md
197 | - name: GitHub Release
198 | uses: ncipollo/release-action@v1
199 | with:
200 | name: pytest-asyncio ${{ needs.lint.outputs.version }}
201 | artifacts: dist/*
202 | bodyFile: release-notes.md
203 | prerelease: ${{ needs.lint.outputs.prerelease }}
204 | token: ${{ secrets.GITHUB_TOKEN }}
205 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 | .hypothesis/
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | coverage/
41 | .pytest_cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
59 | .venv*
60 | .idea
61 | .vscode
62 |
63 | # pyenv
64 | .python-version
65 |
66 |
67 | # generated by setuptools_scm
68 | pytest_asyncio/_version.py
69 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | repos:
3 | - repo: https://github.com/pre-commit/pre-commit-hooks
4 | rev: v5.0.0
5 | hooks:
6 | - id: check-merge-conflict
7 | exclude: rst$
8 | - repo: https://github.com/astral-sh/ruff-pre-commit
9 | rev: v0.11.11
10 | hooks:
11 | - id: ruff
12 | args: [--fix]
13 | - repo: https://github.com/asottile/yesqa
14 | rev: v1.5.0
15 | hooks:
16 | - id: yesqa
17 | - repo: https://github.com/Zac-HD/shed
18 | rev: 2024.10.1
19 | hooks:
20 | - id: shed
21 | args:
22 | - --refactor
23 | types_or:
24 | - python
25 | - markdown
26 | - rst
27 | - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
28 | rev: 0.2.3
29 | hooks:
30 | - id: yamlfmt
31 | args: [--mapping, '2', --sequence, '2', --offset, '0']
32 | - repo: https://github.com/pre-commit/pre-commit-hooks
33 | rev: v5.0.0
34 | hooks:
35 | - id: trailing-whitespace
36 | - id: end-of-file-fixer
37 | - id: fix-encoding-pragma
38 | args: [--remove]
39 | - id: check-case-conflict
40 | - id: check-json
41 | - id: check-xml
42 | - id: check-yaml
43 | - id: debug-statements
44 | - repo: https://github.com/pre-commit/mirrors-mypy
45 | rev: v1.15.0
46 | hooks:
47 | - id: mypy
48 | exclude: ^(docs|tests)/.*
49 | additional_dependencies:
50 | - pytest
51 | - repo: https://github.com/pre-commit/pygrep-hooks
52 | rev: v1.10.0
53 | hooks:
54 | - id: python-use-type-annotations
55 | - repo: https://github.com/rhysd/actionlint
56 | rev: v1.7.7
57 | hooks:
58 | - id: actionlint-docker
59 | args:
60 | - -ignore
61 | - 'SC2155:'
62 | - -ignore
63 | - 'SC2086:'
64 | - -ignore
65 | - 'SC1004:'
66 | stages: [manual]
67 | - repo: https://github.com/sirosen/check-jsonschema
68 | rev: 0.33.0
69 | hooks:
70 | - id: check-github-actions
71 | - repo: https://github.com/tox-dev/pyproject-fmt
72 | rev: v2.6.0
73 | hooks:
74 | - id: pyproject-fmt
75 | # https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
76 | additional_dependencies: [tox>=4.9]
77 | ci:
78 | skip:
79 | - actionlint-docker
80 | - check-github-actions
81 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | version: 2
4 |
5 | build:
6 | os: ubuntu-24.04
7 | tools:
8 | python: >-
9 | 3.12
10 | commands:
11 | - >-
12 | PYTHONWARNINGS=error
13 | python3 -Im venv "${READTHEDOCS_VIRTUALENV_PATH}"
14 | - >-
15 | PYTHONWARNINGS=error
16 | "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im
17 | pip install tox
18 | - >-
19 | PYTHONWARNINGS=error
20 | "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im
21 | tox -e docs --notest -vvvvv
22 | - >-
23 | PYTHONWARNINGS=error
24 | "${READTHEDOCS_VIRTUALENV_PATH}"/bin/python -Im
25 | tox -e docs --skip-pkg-install -q
26 | --
27 | "${READTHEDOCS_OUTPUT}"/html
28 | -b html
29 | -D language=en
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-exclude .github *
2 | exclude .gitignore
3 | exclude .pre-commit-config.yaml
4 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: clean clean-build clean-pyc clean-test lint test
2 |
3 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
4 |
5 | clean-build: ## remove build artifacts
6 | rm -fr build/
7 | rm -fr dist/
8 | rm -fr .eggs/
9 | find . -name '*.egg-info' -exec rm -fr {} +
10 | find . -name '*.egg' -exec rm -f {} +
11 |
12 | clean-pyc: ## remove Python file artifacts
13 | find . -name '*.pyc' -exec rm -f {} +
14 | find . -name '*.pyo' -exec rm -f {} +
15 | find . -name '*~' -exec rm -f {} +
16 | find . -name '__pycache__' -exec rm -fr {} +
17 |
18 | clean-test: ## remove test and coverage artifacts
19 | rm -fr .tox/
20 | rm -fr coverage/
21 | rm -fr htmlcov/
22 |
23 | test:
24 | coverage run -m pytest
25 |
26 | install:
27 | pip install -U pre-commit
28 | pre-commit install
29 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | pytest-asyncio
2 | ==============
3 |
4 | .. image:: https://img.shields.io/pypi/v/pytest-asyncio.svg
5 | :target: https://pypi.python.org/pypi/pytest-asyncio
6 | .. image:: https://github.com/pytest-dev/pytest-asyncio/workflows/CI/badge.svg
7 | :target: https://github.com/pytest-dev/pytest-asyncio/actions?workflow=CI
8 | .. image:: https://codecov.io/gh/pytest-dev/pytest-asyncio/branch/main/graph/badge.svg
9 | :target: https://codecov.io/gh/pytest-dev/pytest-asyncio
10 | .. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg
11 | :target: https://github.com/pytest-dev/pytest-asyncio
12 | :alt: Supported Python versions
13 | .. image:: https://img.shields.io/badge/Matrix-%23pytest--asyncio-brightgreen
14 | :alt: Matrix chat room: #pytest-asyncio
15 | :target: https://matrix.to/#/#pytest-asyncio:matrix.org
16 |
17 | `pytest-asyncio `_ is a `pytest `_ plugin. It facilitates testing of code that uses the `asyncio `_ library.
18 |
19 | 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:
20 |
21 | .. code-block:: python
22 |
23 | @pytest.mark.asyncio
24 | async def test_some_asyncio_code():
25 | res = await library.do_something()
26 | assert b"expected result" == res
27 |
28 | More details can be found in the `documentation `_.
29 |
30 | Note that test classes subclassing the standard `unittest `__ library are not supported. Users
31 | are advised to use `unittest.IsolatedAsyncioTestCase `__
32 | or an async framework such as `asynctest `__.
33 |
34 |
35 | pytest-asyncio is available under the `Apache License 2.0 `_.
36 |
37 |
38 | Installation
39 | ------------
40 |
41 | To install pytest-asyncio, simply:
42 |
43 | .. code-block:: bash
44 |
45 | $ pip install pytest-asyncio
46 |
47 | This is enough for pytest to pick up pytest-asyncio.
48 |
49 |
50 | Contributing
51 | ------------
52 | Contributions are very welcome. Tests can be run with ``tox``, please ensure
53 | the coverage at least stays the same before you submit a pull request.
54 |
--------------------------------------------------------------------------------
/SECURITY.rst:
--------------------------------------------------------------------------------
1 | Security contact information
2 | ============================
3 |
4 | To report a security vulnerability, please use the `Tidelift security contact. `__ Tidelift will coordinate the fix and disclosure.
5 |
--------------------------------------------------------------------------------
/changelog.d/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pytest-dev/pytest-asyncio/bd741b82e22030732e42a92eedebdb133cc3be36/changelog.d/.gitkeep
--------------------------------------------------------------------------------
/dependencies/default/constraints.txt:
--------------------------------------------------------------------------------
1 | attrs==25.3.0
2 | coverage==7.8.2
3 | exceptiongroup==1.3.0
4 | hypothesis==6.131.28
5 | iniconfig==2.1.0
6 | packaging==25.0
7 | pluggy==1.6.0
8 | pytest==8.3.5
9 | sortedcontainers==2.4.0
10 | tomli==2.2.1
11 | typing_extensions==4.13.2
12 |
--------------------------------------------------------------------------------
/dependencies/default/requirements.txt:
--------------------------------------------------------------------------------
1 | # Always adjust install_requires in setup.cfg and pytest-min-requirements.txt
2 | # when changing runtime dependencies
3 | pytest >= 8.2,<9
4 |
--------------------------------------------------------------------------------
/dependencies/docs/constraints.txt:
--------------------------------------------------------------------------------
1 | alabaster==0.7.16
2 | Babel==2.17.0
3 | certifi==2025.4.26
4 | charset-normalizer==3.4.2
5 | docutils==0.21.2
6 | idna==3.10
7 | imagesize==1.4.1
8 | Jinja2==3.1.6
9 | MarkupSafe==3.0.2
10 | packaging==25.0
11 | Pygments==2.19.1
12 | requests==2.32.3
13 | snowballstemmer==3.0.1
14 | Sphinx==8.0.2
15 | sphinx-rtd-theme==3.0.2
16 | sphinxcontrib-applehelp==2.0.0
17 | sphinxcontrib-devhelp==2.0.0
18 | sphinxcontrib-htmlhelp==2.1.0
19 | sphinxcontrib-jquery==4.1
20 | sphinxcontrib-jsmath==1.0.1
21 | sphinxcontrib-qthelp==2.0.0
22 | sphinxcontrib-serializinghtml==2.0.0
23 | urllib3==2.4.0
24 |
--------------------------------------------------------------------------------
/dependencies/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx >= 5.3
2 | sphinx-rtd-theme >= 1.0
3 |
--------------------------------------------------------------------------------
/dependencies/pytest-min/constraints.txt:
--------------------------------------------------------------------------------
1 | argcomplete==3.1.2
2 | attrs==23.1.0
3 | certifi==2023.7.22
4 | charset-normalizer==3.3.1
5 | coverage==7.3.2
6 | elementpath==4.1.5
7 | exceptiongroup==1.1.3
8 | hypothesis==6.88.3
9 | idna==3.4
10 | iniconfig==2.0.0
11 | mock==5.1.0
12 | nose==1.3.7
13 | packaging==23.2
14 | pluggy==1.5.0
15 | py==1.11.0
16 | Pygments==2.16.1
17 | pytest==8.2.0
18 | requests==2.31.0
19 | sortedcontainers==2.4.0
20 | tomli==2.0.1
21 | urllib3==2.0.7
22 | xmlschema==2.5.0
23 |
--------------------------------------------------------------------------------
/dependencies/pytest-min/requirements.txt:
--------------------------------------------------------------------------------
1 | # Always adjust install_requires in setup.cfg and requirements.txt
2 | # when changing minimum version dependencies
3 | pytest[testing] == 8.2.0
4 |
--------------------------------------------------------------------------------
/docs/concepts.rst:
--------------------------------------------------------------------------------
1 | ========
2 | Concepts
3 | ========
4 |
5 | .. _concepts/event_loops:
6 |
7 | asyncio event loops
8 | ===================
9 | In order to understand how pytest-asyncio works, it helps to understand how pytest collectors work.
10 | If you already know about pytest collectors, please :ref:`skip ahead `.
11 | Otherwise, continue reading.
12 | Let's assume we have a test suite with a file named *test_all_the_things.py* holding a single test, async or not:
13 |
14 | .. include:: concepts_function_scope_example.py
15 | :code: python
16 |
17 | The file *test_all_the_things.py* is a Python module with a Python test function.
18 | 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.
19 | This process is referred to as *test collection* by pytest.
20 | In our particular example, pytest will find our test module and the test function.
21 | We can visualize the collection result by running ``pytest --collect-only``::
22 |
23 |
24 |
25 |
26 | The example illustrates that the code of our test suite is hierarchical.
27 | Pytest uses so called *collectors* for each level of the hierarchy.
28 | 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.
29 | There's also a special *Session* collector at the root of the hierarchy.
30 | You may notice that the individual levels resemble the possible `scopes of a pytest fixture. `__
31 |
32 | .. _pytest-asyncio-event-loops:
33 |
34 | Pytest-asyncio provides one asyncio event loop for each pytest collector.
35 | By default, each test runs in the event loop provided by the *Function* collector, i.e. tests use the loop with the narrowest scope.
36 | This gives the highest level of isolation between tests.
37 | 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.
38 | For example, the following two tests use the asyncio event loop provided by the *Module* collector:
39 |
40 | .. include:: concepts_module_scope_example.py
41 | :code: python
42 |
43 | It's highly recommended for neighboring tests to use the same event loop scope.
44 | For example, all tests in a class or module should use the same scope.
45 | Assigning neighboring tests to different event loop scopes is discouraged as it can make test code hard to follow.
46 |
47 | Test discovery modes
48 | ====================
49 |
50 | Pytest-asyncio provides two modes for test discovery, *strict* and *auto*. This can be set through Pytest's ``--asyncio-mode`` command line flag, or through the configuration file:
51 |
52 | .. code-block:: toml
53 |
54 | [tool.pytest.ini_options]
55 | asyncio_mode = "auto" # or "strict"
56 |
57 | Strict mode
58 | -----------
59 |
60 | 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.
61 |
62 | 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.
63 |
64 | 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.
65 |
66 | Auto mode
67 | ---------
68 |
69 | 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``.
70 |
71 | 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.
72 |
73 | If you intend to support multiple asynchronous programming libraries, e.g. *asyncio* and *trio*, strict mode will be the preferred option.
74 |
--------------------------------------------------------------------------------
/docs/concepts_function_scope_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.asyncio
7 | async def test_runs_in_a_loop():
8 | assert asyncio.get_running_loop()
9 |
--------------------------------------------------------------------------------
/docs/concepts_module_scope_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 | loop: asyncio.AbstractEventLoop
6 |
7 |
8 | @pytest.mark.asyncio(loop_scope="module")
9 | async def test_remember_loop():
10 | global loop
11 | loop = asyncio.get_running_loop()
12 |
13 |
14 | @pytest.mark.asyncio(loop_scope="module")
15 | async def test_runs_in_a_loop():
16 | global loop
17 | assert asyncio.get_running_loop() is loop
18 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | # -- Project information -----------------------------------------------------
7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8 |
9 | import importlib.metadata
10 |
11 | project = "pytest-asyncio"
12 | copyright = "2023, pytest-asyncio contributors"
13 | author = "Tin Tvrtković"
14 | release = importlib.metadata.version(project)
15 |
16 | # -- General configuration ---------------------------------------------------
17 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
18 |
19 | extensions = []
20 |
21 | templates_path = ["_templates"]
22 | exclude_patterns = []
23 |
24 |
25 | # -- Options for HTML output -------------------------------------------------
26 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
27 |
28 | html_theme = "sphinx_rtd_theme"
29 | html_static_path = []
30 |
--------------------------------------------------------------------------------
/docs/how-to-guides/change_default_fixture_loop.rst:
--------------------------------------------------------------------------------
1 | ==========================================================
2 | How to change the default event loop scope of all fixtures
3 | ==========================================================
4 | 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:
5 |
6 | .. code-block:: ini
7 | :caption: pytest.ini
8 |
9 | [pytest]
10 | asyncio_default_fixture_loop_scope = session
11 |
12 | .. code-block:: toml
13 | :caption: pyproject.toml
14 |
15 | [tool.pytest.ini_options]
16 | asyncio_default_fixture_loop_scope = "session"
17 |
18 | .. code-block:: ini
19 | :caption: setup.cfg
20 |
21 | [tool:pytest]
22 | asyncio_default_fixture_loop_scope = session
23 |
24 | Please refer to :ref:`configuration/asyncio_default_fixture_loop_scope` for other valid scopes.
25 |
--------------------------------------------------------------------------------
/docs/how-to-guides/change_default_test_loop.rst:
--------------------------------------------------------------------------------
1 | =======================================================
2 | How to change the default event loop scope of all tests
3 | =======================================================
4 | The :ref:`configuration/asyncio_default_test_loop_scope` configuration option sets the default event loop scope for asynchronous tests. The following code snippets configure all tests to run in a session-scoped loop by default:
5 |
6 | .. code-block:: ini
7 | :caption: pytest.ini
8 |
9 | [pytest]
10 | asyncio_default_test_loop_scope = session
11 |
12 | .. code-block:: toml
13 | :caption: pyproject.toml
14 |
15 | [tool.pytest.ini_options]
16 | asyncio_default_test_loop_scope = "session"
17 |
18 | .. code-block:: ini
19 | :caption: setup.cfg
20 |
21 | [tool:pytest]
22 | asyncio_default_test_loop_scope = session
23 |
24 | Please refer to :ref:`configuration/asyncio_default_test_loop_scope` for other valid scopes.
25 |
--------------------------------------------------------------------------------
/docs/how-to-guides/change_fixture_loop.rst:
--------------------------------------------------------------------------------
1 | ===============================================
2 | How to change the event loop scope of a fixture
3 | ===============================================
4 | 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:
5 |
6 | .. include:: change_fixture_loop_example.py
7 | :code: python
8 |
--------------------------------------------------------------------------------
/docs/how-to-guides/change_fixture_loop_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 | import pytest_asyncio
6 |
7 |
8 | @pytest_asyncio.fixture(loop_scope="module")
9 | async def current_loop():
10 | return asyncio.get_running_loop()
11 |
12 |
13 | @pytest.mark.asyncio(loop_scope="module")
14 | async def test_runs_in_module_loop(current_loop):
15 | assert current_loop is asyncio.get_running_loop()
16 |
--------------------------------------------------------------------------------
/docs/how-to-guides/class_scoped_loop_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.asyncio(loop_scope="class")
7 | class TestInOneEventLoopPerClass:
8 | loop: asyncio.AbstractEventLoop
9 |
10 | async def test_remember_loop(self):
11 | TestInOneEventLoopPerClass.loop = asyncio.get_running_loop()
12 |
13 | async def test_assert_same_loop(self):
14 | assert asyncio.get_running_loop() is TestInOneEventLoopPerClass.loop
15 |
--------------------------------------------------------------------------------
/docs/how-to-guides/index.rst:
--------------------------------------------------------------------------------
1 | =============
2 | How-To Guides
3 | =============
4 |
5 | .. toctree::
6 | :hidden:
7 |
8 | migrate_from_0_21
9 | migrate_from_0_23
10 | change_fixture_loop
11 | change_default_fixture_loop
12 | change_default_test_loop
13 | run_class_tests_in_same_loop
14 | run_module_tests_in_same_loop
15 | run_package_tests_in_same_loop
16 | multiple_loops
17 | uvloop
18 | test_item_is_async
19 |
20 | This section of the documentation provides code snippets and recipes to accomplish specific tasks with pytest-asyncio.
21 |
--------------------------------------------------------------------------------
/docs/how-to-guides/migrate_from_0_21.rst:
--------------------------------------------------------------------------------
1 | .. _how_to_guides/migrate_from_0_21:
2 |
3 | ========================================
4 | How to migrate from pytest-asyncio v0.21
5 | ========================================
6 | 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.
7 | 2. Convert all synchronous test cases requesting the *event_loop* fixture to asynchronous test cases.
8 | 3. Convert all synchronous fixtures requesting the *event_loop* fixture to asynchronous fixtures.
9 | 4. Remove the *event_loop* argument from all asynchronous test cases in favor of ``event_loop = asyncio.get_running_loop()``.
10 | 5. Remove the *event_loop* argument from all asynchronous fixtures in favor of ``event_loop = asyncio.get_running_loop()``.
11 |
12 | 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:
13 |
14 | 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.
15 | 2. Remove the re-implemented *event_loop* fixture.
16 |
17 | If you haven't set the *asyncio_default_fixture_loop_scope* configuration option, yet, set it to *function* to silence the deprecation warning.
18 |
--------------------------------------------------------------------------------
/docs/how-to-guides/migrate_from_0_23.rst:
--------------------------------------------------------------------------------
1 | ========================================
2 | How to migrate from pytest-asyncio v0.23
3 | ========================================
4 | 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. `
5 |
6 | 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.
7 | 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.
8 | 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.
9 |
--------------------------------------------------------------------------------
/docs/how-to-guides/module_scoped_loop_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 | pytestmark = pytest.mark.asyncio(loop_scope="module")
6 |
7 | loop: asyncio.AbstractEventLoop
8 |
9 |
10 | async def test_remember_loop():
11 | global loop
12 | loop = asyncio.get_running_loop()
13 |
14 |
15 | async def test_assert_same_loop():
16 | global loop
17 | assert asyncio.get_running_loop() is loop
18 |
--------------------------------------------------------------------------------
/docs/how-to-guides/multiple_loops.rst:
--------------------------------------------------------------------------------
1 | ======================================
2 | How to test with different event loops
3 | ======================================
4 |
5 | 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:
6 |
7 | .. include:: multiple_loops_example.py
8 | :code: python
9 |
10 | 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.
11 |
--------------------------------------------------------------------------------
/docs/how-to-guides/multiple_loops_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import warnings
3 |
4 | with warnings.catch_warnings():
5 | warnings.simplefilter("ignore", DeprecationWarning)
6 | from asyncio import DefaultEventLoopPolicy
7 |
8 | import pytest
9 |
10 |
11 | class CustomEventLoopPolicy(DefaultEventLoopPolicy):
12 | pass
13 |
14 |
15 | @pytest.fixture(
16 | scope="session",
17 | params=(
18 | CustomEventLoopPolicy(),
19 | CustomEventLoopPolicy(),
20 | ),
21 | )
22 | def event_loop_policy(request):
23 | return request.param
24 |
25 |
26 | @pytest.mark.asyncio
27 | @pytest.mark.filterwarnings("ignore::DeprecationWarning")
28 | async def test_uses_custom_event_loop_policy():
29 | assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
30 |
--------------------------------------------------------------------------------
/docs/how-to-guides/package_scoped_loop_example.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | pytestmark = pytest.mark.asyncio(loop_scope="package")
4 |
--------------------------------------------------------------------------------
/docs/how-to-guides/run_class_tests_in_same_loop.rst:
--------------------------------------------------------------------------------
1 | ======================================================
2 | How to run all tests in a class in the same event loop
3 | ======================================================
4 | All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="class")``.
5 | This is easily achieved by using the *asyncio* marker as a class decorator.
6 |
7 | .. include:: class_scoped_loop_example.py
8 | :code: python
9 |
--------------------------------------------------------------------------------
/docs/how-to-guides/run_module_tests_in_same_loop.rst:
--------------------------------------------------------------------------------
1 | =======================================================
2 | How to run all tests in a module in the same event loop
3 | =======================================================
4 | All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="module")``.
5 | This is easily achieved by adding a `pytestmark` statement to your module.
6 |
7 | .. include:: module_scoped_loop_example.py
8 | :code: python
9 |
--------------------------------------------------------------------------------
/docs/how-to-guides/run_package_tests_in_same_loop.rst:
--------------------------------------------------------------------------------
1 | ========================================================
2 | How to run all tests in a package in the same event loop
3 | ========================================================
4 | All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="package")``.
5 | Add the following code to the ``__init__.py`` of the test package:
6 |
7 | .. include:: package_scoped_loop_example.py
8 | :code: python
9 |
10 | Note that this marker is not passed down to tests in subpackages.
11 | Subpackages constitute their own, separate package.
12 |
--------------------------------------------------------------------------------
/docs/how-to-guides/test_item_is_async.rst:
--------------------------------------------------------------------------------
1 | =======================================
2 | How to tell if a test function is async
3 | =======================================
4 | Use ``pytest_asyncio.is_async_item`` to determine if a test item is asynchronous and managed by pytest-asyncio.
5 |
6 | .. include:: test_item_is_async_example.py
7 | :code: python
8 |
--------------------------------------------------------------------------------
/docs/how-to-guides/test_item_is_async_example.py:
--------------------------------------------------------------------------------
1 | from pytest_asyncio import is_async_test
2 |
3 |
4 | def pytest_collection_modifyitems(items):
5 | for item in items:
6 | if is_async_test(item):
7 | pass
8 |
--------------------------------------------------------------------------------
/docs/how-to-guides/uvloop.rst:
--------------------------------------------------------------------------------
1 | =======================
2 | How to test with uvloop
3 | =======================
4 |
5 | Redefining 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:
6 | Replace the default event loop policy in your *conftest.py:*
7 |
8 | .. code-block:: python
9 |
10 | import pytest
11 | import uvloop
12 |
13 |
14 | @pytest.fixture(scope="session")
15 | def event_loop_policy():
16 | return uvloop.EventLoopPolicy()
17 |
18 | 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.
19 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | ==========================
2 | Welcome to pytest-asyncio!
3 | ==========================
4 |
5 | .. toctree::
6 | :maxdepth: 1
7 | :hidden:
8 |
9 | concepts
10 | how-to-guides/index
11 | reference/index
12 | support
13 |
14 | pytest-asyncio is a `pytest `_ plugin. It facilitates testing of code that uses the `asyncio `_ library.
15 |
16 | 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:
17 |
18 | .. code-block:: python
19 |
20 | @pytest.mark.asyncio
21 | async def test_some_asyncio_code():
22 | res = await library.do_something()
23 | assert b"expected result" == res
24 |
25 |
26 | Note that test classes subclassing the standard `unittest `__ library are not supported. Users
27 | are advised to use `unittest.IsolatedAsyncioTestCase `__
28 | or an async framework such as `asynctest `__.
29 |
30 |
31 | pytest-asyncio is available under the `Apache License 2.0 `_.
32 |
--------------------------------------------------------------------------------
/docs/reference/configuration.rst:
--------------------------------------------------------------------------------
1 | =============
2 | Configuration
3 | =============
4 |
5 | .. _configuration/asyncio_default_fixture_loop_scope:
6 |
7 | asyncio_default_fixture_loop_scope
8 | ==================================
9 | 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``
10 |
11 | .. _configuration/asyncio_default_test_loop_scope:
12 |
13 | asyncio_default_test_loop_scope
14 | ===============================
15 | Determines the default event loop scope of asynchronous tests. When this configuration option is unset, it default to function scope. Possible values are: ``function``, ``class``, ``module``, ``package``, ``session``
16 |
17 | asyncio_mode
18 | ============
19 | The pytest-asyncio mode can be set by the ``asyncio_mode`` configuration option in the `configuration file
20 | `_:
21 |
22 | .. code-block:: ini
23 |
24 | # pytest.ini
25 | [pytest]
26 | asyncio_mode = auto
27 |
28 | The value can also be set via the ``--asyncio-mode`` command-line option:
29 |
30 | .. code-block:: bash
31 |
32 | $ pytest tests --asyncio-mode=strict
33 |
34 |
35 | 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`.
36 |
--------------------------------------------------------------------------------
/docs/reference/decorators/index.rst:
--------------------------------------------------------------------------------
1 | .. _decorators/pytest_asyncio_fixture:
2 |
3 | ==========
4 | Decorators
5 | ==========
6 | The ``@pytest_asyncio.fixture`` decorator allows coroutines and async generator functions to be used as pytest fixtures.
7 |
8 | The decorator takes all arguments supported by `@pytest.fixture`.
9 | 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`).
10 | The default event loop scope is *function* scope.
11 | Possible loop scopes are *session,* *package,* *module,* *class,* and *function*.
12 |
13 | The *loop_scope* of a fixture can be chosen independently from its caching *scope*.
14 | However, the event loop scope must be larger or the same as the fixture's caching scope.
15 | 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.
16 |
17 | Examples:
18 |
19 | .. include:: pytest_asyncio_fixture_example.py
20 | :code: python
21 |
22 | *auto* mode automatically converts coroutines and async generator functions declared with the standard ``@pytest.fixture`` decorator to pytest-asyncio fixtures.
23 |
--------------------------------------------------------------------------------
/docs/reference/decorators/pytest_asyncio_fixture_example.py:
--------------------------------------------------------------------------------
1 | import pytest_asyncio
2 |
3 |
4 | @pytest_asyncio.fixture
5 | async def fixture_runs_in_fresh_loop_for_every_function(): ...
6 |
7 |
8 | @pytest_asyncio.fixture(loop_scope="session", scope="module")
9 | async def fixture_runs_in_session_loop_once_per_module(): ...
10 |
11 |
12 | @pytest_asyncio.fixture(loop_scope="module", scope="module")
13 | async def fixture_runs_in_module_loop_once_per_module(): ...
14 |
15 |
16 | @pytest_asyncio.fixture(loop_scope="module")
17 | async def fixture_runs_in_module_loop_once_per_function(): ...
18 |
--------------------------------------------------------------------------------
/docs/reference/fixtures/event_loop_policy_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import warnings
3 |
4 | with warnings.catch_warnings():
5 | warnings.simplefilter("ignore", DeprecationWarning)
6 | from asyncio import DefaultEventLoopPolicy
7 |
8 | import pytest
9 |
10 |
11 | class CustomEventLoopPolicy(DefaultEventLoopPolicy):
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def event_loop_policy(request):
17 | return CustomEventLoopPolicy()
18 |
19 |
20 | @pytest.mark.asyncio(loop_scope="module")
21 | @pytest.mark.filterwarnings("ignore::DeprecationWarning")
22 | async def test_uses_custom_event_loop_policy():
23 | assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
24 |
--------------------------------------------------------------------------------
/docs/reference/fixtures/event_loop_policy_parametrized_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import warnings
3 |
4 | with warnings.catch_warnings():
5 | warnings.simplefilter("ignore", DeprecationWarning)
6 | from asyncio import DefaultEventLoopPolicy
7 |
8 | import pytest
9 |
10 |
11 | class CustomEventLoopPolicy(DefaultEventLoopPolicy):
12 | pass
13 |
14 |
15 | @pytest.fixture(
16 | params=(
17 | DefaultEventLoopPolicy(),
18 | CustomEventLoopPolicy(),
19 | ),
20 | )
21 | def event_loop_policy(request):
22 | return request.param
23 |
24 |
25 | @pytest.mark.asyncio
26 | @pytest.mark.filterwarnings("ignore::DeprecationWarning")
27 | async def test_uses_custom_event_loop_policy():
28 | assert isinstance(asyncio.get_event_loop_policy(), DefaultEventLoopPolicy)
29 |
--------------------------------------------------------------------------------
/docs/reference/fixtures/index.rst:
--------------------------------------------------------------------------------
1 | ========
2 | Fixtures
3 | ========
4 |
5 | event_loop_policy
6 | =================
7 | Returns the event loop policy used to create asyncio event loops.
8 | The default return value is *asyncio.get_event_loop_policy().*
9 |
10 | This fixture can be overridden when a different event loop policy should be used.
11 |
12 | .. include:: event_loop_policy_example.py
13 | :code: python
14 |
15 | Multiple policies can be provided via fixture parameters.
16 | The fixture is automatically applied to all pytest-asyncio tests.
17 | Therefore, all tests managed by pytest-asyncio are run once for each fixture parameter.
18 | The following example runs the test with different event loop policies.
19 |
20 | .. include:: event_loop_policy_parametrized_example.py
21 | :code: python
22 |
23 | unused_tcp_port
24 | ===============
25 | Finds and yields a single unused TCP port on the localhost interface. Useful for
26 | binding temporary test servers.
27 |
28 | unused_tcp_port_factory
29 | =======================
30 | A callable which returns a different unused TCP port each invocation. Useful
31 | when several unused TCP ports are required in a test.
32 |
33 | .. code-block:: python
34 |
35 | def a_test(unused_tcp_port_factory):
36 | _port1, _port2 = unused_tcp_port_factory(), unused_tcp_port_factory()
37 |
38 | unused_udp_port and unused_udp_port_factory
39 | ===========================================
40 | Works just like their TCP counterparts but returns unused UDP ports.
41 |
--------------------------------------------------------------------------------
/docs/reference/functions.rst:
--------------------------------------------------------------------------------
1 | =========
2 | Functions
3 | =========
4 |
5 | is_async_test
6 | =============
7 | Returns whether a specific pytest Item is an asynchronous test managed by pytest-asyncio.
8 |
9 | This function is intended to be used in pytest hooks or by plugins that depend on pytest-asyncio.
10 |
--------------------------------------------------------------------------------
/docs/reference/index.rst:
--------------------------------------------------------------------------------
1 | =========
2 | Reference
3 | =========
4 |
5 | .. toctree::
6 | :hidden:
7 |
8 | configuration
9 | fixtures/index
10 | functions
11 | markers/index
12 | decorators/index
13 | changelog
14 |
15 | This section of the documentation provides descriptions of the individual parts provided by pytest-asyncio.
16 | The reference section also provides a chronological list of changes for each release.
17 |
--------------------------------------------------------------------------------
/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py:
--------------------------------------------------------------------------------
1 | import warnings
2 |
3 | with warnings.catch_warnings():
4 | warnings.simplefilter("ignore", DeprecationWarning)
5 | from asyncio import DefaultEventLoopPolicy
6 |
7 | import pytest
8 |
9 |
10 | @pytest.fixture(
11 | params=[
12 | DefaultEventLoopPolicy(),
13 | DefaultEventLoopPolicy(),
14 | ]
15 | )
16 | def event_loop_policy(request):
17 | return request.param
18 |
19 |
20 | class TestWithDifferentLoopPolicies:
21 | @pytest.mark.asyncio
22 | async def test_parametrized_loop(self):
23 | pass
24 |
--------------------------------------------------------------------------------
/docs/reference/markers/class_scoped_loop_strict_mode_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.asyncio(loop_scope="class")
7 | class TestClassScopedLoop:
8 | loop: asyncio.AbstractEventLoop
9 |
10 | async def test_remember_loop(self):
11 | TestClassScopedLoop.loop = asyncio.get_running_loop()
12 |
13 | async def test_this_runs_in_same_loop(self):
14 | assert asyncio.get_running_loop() is TestClassScopedLoop.loop
15 |
--------------------------------------------------------------------------------
/docs/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 | import pytest_asyncio
6 |
7 |
8 | @pytest.mark.asyncio(loop_scope="class")
9 | class TestClassScopedLoop:
10 | loop: asyncio.AbstractEventLoop
11 |
12 | @pytest_asyncio.fixture(loop_scope="class")
13 | async def my_fixture(self):
14 | TestClassScopedLoop.loop = asyncio.get_running_loop()
15 |
16 | async def test_runs_is_same_loop_as_fixture(self, my_fixture):
17 | assert asyncio.get_running_loop() is TestClassScopedLoop.loop
18 |
--------------------------------------------------------------------------------
/docs/reference/markers/function_scoped_loop_pytestmark_strict_mode_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 | # Marks all test coroutines in this module
6 | pytestmark = pytest.mark.asyncio
7 |
8 |
9 | async def test_runs_in_asyncio_event_loop():
10 | assert asyncio.get_running_loop()
11 |
--------------------------------------------------------------------------------
/docs/reference/markers/function_scoped_loop_strict_mode_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.asyncio
7 | async def test_runs_in_asyncio_event_loop():
8 | assert asyncio.get_running_loop()
9 |
--------------------------------------------------------------------------------
/docs/reference/markers/index.rst:
--------------------------------------------------------------------------------
1 | =======
2 | Markers
3 | =======
4 |
5 | .. _reference/markers/asyncio:
6 |
7 | ``pytest.mark.asyncio``
8 | =======================
9 | A coroutine or async generator with this marker is treated as a test function by pytest.
10 | The marked function is executed as an asyncio task in the event loop provided by pytest-asyncio.
11 |
12 | .. include:: function_scoped_loop_strict_mode_example.py
13 | :code: python
14 |
15 | Multiple async tests in a single class or module can be marked using |pytestmark|_.
16 |
17 | .. include:: function_scoped_loop_pytestmark_strict_mode_example.py
18 | :code: python
19 |
20 | The ``pytest.mark.asyncio`` marker can be omitted entirely in |auto mode|_ where the *asyncio* marker is added automatically to *async* test functions.
21 |
22 | By default, each test runs in it's own asyncio event loop.
23 | Multiple tests can share the same event loop by providing a *loop_scope* keyword argument to the *asyncio* mark.
24 | The supported scopes are *function,* *class,* and *module,* *package,* and *session*.
25 | The following code example provides a shared event loop for all tests in `TestClassScopedLoop`:
26 |
27 | .. include:: class_scoped_loop_strict_mode_example.py
28 | :code: python
29 |
30 | Similar to class-scoped event loops, a module-scoped loop is provided when setting mark's scope to *module:*
31 |
32 | .. include:: module_scoped_loop_strict_mode_example.py
33 | :code: python
34 |
35 | Subpackages do not share the loop with their parent package.
36 |
37 | Tests marked with *session* scope share the same event loop, even if the tests exist in different packages.
38 |
39 | .. |auto mode| replace:: *auto mode*
40 | .. _auto mode: ../../concepts.html#auto-mode
41 | .. |pytestmark| replace:: ``pytestmark``
42 | .. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules
43 |
--------------------------------------------------------------------------------
/docs/reference/markers/module_scoped_loop_strict_mode_example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 | pytestmark = pytest.mark.asyncio(loop_scope="module")
6 |
7 | loop: asyncio.AbstractEventLoop
8 |
9 |
10 | async def test_remember_loop():
11 | global loop
12 | loop = asyncio.get_running_loop()
13 |
14 |
15 | async def test_this_runs_in_same_loop():
16 | global loop
17 | assert asyncio.get_running_loop() is loop
18 |
19 |
20 | class TestClassA:
21 | async def test_this_runs_in_same_loop(self):
22 | global loop
23 | assert asyncio.get_running_loop() is loop
24 |
--------------------------------------------------------------------------------
/docs/support.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | Getting support
3 | ===============
4 |
5 | Enterprise support
6 | ==================
7 | `Tidelift `_ works with maintainers of numerous open source projects to ensure enterprise-grade support for your software supply chain.
8 |
9 | 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.
10 |
11 | Consider `signing up for the Tidelift subscription `__.
12 |
13 |
14 | Direct maintainer support
15 | =========================
16 | If you require commercial support outside of the Tidelift subscription, reach out to `Michael Seifert, `__ one of the project's maintainers.
17 |
18 |
19 | Community support
20 | =================
21 | 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.
22 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | build-backend = "setuptools.build_meta"
3 | requires = [
4 | "setuptools>=77",
5 | "setuptools-scm[toml]>=6.2",
6 | ]
7 |
8 | [project]
9 | name = "pytest-asyncio"
10 | description = "Pytest support for asyncio"
11 | readme.content-type = "text/x-rst"
12 | readme.file = "README.rst"
13 | license = "Apache-2.0"
14 | license-files = [
15 | "LICENSE",
16 | ]
17 | authors = [
18 | { name = "Tin Tvrtković ", email = "tinchester@gmail.com" },
19 | ]
20 | requires-python = ">=3.9"
21 | classifiers = [
22 | "Development Status :: 4 - Beta",
23 | "Framework :: AsyncIO",
24 | "Framework :: Pytest",
25 | "Intended Audience :: Developers",
26 | "Programming Language :: Python :: 3 :: Only",
27 | "Programming Language :: Python :: 3.9",
28 | "Programming Language :: Python :: 3.10",
29 | "Programming Language :: Python :: 3.11",
30 | "Programming Language :: Python :: 3.12",
31 | "Programming Language :: Python :: 3.13",
32 | "Topic :: Software Development :: Testing",
33 | "Typing :: Typed",
34 | ]
35 | dynamic = [
36 | "version",
37 | ]
38 |
39 | dependencies = [
40 | "pytest>=8.2,<9",
41 | "typing-extensions>=4.12; python_version<'3.10'",
42 | ]
43 | optional-dependencies.docs = [
44 | "sphinx>=5.3",
45 | "sphinx-rtd-theme>=1",
46 | ]
47 | optional-dependencies.testing = [
48 | "coverage>=6.2",
49 | "hypothesis>=5.7.1",
50 | ]
51 | urls."Bug Tracker" = "https://github.com/pytest-dev/pytest-asyncio/issues"
52 | urls.Changelog = "https://pytest-asyncio.readthedocs.io/en/latest/reference/changelog.html"
53 | urls.Documentation = "https://pytest-asyncio.readthedocs.io"
54 | urls.Homepage = "https://github.com/pytest-dev/pytest-asyncio"
55 | urls."Source Code" = "https://github.com/pytest-dev/pytest-asyncio"
56 | entry-points.pytest11.asyncio = "pytest_asyncio.plugin"
57 |
58 | [tool.setuptools]
59 | packages = [
60 | "pytest_asyncio",
61 | ]
62 | include-package-data = true
63 |
64 | [tool.setuptools_scm]
65 | write_to = "pytest_asyncio/_version.py"
66 |
67 | [tool.ruff]
68 | line-length = 88
69 | format.docstring-code-format = true
70 | lint.select = [
71 | "B", # bugbear
72 | "D", # pydocstyle
73 | "E", # pycodestyle
74 | "F", # pyflakes
75 | "FA100", # add future annotations
76 | "PGH004", # pygrep-hooks - Use specific rule codes when using noqa
77 | "PIE", # flake8-pie
78 | "PLE", # pylint error
79 | "PYI", # flake8-pyi
80 | "RUF", # ruff
81 | "T100", # flake8-debugger
82 | "UP", # pyupgrade
83 | "W", # pycodestyle
84 | ]
85 |
86 | lint.ignore = [
87 | # bugbear ignore
88 | "B028", # No explicit `stacklevel` keyword argument found
89 | # pydocstyle ignore
90 | "D100", # Missing docstring in public module
91 | "D101", # Missing docstring in public class
92 | "D102", # Missing docstring in public method
93 | "D103", # Missing docstring in public function
94 | "D104", # Missing docstring in public package
95 | "D105", # Missing docstring in magic method
96 | "D106", # Missing docstring in public nested class
97 | "D107", # Missing docstring in `__init__`
98 | "D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible
99 | "D205", # 1 blank line required between summary line and description
100 | "D209", # [*] Multi-line docstring closing quotes should be on a separate line
101 | "D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible.
102 | "D400", # First line should end with a period
103 | "D401", # First line of docstring should be in imperative mood
104 | "D402", # First line should not be the function's signature
105 | "D404", # First word of the docstring should not be "This"
106 | "D415", # First line should end with a period, question mark, or exclamation point
107 | ]
108 |
109 | [tool.pytest.ini_options]
110 | python_files = [
111 | "test_*.py",
112 | "*_example.py",
113 | ]
114 | addopts = "-rsx --tb=short"
115 | testpaths = [
116 | "docs",
117 | "tests",
118 | ]
119 | asyncio_mode = "auto"
120 | asyncio_default_fixture_loop_scope = "function"
121 | junit_family = "xunit2"
122 | filterwarnings = [
123 | "error",
124 | ]
125 |
126 | [tool.coverage.run]
127 | source = [
128 | "pytest_asyncio",
129 | ]
130 | branch = true
131 | data_file = "coverage/coverage"
132 | omit = [
133 | "*/_version.py",
134 | ]
135 | parallel = true
136 |
137 | [tool.coverage.report]
138 | show_missing = true
139 |
140 | [tool.towncrier]
141 | directory = "changelog.d"
142 | filename = "docs/reference/changelog.rst"
143 | title_format = "`{version} `_ - {project_date}"
144 | issue_format = "`#{issue} `_"
145 |
146 | [[tool.towncrier.type]]
147 | directory = "security"
148 | name = "Security"
149 | showcontent = true
150 |
151 | [[tool.towncrier.type]]
152 | directory = "removed"
153 | name = "Removed"
154 | showcontent = true
155 |
156 | [[tool.towncrier.type]]
157 | directory = "deprecated"
158 | name = "Deprecated"
159 | showcontent = true
160 |
161 | [[tool.towncrier.type]]
162 | directory = "added"
163 | name = "Added"
164 | showcontent = true
165 |
166 | [[tool.towncrier.type]]
167 | directory = "changed"
168 | name = "Changed"
169 | showcontent = true
170 |
171 | [[tool.towncrier.type]]
172 | directory = "fixed"
173 | name = "Fixed"
174 | showcontent = true
175 |
176 | [[tool.towncrier.type]]
177 | directory = "downstream"
178 | name = "Notes for Downstream Packagers"
179 | showcontent = true
180 |
--------------------------------------------------------------------------------
/pytest_asyncio/__init__.py:
--------------------------------------------------------------------------------
1 | """The main point for importing pytest-asyncio items."""
2 |
3 | from __future__ import annotations
4 |
5 | from ._version import version as __version__ # noqa: F401
6 | from .plugin import fixture, is_async_test
7 |
8 | __all__ = ("fixture", "is_async_test")
9 |
--------------------------------------------------------------------------------
/pytest_asyncio/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pytest-dev/pytest-asyncio/bd741b82e22030732e42a92eedebdb133cc3be36/pytest_asyncio/py.typed
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | # Not everything is in in pyproject.toml because of this issue:
3 | ; Traceback (most recent call last):
4 | ; File "/tmp/build-env-rud8b5r6/lib/python3.12/site-packages/setuptools/config/expand.py", line 69, in __getattr__
5 | ; return next(
6 | ; ^^^^^
7 | ;StopIteration
8 | ;
9 | ;The above exception was the direct cause of the following exception:
10 | ;
11 | ;Traceback (most recent call last):
12 | ; File "/tmp/build-env-rud8b5r6/lib/python3.12/site-packages/setuptools/config/expand.py", line 183, in read_attr
13 | ; return getattr(StaticModule(module_name, spec), attr_name)
14 | ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 | ; File "/tmp/build-env-rud8b5r6/lib/python3.12/site-packages/setuptools/config/expand.py", line 75, in __getattr__
16 | ; raise AttributeError(f"{self.name} has no attribute {attr}") from e
17 | ;AttributeError: pytest_asyncio has no attribute __version__
18 | version = attr: pytest_asyncio.__version__
19 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pytest-dev/pytest-asyncio/bd741b82e22030732e42a92eedebdb133cc3be36/tests/__init__.py
--------------------------------------------------------------------------------
/tests/async_fixtures/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pytest-dev/pytest-asyncio/bd741b82e22030732e42a92eedebdb133cc3be36/tests/async_fixtures/__init__.py
--------------------------------------------------------------------------------
/tests/async_fixtures/test_async_fixtures.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import asyncio
4 | import unittest.mock
5 |
6 | import pytest
7 |
8 | START = object()
9 | END = object()
10 | RETVAL = object()
11 |
12 |
13 | @pytest.fixture
14 | def mock():
15 | return unittest.mock.Mock(return_value=RETVAL)
16 |
17 |
18 | @pytest.fixture
19 | async def async_fixture(mock):
20 | return await asyncio.sleep(0.1, result=mock(START))
21 |
22 |
23 | @pytest.mark.asyncio
24 | async def test_async_fixture(async_fixture, mock):
25 | assert mock.call_count == 1
26 | assert mock.call_args_list[-1] == unittest.mock.call(START)
27 | assert async_fixture is RETVAL
28 |
29 |
30 | class TestAsyncFixtureMethod:
31 | is_same_instance = False
32 |
33 | @pytest.fixture(autouse=True)
34 | async def async_fixture_method(self):
35 | self.is_same_instance = True
36 |
37 | @pytest.mark.asyncio
38 | async def test_async_fixture_method(self):
39 | assert self.is_same_instance
40 |
--------------------------------------------------------------------------------
/tests/async_fixtures/test_async_fixtures_contextvars.py:
--------------------------------------------------------------------------------
1 | """
2 | Regression test for https://github.com/pytest-dev/pytest-asyncio/issues/127:
3 | contextvars were not properly maintained among fixtures and tests.
4 | """
5 |
6 | from __future__ import annotations
7 |
8 | import sys
9 | from textwrap import dedent
10 |
11 | import pytest
12 | from pytest import Pytester
13 |
14 | _prelude = dedent(
15 | """
16 | import pytest
17 | import pytest_asyncio
18 | from contextlib import contextmanager
19 | from contextvars import ContextVar
20 |
21 | _context_var = ContextVar("context_var")
22 |
23 | @contextmanager
24 | def context_var_manager(value):
25 | token = _context_var.set(value)
26 | try:
27 | yield
28 | finally:
29 | _context_var.reset(token)
30 | """
31 | )
32 |
33 |
34 | def test_var_from_sync_generator_propagates_to_async(pytester: Pytester):
35 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
36 | pytester.makepyfile(
37 | _prelude
38 | + dedent(
39 | """
40 | @pytest.fixture
41 | def var_fixture():
42 | with context_var_manager("value"):
43 | yield
44 |
45 | @pytest_asyncio.fixture
46 | async def check_var_fixture(var_fixture):
47 | assert _context_var.get() == "value"
48 |
49 | @pytest.mark.asyncio
50 | async def test(check_var_fixture):
51 | assert _context_var.get() == "value"
52 | """
53 | )
54 | )
55 | result = pytester.runpytest("--asyncio-mode=strict")
56 | result.assert_outcomes(passed=1)
57 |
58 |
59 | @pytest.mark.xfail(
60 | sys.version_info < (3, 11),
61 | reason="requires asyncio Task context support",
62 | strict=True,
63 | )
64 | def test_var_from_async_generator_propagates_to_sync(pytester: Pytester):
65 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
66 | pytester.makepyfile(
67 | _prelude
68 | + dedent(
69 | """
70 | @pytest_asyncio.fixture
71 | async def var_fixture():
72 | with context_var_manager("value"):
73 | yield
74 |
75 | @pytest.fixture
76 | def check_var_fixture(var_fixture):
77 | assert _context_var.get() == "value"
78 |
79 | @pytest.mark.asyncio
80 | async def test(check_var_fixture):
81 | assert _context_var.get() == "value"
82 | """
83 | )
84 | )
85 | result = pytester.runpytest("--asyncio-mode=strict")
86 | result.assert_outcomes(passed=1)
87 |
88 |
89 | @pytest.mark.xfail(
90 | sys.version_info < (3, 11),
91 | reason="requires asyncio Task context support",
92 | strict=True,
93 | )
94 | def test_var_from_async_fixture_propagates_to_sync(pytester: Pytester):
95 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
96 | pytester.makepyfile(
97 | _prelude
98 | + dedent(
99 | """
100 | @pytest_asyncio.fixture
101 | async def var_fixture():
102 | _context_var.set("value")
103 | # Rely on async fixture teardown to reset the context var.
104 |
105 | @pytest.fixture
106 | def check_var_fixture(var_fixture):
107 | assert _context_var.get() == "value"
108 |
109 | def test(check_var_fixture):
110 | assert _context_var.get() == "value"
111 | """
112 | )
113 | )
114 | result = pytester.runpytest("--asyncio-mode=strict")
115 | result.assert_outcomes(passed=1)
116 |
117 |
118 | @pytest.mark.xfail(
119 | sys.version_info < (3, 11),
120 | reason="requires asyncio Task context support",
121 | strict=True,
122 | )
123 | def test_var_from_generator_reset_before_previous_fixture_cleanup(pytester: Pytester):
124 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
125 | pytester.makepyfile(
126 | _prelude
127 | + dedent(
128 | """
129 | @pytest_asyncio.fixture
130 | async def no_var_fixture():
131 | with pytest.raises(LookupError):
132 | _context_var.get()
133 | yield
134 | with pytest.raises(LookupError):
135 | _context_var.get()
136 |
137 | @pytest_asyncio.fixture
138 | async def var_fixture(no_var_fixture):
139 | with context_var_manager("value"):
140 | yield
141 |
142 | @pytest.mark.asyncio
143 | async def test(var_fixture):
144 | assert _context_var.get() == "value"
145 | """
146 | )
147 | )
148 | result = pytester.runpytest("--asyncio-mode=strict")
149 | result.assert_outcomes(passed=1)
150 |
151 |
152 | @pytest.mark.xfail(
153 | sys.version_info < (3, 11),
154 | reason="requires asyncio Task context support",
155 | strict=True,
156 | )
157 | def test_var_from_fixture_reset_before_previous_fixture_cleanup(pytester: Pytester):
158 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
159 | pytester.makepyfile(
160 | _prelude
161 | + dedent(
162 | """
163 | @pytest_asyncio.fixture
164 | async def no_var_fixture():
165 | with pytest.raises(LookupError):
166 | _context_var.get()
167 | yield
168 | with pytest.raises(LookupError):
169 | _context_var.get()
170 |
171 | @pytest_asyncio.fixture
172 | async def var_fixture(no_var_fixture):
173 | _context_var.set("value")
174 | # Rely on async fixture teardown to reset the context var.
175 |
176 | @pytest.mark.asyncio
177 | async def test(var_fixture):
178 | assert _context_var.get() == "value"
179 | """
180 | )
181 | )
182 | result = pytester.runpytest("--asyncio-mode=strict")
183 | result.assert_outcomes(passed=1)
184 |
185 |
186 | @pytest.mark.xfail(
187 | sys.version_info < (3, 11),
188 | reason="requires asyncio Task context support",
189 | strict=True,
190 | )
191 | def test_var_previous_value_restored_after_fixture(pytester: Pytester):
192 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
193 | pytester.makepyfile(
194 | _prelude
195 | + dedent(
196 | """
197 | @pytest_asyncio.fixture
198 | async def var_fixture_1():
199 | with context_var_manager("value1"):
200 | yield
201 | assert _context_var.get() == "value1"
202 |
203 | @pytest_asyncio.fixture
204 | async def var_fixture_2(var_fixture_1):
205 | with context_var_manager("value2"):
206 | yield
207 | assert _context_var.get() == "value2"
208 |
209 | @pytest.mark.asyncio
210 | async def test(var_fixture_2):
211 | assert _context_var.get() == "value2"
212 | """
213 | )
214 | )
215 | result = pytester.runpytest("--asyncio-mode=strict")
216 | result.assert_outcomes(passed=1)
217 |
218 |
219 | @pytest.mark.xfail(
220 | sys.version_info < (3, 11),
221 | reason="requires asyncio Task context support",
222 | strict=True,
223 | )
224 | def test_var_set_to_existing_value_ok(pytester: Pytester):
225 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
226 | pytester.makepyfile(
227 | _prelude
228 | + dedent(
229 | """
230 | @pytest_asyncio.fixture
231 | async def var_fixture():
232 | with context_var_manager("value"):
233 | yield
234 |
235 | @pytest_asyncio.fixture
236 | async def same_var_fixture(var_fixture):
237 | with context_var_manager(_context_var.get()):
238 | yield
239 |
240 | @pytest.mark.asyncio
241 | async def test(same_var_fixture):
242 | assert _context_var.get() == "value"
243 | """
244 | )
245 | )
246 | result = pytester.runpytest("--asyncio-mode=strict")
247 | result.assert_outcomes(passed=1)
248 |
--------------------------------------------------------------------------------
/tests/async_fixtures/test_nested.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import asyncio
4 |
5 | import pytest
6 |
7 |
8 | @pytest.fixture()
9 | async def async_inner_fixture():
10 | await asyncio.sleep(0.01)
11 | print("inner start")
12 | yield True
13 | print("inner stop")
14 |
15 |
16 | @pytest.fixture()
17 | async def async_fixture_outer(async_inner_fixture):
18 | await asyncio.sleep(0.01)
19 | print("outer start")
20 | assert async_inner_fixture is True
21 | yield True
22 | print("outer stop")
23 |
24 |
25 | @pytest.mark.asyncio
26 | async def test_async_fixture(async_fixture_outer):
27 | assert async_fixture_outer is True
28 | print("test_async_fixture")
29 |
--------------------------------------------------------------------------------
/tests/async_fixtures/test_shared_module_fixture.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_asyncio_mark_provides_package_scoped_loop_strict_mode(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | __init__="",
12 | conftest=dedent(
13 | """\
14 | import pytest_asyncio
15 | @pytest_asyncio.fixture(loop_scope="module", scope="module")
16 | async def async_shared_module_fixture():
17 | return True
18 | """
19 | ),
20 | test_module_one=dedent(
21 | """\
22 | import pytest
23 | @pytest.mark.asyncio
24 | async def test_shared_module_fixture_use_a(async_shared_module_fixture):
25 | assert async_shared_module_fixture is True
26 | """
27 | ),
28 | test_module_two=dedent(
29 | """\
30 | import pytest
31 | @pytest.mark.asyncio
32 | async def test_shared_module_fixture_use_b(async_shared_module_fixture):
33 | assert async_shared_module_fixture is True
34 | """
35 | ),
36 | )
37 | result = pytester.runpytest("--asyncio-mode=strict")
38 | result.assert_outcomes(passed=2)
39 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | pytest_plugins = "pytester"
4 |
--------------------------------------------------------------------------------
/tests/hypothesis/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pytest-dev/pytest-asyncio/bd741b82e22030732e42a92eedebdb133cc3be36/tests/hypothesis/__init__.py
--------------------------------------------------------------------------------
/tests/hypothesis/test_base.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests for the Hypothesis integration, which wraps async functions in a
3 | sync shim for Hypothesis.
4 | """
5 |
6 | from __future__ import annotations
7 |
8 | from textwrap import dedent
9 |
10 | import pytest
11 | from hypothesis import given, strategies as st
12 | from pytest import Pytester
13 |
14 |
15 | def test_hypothesis_given_decorator_before_asyncio_mark(pytester: Pytester):
16 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
17 | pytester.makepyfile(
18 | dedent(
19 | """\
20 | import pytest
21 | from hypothesis import given, strategies as st
22 |
23 | @given(st.integers())
24 | @pytest.mark.asyncio
25 | async def test_mark_inner(n):
26 | assert isinstance(n, int)
27 | """
28 | )
29 | )
30 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
31 | result.assert_outcomes(passed=1)
32 |
33 |
34 | @pytest.mark.asyncio
35 | @given(st.integers())
36 | async def test_mark_outer(n):
37 | assert isinstance(n, int)
38 |
39 |
40 | @pytest.mark.parametrize("y", [1, 2])
41 | @given(x=st.none())
42 | @pytest.mark.asyncio
43 | async def test_mark_and_parametrize(x, y):
44 | assert x is None
45 | assert y in (1, 2)
46 |
47 |
48 | def test_async_auto_marked(pytester: Pytester):
49 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
50 | pytester.makepyfile(
51 | dedent(
52 | """\
53 | import asyncio
54 | import pytest
55 | from hypothesis import given
56 | import hypothesis.strategies as st
57 |
58 | pytest_plugins = 'pytest_asyncio'
59 |
60 | @given(n=st.integers())
61 | async def test_hypothesis(n: int):
62 | assert isinstance(n, int)
63 | """
64 | )
65 | )
66 | result = pytester.runpytest("--asyncio-mode=auto")
67 | result.assert_outcomes(passed=1)
68 |
69 |
70 | def test_sync_not_auto_marked(pytester: Pytester):
71 | """Assert that synchronous Hypothesis functions are not marked with asyncio"""
72 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
73 | pytester.makepyfile(
74 | dedent(
75 | """\
76 | import asyncio
77 | import pytest
78 | from hypothesis import given
79 | import hypothesis.strategies as st
80 |
81 | pytest_plugins = 'pytest_asyncio'
82 |
83 | @given(n=st.integers())
84 | def test_hypothesis(request, n: int):
85 | markers = [marker.name for marker in request.node.own_markers]
86 | assert "asyncio" not in markers
87 | assert isinstance(n, int)
88 | """
89 | )
90 | )
91 | result = pytester.runpytest("--asyncio-mode=auto")
92 | result.assert_outcomes(passed=1)
93 |
--------------------------------------------------------------------------------
/tests/markers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pytest-dev/pytest-asyncio/bd741b82e22030732e42a92eedebdb133cc3be36/tests/markers/__init__.py
--------------------------------------------------------------------------------
/tests/markers/test_class_scope.py:
--------------------------------------------------------------------------------
1 | """Test if pytestmark works when defined on a class."""
2 |
3 | from __future__ import annotations
4 |
5 | import asyncio
6 | from textwrap import dedent
7 |
8 | import pytest
9 |
10 |
11 | class TestPyTestMark:
12 | pytestmark = pytest.mark.asyncio
13 |
14 | async def test_is_asyncio(self, sample_fixture):
15 | assert asyncio.get_event_loop()
16 | counter = 1
17 |
18 | async def inc():
19 | nonlocal counter
20 | counter += 1
21 | await asyncio.sleep(0)
22 |
23 | await asyncio.ensure_future(inc())
24 | assert counter == 2
25 |
26 |
27 | @pytest.fixture
28 | def sample_fixture():
29 | return None
30 |
31 |
32 | def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_functions(
33 | pytester: pytest.Pytester,
34 | ):
35 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
36 | pytester.makepyfile(
37 | dedent(
38 | """\
39 | import asyncio
40 | import pytest
41 |
42 | class TestClassScopedLoop:
43 | loop: asyncio.AbstractEventLoop
44 |
45 | @pytest.mark.asyncio(loop_scope="class")
46 | async def test_remember_loop(self):
47 | TestClassScopedLoop.loop = asyncio.get_running_loop()
48 |
49 | @pytest.mark.asyncio(loop_scope="class")
50 | async def test_this_runs_in_same_loop(self):
51 | assert asyncio.get_running_loop() is TestClassScopedLoop.loop
52 | """
53 | )
54 | )
55 | result = pytester.runpytest("--asyncio-mode=strict")
56 | result.assert_outcomes(passed=2)
57 |
58 |
59 | def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_class(
60 | pytester: pytest.Pytester,
61 | ):
62 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
63 | pytester.makepyfile(
64 | dedent(
65 | """\
66 | import asyncio
67 | import pytest
68 |
69 | @pytest.mark.asyncio(loop_scope="class")
70 | class TestClassScopedLoop:
71 | loop: asyncio.AbstractEventLoop
72 |
73 | async def test_remember_loop(self):
74 | TestClassScopedLoop.loop = asyncio.get_running_loop()
75 |
76 | async def test_this_runs_in_same_loop(self):
77 | assert asyncio.get_running_loop() is TestClassScopedLoop.loop
78 | """
79 | )
80 | )
81 | result = pytester.runpytest("--asyncio-mode=strict")
82 | result.assert_outcomes(passed=2)
83 |
84 |
85 | def test_asyncio_mark_is_inherited_to_subclasses(pytester: pytest.Pytester):
86 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
87 | pytester.makepyfile(
88 | dedent(
89 | """\
90 | import asyncio
91 | import pytest
92 |
93 | @pytest.mark.asyncio(loop_scope="class")
94 | class TestSuperClassWithMark:
95 | pass
96 |
97 | class TestWithoutMark(TestSuperClassWithMark):
98 | loop: asyncio.AbstractEventLoop
99 |
100 | async def test_remember_loop(self):
101 | TestWithoutMark.loop = asyncio.get_running_loop()
102 |
103 | async def test_this_runs_in_same_loop(self):
104 | assert asyncio.get_running_loop() is TestWithoutMark.loop
105 | """
106 | )
107 | )
108 | result = pytester.runpytest("--asyncio-mode=strict")
109 | result.assert_outcomes(passed=2)
110 |
111 |
112 | def test_asyncio_mark_respects_the_loop_policy(
113 | pytester: pytest.Pytester,
114 | ):
115 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
116 | pytester.makepyfile(
117 | dedent(
118 | """\
119 | import asyncio
120 | import pytest
121 |
122 | class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
123 | pass
124 |
125 | class TestUsesCustomEventLoop:
126 | @pytest.fixture(scope="class")
127 | def event_loop_policy(self):
128 | return CustomEventLoopPolicy()
129 |
130 | @pytest.mark.asyncio
131 | async def test_uses_custom_event_loop_policy(self):
132 | assert isinstance(
133 | asyncio.get_event_loop_policy(),
134 | CustomEventLoopPolicy,
135 | )
136 |
137 | @pytest.mark.asyncio
138 | async def test_does_not_use_custom_event_loop_policy():
139 | assert not isinstance(
140 | asyncio.get_event_loop_policy(),
141 | CustomEventLoopPolicy,
142 | )
143 | """
144 | )
145 | )
146 | result = pytester.runpytest("--asyncio-mode=strict")
147 | result.assert_outcomes(passed=2)
148 |
149 |
150 | def test_asyncio_mark_respects_parametrized_loop_policies(
151 | pytester: pytest.Pytester,
152 | ):
153 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
154 | pytester.makepyfile(
155 | dedent(
156 | """\
157 | import asyncio
158 |
159 | import pytest
160 |
161 | @pytest.fixture(
162 | scope="class",
163 | params=[
164 | asyncio.DefaultEventLoopPolicy(),
165 | asyncio.DefaultEventLoopPolicy(),
166 | ]
167 | )
168 | def event_loop_policy(request):
169 | return request.param
170 |
171 | @pytest.mark.asyncio(loop_scope="class")
172 | class TestWithDifferentLoopPolicies:
173 | async def test_parametrized_loop(self, request):
174 | pass
175 | """
176 | )
177 | )
178 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
179 | result.assert_outcomes(passed=2)
180 |
181 |
182 | def test_asyncio_mark_provides_class_scoped_loop_to_fixtures(
183 | pytester: pytest.Pytester,
184 | ):
185 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
186 | pytester.makepyfile(
187 | dedent(
188 | """\
189 | import asyncio
190 |
191 | import pytest
192 | import pytest_asyncio
193 |
194 | @pytest.mark.asyncio(loop_scope="class")
195 | class TestClassScopedLoop:
196 | loop: asyncio.AbstractEventLoop
197 |
198 | @pytest_asyncio.fixture
199 | async def my_fixture(self):
200 | TestClassScopedLoop.loop = asyncio.get_running_loop()
201 |
202 | @pytest.mark.asyncio
203 | async def test_runs_is_same_loop_as_fixture(self, my_fixture):
204 | assert asyncio.get_running_loop() is TestClassScopedLoop.loop
205 | """
206 | )
207 | )
208 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
209 | result.assert_outcomes(passed=1)
210 |
211 |
212 | def test_asyncio_mark_allows_combining_class_scoped_fixture_with_function_scoped_test(
213 | pytester: pytest.Pytester,
214 | ):
215 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
216 | pytester.makepyfile(
217 | dedent(
218 | """\
219 | import asyncio
220 |
221 | import pytest
222 | import pytest_asyncio
223 |
224 | loop: asyncio.AbstractEventLoop
225 |
226 | class TestMixedScopes:
227 | @pytest_asyncio.fixture(loop_scope="class", scope="class")
228 | async def async_fixture(self):
229 | global loop
230 | loop = asyncio.get_running_loop()
231 |
232 | @pytest.mark.asyncio(loop_scope="function")
233 | async def test_runs_in_different_loop_as_fixture(self, async_fixture):
234 | global loop
235 | assert asyncio.get_running_loop() is not loop
236 |
237 | """
238 | ),
239 | )
240 | result = pytester.runpytest("--asyncio-mode=strict")
241 | result.assert_outcomes(passed=1)
242 |
243 |
244 | def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
245 | pytester: pytest.Pytester,
246 | ):
247 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
248 | pytester.makepyfile(
249 | dedent(
250 | """\
251 | import pytest
252 | import asyncio
253 |
254 | class TestClass:
255 | @pytest.fixture(scope="class")
256 | def sets_event_loop_to_none(self):
257 | # asyncio.run() creates a new event loop without closing the
258 | # existing one. For any test, but the first one, this leads to
259 | # a ResourceWarning when the discarded loop is destroyed by the
260 | # garbage collector. We close the current loop to avoid this.
261 | try:
262 | asyncio.get_event_loop().close()
263 | except RuntimeError:
264 | pass
265 | return asyncio.run(asyncio.sleep(0))
266 | # asyncio.run() sets the current event loop to None when finished
267 |
268 | @pytest.mark.asyncio(loop_scope="class")
269 | # parametrization may impact fixture ordering
270 | @pytest.mark.parametrize("n", (0, 1))
271 | async def test_does_not_fail(self, sets_event_loop_to_none, n):
272 | pass
273 | """
274 | )
275 | )
276 | result = pytester.runpytest("--asyncio-mode=strict")
277 | result.assert_outcomes(passed=2)
278 |
279 |
280 | def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
281 | pytester: pytest.Pytester,
282 | ):
283 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
284 | pytester.makepyfile(
285 | dedent(
286 | """\
287 | import pytest
288 |
289 | @pytest.mark.asyncio(loop_scope="class")
290 | class TestClass:
291 | async def test_anything(self):
292 | pass
293 | """
294 | )
295 | )
296 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
297 | result.assert_outcomes(warnings=0, passed=1)
298 |
--------------------------------------------------------------------------------
/tests/markers/test_function_scope.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_asyncio_mark_provides_function_scoped_loop_strict_mode(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | """\
13 | import asyncio
14 | import pytest
15 |
16 | pytestmark = pytest.mark.asyncio
17 |
18 | loop: asyncio.AbstractEventLoop
19 |
20 | async def test_remember_loop():
21 | global loop
22 | loop = asyncio.get_running_loop()
23 |
24 | async def test_does_not_run_in_same_loop():
25 | global loop
26 | assert asyncio.get_running_loop() is not loop
27 | """
28 | )
29 | )
30 | result = pytester.runpytest("--asyncio-mode=strict")
31 | result.assert_outcomes(passed=2)
32 |
33 |
34 | def test_loop_scope_function_provides_function_scoped_event_loop(pytester: Pytester):
35 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
36 | pytester.makepyfile(
37 | dedent(
38 | """\
39 | import asyncio
40 | import pytest
41 |
42 | pytestmark = pytest.mark.asyncio(loop_scope="function")
43 |
44 | loop: asyncio.AbstractEventLoop
45 |
46 | async def test_remember_loop():
47 | global loop
48 | loop = asyncio.get_running_loop()
49 |
50 | async def test_does_not_run_in_same_loop():
51 | global loop
52 | assert asyncio.get_running_loop() is not loop
53 | """
54 | )
55 | )
56 | result = pytester.runpytest("--asyncio-mode=strict")
57 | result.assert_outcomes(passed=2)
58 |
59 |
60 | def test_raises_when_scope_and_loop_scope_arguments_are_present(pytester: Pytester):
61 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
62 | pytester.makepyfile(
63 | dedent(
64 | """\
65 | import pytest
66 |
67 | @pytest.mark.asyncio(scope="function", loop_scope="function")
68 | async def test_raises():
69 | ...
70 | """
71 | )
72 | )
73 | result = pytester.runpytest("--asyncio-mode=strict")
74 | result.assert_outcomes(errors=1)
75 |
76 |
77 | def test_warns_when_scope_argument_is_present(pytester: Pytester):
78 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
79 | pytester.makepyfile(
80 | dedent(
81 | """\
82 | import pytest
83 |
84 | @pytest.mark.asyncio(scope="function")
85 | async def test_warns():
86 | ...
87 | """
88 | )
89 | )
90 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
91 | result.assert_outcomes(passed=1, warnings=2)
92 | result.stdout.fnmatch_lines("*DeprecationWarning*")
93 |
94 |
95 | def test_asyncio_mark_respects_the_loop_policy(
96 | pytester: Pytester,
97 | ):
98 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
99 | pytester.makepyfile(
100 | dedent(
101 | """\
102 | import asyncio
103 | import pytest
104 |
105 | pytestmark = pytest.mark.asyncio
106 |
107 | class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
108 | pass
109 |
110 | @pytest.fixture(scope="function")
111 | def event_loop_policy():
112 | return CustomEventLoopPolicy()
113 |
114 | async def test_uses_custom_event_loop_policy():
115 | assert isinstance(
116 | asyncio.get_event_loop_policy(),
117 | CustomEventLoopPolicy,
118 | )
119 | """
120 | ),
121 | )
122 | result = pytester.runpytest("--asyncio-mode=strict")
123 | result.assert_outcomes(passed=1)
124 |
125 |
126 | def test_asyncio_mark_respects_parametrized_loop_policies(
127 | pytester: Pytester,
128 | ):
129 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
130 | pytester.makepyfile(
131 | dedent(
132 | """\
133 | import asyncio
134 |
135 | import pytest
136 |
137 | pytestmark = pytest.mark.asyncio
138 |
139 | class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
140 | pass
141 |
142 | @pytest.fixture(
143 | scope="module",
144 | params=[
145 | CustomEventLoopPolicy(),
146 | CustomEventLoopPolicy(),
147 | ],
148 | )
149 | def event_loop_policy(request):
150 | return request.param
151 |
152 | async def test_parametrized_loop():
153 | assert isinstance(
154 | asyncio.get_event_loop_policy(),
155 | CustomEventLoopPolicy,
156 | )
157 | """
158 | )
159 | )
160 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
161 | result.assert_outcomes(passed=2)
162 |
163 |
164 | def test_asyncio_mark_provides_function_scoped_loop_to_fixtures(
165 | pytester: Pytester,
166 | ):
167 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
168 | pytester.makepyfile(
169 | dedent(
170 | """\
171 | import asyncio
172 |
173 | import pytest
174 | import pytest_asyncio
175 |
176 | pytestmark = pytest.mark.asyncio
177 |
178 | loop: asyncio.AbstractEventLoop
179 |
180 | @pytest_asyncio.fixture
181 | async def my_fixture():
182 | global loop
183 | loop = asyncio.get_running_loop()
184 |
185 | async def test_runs_is_same_loop_as_fixture(my_fixture):
186 | global loop
187 | assert asyncio.get_running_loop() is loop
188 | """
189 | )
190 | )
191 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
192 | result.assert_outcomes(passed=1)
193 |
194 |
195 | def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
196 | pytester: Pytester,
197 | ):
198 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
199 | pytester.makepyfile(
200 | dedent(
201 | """\
202 | import pytest
203 | import asyncio
204 |
205 | @pytest.fixture
206 | def sets_event_loop_to_none():
207 | # asyncio.run() creates a new event loop without closing the existing
208 | # one. For any test, but the first one, this leads to a ResourceWarning
209 | # when the discarded loop is destroyed by the garbage collector.
210 | # We close the current loop to avoid this
211 | try:
212 | asyncio.get_event_loop().close()
213 | except RuntimeError:
214 | pass
215 | return asyncio.run(asyncio.sleep(0))
216 | # asyncio.run() sets the current event loop to None when finished
217 |
218 | @pytest.mark.asyncio
219 | # parametrization may impact fixture ordering
220 | @pytest.mark.parametrize("n", (0, 1))
221 | async def test_does_not_fail(sets_event_loop_to_none, n):
222 | pass
223 | """
224 | )
225 | )
226 | result = pytester.runpytest("--asyncio-mode=strict")
227 | result.assert_outcomes(passed=2)
228 |
229 |
230 | def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
231 | pytester: Pytester,
232 | ):
233 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
234 | pytester.makepyfile(
235 | dedent(
236 | """\
237 | import pytest
238 |
239 | @pytest.mark.asyncio
240 | async def test_anything():
241 | pass
242 | """
243 | )
244 | )
245 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
246 | result.assert_outcomes(warnings=0, passed=1)
247 |
248 |
249 | def test_asyncio_mark_does_not_duplicate_other_marks_in_auto_mode(
250 | pytester: Pytester,
251 | ):
252 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
253 | pytester.makeconftest(
254 | dedent(
255 | """\
256 | def pytest_configure(config):
257 | config.addinivalue_line(
258 | "markers", "dummy_marker: mark used for testing purposes"
259 | )
260 | """
261 | )
262 | )
263 | pytester.makepyfile(
264 | dedent(
265 | """\
266 | import pytest
267 |
268 | @pytest.mark.dummy_marker
269 | async def test_markers_not_duplicated(request):
270 | markers = []
271 | for node, marker in request.node.iter_markers_with_node():
272 | markers.append(marker)
273 | assert len(markers) == 2
274 | """
275 | )
276 | )
277 | result = pytester.runpytest_subprocess("--asyncio-mode=auto")
278 | result.assert_outcomes(warnings=0, passed=1)
279 |
--------------------------------------------------------------------------------
/tests/markers/test_invalid_arguments.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | import pytest
6 |
7 |
8 | def test_no_error_when_scope_passed_as_sole_keyword_argument(
9 | pytester: pytest.Pytester,
10 | ):
11 | pytester.makepyfile(
12 | dedent(
13 | """\
14 | import pytest
15 |
16 | @pytest.mark.asyncio(loop_scope="session")
17 | async def test_anything():
18 | pass
19 | """
20 | )
21 | )
22 | result = pytester.runpytest_subprocess()
23 | result.assert_outcomes(passed=1)
24 | result.stdout.no_fnmatch_line("*ValueError*")
25 |
26 |
27 | def test_error_when_scope_passed_as_positional_argument(
28 | pytester: pytest.Pytester,
29 | ):
30 | pytester.makepyfile(
31 | dedent(
32 | """\
33 | import pytest
34 |
35 | @pytest.mark.asyncio("session")
36 | async def test_anything():
37 | pass
38 | """
39 | )
40 | )
41 | result = pytester.runpytest_subprocess()
42 | result.assert_outcomes(errors=1)
43 | result.stdout.fnmatch_lines(
44 | ["*ValueError: mark.asyncio accepts only a keyword argument*"]
45 | )
46 |
47 |
48 | def test_error_when_wrong_keyword_argument_is_passed(
49 | pytester: pytest.Pytester,
50 | ):
51 | pytester.makepyfile(
52 | dedent(
53 | """\
54 | import pytest
55 |
56 | @pytest.mark.asyncio(cope="session")
57 | async def test_anything():
58 | pass
59 | """
60 | )
61 | )
62 | result = pytester.runpytest_subprocess()
63 | result.assert_outcomes(errors=1)
64 | result.stdout.fnmatch_lines(
65 | ["*ValueError: mark.asyncio accepts only a keyword argument 'loop_scope'*"]
66 | )
67 |
68 |
69 | def test_error_when_additional_keyword_arguments_are_passed(
70 | pytester: pytest.Pytester,
71 | ):
72 | pytester.makepyfile(
73 | dedent(
74 | """\
75 | import pytest
76 |
77 | @pytest.mark.asyncio(loop_scope="session", more="stuff")
78 | async def test_anything():
79 | pass
80 | """
81 | )
82 | )
83 | result = pytester.runpytest_subprocess()
84 | result.assert_outcomes(errors=1)
85 | result.stdout.fnmatch_lines(
86 | ["*ValueError: mark.asyncio accepts only a keyword argument*"]
87 | )
88 |
--------------------------------------------------------------------------------
/tests/markers/test_mixed_scope.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_function_scoped_loop_restores_previous_loop_scope(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | """\
13 | import asyncio
14 | import pytest
15 |
16 |
17 | module_loop: asyncio.AbstractEventLoop
18 |
19 | @pytest.mark.asyncio(loop_scope="module")
20 | async def test_remember_loop():
21 | global module_loop
22 | module_loop = asyncio.get_running_loop()
23 |
24 | @pytest.mark.asyncio(loop_scope="function")
25 | async def test_with_function_scoped_loop():
26 | pass
27 |
28 | @pytest.mark.asyncio(loop_scope="module")
29 | async def test_runs_in_same_loop():
30 | global module_loop
31 | assert asyncio.get_running_loop() is module_loop
32 | """
33 | )
34 | )
35 | result = pytester.runpytest("--asyncio-mode=strict")
36 | result.assert_outcomes(passed=3)
37 |
--------------------------------------------------------------------------------
/tests/markers/test_module_scope.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_asyncio_mark_provides_module_scoped_loop_strict_mode(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | """\
13 | import asyncio
14 | import pytest
15 |
16 | pytestmark = pytest.mark.asyncio(loop_scope="module")
17 |
18 | loop: asyncio.AbstractEventLoop
19 |
20 | async def test_remember_loop():
21 | global loop
22 | loop = asyncio.get_running_loop()
23 |
24 | async def test_this_runs_in_same_loop():
25 | global loop
26 | assert asyncio.get_running_loop() is loop
27 |
28 | class TestClassA:
29 | async def test_this_runs_in_same_loop(self):
30 | global loop
31 | assert asyncio.get_running_loop() is loop
32 | """
33 | )
34 | )
35 | result = pytester.runpytest("--asyncio-mode=strict")
36 | result.assert_outcomes(passed=3)
37 |
38 |
39 | def test_asyncio_mark_respects_the_loop_policy(
40 | pytester: Pytester,
41 | ):
42 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
43 | pytester.makepyfile(
44 | __init__="",
45 | custom_policy=dedent(
46 | """\
47 | import asyncio
48 |
49 | class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
50 | pass
51 | """
52 | ),
53 | test_uses_custom_policy=dedent(
54 | """\
55 | import asyncio
56 | import pytest
57 |
58 | from .custom_policy import CustomEventLoopPolicy
59 |
60 | pytestmark = pytest.mark.asyncio(loop_scope="module")
61 |
62 | @pytest.fixture(scope="module")
63 | def event_loop_policy():
64 | return CustomEventLoopPolicy()
65 |
66 | async def test_uses_custom_event_loop_policy():
67 | assert isinstance(
68 | asyncio.get_event_loop_policy(),
69 | CustomEventLoopPolicy,
70 | )
71 | """
72 | ),
73 | test_does_not_use_custom_policy=dedent(
74 | """\
75 | import asyncio
76 | import pytest
77 |
78 | from .custom_policy import CustomEventLoopPolicy
79 |
80 | pytestmark = pytest.mark.asyncio(loop_scope="module")
81 |
82 | async def test_does_not_use_custom_event_loop_policy():
83 | assert not isinstance(
84 | asyncio.get_event_loop_policy(),
85 | CustomEventLoopPolicy,
86 | )
87 | """
88 | ),
89 | )
90 | result = pytester.runpytest("--asyncio-mode=strict")
91 | result.assert_outcomes(passed=2)
92 |
93 |
94 | def test_asyncio_mark_respects_parametrized_loop_policies(
95 | pytester: Pytester,
96 | ):
97 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
98 | pytester.makepyfile(
99 | dedent(
100 | """\
101 | import asyncio
102 |
103 | import pytest
104 |
105 | pytestmark = pytest.mark.asyncio(loop_scope="module")
106 |
107 | @pytest.fixture(
108 | scope="module",
109 | params=[
110 | asyncio.DefaultEventLoopPolicy(),
111 | asyncio.DefaultEventLoopPolicy(),
112 | ],
113 | )
114 | def event_loop_policy(request):
115 | return request.param
116 |
117 | async def test_parametrized_loop():
118 | pass
119 | """
120 | )
121 | )
122 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
123 | result.assert_outcomes(passed=2)
124 |
125 |
126 | def test_asyncio_mark_provides_module_scoped_loop_to_fixtures(
127 | pytester: Pytester,
128 | ):
129 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
130 | pytester.makepyfile(
131 | dedent(
132 | """\
133 | import asyncio
134 |
135 | import pytest
136 | import pytest_asyncio
137 |
138 | pytestmark = pytest.mark.asyncio(loop_scope="module")
139 |
140 | loop: asyncio.AbstractEventLoop
141 |
142 | @pytest_asyncio.fixture(loop_scope="module", scope="module")
143 | async def my_fixture():
144 | global loop
145 | loop = asyncio.get_running_loop()
146 |
147 | async def test_runs_is_same_loop_as_fixture(my_fixture):
148 | global loop
149 | assert asyncio.get_running_loop() is loop
150 | """
151 | )
152 | )
153 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
154 | result.assert_outcomes(passed=1)
155 |
156 |
157 | def test_asyncio_mark_allows_combining_module_scoped_fixture_with_class_scoped_test(
158 | pytester: Pytester,
159 | ):
160 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
161 | pytester.makepyfile(
162 | dedent(
163 | """\
164 | import asyncio
165 |
166 | import pytest
167 | import pytest_asyncio
168 |
169 | loop: asyncio.AbstractEventLoop
170 |
171 | @pytest_asyncio.fixture(loop_scope="module", scope="module")
172 | async def async_fixture():
173 | global loop
174 | loop = asyncio.get_running_loop()
175 |
176 | @pytest.mark.asyncio(loop_scope="class")
177 | class TestMixedScopes:
178 | async def test_runs_in_different_loop_as_fixture(self, async_fixture):
179 | global loop
180 | assert asyncio.get_running_loop() is not loop
181 |
182 | """
183 | ),
184 | )
185 | result = pytester.runpytest("--asyncio-mode=strict")
186 | result.assert_outcomes(passed=1)
187 |
188 |
189 | def test_asyncio_mark_allows_combining_module_scoped_fixture_with_function_scoped_test(
190 | pytester: Pytester,
191 | ):
192 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
193 | pytester.makepyfile(
194 | __init__="",
195 | test_mixed_scopes=dedent(
196 | """\
197 | import asyncio
198 |
199 | import pytest
200 | import pytest_asyncio
201 |
202 | loop: asyncio.AbstractEventLoop
203 |
204 | @pytest_asyncio.fixture(loop_scope="module", scope="module")
205 | async def async_fixture():
206 | global loop
207 | loop = asyncio.get_running_loop()
208 |
209 | @pytest.mark.asyncio(loop_scope="function")
210 | async def test_runs_in_different_loop_as_fixture(async_fixture):
211 | global loop
212 | assert asyncio.get_running_loop() is not loop
213 | """
214 | ),
215 | )
216 | result = pytester.runpytest("--asyncio-mode=strict")
217 | result.assert_outcomes(passed=1)
218 |
219 |
220 | def test_allows_combining_module_scoped_asyncgen_fixture_with_function_scoped_test(
221 | pytester: Pytester,
222 | ):
223 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
224 | pytester.makepyfile(
225 | dedent(
226 | """\
227 | import asyncio
228 |
229 | import pytest
230 | import pytest_asyncio
231 |
232 | loop: asyncio.AbstractEventLoop
233 |
234 | @pytest_asyncio.fixture(loop_scope="module", scope="module")
235 | async def async_fixture():
236 | global loop
237 | loop = asyncio.get_running_loop()
238 | yield
239 |
240 | @pytest.mark.asyncio(loop_scope="function")
241 | async def test_runs_in_different_loop_as_fixture(async_fixture):
242 | global loop
243 | assert asyncio.get_running_loop() is not loop
244 | """
245 | ),
246 | )
247 | result = pytester.runpytest("--asyncio-mode=strict")
248 | result.assert_outcomes(passed=1)
249 |
250 |
251 | def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
252 | pytester: Pytester,
253 | ):
254 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
255 | pytester.makepyfile(
256 | dedent(
257 | """\
258 | import pytest
259 | import asyncio
260 |
261 | @pytest.fixture(scope="module")
262 | def sets_event_loop_to_none():
263 | # asyncio.run() creates a new event loop without closing the existing
264 | # one. For any test, but the first one, this leads to a ResourceWarning
265 | # when the discarded loop is destroyed by the garbage collector.
266 | # We close the current loop to avoid this
267 | try:
268 | asyncio.get_event_loop().close()
269 | except RuntimeError:
270 | pass
271 | return asyncio.run(asyncio.sleep(0))
272 | # asyncio.run() sets the current event loop to None when finished
273 |
274 | @pytest.mark.asyncio(loop_scope="module")
275 | # parametrization may impact fixture ordering
276 | @pytest.mark.parametrize("n", (0, 1))
277 | async def test_does_not_fail(sets_event_loop_to_none, n):
278 | pass
279 | """
280 | )
281 | )
282 | result = pytester.runpytest("--asyncio-mode=strict")
283 | result.assert_outcomes(passed=2)
284 |
285 |
286 | def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
287 | pytester: Pytester,
288 | ):
289 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
290 | pytester.makepyfile(
291 | dedent(
292 | """\
293 | import pytest
294 |
295 | @pytest.mark.asyncio(loop_scope="module")
296 | async def test_anything():
297 | pass
298 | """
299 | )
300 | )
301 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
302 | result.assert_outcomes(warnings=0, passed=1)
303 |
--------------------------------------------------------------------------------
/tests/markers/test_package_scope.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_asyncio_mark_provides_package_scoped_loop_strict_mode(pytester: Pytester):
9 | package_name = pytester.path.name
10 | subpackage_name = "subpkg"
11 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
12 | pytester.makepyfile(
13 | __init__="",
14 | shared_module=dedent(
15 | """\
16 | import asyncio
17 |
18 | loop: asyncio.AbstractEventLoop = None
19 | """
20 | ),
21 | test_module_one=dedent(
22 | f"""\
23 | import asyncio
24 | import pytest
25 |
26 | from {package_name} import shared_module
27 |
28 | @pytest.mark.asyncio(loop_scope="package")
29 | async def test_remember_loop():
30 | shared_module.loop = asyncio.get_running_loop()
31 | """
32 | ),
33 | test_module_two=dedent(
34 | f"""\
35 | import asyncio
36 | import pytest
37 |
38 | from {package_name} import shared_module
39 |
40 | pytestmark = pytest.mark.asyncio(loop_scope="package")
41 |
42 | async def test_this_runs_in_same_loop():
43 | assert asyncio.get_running_loop() is shared_module.loop
44 |
45 | class TestClassA:
46 | async def test_this_runs_in_same_loop(self):
47 | assert asyncio.get_running_loop() is shared_module.loop
48 | """
49 | ),
50 | )
51 | subpkg = pytester.mkpydir(subpackage_name)
52 | subpkg.joinpath("__init__.py").touch()
53 | subpkg.joinpath("test_subpkg.py").write_text(
54 | dedent(
55 | f"""\
56 | import asyncio
57 | import pytest
58 |
59 | from {package_name} import shared_module
60 |
61 | pytestmark = pytest.mark.asyncio(loop_scope="package")
62 |
63 | async def test_subpackage_runs_in_different_loop():
64 | assert asyncio.get_running_loop() is not shared_module.loop
65 | """
66 | )
67 | )
68 | result = pytester.runpytest("--asyncio-mode=strict")
69 | result.assert_outcomes(passed=4)
70 |
71 |
72 | def test_asyncio_mark_respects_the_loop_policy(
73 | pytester: Pytester,
74 | ):
75 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
76 | pytester.makepyfile(
77 | __init__="",
78 | conftest=dedent(
79 | """\
80 | import pytest
81 |
82 | from .custom_policy import CustomEventLoopPolicy
83 |
84 | @pytest.fixture(scope="package")
85 | def event_loop_policy():
86 | return CustomEventLoopPolicy()
87 | """
88 | ),
89 | custom_policy=dedent(
90 | """\
91 | import asyncio
92 |
93 | class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
94 | pass
95 | """
96 | ),
97 | test_uses_custom_policy=dedent(
98 | """\
99 | import asyncio
100 | import pytest
101 |
102 | from .custom_policy import CustomEventLoopPolicy
103 |
104 | pytestmark = pytest.mark.asyncio(loop_scope="package")
105 |
106 | async def test_uses_custom_event_loop_policy():
107 | assert isinstance(
108 | asyncio.get_event_loop_policy(),
109 | CustomEventLoopPolicy,
110 | )
111 | """
112 | ),
113 | test_also_uses_custom_policy=dedent(
114 | """\
115 | import asyncio
116 | import pytest
117 |
118 | from .custom_policy import CustomEventLoopPolicy
119 |
120 | pytestmark = pytest.mark.asyncio(loop_scope="package")
121 |
122 | async def test_also_uses_custom_event_loop_policy():
123 | assert isinstance(
124 | asyncio.get_event_loop_policy(),
125 | CustomEventLoopPolicy,
126 | )
127 | """
128 | ),
129 | )
130 | result = pytester.runpytest("--asyncio-mode=strict")
131 | result.assert_outcomes(passed=2)
132 |
133 |
134 | def test_asyncio_mark_respects_parametrized_loop_policies(
135 | pytester: Pytester,
136 | ):
137 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
138 | pytester.makepyfile(
139 | __init__="",
140 | test_parametrization=dedent(
141 | """\
142 | import asyncio
143 |
144 | import pytest
145 |
146 | pytestmark = pytest.mark.asyncio(loop_scope="package")
147 |
148 | @pytest.fixture(
149 | scope="package",
150 | params=[
151 | asyncio.DefaultEventLoopPolicy(),
152 | asyncio.DefaultEventLoopPolicy(),
153 | ],
154 | )
155 | def event_loop_policy(request):
156 | return request.param
157 |
158 | async def test_parametrized_loop():
159 | pass
160 | """
161 | ),
162 | )
163 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
164 | result.assert_outcomes(passed=2)
165 |
166 |
167 | def test_asyncio_mark_provides_package_scoped_loop_to_fixtures(
168 | pytester: Pytester,
169 | ):
170 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
171 | package_name = pytester.path.name
172 | pytester.makepyfile(
173 | __init__="",
174 | conftest=dedent(
175 | f"""\
176 | import asyncio
177 |
178 | import pytest_asyncio
179 |
180 | from {package_name} import shared_module
181 |
182 | @pytest_asyncio.fixture(loop_scope="package", scope="package")
183 | async def my_fixture():
184 | shared_module.loop = asyncio.get_running_loop()
185 | """
186 | ),
187 | shared_module=dedent(
188 | """\
189 | import asyncio
190 |
191 | loop: asyncio.AbstractEventLoop = None
192 | """
193 | ),
194 | test_fixture_runs_in_scoped_loop=dedent(
195 | f"""\
196 | import asyncio
197 |
198 | import pytest
199 | import pytest_asyncio
200 |
201 | from {package_name} import shared_module
202 |
203 | pytestmark = pytest.mark.asyncio(loop_scope="package")
204 |
205 | async def test_runs_in_same_loop_as_fixture(my_fixture):
206 | assert asyncio.get_running_loop() is shared_module.loop
207 | """
208 | ),
209 | )
210 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
211 | result.assert_outcomes(passed=1)
212 |
213 |
214 | def test_asyncio_mark_allows_combining_package_scoped_fixture_with_module_scoped_test(
215 | pytester: Pytester,
216 | ):
217 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
218 | pytester.makepyfile(
219 | __init__="",
220 | test_mixed_scopes=dedent(
221 | """\
222 | import asyncio
223 |
224 | import pytest
225 | import pytest_asyncio
226 |
227 | loop: asyncio.AbstractEventLoop
228 |
229 | @pytest_asyncio.fixture(loop_scope="package", scope="package")
230 | async def async_fixture():
231 | global loop
232 | loop = asyncio.get_running_loop()
233 |
234 | @pytest.mark.asyncio(loop_scope="module")
235 | async def test_runs_in_different_loop_as_fixture(async_fixture):
236 | global loop
237 | assert asyncio.get_running_loop() is not loop
238 | """
239 | ),
240 | )
241 | result = pytester.runpytest("--asyncio-mode=strict")
242 | result.assert_outcomes(passed=1)
243 |
244 |
245 | def test_asyncio_mark_allows_combining_package_scoped_fixture_with_class_scoped_test(
246 | pytester: Pytester,
247 | ):
248 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
249 | pytester.makepyfile(
250 | __init__="",
251 | test_mixed_scopes=dedent(
252 | """\
253 | import asyncio
254 |
255 | import pytest
256 | import pytest_asyncio
257 |
258 | loop: asyncio.AbstractEventLoop
259 |
260 | @pytest_asyncio.fixture(loop_scope="package", scope="package")
261 | async def async_fixture():
262 | global loop
263 | loop = asyncio.get_running_loop()
264 |
265 | @pytest.mark.asyncio(loop_scope="class")
266 | class TestMixedScopes:
267 | async def test_runs_in_different_loop_as_fixture(self, async_fixture):
268 | global loop
269 | assert asyncio.get_running_loop() is not loop
270 | """
271 | ),
272 | )
273 | result = pytester.runpytest("--asyncio-mode=strict")
274 | result.assert_outcomes(passed=1)
275 |
276 |
277 | def test_asyncio_mark_allows_combining_package_scoped_fixture_with_function_scoped_test(
278 | pytester: Pytester,
279 | ):
280 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
281 | pytester.makepyfile(
282 | __init__="",
283 | test_mixed_scopes=dedent(
284 | """\
285 | import asyncio
286 |
287 | import pytest
288 | import pytest_asyncio
289 |
290 | loop: asyncio.AbstractEventLoop
291 |
292 | @pytest_asyncio.fixture(loop_scope="package", scope="package")
293 | async def async_fixture():
294 | global loop
295 | loop = asyncio.get_running_loop()
296 |
297 | @pytest.mark.asyncio
298 | async def test_runs_in_different_loop_as_fixture(async_fixture):
299 | global loop
300 | assert asyncio.get_running_loop() is not loop
301 | """
302 | ),
303 | )
304 | result = pytester.runpytest("--asyncio-mode=strict")
305 | result.assert_outcomes(passed=1)
306 |
307 |
308 | def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
309 | pytester: Pytester,
310 | ):
311 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
312 | pytester.makepyfile(
313 | __init__="",
314 | test_loop_is_none=dedent(
315 | """\
316 | import pytest
317 | import asyncio
318 |
319 | @pytest.fixture(scope="package")
320 | def sets_event_loop_to_none():
321 | # asyncio.run() creates a new event loop without closing the existing
322 | # one. For any test, but the first one, this leads to a ResourceWarning
323 | # when the discarded loop is destroyed by the garbage collector.
324 | # We close the current loop to avoid this
325 | try:
326 | asyncio.get_event_loop().close()
327 | except RuntimeError:
328 | pass
329 | return asyncio.run(asyncio.sleep(0))
330 | # asyncio.run() sets the current event loop to None when finished
331 |
332 | @pytest.mark.asyncio(loop_scope="package")
333 | # parametrization may impact fixture ordering
334 | @pytest.mark.parametrize("n", (0, 1))
335 | async def test_does_not_fail(sets_event_loop_to_none, n):
336 | pass
337 | """
338 | ),
339 | )
340 | result = pytester.runpytest("--asyncio-mode=strict")
341 | result.assert_outcomes(passed=2)
342 |
--------------------------------------------------------------------------------
/tests/markers/test_session_scope.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_asyncio_mark_provides_session_scoped_loop_strict_mode(pytester: Pytester):
9 | package_name = pytester.path.name
10 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
11 | pytester.makepyfile(
12 | __init__="",
13 | shared_module=dedent(
14 | """\
15 | import asyncio
16 |
17 | loop: asyncio.AbstractEventLoop = None
18 | """
19 | ),
20 | test_module_one=dedent(
21 | f"""\
22 | import asyncio
23 | import pytest
24 |
25 | from {package_name} import shared_module
26 |
27 | @pytest.mark.asyncio(loop_scope="session")
28 | async def test_remember_loop():
29 | shared_module.loop = asyncio.get_running_loop()
30 | """
31 | ),
32 | test_module_two=dedent(
33 | f"""\
34 | import asyncio
35 | import pytest
36 |
37 | from {package_name} import shared_module
38 |
39 | pytestmark = pytest.mark.asyncio(loop_scope="session")
40 |
41 | async def test_this_runs_in_same_loop():
42 | assert asyncio.get_running_loop() is shared_module.loop
43 |
44 | class TestClassA:
45 | async def test_this_runs_in_same_loop(self):
46 | assert asyncio.get_running_loop() is shared_module.loop
47 | """
48 | ),
49 | )
50 |
51 | # subpackage_name must alphabetically come after test_module_one.py
52 | subpackage_name = "z_subpkg"
53 | subpkg = pytester.mkpydir(subpackage_name)
54 | subpkg.joinpath("test_subpkg.py").write_text(
55 | dedent(
56 | f"""\
57 | import asyncio
58 | import pytest
59 |
60 | from {package_name} import shared_module
61 |
62 | pytestmark = pytest.mark.asyncio(loop_scope="session")
63 |
64 | async def test_subpackage_runs_in_same_loop():
65 | assert asyncio.get_running_loop() is shared_module.loop
66 | """
67 | )
68 | )
69 | result = pytester.runpytest("--asyncio-mode=strict")
70 | result.assert_outcomes(passed=4)
71 |
72 |
73 | def test_asyncio_mark_respects_the_loop_policy(
74 | pytester: Pytester,
75 | ):
76 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
77 | pytester.makepyfile(
78 | __init__="",
79 | conftest=dedent(
80 | """\
81 | import pytest
82 |
83 | from .custom_policy import CustomEventLoopPolicy
84 |
85 | @pytest.fixture(scope="session")
86 | def event_loop_policy():
87 | return CustomEventLoopPolicy()
88 | """
89 | ),
90 | custom_policy=dedent(
91 | """\
92 | import asyncio
93 |
94 | class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
95 | pass
96 | """
97 | ),
98 | test_uses_custom_policy=dedent(
99 | """\
100 | import asyncio
101 | import pytest
102 |
103 | from .custom_policy import CustomEventLoopPolicy
104 |
105 | pytestmark = pytest.mark.asyncio(loop_scope="session")
106 |
107 | async def test_uses_custom_event_loop_policy():
108 | assert isinstance(
109 | asyncio.get_event_loop_policy(),
110 | CustomEventLoopPolicy,
111 | )
112 | """
113 | ),
114 | test_also_uses_custom_policy=dedent(
115 | """\
116 | import asyncio
117 | import pytest
118 |
119 | from .custom_policy import CustomEventLoopPolicy
120 |
121 | pytestmark = pytest.mark.asyncio(loop_scope="session")
122 |
123 | async def test_also_uses_custom_event_loop_policy():
124 | assert isinstance(
125 | asyncio.get_event_loop_policy(),
126 | CustomEventLoopPolicy,
127 | )
128 | """
129 | ),
130 | )
131 | result = pytester.runpytest("--asyncio-mode=strict")
132 | result.assert_outcomes(passed=2)
133 |
134 |
135 | def test_asyncio_mark_respects_parametrized_loop_policies(
136 | pytester: Pytester,
137 | ):
138 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
139 | pytester.makepyfile(
140 | __init__="",
141 | test_parametrization=dedent(
142 | """\
143 | import asyncio
144 |
145 | import pytest
146 |
147 | pytestmark = pytest.mark.asyncio(loop_scope="session")
148 |
149 | @pytest.fixture(
150 | scope="session",
151 | params=[
152 | asyncio.DefaultEventLoopPolicy(),
153 | asyncio.DefaultEventLoopPolicy(),
154 | ],
155 | )
156 | def event_loop_policy(request):
157 | return request.param
158 |
159 | async def test_parametrized_loop():
160 | pass
161 | """
162 | ),
163 | )
164 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
165 | result.assert_outcomes(passed=2)
166 |
167 |
168 | def test_asyncio_mark_provides_session_scoped_loop_to_fixtures(
169 | pytester: Pytester,
170 | ):
171 | package_name = pytester.path.name
172 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
173 | pytester.makepyfile(
174 | __init__="",
175 | conftest=dedent(
176 | f"""\
177 | import asyncio
178 |
179 | import pytest_asyncio
180 |
181 | from {package_name} import shared_module
182 |
183 | @pytest_asyncio.fixture(loop_scope="session", scope="session")
184 | async def my_fixture():
185 | shared_module.loop = asyncio.get_running_loop()
186 | """
187 | ),
188 | shared_module=dedent(
189 | """\
190 | import asyncio
191 |
192 | loop: asyncio.AbstractEventLoop = None
193 | """
194 | ),
195 | )
196 | subpackage_name = "subpkg"
197 | subpkg = pytester.mkpydir(subpackage_name)
198 | subpkg.joinpath("test_subpkg.py").write_text(
199 | dedent(
200 | f"""\
201 | import asyncio
202 |
203 | import pytest
204 | import pytest_asyncio
205 |
206 | from {package_name} import shared_module
207 |
208 | pytestmark = pytest.mark.asyncio(loop_scope="session")
209 |
210 | async def test_runs_in_same_loop_as_fixture(my_fixture):
211 | assert asyncio.get_running_loop() is shared_module.loop
212 | """
213 | )
214 | )
215 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
216 | result.assert_outcomes(passed=1)
217 |
218 |
219 | def test_asyncio_mark_allows_combining_session_scoped_fixture_with_package_scoped_test(
220 | pytester: Pytester,
221 | ):
222 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
223 | pytester.makepyfile(
224 | __init__="",
225 | test_mixed_scopes=dedent(
226 | """\
227 | import asyncio
228 |
229 | import pytest
230 | import pytest_asyncio
231 |
232 | loop: asyncio.AbstractEventLoop
233 |
234 | @pytest_asyncio.fixture(loop_scope="session", scope="session")
235 | async def async_fixture():
236 | global loop
237 | loop = asyncio.get_running_loop()
238 |
239 | @pytest.mark.asyncio(loop_scope="package")
240 | async def test_runs_in_different_loop_as_fixture(async_fixture):
241 | global loop
242 | assert asyncio.get_running_loop() is not loop
243 | """
244 | ),
245 | )
246 | result = pytester.runpytest("--asyncio-mode=strict")
247 | result.assert_outcomes(passed=1)
248 |
249 |
250 | def test_asyncio_mark_allows_combining_session_scoped_fixture_with_module_scoped_test(
251 | pytester: Pytester,
252 | ):
253 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
254 | pytester.makepyfile(
255 | __init__="",
256 | test_mixed_scopes=dedent(
257 | """\
258 | import asyncio
259 |
260 | import pytest
261 | import pytest_asyncio
262 |
263 | loop: asyncio.AbstractEventLoop
264 |
265 | @pytest_asyncio.fixture(loop_scope="session", scope="session")
266 | async def async_fixture():
267 | global loop
268 | loop = asyncio.get_running_loop()
269 |
270 | @pytest.mark.asyncio(loop_scope="module")
271 | async def test_runs_in_different_loop_as_fixture(async_fixture):
272 | global loop
273 | assert asyncio.get_running_loop() is not loop
274 | """
275 | ),
276 | )
277 | result = pytester.runpytest("--asyncio-mode=strict")
278 | result.assert_outcomes(passed=1)
279 |
280 |
281 | def test_asyncio_mark_allows_combining_session_scoped_fixture_with_class_scoped_test(
282 | pytester: Pytester,
283 | ):
284 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
285 | pytester.makepyfile(
286 | __init__="",
287 | test_mixed_scopes=dedent(
288 | """\
289 | import asyncio
290 |
291 | import pytest
292 | import pytest_asyncio
293 |
294 | loop: asyncio.AbstractEventLoop
295 |
296 | @pytest_asyncio.fixture(loop_scope="session", scope="session")
297 | async def async_fixture():
298 | global loop
299 | loop = asyncio.get_running_loop()
300 |
301 | @pytest.mark.asyncio(loop_scope="class")
302 | class TestMixedScopes:
303 | async def test_runs_in_different_loop_as_fixture(self, async_fixture):
304 | global loop
305 | assert asyncio.get_running_loop() is not loop
306 | """
307 | ),
308 | )
309 | result = pytester.runpytest("--asyncio-mode=strict")
310 | result.assert_outcomes(passed=1)
311 |
312 |
313 | def test_asyncio_mark_allows_combining_session_scoped_fixture_with_function_scoped_test(
314 | pytester: Pytester,
315 | ):
316 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
317 | pytester.makepyfile(
318 | __init__="",
319 | test_mixed_scopes=dedent(
320 | """\
321 | import asyncio
322 |
323 | import pytest
324 | import pytest_asyncio
325 |
326 | loop: asyncio.AbstractEventLoop
327 |
328 | @pytest_asyncio.fixture(loop_scope="session", scope="session")
329 | async def async_fixture():
330 | global loop
331 | loop = asyncio.get_running_loop()
332 |
333 | @pytest.mark.asyncio
334 | async def test_runs_in_different_loop_as_fixture(async_fixture):
335 | global loop
336 | assert asyncio.get_running_loop() is not loop
337 | """
338 | ),
339 | )
340 | result = pytester.runpytest("--asyncio-mode=strict")
341 | result.assert_outcomes(passed=1)
342 |
343 |
344 | def test_allows_combining_session_scoped_asyncgen_fixture_with_function_scoped_test(
345 | pytester: Pytester,
346 | ):
347 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
348 | pytester.makepyfile(
349 | __init__="",
350 | test_mixed_scopes=dedent(
351 | """\
352 | import asyncio
353 |
354 | import pytest
355 | import pytest_asyncio
356 |
357 | loop: asyncio.AbstractEventLoop
358 |
359 | @pytest_asyncio.fixture(loop_scope="session", scope="session")
360 | async def async_fixture():
361 | global loop
362 | loop = asyncio.get_running_loop()
363 | yield
364 |
365 | @pytest.mark.asyncio
366 | async def test_runs_in_different_loop_as_fixture(async_fixture):
367 | global loop
368 | assert asyncio.get_running_loop() is not loop
369 | """
370 | ),
371 | )
372 | result = pytester.runpytest("--asyncio-mode=strict")
373 | result.assert_outcomes(passed=1)
374 |
375 |
376 | def test_asyncio_mark_handles_missing_event_loop_triggered_by_fixture(
377 | pytester: Pytester,
378 | ):
379 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
380 | pytester.makepyfile(
381 | dedent(
382 | """\
383 | import pytest
384 | import asyncio
385 |
386 | @pytest.fixture(scope="session")
387 | def sets_event_loop_to_none():
388 | # asyncio.run() creates a new event loop without closing the existing
389 | # one. For any test, but the first one, this leads to a ResourceWarning
390 | # when the discarded loop is destroyed by the garbage collector.
391 | # We close the current loop to avoid this
392 | try:
393 | asyncio.get_event_loop().close()
394 | except RuntimeError:
395 | pass
396 | return asyncio.run(asyncio.sleep(0))
397 | # asyncio.run() sets the current event loop to None when finished
398 |
399 | @pytest.mark.asyncio(loop_scope="session")
400 | # parametrization may impact fixture ordering
401 | @pytest.mark.parametrize("n", (0, 1))
402 | async def test_does_not_fail(sets_event_loop_to_none, n):
403 | pass
404 | """
405 | )
406 | )
407 | result = pytester.runpytest("--asyncio-mode=strict")
408 | result.assert_outcomes(passed=2)
409 |
410 |
411 | def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
412 | pytester: Pytester,
413 | ):
414 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
415 | pytester.makepyfile(
416 | dedent(
417 | """\
418 | import pytest
419 |
420 | @pytest.mark.asyncio(loop_scope="session")
421 | async def test_anything():
422 | pass
423 | """
424 | )
425 | )
426 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
427 | result.assert_outcomes(warnings=0, passed=1)
428 |
--------------------------------------------------------------------------------
/tests/modes/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pytest-dev/pytest-asyncio/bd741b82e22030732e42a92eedebdb133cc3be36/tests/modes/__init__.py
--------------------------------------------------------------------------------
/tests/modes/test_auto_mode.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_auto_mode_cmdline(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | """\
13 | import asyncio
14 | import pytest
15 |
16 | pytest_plugins = 'pytest_asyncio'
17 |
18 | async def test_a():
19 | await asyncio.sleep(0)
20 | """
21 | )
22 | )
23 | result = pytester.runpytest("--asyncio-mode=auto")
24 | result.assert_outcomes(passed=1)
25 |
26 |
27 | def test_auto_mode_cfg(pytester: Pytester):
28 | pytester.makeini(
29 | dedent(
30 | """\
31 | [pytest]
32 | asyncio_default_fixture_loop_scope = function
33 | asyncio_mode = auto
34 | """
35 | )
36 | )
37 | pytester.makepyfile(
38 | dedent(
39 | """\
40 | import asyncio
41 | import pytest
42 |
43 | pytest_plugins = 'pytest_asyncio'
44 |
45 | async def test_a():
46 | await asyncio.sleep(0)
47 | """
48 | )
49 | )
50 | result = pytester.runpytest("--asyncio-mode=auto")
51 | result.assert_outcomes(passed=1)
52 |
53 |
54 | def test_auto_mode_async_fixture(pytester: Pytester):
55 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
56 | pytester.makepyfile(
57 | dedent(
58 | """\
59 | import asyncio
60 | import pytest
61 |
62 | pytest_plugins = 'pytest_asyncio'
63 |
64 | @pytest.fixture
65 | async def fixture_a():
66 | await asyncio.sleep(0)
67 | return 1
68 |
69 | async def test_a(fixture_a):
70 | await asyncio.sleep(0)
71 | assert fixture_a == 1
72 | """
73 | )
74 | )
75 | result = pytester.runpytest("--asyncio-mode=auto")
76 | result.assert_outcomes(passed=1)
77 |
78 |
79 | def test_auto_mode_method_fixture(pytester: Pytester):
80 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
81 | pytester.makepyfile(
82 | dedent(
83 | """\
84 | import asyncio
85 | import pytest
86 |
87 | pytest_plugins = 'pytest_asyncio'
88 |
89 |
90 | class TestA:
91 |
92 | @pytest.fixture
93 | async def fixture_a(self):
94 | await asyncio.sleep(0)
95 | return 1
96 |
97 | async def test_a(self, fixture_a):
98 | await asyncio.sleep(0)
99 | assert fixture_a == 1
100 | """
101 | )
102 | )
103 | result = pytester.runpytest("--asyncio-mode=auto")
104 | result.assert_outcomes(passed=1)
105 |
106 |
107 | def test_auto_mode_static_method(pytester: Pytester):
108 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
109 | pytester.makepyfile(
110 | dedent(
111 | """\
112 | import asyncio
113 |
114 | pytest_plugins = 'pytest_asyncio'
115 |
116 |
117 | class TestA:
118 |
119 | @staticmethod
120 | async def test_a():
121 | await asyncio.sleep(0)
122 | """
123 | )
124 | )
125 | result = pytester.runpytest("--asyncio-mode=auto")
126 | result.assert_outcomes(passed=1)
127 |
128 |
129 | def test_auto_mode_static_method_fixture(pytester: Pytester):
130 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
131 | pytester.makepyfile(
132 | dedent(
133 | """\
134 | import asyncio
135 | import pytest
136 |
137 | pytest_plugins = 'pytest_asyncio'
138 |
139 |
140 | class TestA:
141 |
142 | @staticmethod
143 | @pytest.fixture
144 | async def fixture_a():
145 | await asyncio.sleep(0)
146 | return 1
147 |
148 | @staticmethod
149 | async def test_a(fixture_a):
150 | await asyncio.sleep(0)
151 | assert fixture_a == 1
152 | """
153 | )
154 | )
155 | result = pytester.runpytest("--asyncio-mode=auto")
156 | result.assert_outcomes(passed=1)
157 |
--------------------------------------------------------------------------------
/tests/modes/test_strict_mode.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_strict_mode_cmdline(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | """\
13 | import asyncio
14 | import pytest
15 |
16 | pytest_plugins = 'pytest_asyncio'
17 |
18 | @pytest.mark.asyncio
19 | async def test_a():
20 | await asyncio.sleep(0)
21 | """
22 | )
23 | )
24 | result = pytester.runpytest("--asyncio-mode=strict")
25 | result.assert_outcomes(passed=1)
26 |
27 |
28 | def test_strict_mode_cfg(pytester: Pytester):
29 | pytester.makeini(
30 | dedent(
31 | """\
32 | [pytest]
33 | asyncio_default_fixture_loop_scope = function
34 | asyncio_mode = strict
35 | """
36 | )
37 | )
38 | pytester.makepyfile(
39 | dedent(
40 | """\
41 | import asyncio
42 | import pytest
43 |
44 | pytest_plugins = 'pytest_asyncio'
45 |
46 | @pytest.mark.asyncio
47 | async def test_a():
48 | await asyncio.sleep(0)
49 | """
50 | )
51 | )
52 | result = pytester.runpytest()
53 | result.assert_outcomes(passed=1)
54 |
55 |
56 | def test_strict_mode_method_fixture(pytester: Pytester):
57 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
58 | pytester.makepyfile(
59 | dedent(
60 | """\
61 | import asyncio
62 | import pytest
63 | import pytest_asyncio
64 |
65 | pytest_plugins = 'pytest_asyncio'
66 |
67 | class TestA:
68 |
69 | @pytest_asyncio.fixture
70 | async def fixture_a(self):
71 | await asyncio.sleep(0)
72 | return 1
73 |
74 | @pytest.mark.asyncio
75 | async def test_a(self, fixture_a):
76 | await asyncio.sleep(0)
77 | assert fixture_a == 1
78 | """
79 | )
80 | )
81 | result = pytester.runpytest("--asyncio-mode=auto")
82 | result.assert_outcomes(passed=1)
83 |
84 |
85 | def test_strict_mode_ignores_unmarked_coroutine(pytester: Pytester):
86 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
87 | pytester.makepyfile(
88 | dedent(
89 | """\
90 | import pytest
91 |
92 | async def test_anything():
93 | pass
94 | """
95 | )
96 | )
97 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
98 | result.assert_outcomes(skipped=1, warnings=1)
99 | result.stdout.fnmatch_lines(["*async def functions are not natively supported*"])
100 |
101 |
102 | def test_strict_mode_ignores_unmarked_fixture(pytester: Pytester):
103 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
104 | pytester.makepyfile(
105 | dedent(
106 | """\
107 | import pytest
108 |
109 | # Not using pytest_asyncio.fixture
110 | @pytest.fixture()
111 | async def any_fixture():
112 | raise RuntimeError()
113 |
114 | async def test_anything(any_fixture):
115 | pass
116 | """
117 | )
118 | )
119 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
120 | result.assert_outcomes(skipped=1, warnings=2)
121 | result.stdout.fnmatch_lines(
122 | [
123 | "*async def functions are not natively supported*",
124 | "*coroutine 'any_fixture' was never awaited*",
125 | ],
126 | )
127 |
128 |
129 | def test_strict_mode_marked_test_unmarked_fixture_warning(pytester: Pytester):
130 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
131 | pytester.makepyfile(
132 | dedent(
133 | """\
134 | import pytest
135 |
136 | # Not using pytest_asyncio.fixture
137 | @pytest.fixture()
138 | async def any_fixture():
139 | pass
140 |
141 | @pytest.mark.asyncio
142 | async def test_anything(any_fixture):
143 | # suppress unawaited coroutine warning
144 | try:
145 | any_fixture.send(None)
146 | except StopIteration:
147 | pass
148 | """
149 | )
150 | )
151 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
152 | result.assert_outcomes(passed=1, failed=0, skipped=0, warnings=1)
153 | result.stdout.fnmatch_lines(
154 | [
155 | "*warnings summary*",
156 | (
157 | "test_strict_mode_marked_test_unmarked_fixture_warning.py::"
158 | "test_anything"
159 | ),
160 | (
161 | "*/pytest_asyncio/plugin.py:*: PytestDeprecationWarning: "
162 | "asyncio test 'test_anything' requested async "
163 | "@pytest.fixture 'any_fixture' in strict mode. "
164 | "You might want to use @pytest_asyncio.fixture or switch to "
165 | "auto mode. "
166 | "This will become an error in future versions of flake8-asyncio."
167 | ),
168 | ],
169 | )
170 |
171 |
172 | # autouse is not handled in any special way currently
173 | def test_strict_mode_marked_test_unmarked_autouse_fixture_warning(pytester: Pytester):
174 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
175 | pytester.makepyfile(
176 | dedent(
177 | """\
178 | import pytest
179 |
180 | # Not using pytest_asyncio.fixture
181 | @pytest.fixture(autouse=True)
182 | async def any_fixture():
183 | pass
184 |
185 | @pytest.mark.asyncio
186 | async def test_anything(any_fixture):
187 | # suppress unawaited coroutine warning
188 | try:
189 | any_fixture.send(None)
190 | except StopIteration:
191 | pass
192 | """
193 | )
194 | )
195 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
196 | result.assert_outcomes(passed=1, warnings=1)
197 | result.stdout.fnmatch_lines(
198 | [
199 | "*warnings summary*",
200 | (
201 | "test_strict_mode_marked_test_unmarked_autouse_fixture_warning.py::"
202 | "test_anything"
203 | ),
204 | (
205 | "*/pytest_asyncio/plugin.py:*: PytestDeprecationWarning: "
206 | "*asyncio test 'test_anything' requested async "
207 | "@pytest.fixture 'any_fixture' in strict mode. "
208 | "You might want to use @pytest_asyncio.fixture or switch to "
209 | "auto mode. "
210 | "This will become an error in future versions of flake8-asyncio."
211 | ),
212 | ],
213 | )
214 |
--------------------------------------------------------------------------------
/tests/test_asyncio_fixture.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import asyncio
4 | from textwrap import dedent
5 |
6 | import pytest
7 | from pytest import Pytester
8 |
9 | import pytest_asyncio
10 |
11 |
12 | @pytest_asyncio.fixture
13 | async def fixture_bare():
14 | await asyncio.sleep(0)
15 | return 1
16 |
17 |
18 | @pytest.mark.asyncio
19 | async def test_bare_fixture(fixture_bare):
20 | await asyncio.sleep(0)
21 | assert fixture_bare == 1
22 |
23 |
24 | @pytest_asyncio.fixture(name="new_fixture_name")
25 | async def fixture_with_name(request):
26 | await asyncio.sleep(0)
27 | return request.fixturename
28 |
29 |
30 | @pytest.mark.asyncio
31 | async def test_fixture_with_name(new_fixture_name):
32 | await asyncio.sleep(0)
33 | assert new_fixture_name == "new_fixture_name"
34 |
35 |
36 | @pytest_asyncio.fixture(params=[2, 4])
37 | async def fixture_with_params(request):
38 | await asyncio.sleep(0)
39 | return request.param
40 |
41 |
42 | @pytest.mark.asyncio
43 | async def test_fixture_with_params(fixture_with_params):
44 | await asyncio.sleep(0)
45 | assert fixture_with_params % 2 == 0
46 |
47 |
48 | @pytest.mark.parametrize("mode", ("auto", "strict"))
49 | def test_sync_function_uses_async_fixture(pytester: Pytester, mode):
50 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
51 | pytester.makepyfile(
52 | dedent(
53 | """\
54 | import pytest_asyncio
55 |
56 | pytest_plugins = 'pytest_asyncio'
57 |
58 | @pytest_asyncio.fixture
59 | async def always_true():
60 | return True
61 |
62 | def test_sync_function_uses_async_fixture(always_true):
63 | assert always_true is True
64 | """
65 | )
66 | )
67 | result = pytester.runpytest(f"--asyncio-mode={mode}")
68 | result.assert_outcomes(passed=1)
69 |
--------------------------------------------------------------------------------
/tests/test_asyncio_mark.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_asyncio_mark_on_sync_function_emits_warning(pytester: Pytester):
9 | pytester.makepyfile(
10 | dedent(
11 | """\
12 | import pytest
13 |
14 | @pytest.mark.asyncio
15 | def test_a():
16 | pass
17 | """
18 | )
19 | )
20 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
21 | result.assert_outcomes(passed=1)
22 | result.stdout.fnmatch_lines(
23 | ["*is marked with '@pytest.mark.asyncio' but it is not an async function.*"]
24 | )
25 |
26 |
27 | def test_asyncio_mark_on_async_generator_function_emits_warning_in_strict_mode(
28 | pytester: Pytester,
29 | ):
30 | pytester.makepyfile(
31 | dedent(
32 | """\
33 | import pytest
34 |
35 | @pytest.mark.asyncio
36 | async def test_a():
37 | yield
38 | """
39 | )
40 | )
41 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
42 | result.assert_outcomes(xfailed=1, warnings=1)
43 | result.stdout.fnmatch_lines(
44 | ["*Tests based on asynchronous generators are not supported*"]
45 | )
46 |
47 |
48 | def test_asyncio_mark_on_async_generator_function_emits_warning_in_auto_mode(
49 | pytester: Pytester,
50 | ):
51 | pytester.makepyfile(
52 | dedent(
53 | """\
54 | async def test_a():
55 | yield
56 | """
57 | )
58 | )
59 | result = pytester.runpytest_subprocess("--asyncio-mode=auto", "-W default")
60 | result.assert_outcomes(xfailed=1, warnings=1)
61 | result.stdout.fnmatch_lines(
62 | ["*Tests based on asynchronous generators are not supported*"]
63 | )
64 |
65 |
66 | def test_asyncio_mark_on_async_generator_method_emits_warning_in_strict_mode(
67 | pytester: Pytester,
68 | ):
69 | pytester.makepyfile(
70 | dedent(
71 | """\
72 | import pytest
73 |
74 | class TestAsyncGenerator:
75 | @pytest.mark.asyncio
76 | async def test_a(self):
77 | yield
78 | """
79 | )
80 | )
81 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
82 | result.assert_outcomes(xfailed=1, warnings=1)
83 | result.stdout.fnmatch_lines(
84 | ["*Tests based on asynchronous generators are not supported*"]
85 | )
86 |
87 |
88 | def test_asyncio_mark_on_async_generator_method_emits_warning_in_auto_mode(
89 | pytester: Pytester,
90 | ):
91 | pytester.makepyfile(
92 | dedent(
93 | """\
94 | class TestAsyncGenerator:
95 | @staticmethod
96 | async def test_a():
97 | yield
98 | """
99 | )
100 | )
101 | result = pytester.runpytest_subprocess("--asyncio-mode=auto", "-W default")
102 | result.assert_outcomes(xfailed=1, warnings=1)
103 | result.stdout.fnmatch_lines(
104 | ["*Tests based on asynchronous generators are not supported*"]
105 | )
106 |
107 |
108 | def test_asyncio_mark_on_async_generator_staticmethod_emits_warning_in_strict_mode(
109 | pytester: Pytester,
110 | ):
111 | pytester.makepyfile(
112 | dedent(
113 | """\
114 | import pytest
115 |
116 | class TestAsyncGenerator:
117 | @staticmethod
118 | @pytest.mark.asyncio
119 | async def test_a():
120 | yield
121 | """
122 | )
123 | )
124 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
125 | result.assert_outcomes(xfailed=1, warnings=1)
126 | result.stdout.fnmatch_lines(
127 | ["*Tests based on asynchronous generators are not supported*"]
128 | )
129 |
130 |
131 | def test_asyncio_mark_on_async_generator_staticmethod_emits_warning_in_auto_mode(
132 | pytester: Pytester,
133 | ):
134 | pytester.makepyfile(
135 | dedent(
136 | """\
137 | class TestAsyncGenerator:
138 | @staticmethod
139 | async def test_a():
140 | yield
141 | """
142 | )
143 | )
144 | result = pytester.runpytest_subprocess("--asyncio-mode=auto", "-W default")
145 | result.assert_outcomes(xfailed=1, warnings=1)
146 | result.stdout.fnmatch_lines(
147 | ["*Tests based on asynchronous generators are not supported*"]
148 | )
149 |
150 |
151 | def test_asyncio_marker_fallbacks_to_configured_default_loop_scope_if_not_set(
152 | pytester: Pytester,
153 | ):
154 | pytester.makeini(
155 | dedent(
156 | """\
157 | [pytest]
158 | asyncio_default_fixture_loop_scope = function
159 | asyncio_default_test_loop_scope = session
160 | """
161 | )
162 | )
163 |
164 | pytester.makepyfile(
165 | dedent(
166 | """\
167 | import asyncio
168 | import pytest_asyncio
169 | import pytest
170 |
171 | loop: asyncio.AbstractEventLoop
172 |
173 | @pytest_asyncio.fixture(loop_scope="session", scope="session")
174 | async def session_loop_fixture():
175 | global loop
176 | loop = asyncio.get_running_loop()
177 |
178 | async def test_a(session_loop_fixture):
179 | global loop
180 | assert asyncio.get_running_loop() is loop
181 | """
182 | )
183 | )
184 |
185 | result = pytester.runpytest("--asyncio-mode=auto")
186 | result.assert_outcomes(passed=1)
187 |
188 |
189 | def test_asyncio_marker_uses_marker_loop_scope_even_if_config_is_set(
190 | pytester: Pytester,
191 | ):
192 | pytester.makeini(
193 | dedent(
194 | """\
195 | [pytest]
196 | asyncio_default_fixture_loop_scope = function
197 | asyncio_default_test_loop_scope = module
198 | """
199 | )
200 | )
201 |
202 | pytester.makepyfile(
203 | dedent(
204 | """\
205 | import asyncio
206 | import pytest_asyncio
207 | import pytest
208 |
209 | loop: asyncio.AbstractEventLoop
210 |
211 | @pytest_asyncio.fixture(loop_scope="session", scope="session")
212 | async def session_loop_fixture():
213 | global loop
214 | loop = asyncio.get_running_loop()
215 |
216 | @pytest.mark.asyncio(loop_scope="session")
217 | async def test_a(session_loop_fixture):
218 | global loop
219 | assert asyncio.get_running_loop() is loop
220 | """
221 | )
222 | )
223 |
224 | result = pytester.runpytest("--asyncio-mode=auto")
225 | result.assert_outcomes(passed=1)
226 |
--------------------------------------------------------------------------------
/tests/test_doctest.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_plugin_does_not_interfere_with_doctest_collection(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | '''\
13 | def any_function():
14 | """
15 | >>> 42
16 | 42
17 | """
18 | '''
19 | ),
20 | )
21 | result = pytester.runpytest("--asyncio-mode=strict", "--doctest-modules")
22 | result.assert_outcomes(passed=1)
23 |
24 |
25 | def test_plugin_does_not_interfere_with_doctest_textfile_collection(pytester: Pytester):
26 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
27 | pytester.makefile(".txt", "") # collected as DoctestTextfile
28 | pytester.makepyfile(
29 | __init__="",
30 | test_python_file=dedent(
31 | """\
32 | import pytest
33 |
34 | pytest_plugins = "pytest_asyncio"
35 |
36 | @pytest.mark.asyncio
37 | async def test_anything():
38 | pass
39 | """
40 | ),
41 | )
42 | result = pytester.runpytest("--asyncio-mode=strict")
43 | result.assert_outcomes(passed=1)
44 |
--------------------------------------------------------------------------------
/tests/test_event_loop_fixture.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_event_loop_fixture_respects_event_loop_policy(pytester: Pytester):
9 | pytester.makeconftest(
10 | dedent(
11 | """\
12 | '''Defines and sets a custom event loop policy'''
13 | import asyncio
14 | from asyncio import DefaultEventLoopPolicy, SelectorEventLoop
15 |
16 | class TestEventLoop(SelectorEventLoop):
17 | pass
18 |
19 | class TestEventLoopPolicy(DefaultEventLoopPolicy):
20 | def new_event_loop(self):
21 | return TestEventLoop()
22 |
23 | # This statement represents a code which sets a custom event loop policy
24 | asyncio.set_event_loop_policy(TestEventLoopPolicy())
25 | """
26 | )
27 | )
28 | pytester.makepyfile(
29 | dedent(
30 | """\
31 | '''Tests that any externally provided event loop policy remains unaltered'''
32 | import asyncio
33 |
34 | import pytest
35 |
36 |
37 | @pytest.mark.asyncio
38 | async def test_uses_loop_provided_by_custom_policy():
39 | '''Asserts that test cases use the event loop
40 | provided by the custom event loop policy'''
41 | assert type(asyncio.get_event_loop()).__name__ == "TestEventLoop"
42 |
43 |
44 | @pytest.mark.asyncio
45 | async def test_custom_policy_is_not_overwritten():
46 | '''
47 | Asserts that any custom event loop policy stays the same
48 | across test cases.
49 | '''
50 | assert type(asyncio.get_event_loop()).__name__ == "TestEventLoop"
51 | """
52 | )
53 | )
54 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
55 | result.assert_outcomes(passed=2)
56 |
57 |
58 | def test_event_loop_fixture_handles_unclosed_async_gen(
59 | pytester: Pytester,
60 | ):
61 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
62 | pytester.makepyfile(
63 | dedent(
64 | """\
65 | import asyncio
66 | import pytest
67 |
68 | pytest_plugins = 'pytest_asyncio'
69 |
70 | @pytest.mark.asyncio
71 | async def test_something():
72 | async def generator_fn():
73 | yield
74 | yield
75 |
76 | gen = generator_fn()
77 | await gen.__anext__()
78 | """
79 | )
80 | )
81 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
82 | result.assert_outcomes(passed=1, warnings=0)
83 |
84 |
85 | def test_event_loop_already_closed(
86 | pytester: Pytester,
87 | ):
88 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
89 | pytester.makepyfile(
90 | dedent(
91 | """\
92 | import asyncio
93 | import pytest
94 | import pytest_asyncio
95 | pytest_plugins = 'pytest_asyncio'
96 |
97 | @pytest_asyncio.fixture
98 | async def _event_loop():
99 | return asyncio.get_running_loop()
100 |
101 | @pytest.fixture
102 | def cleanup_after(_event_loop):
103 | yield
104 | # fixture has its own cleanup code
105 | _event_loop.close()
106 |
107 | @pytest.mark.asyncio
108 | async def test_something(cleanup_after):
109 | await asyncio.sleep(0.01)
110 | """
111 | )
112 | )
113 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
114 | result.assert_outcomes(passed=1, warnings=0)
115 |
116 |
117 | def test_event_loop_fixture_asyncgen_error(
118 | pytester: Pytester,
119 | ):
120 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
121 | pytester.makepyfile(
122 | dedent(
123 | """\
124 | import asyncio
125 | import pytest
126 |
127 | pytest_plugins = 'pytest_asyncio'
128 |
129 | @pytest.mark.asyncio
130 | async def test_something():
131 | # mock shutdown_asyncgen failure
132 | loop = asyncio.get_running_loop()
133 | async def fail():
134 | raise RuntimeError("mock error cleaning up...")
135 | loop.shutdown_asyncgens = fail
136 | """
137 | )
138 | )
139 | result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
140 | result.assert_outcomes(passed=1, warnings=1)
141 |
--------------------------------------------------------------------------------
/tests/test_fixture_loop_scopes.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | import pytest
6 | from pytest import Pytester
7 |
8 |
9 | @pytest.mark.parametrize(
10 | "fixture_scope", ("session", "package", "module", "class", "function")
11 | )
12 | def test_loop_scope_session_is_independent_of_fixture_scope(
13 | pytester: Pytester,
14 | fixture_scope: str,
15 | ):
16 | pytester.makepyfile(
17 | dedent(
18 | f"""\
19 | import asyncio
20 | import pytest
21 | import pytest_asyncio
22 |
23 | loop: asyncio.AbstractEventLoop = None
24 |
25 | @pytest_asyncio.fixture(scope="{fixture_scope}", loop_scope="session")
26 | async def fixture():
27 | global loop
28 | loop = asyncio.get_running_loop()
29 |
30 | @pytest.mark.asyncio(loop_scope="session")
31 | async def test_runs_in_same_loop_as_fixture(fixture):
32 | global loop
33 | assert loop == asyncio.get_running_loop()
34 | """
35 | )
36 | )
37 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
38 | result.assert_outcomes(passed=1)
39 |
40 |
41 | @pytest.mark.parametrize("default_loop_scope", ("function", "module", "session"))
42 | def test_default_loop_scope_config_option_changes_fixture_loop_scope(
43 | pytester: Pytester,
44 | default_loop_scope: str,
45 | ):
46 | pytester.makeini(
47 | dedent(
48 | f"""\
49 | [pytest]
50 | asyncio_default_fixture_loop_scope = {default_loop_scope}
51 | """
52 | )
53 | )
54 | pytester.makepyfile(
55 | dedent(
56 | f"""\
57 | import asyncio
58 | import pytest
59 | import pytest_asyncio
60 |
61 | @pytest_asyncio.fixture
62 | async def fixture_loop():
63 | return asyncio.get_running_loop()
64 |
65 | @pytest.mark.asyncio(loop_scope="{default_loop_scope}")
66 | async def test_runs_in_fixture_loop(fixture_loop):
67 | assert asyncio.get_running_loop() is fixture_loop
68 | """
69 | )
70 | )
71 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
72 | result.assert_outcomes(passed=1)
73 |
74 |
75 | def test_default_class_loop_scope_config_option_changes_fixture_loop_scope(
76 | pytester: Pytester,
77 | ):
78 | pytester.makeini(
79 | dedent(
80 | """\
81 | [pytest]
82 | asyncio_default_fixture_loop_scope = class
83 | """
84 | )
85 | )
86 | pytester.makepyfile(
87 | dedent(
88 | """\
89 | import asyncio
90 | import pytest
91 | import pytest_asyncio
92 |
93 | class TestClass:
94 | @pytest_asyncio.fixture
95 | async def fixture_loop(self):
96 | return asyncio.get_running_loop()
97 |
98 | @pytest.mark.asyncio(loop_scope="class")
99 | async def test_runs_in_fixture_loop(self, fixture_loop):
100 | assert asyncio.get_running_loop() is fixture_loop
101 | """
102 | )
103 | )
104 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
105 | result.assert_outcomes(passed=1)
106 |
107 |
108 | def test_default_package_loop_scope_config_option_changes_fixture_loop_scope(
109 | pytester: Pytester,
110 | ):
111 | pytester.makeini(
112 | dedent(
113 | """\
114 | [pytest]
115 | asyncio_default_fixture_loop_scope = package
116 | """
117 | )
118 | )
119 | pytester.makepyfile(
120 | __init__="",
121 | test_a=dedent(
122 | """\
123 | import asyncio
124 | import pytest
125 | import pytest_asyncio
126 |
127 | @pytest_asyncio.fixture
128 | async def fixture_loop():
129 | return asyncio.get_running_loop()
130 |
131 | @pytest.mark.asyncio(loop_scope="package")
132 | async def test_runs_in_fixture_loop(fixture_loop):
133 | assert asyncio.get_running_loop() is fixture_loop
134 | """
135 | ),
136 | )
137 | result = pytester.runpytest_subprocess("--asyncio-mode=strict")
138 | result.assert_outcomes(passed=1)
139 |
--------------------------------------------------------------------------------
/tests/test_import.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_import_warning_does_not_cause_internal_error(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | """\
13 | raise ImportWarning()
14 |
15 | async def test_errors_out():
16 | pass
17 | """
18 | )
19 | )
20 | result = pytester.runpytest("--asyncio-mode=auto")
21 | result.assert_outcomes(errors=1)
22 |
23 |
24 | def test_import_warning_in_package_does_not_cause_internal_error(pytester: Pytester):
25 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
26 | pytester.makepyfile(
27 | __init__=dedent(
28 | """\
29 | raise ImportWarning()
30 | """
31 | ),
32 | test_a=dedent(
33 | """\
34 | async def test_errors_out():
35 | pass
36 | """
37 | ),
38 | )
39 | result = pytester.runpytest("--asyncio-mode=auto")
40 | result.assert_outcomes(errors=1)
41 |
42 |
43 | def test_does_not_import_unrelated_packages(pytester: Pytester):
44 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
45 | pkg_dir = pytester.mkpydir("mypkg")
46 | pkg_dir.joinpath("__init__.py").write_text(
47 | dedent(
48 | """\
49 | raise ImportError()
50 | """
51 | ),
52 | )
53 | test_dir = pytester.mkdir("tests")
54 | test_dir.joinpath("test_a.py").write_text(
55 | dedent(
56 | """\
57 | async def test_passes():
58 | pass
59 | """
60 | ),
61 | )
62 | result = pytester.runpytest("--asyncio-mode=auto")
63 | result.assert_outcomes(passed=1)
64 |
--------------------------------------------------------------------------------
/tests/test_is_async_test.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_returns_false_for_sync_item(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | """\
13 | import pytest
14 | import pytest_asyncio
15 |
16 | def test_sync():
17 | pass
18 |
19 | def pytest_collection_modifyitems(items):
20 | async_tests = [
21 | item
22 | for item in items
23 | if pytest_asyncio.is_async_test(item)
24 | ]
25 | assert len(async_tests) == 0
26 | """
27 | )
28 | )
29 | result = pytester.runpytest("--asyncio-mode=strict")
30 | result.assert_outcomes(passed=1)
31 |
32 |
33 | def test_returns_true_for_marked_coroutine_item_in_strict_mode(pytester: Pytester):
34 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
35 | pytester.makepyfile(
36 | dedent(
37 | """\
38 | import pytest
39 | import pytest_asyncio
40 |
41 | @pytest.mark.asyncio
42 | async def test_coro():
43 | pass
44 |
45 | def pytest_collection_modifyitems(items):
46 | async_tests = [
47 | item
48 | for item in items
49 | if pytest_asyncio.is_async_test(item)
50 | ]
51 | assert len(async_tests) == 1
52 | """
53 | )
54 | )
55 | result = pytester.runpytest("--asyncio-mode=strict")
56 | result.assert_outcomes(passed=1)
57 |
58 |
59 | def test_returns_false_for_unmarked_coroutine_item_in_strict_mode(pytester: Pytester):
60 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
61 | pytester.makepyfile(
62 | dedent(
63 | """\
64 | import pytest
65 | import pytest_asyncio
66 |
67 | async def test_coro():
68 | pass
69 |
70 | def pytest_collection_modifyitems(items):
71 | async_tests = [
72 | item
73 | for item in items
74 | if pytest_asyncio.is_async_test(item)
75 | ]
76 | assert len(async_tests) == 0
77 | """
78 | )
79 | )
80 | result = pytester.runpytest("--asyncio-mode=strict")
81 | result.assert_outcomes(failed=1)
82 |
83 |
84 | def test_returns_true_for_unmarked_coroutine_item_in_auto_mode(pytester: Pytester):
85 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
86 | pytester.makepyfile(
87 | dedent(
88 | """\
89 | import pytest
90 | import pytest_asyncio
91 |
92 | async def test_coro():
93 | pass
94 |
95 | def pytest_collection_modifyitems(items):
96 | async_tests = [
97 | item
98 | for item in items
99 | if pytest_asyncio.is_async_test(item)
100 | ]
101 | assert len(async_tests) == 1
102 | """
103 | )
104 | )
105 | result = pytester.runpytest("--asyncio-mode=auto")
106 | result.assert_outcomes(passed=1)
107 |
--------------------------------------------------------------------------------
/tests/test_port_factories.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 | import pytest_asyncio.plugin
8 |
9 |
10 | def test_unused_tcp_port_selects_unused_port(pytester: Pytester):
11 | pytester.makepyfile(
12 | dedent(
13 | """\
14 | import asyncio
15 |
16 | import pytest
17 |
18 | @pytest.mark.asyncio
19 | async def test_unused_port_fixture(unused_tcp_port):
20 | async def closer(_, writer):
21 | writer.close()
22 |
23 | server1 = await asyncio.start_server(
24 | closer, host="localhost", port=unused_tcp_port
25 | )
26 |
27 | with pytest.raises(IOError):
28 | await asyncio.start_server(
29 | closer, host="localhost", port=unused_tcp_port
30 | )
31 |
32 | server1.close()
33 | await server1.wait_closed()
34 | """
35 | )
36 | )
37 |
38 |
39 | def test_unused_udp_port_selects_unused_port(pytester: Pytester):
40 | pytester.makepyfile(
41 | dedent(
42 | """\
43 | @pytest.mark.asyncio
44 | async def test_unused_udp_port_fixture(unused_udp_port):
45 | class Closer:
46 | def connection_made(self, transport):
47 | pass
48 |
49 | def connection_lost(self, *arg, **kwd):
50 | pass
51 |
52 | event_loop = asyncio.get_running_loop()
53 | transport1, _ = await event_loop.create_datagram_endpoint(
54 | Closer,
55 | local_addr=("127.0.0.1", unused_udp_port),
56 | reuse_port=False,
57 | )
58 |
59 | with pytest.raises(IOError):
60 | await event_loop.create_datagram_endpoint(
61 | Closer,
62 | local_addr=("127.0.0.1", unused_udp_port),
63 | reuse_port=False,
64 | )
65 |
66 | transport1.abort()
67 | """
68 | )
69 | )
70 |
71 |
72 | def test_unused_tcp_port_factory_selects_unused_port(pytester: Pytester):
73 | pytester.makepyfile(
74 | dedent(
75 | """\
76 | @pytest.mark.asyncio
77 | async def test_unused_port_factory_fixture(unused_tcp_port_factory):
78 | async def closer(_, writer):
79 | writer.close()
80 |
81 | port1, port2, port3 = (
82 | unused_tcp_port_factory(),
83 | unused_tcp_port_factory(),
84 | unused_tcp_port_factory(),
85 | )
86 |
87 | server1 = await asyncio.start_server(
88 | closer, host="localhost", port=port1
89 | )
90 | server2 = await asyncio.start_server(
91 | closer, host="localhost", port=port2
92 | )
93 | server3 = await asyncio.start_server(
94 | closer, host="localhost", port=port3
95 | )
96 |
97 | for port in port1, port2, port3:
98 | with pytest.raises(IOError):
99 | await asyncio.start_server(closer, host="localhost", port=port)
100 |
101 | server1.close()
102 | await server1.wait_closed()
103 | server2.close()
104 | await server2.wait_closed()
105 | server3.close()
106 | await server3.wait_closed()
107 | """
108 | )
109 | )
110 |
111 |
112 | def test_unused_udp_port_factory_selects_unused_port(pytester: Pytester):
113 | pytester.makepyfile(
114 | dedent(
115 | """\
116 | @pytest.mark.asyncio
117 | async def test_unused_udp_port_factory_fixture(unused_udp_port_factory):
118 | class Closer:
119 | def connection_made(self, transport):
120 | pass
121 |
122 | def connection_lost(self, *arg, **kwd):
123 | pass
124 |
125 | port1, port2, port3 = (
126 | unused_udp_port_factory(),
127 | unused_udp_port_factory(),
128 | unused_udp_port_factory(),
129 | )
130 |
131 | event_loop = asyncio.get_running_loop()
132 | transport1, _ = await event_loop.create_datagram_endpoint(
133 | Closer,
134 | local_addr=("127.0.0.1", port1),
135 | reuse_port=False,
136 | )
137 | transport2, _ = await event_loop.create_datagram_endpoint(
138 | Closer,
139 | local_addr=("127.0.0.1", port2),
140 | reuse_port=False,
141 | )
142 | transport3, _ = await event_loop.create_datagram_endpoint(
143 | Closer,
144 | local_addr=("127.0.0.1", port3),
145 | reuse_port=False,
146 | )
147 |
148 | for port in port1, port2, port3:
149 | with pytest.raises(IOError):
150 | await event_loop.create_datagram_endpoint(
151 | Closer,
152 | local_addr=("127.0.0.1", port),
153 | reuse_port=False,
154 | )
155 |
156 | transport1.abort()
157 | transport2.abort()
158 | transport3.abort()
159 | """
160 | )
161 | )
162 |
163 |
164 | def test_unused_port_factory_duplicate(unused_tcp_port_factory, monkeypatch):
165 | """Test correct avoidance of duplicate ports."""
166 | counter = 0
167 |
168 | def mock_unused_tcp_port(_ignored):
169 | """Force some duplicate ports."""
170 | nonlocal counter
171 | counter += 1
172 | if counter < 5:
173 | return 10000
174 | else:
175 | return 10000 + counter
176 |
177 | monkeypatch.setattr(pytest_asyncio.plugin, "_unused_port", mock_unused_tcp_port)
178 |
179 | assert unused_tcp_port_factory() == 10000
180 | assert unused_tcp_port_factory() > 10000
181 |
182 |
183 | def test_unused_udp_port_factory_duplicate(unused_udp_port_factory, monkeypatch):
184 | """Test correct avoidance of duplicate UDP ports."""
185 | counter = 0
186 |
187 | def mock_unused_udp_port(_ignored):
188 | """Force some duplicate ports."""
189 | nonlocal counter
190 | counter += 1
191 | if counter < 5:
192 | return 10000
193 | else:
194 | return 10000 + counter
195 |
196 | monkeypatch.setattr(pytest_asyncio.plugin, "_unused_port", mock_unused_udp_port)
197 |
198 | assert unused_udp_port_factory() == 10000
199 | assert unused_udp_port_factory() > 10000
200 |
--------------------------------------------------------------------------------
/tests/test_simple.py:
--------------------------------------------------------------------------------
1 | """Quick'n'dirty unit tests for provided fixtures and markers."""
2 |
3 | from __future__ import annotations
4 |
5 | import asyncio
6 | from textwrap import dedent
7 |
8 | import pytest
9 | from pytest import Pytester
10 |
11 |
12 | async def async_coro():
13 | await asyncio.sleep(0)
14 | return "ok"
15 |
16 |
17 | @pytest.mark.asyncio
18 | async def test_asyncio_marker():
19 | """Test the asyncio pytest marker."""
20 | await asyncio.sleep(0)
21 |
22 |
23 | def test_asyncio_marker_compatibility_with_xfail(pytester: Pytester):
24 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
25 | pytester.makepyfile(
26 | dedent(
27 | """\
28 | import pytest
29 |
30 | pytest_plugins = "pytest_asyncio"
31 |
32 | @pytest.mark.xfail(reason="need a failure", strict=True)
33 | @pytest.mark.asyncio
34 | async def test_asyncio_marker_fail():
35 | raise AssertionError
36 | """
37 | )
38 | )
39 | result = pytester.runpytest("--asyncio-mode=strict")
40 | result.assert_outcomes(xfailed=1)
41 |
42 |
43 | def test_asyncio_auto_mode_compatibility_with_xfail(pytester: Pytester):
44 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
45 | pytester.makepyfile(
46 | dedent(
47 | """\
48 | import pytest
49 |
50 | pytest_plugins = "pytest_asyncio"
51 |
52 | @pytest.mark.xfail(reason="need a failure", strict=True)
53 | async def test_asyncio_marker_fail():
54 | raise AssertionError
55 | """
56 | )
57 | )
58 | result = pytester.runpytest("--asyncio-mode=auto")
59 | result.assert_outcomes(xfailed=1)
60 |
61 |
62 | @pytest.mark.asyncio
63 | async def test_asyncio_marker_with_default_param(a_param=None):
64 | """Test the asyncio pytest marker."""
65 | await asyncio.sleep(0)
66 |
67 |
68 | class TestMarkerInClassBasedTests:
69 | """Test that asyncio marked functions work for methods of test classes."""
70 |
71 | @pytest.mark.asyncio
72 | async def test_asyncio_marker_with_implicit_loop_fixture(self):
73 | """
74 | Test the "asyncio" marker works on a method in
75 | a class-based test with implicit loop fixture.
76 | """
77 | ret = await async_coro()
78 | assert ret == "ok"
79 |
80 |
81 | class TestEventLoopStartedBeforeFixtures:
82 | @pytest.fixture
83 | async def loop(self):
84 | return asyncio.get_event_loop()
85 |
86 | @staticmethod
87 | def foo():
88 | return 1
89 |
90 | @pytest.mark.asyncio
91 | async def test_no_event_loop(self, loop):
92 | assert await loop.run_in_executor(None, self.foo) == 1
93 |
94 | @pytest.mark.asyncio
95 | async def test_event_loop_after_fixture(self, loop):
96 | assert await loop.run_in_executor(None, self.foo) == 1
97 |
98 | @pytest.mark.asyncio
99 | async def test_event_loop_before_fixture(self, loop):
100 | assert await loop.run_in_executor(None, self.foo) == 1
101 |
102 |
103 | def test_invalid_asyncio_mode(pytester):
104 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
105 | result = pytester.runpytest("-o", "asyncio_mode=True")
106 | result.stderr.no_fnmatch_line("INTERNALERROR> *")
107 | result.stderr.fnmatch_lines(
108 | "ERROR: 'True' is not a valid asyncio_mode. Valid modes: auto, strict."
109 | )
110 |
--------------------------------------------------------------------------------
/tests/test_skips.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from textwrap import dedent
4 |
5 | from pytest import Pytester
6 |
7 |
8 | def test_asyncio_strict_mode_skip(pytester: Pytester):
9 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
10 | pytester.makepyfile(
11 | dedent(
12 | """\
13 | import pytest
14 |
15 | pytest_plugins = "pytest_asyncio"
16 |
17 | @pytest.mark.asyncio
18 | async def test_no_warning_on_skip():
19 | pytest.skip("Test a skip error inside asyncio")
20 | """
21 | )
22 | )
23 | result = pytester.runpytest("--asyncio-mode=strict")
24 | result.assert_outcomes(skipped=1)
25 |
26 |
27 | def test_asyncio_auto_mode_skip(pytester: Pytester):
28 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
29 | pytester.makepyfile(
30 | dedent(
31 | """\
32 | import pytest
33 |
34 | pytest_plugins = "pytest_asyncio"
35 |
36 | async def test_no_warning_on_skip():
37 | pytest.skip("Test a skip error inside asyncio")
38 | """
39 | )
40 | )
41 | result = pytester.runpytest("--asyncio-mode=auto")
42 | result.assert_outcomes(skipped=1)
43 |
44 |
45 | def test_asyncio_strict_mode_module_level_skip(pytester: Pytester):
46 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
47 | pytester.makepyfile(
48 | dedent(
49 | """\
50 | import pytest
51 |
52 | pytest.skip("Skip all tests", allow_module_level=True)
53 |
54 | @pytest.mark.asyncio
55 | async def test_is_skipped():
56 | pass
57 | """
58 | )
59 | )
60 | result = pytester.runpytest("--asyncio-mode=strict")
61 | result.assert_outcomes(skipped=1)
62 |
63 |
64 | def test_asyncio_auto_mode_module_level_skip(pytester: Pytester):
65 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
66 | pytester.makepyfile(
67 | dedent(
68 | """\
69 | import pytest
70 |
71 | pytest.skip("Skip all tests", allow_module_level=True)
72 |
73 | async def test_is_skipped():
74 | pass
75 | """
76 | )
77 | )
78 | result = pytester.runpytest("--asyncio-mode=auto")
79 | result.assert_outcomes(skipped=1)
80 |
81 |
82 | def test_asyncio_auto_mode_wrong_skip_usage(pytester: Pytester):
83 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
84 | pytester.makepyfile(
85 | dedent(
86 | """\
87 | import pytest
88 |
89 | pytest.skip("Skip all tests")
90 |
91 | async def test_is_skipped():
92 | pass
93 | """
94 | )
95 | )
96 | result = pytester.runpytest("--asyncio-mode=auto")
97 | result.assert_outcomes(errors=1)
98 |
99 |
100 | def test_unittest_skiptest_compatibility(pytester: Pytester):
101 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
102 | pytester.makepyfile(
103 | dedent(
104 | """\
105 | from unittest import SkipTest
106 |
107 | raise SkipTest("Skip all tests")
108 |
109 | async def test_is_skipped():
110 | pass
111 | """
112 | )
113 | )
114 | result = pytester.runpytest("--asyncio-mode=auto")
115 | result.assert_outcomes(skipped=1)
116 |
117 |
118 | def test_skip_in_module_does_not_skip_package(pytester: Pytester):
119 | pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
120 | pytester.makepyfile(
121 | __init__="",
122 | test_skip=dedent(
123 | """\
124 | import pytest
125 |
126 | pytest.skip("Skip all tests", allow_module_level=True)
127 |
128 | def test_a():
129 | pass
130 |
131 | def test_b():
132 | pass
133 | """
134 | ),
135 | test_something=dedent(
136 | """\
137 | import pytest
138 |
139 | @pytest.mark.asyncio
140 | async def test_something():
141 | pass
142 | """
143 | ),
144 | )
145 | result = pytester.runpytest("--asyncio-mode=strict")
146 | result.assert_outcomes(passed=1, skipped=1)
147 |
--------------------------------------------------------------------------------
/tests/test_subprocess.py:
--------------------------------------------------------------------------------
1 | """Tests for using subprocesses in tests."""
2 |
3 | from __future__ import annotations
4 |
5 | import asyncio.subprocess
6 | import sys
7 |
8 | import pytest
9 |
10 |
11 | @pytest.mark.asyncio
12 | async def test_subprocess():
13 | """Starting a subprocess should be possible."""
14 | proc = await asyncio.subprocess.create_subprocess_exec(
15 | sys.executable, "--version", stdout=asyncio.subprocess.PIPE
16 | )
17 | await proc.communicate()
18 |
--------------------------------------------------------------------------------
/tools/get-version.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import json
4 | import sys
5 | from importlib import metadata
6 |
7 | from packaging.version import parse as parse_version
8 |
9 |
10 | def main():
11 | version_string = metadata.version("pytest-asyncio")
12 | version = parse_version(version_string)
13 | print(f"version={version}")
14 | prerelease = json.dumps(version.is_prerelease)
15 | print(f"prerelease={prerelease}")
16 |
17 |
18 | if __name__ == "__main__":
19 | sys.exit(main())
20 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | minversion = 4.9.0
3 | envlist = py39, py310, py311, py312, py313, py314, pytest-min, docs
4 | isolated_build = true
5 | passenv =
6 | CI
7 |
8 | [testenv]
9 | extras = testing
10 | install_command = python -m pip install \
11 | --requirement dependencies/default/requirements.txt \
12 | --constraint dependencies/default/constraints.txt \
13 | {opts} {packages}
14 | commands = make test
15 | allowlist_externals =
16 | make
17 |
18 | [testenv:pytest-min]
19 | extras = testing
20 | install_command = python -m pip install \
21 | --requirement dependencies/pytest-min/requirements.txt \
22 | --constraint dependencies/pytest-min/constraints.txt \
23 | {opts} {packages}
24 | commands = make test
25 | allowlist_externals =
26 | make
27 |
28 | [testenv:docs]
29 | allowlist_externals =
30 | git
31 | extras = docs
32 | deps =
33 | --requirement dependencies/docs/requirements.txt
34 | --constraint dependencies/docs/constraints.txt
35 | change_dir = docs
36 | description = Build The Docs with {basepython}
37 | commands =
38 | # Retrieve possibly missing commits:
39 | -git fetch --unshallow
40 | -git fetch --tags
41 |
42 | # Build the html docs with Sphinx:
43 | {envpython} -Im sphinx \
44 | -j auto \
45 | {tty:--color} \
46 | -a \
47 | -T \
48 | -n \
49 | -W --keep-going \
50 | -d "{temp_dir}{/}.doctrees" \
51 | . \
52 | {posargs:"{envdir}{/}docs_out" -b html}
53 |
54 | # Print out the output docs dir and a way to serve html:
55 | -{envpython} -c\
56 | 'import pathlib;\
57 | docs_dir = pathlib.Path(r"{envdir}") / "docs_out";\
58 | index_file = docs_dir / "index.html";\
59 | print("\n" + "=" * 120 +\
60 | f"\n\nOpen the documentation with:\n\n\
61 | \t$ python3 -Im webbrowser \N\{QUOTATION MARK\}file://\{index_file\}\N\{QUOTATION MARK\}\n\n\
62 | To serve docs, use\n\n\
63 | \t$ python3 -Im http.server --directory \
64 | \N\{QUOTATION MARK\}\{docs_dir\}\N\{QUOTATION MARK\} 0\n\n" +\
65 | "=" * 120)'
66 | changedir = {toxinidir}{/}docs
67 | isolated_build = true
68 | passenv =
69 | SSH_AUTH_SOCK
70 | skip_install = false
71 |
72 | [gh-actions]
73 | python =
74 | 3.9: py39, pytest-min
75 | 3.10: py310
76 | 3.11: py311
77 | 3.12: py312
78 | 3.13: py313
79 | 3.14-dev: py314
80 | pypy3: pypy3
81 |
--------------------------------------------------------------------------------