diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index e12ad385..89d990ac 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -58,20 +58,24 @@ jobs:
test:
name: ${{ matrix.os }} - Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}-latest
-
+ continue-on-error: ${{ !matrix.required }}
strategy:
matrix:
os: [ubuntu, windows]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
+ required: [true]
+ include:
+ - os: ubuntu
+ python-version: 3.14-dev
+ required: false
+ - os: windows
+ python-version: 3.14-dev
+ required: false
+
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- if: "!endsWith(matrix.python-version, '-dev')"
- with:
- python-version: ${{ matrix.python-version }}
- - uses: deadsnakes/action@v3.2.0
- if: endsWith(matrix.python-version, '-dev')
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -126,19 +130,51 @@ jobs:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
+ prepare-release-notes:
+ name: Prepare Release Notes
+ needs: [lint]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Install Python
+ uses: actions/setup-python@v5
+ - name: Install towncrier
+ run: pip install towncrier==24.8.0
+ - name: Install pandoc
+ run: |
+ sudo apt-get install -y pandoc
+ - name: Install pytest-asyncio
+ run: pip install .
+ - name: Compile Release Notes Draft
+ if: ${{ !contains(github.ref, 'refs/tags/') }}
+ run: towncrier build --draft --version "${{ needs.lint.outputs.version }}" > release-notes.rst
+ - name: Extract release notes from Git tag
+ if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
+ run: |
+ set -e
+ git for-each-ref "${GITHUB_REF}" --format='%(contents)' > release-notes.rst
+ # Strip PGP signature from signed tags
+ sed -i "/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----\n/d" release-notes.rst
+ - name: Convert Release Notes to Markdown
+ run: |
+ pandoc -s -o release-notes.md release-notes.rst
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: release-notes.md
+ path: release-notes.md
+
deploy:
name: Deploy
environment: release
# Run only on pushing a tag
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
- needs: [lint, check]
+ needs: [lint, check, prepare-release-notes]
runs-on: ubuntu-latest
steps:
- - name: Install pandoc
- run: |
- sudo apt-get install -y pandoc
- - name: Checkout
- uses: actions/checkout@v4
- name: Download distributions
uses: actions/download-artifact@v4
with:
@@ -147,20 +183,22 @@ jobs:
- name: Collected dists
run: |
tree dist
- - name: Convert README.rst to Markdown
- run: |
- pandoc -s -o README.md README.rst
- name: PyPI upload
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
attestations: true
packages-dir: dist
password: ${{ secrets.PYPI_API_TOKEN }}
+ - name: Download Release Notes
+ uses: actions/download-artifact@v4
+ with:
+ name: release-notes.md
+ path: release-notes.md
- name: GitHub Release
uses: ncipollo/release-action@v1
with:
name: pytest-asyncio ${{ needs.lint.outputs.version }}
artifacts: dist/*
- bodyFile: README.md
+ bodyFile: release-notes.md
prerelease: ${{ needs.lint.outputs.prerelease }}
token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 973ac3f5..bd705265 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,7 +6,7 @@ repos:
- id: check-merge-conflict
exclude: rst$
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.11.2
+ rev: v0.11.10
hooks:
- id: ruff
args: [--fix]
@@ -65,7 +65,7 @@ repos:
- 'SC1004:'
stages: [manual]
- repo: https://github.com/sirosen/check-jsonschema
- rev: 0.31.3
+ rev: 0.33.0
hooks:
- id: check-github-actions
- repo: https://github.com/tox-dev/pyproject-fmt
diff --git a/tests/loop_fixture_scope/__init__.py b/changelog.d/.gitkeep
similarity index 100%
rename from tests/loop_fixture_scope/__init__.py
rename to changelog.d/.gitkeep
diff --git a/dependencies/default/constraints.txt b/dependencies/default/constraints.txt
index db8d6faf..d8cf2319 100644
--- a/dependencies/default/constraints.txt
+++ b/dependencies/default/constraints.txt
@@ -1,11 +1,11 @@
attrs==25.3.0
-coverage==7.7.1
-exceptiongroup==1.2.2
-hypothesis==6.130.3
+coverage==7.8.0
+exceptiongroup==1.3.0
+hypothesis==6.131.18
iniconfig==2.1.0
-packaging==24.2
-pluggy==1.5.0
+packaging==25.0
+pluggy==1.6.0
pytest==8.3.5
sortedcontainers==2.4.0
tomli==2.2.1
-typing_extensions==4.12.2
+typing_extensions==4.13.2
diff --git a/dependencies/docs/constraints.txt b/dependencies/docs/constraints.txt
index 85afd831..06376f30 100644
--- a/dependencies/docs/constraints.txt
+++ b/dependencies/docs/constraints.txt
@@ -1,16 +1,16 @@
alabaster==0.7.16
Babel==2.17.0
-certifi==2025.1.31
-charset-normalizer==3.4.1
+certifi==2025.4.26
+charset-normalizer==3.4.2
docutils==0.21.2
idna==3.10
imagesize==1.4.1
Jinja2==3.1.6
MarkupSafe==3.0.2
-packaging==24.2
+packaging==25.0
Pygments==2.19.1
requests==2.32.3
-snowballstemmer==2.2.0
+snowballstemmer==3.0.1
Sphinx==8.0.2
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
@@ -20,4 +20,4 @@ sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
-urllib3==2.3.0
+urllib3==2.4.0
diff --git a/docs/how-to-guides/multiple_loops_example.py b/docs/how-to-guides/multiple_loops_example.py
index a4c7a01c..2083e8b6 100644
--- a/docs/how-to-guides/multiple_loops_example.py
+++ b/docs/how-to-guides/multiple_loops_example.py
@@ -1,5 +1,9 @@
import asyncio
-from asyncio import DefaultEventLoopPolicy
+import warnings
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ from asyncio import DefaultEventLoopPolicy
import pytest
@@ -20,5 +24,6 @@ def event_loop_policy(request):
@pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst
index 11c35a1b..ea6ea8da 100644
--- a/docs/reference/changelog.rst
+++ b/docs/reference/changelog.rst
@@ -2,6 +2,50 @@
Changelog
=========
+All notable changes to this project will be documented in this file.
+
+The format is based on `Keep a Changelog `__, and this project adheres to `Semantic Versioning `__.
+
+This project uses `towncrier `__ for changlog management and the changes for the upcoming release can be found in https://github.com/pytest-dev/pytest-asyncio/tree/main/changelog.d/.
+
+.. towncrier release notes start
+
+`1.0.0 `_ - 2025-05-26
+===============================================================================
+
+Removed
+-------
+
+- The deprecated *event_loop* fixture. (`#1106 `_)
+
+
+Added
+-----
+
+- Prelimiary support for Python 3.14 (`#1025 `_)
+
+
+Changed
+-------
+
+- Scoped event loops (e.g. module-scoped loops) are created once rather than per scope (e.g. per module). This reduces the number of fixtures and speeds up collection time, especially for large test suites. (`#1107 `_)
+- The *loop_scope* argument to ``pytest.mark.asyncio`` no longer forces that a pytest Collector exists at the level of the specified scope. For example, a test function marked with ``pytest.mark.asyncio(loop_scope="class")`` no longer requires a class surrounding the test. This is consistent with the behavior of the *scope* argument to ``pytest_asyncio.fixture``. (`#1112 `_)
+
+
+Fixed
+-----
+
+- An error caused when using pytest's `--setup-plan` option. (`#630 `_)
+- Unsuppressed import errors with pytest option ``--doctest-ignore-import-errors`` (`#797 `_)
+- A "fixture not found" error in connection with package-scoped loops (`#1052 `_)
+
+
+Notes for Downstream Packagers
+------------------------------
+
+- Removed a test that had an ordering dependency on other tests. (`#1114 `_)
+
+
0.26.0 (2025-03-25)
===================
- Adds configuration option that sets default event loop scope for all tests `#793 `_
diff --git a/docs/reference/fixtures/event_loop_example.py b/docs/reference/fixtures/event_loop_example.py
deleted file mode 100644
index b5a82b62..00000000
--- a/docs/reference/fixtures/event_loop_example.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import asyncio
-
-
-def test_event_loop_fixture(event_loop):
- event_loop.run_until_complete(asyncio.sleep(0))
diff --git a/docs/reference/fixtures/event_loop_policy_example.py b/docs/reference/fixtures/event_loop_policy_example.py
index 5fd87b73..e8642527 100644
--- a/docs/reference/fixtures/event_loop_policy_example.py
+++ b/docs/reference/fixtures/event_loop_policy_example.py
@@ -1,9 +1,14 @@
import asyncio
+import warnings
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ from asyncio import DefaultEventLoopPolicy
import pytest
-class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
+class CustomEventLoopPolicy(DefaultEventLoopPolicy):
pass
@@ -13,5 +18,6 @@ def event_loop_policy(request):
@pytest.mark.asyncio(loop_scope="module")
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
diff --git a/docs/reference/fixtures/event_loop_policy_parametrized_example.py b/docs/reference/fixtures/event_loop_policy_parametrized_example.py
index 1560889b..19552d81 100644
--- a/docs/reference/fixtures/event_loop_policy_parametrized_example.py
+++ b/docs/reference/fixtures/event_loop_policy_parametrized_example.py
@@ -1,10 +1,14 @@
import asyncio
-from asyncio import DefaultEventLoopPolicy
+import warnings
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ from asyncio import DefaultEventLoopPolicy
import pytest
-class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
+class CustomEventLoopPolicy(DefaultEventLoopPolicy):
pass
@@ -19,5 +23,6 @@ def event_loop_policy(request):
@pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), DefaultEventLoopPolicy)
diff --git a/docs/reference/fixtures/index.rst b/docs/reference/fixtures/index.rst
index 04953783..3d151dcb 100644
--- a/docs/reference/fixtures/index.rst
+++ b/docs/reference/fixtures/index.rst
@@ -2,33 +2,6 @@
Fixtures
========
-event_loop
-==========
-*This fixture is deprecated.*
-
-*If you want to request an asyncio event loop with a scope other than function
-scope, use the "loop_scope" argument to* :ref:`reference/markers/asyncio` *when marking the tests.
-If you want to return different types of event loops, use the* :ref:`reference/fixtures/event_loop_policy`
-*fixture.*
-
-Creates a new asyncio event loop based on the current event loop policy. The new loop
-is available as the return value of this fixture for synchronous functions, or via `asyncio.get_running_loop `__ for asynchronous functions.
-The event loop is closed when the fixture scope ends.
-The fixture scope defaults to ``function`` scope.
-
-.. include:: event_loop_example.py
- :code: python
-
-Note that, when using the ``event_loop`` fixture, you need to interact with the event loop using methods like ``event_loop.run_until_complete``. If you want to *await* code inside your test function, you need to write a coroutine and use it as a test function. The :ref:`asyncio ` marker
-is used to mark coroutines that should be treated as test functions.
-
-If you need to change the type of the event loop, prefer setting a custom event loop policy over redefining the ``event_loop`` fixture.
-
-If the ``pytest.mark.asyncio`` decorator is applied to a test function, the ``event_loop``
-fixture will be requested automatically by the test function.
-
-.. _reference/fixtures/event_loop_policy:
-
event_loop_policy
=================
Returns the event loop policy used to create asyncio event loops.
diff --git a/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py b/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py
index afb4cc8a..5bb26247 100644
--- a/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py
+++ b/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py
@@ -1,12 +1,16 @@
-import asyncio
+import warnings
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ from asyncio import DefaultEventLoopPolicy
import pytest
@pytest.fixture(
params=[
- asyncio.DefaultEventLoopPolicy(),
- asyncio.DefaultEventLoopPolicy(),
+ DefaultEventLoopPolicy(),
+ DefaultEventLoopPolicy(),
]
)
def event_loop_policy(request):
diff --git a/docs/reference/markers/index.rst b/docs/reference/markers/index.rst
index e7d700c9..7715077b 100644
--- a/docs/reference/markers/index.rst
+++ b/docs/reference/markers/index.rst
@@ -21,21 +21,17 @@ The ``pytest.mark.asyncio`` marker can be omitted entirely in |auto mode|_ where
By default, each test runs in it's own asyncio event loop.
Multiple tests can share the same event loop by providing a *loop_scope* keyword argument to the *asyncio* mark.
-The supported scopes are *class,* and *module,* and *package*.
+The supported scopes are *function,* *class,* and *module,* *package,* and *session*.
The following code example provides a shared event loop for all tests in `TestClassScopedLoop`:
.. include:: class_scoped_loop_strict_mode_example.py
:code: python
-If you request class scope for a test that is not part of a class, it will result in a *UsageError*.
Similar to class-scoped event loops, a module-scoped loop is provided when setting mark's scope to *module:*
.. include:: module_scoped_loop_strict_mode_example.py
:code: python
-Package-scoped loops only work with `regular Python packages. `__
-That means they require an *__init__.py* to be present.
-Package-scoped loops do not work in `namespace packages. `__
Subpackages do not share the loop with their parent package.
Tests marked with *session* scope share the same event loop, even if the tests exist in different packages.
diff --git a/pyproject.toml b/pyproject.toml
index b368b481..b55bc8e6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -121,7 +121,6 @@ asyncio_default_fixture_loop_scope = "function"
junit_family = "xunit2"
filterwarnings = [
"error",
- "ignore:The event_loop fixture provided by pytest-asyncio has been redefined.*:DeprecationWarning",
]
[tool.coverage.run]
@@ -137,3 +136,44 @@ parallel = true
[tool.coverage.report]
show_missing = true
+
+[tool.towncrier]
+directory = "changelog.d"
+filename = "docs/reference/changelog.rst"
+title_format = "`{version} `_ - {project_date}"
+issue_format = "`#{issue} `_"
+
+[[tool.towncrier.type]]
+directory = "security"
+name = "Security"
+showcontent = true
+
+[[tool.towncrier.type]]
+directory = "removed"
+name = "Removed"
+showcontent = true
+
+[[tool.towncrier.type]]
+directory = "deprecated"
+name = "Deprecated"
+showcontent = true
+
+[[tool.towncrier.type]]
+directory = "added"
+name = "Added"
+showcontent = true
+
+[[tool.towncrier.type]]
+directory = "changed"
+name = "Changed"
+showcontent = true
+
+[[tool.towncrier.type]]
+directory = "fixed"
+name = "Fixed"
+showcontent = true
+
+[[tool.towncrier.type]]
+directory = "downstream"
+name = "Notes for Downstream Packagers"
+showcontent = true
diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py
index 8a1d8733..aecf6e96 100644
--- a/pytest_asyncio/plugin.py
+++ b/pytest_asyncio/plugin.py
@@ -19,23 +19,22 @@
Generator,
Iterable,
Iterator,
- Mapping,
Sequence,
)
-from textwrap import dedent
from typing import (
Any,
Callable,
Literal,
TypeVar,
Union,
+ cast,
overload,
)
import pluggy
import pytest
+from _pytest.scope import Scope
from pytest import (
- Class,
Collector,
Config,
FixtureDef,
@@ -44,14 +43,10 @@
Item,
Mark,
Metafunc,
- Module,
- Package,
Parser,
PytestCollectionWarning,
PytestDeprecationWarning,
PytestPluginManager,
- Session,
- StashKey,
)
if sys.version_info >= (3, 10):
@@ -71,10 +66,6 @@ class PytestAsyncioError(Exception):
"""Base class for exceptions raised by pytest-asyncio"""
-class MultipleEventLoopsRequestedError(PytestAsyncioError):
- """Raised when a test requests multiple asyncio event loops."""
-
-
class Mode(str, enum.Enum):
AUTO = "auto"
STRICT = "strict"
@@ -259,24 +250,12 @@ def _preprocess_async_fixtures(
# Ignore async fixtures without explicit asyncio mark in strict mode
# This applies to pytest_trio fixtures, for example
continue
- scope = (
+ loop_scope = (
getattr(func, "_loop_scope", None)
or default_loop_scope
or fixturedef.scope
)
- if scope == "function" and "event_loop" not in fixturedef.argnames:
- fixturedef.argnames += ("event_loop",)
- _make_asyncio_fixture_function(func, scope)
- function_signature = inspect.signature(func)
- if "event_loop" in function_signature.parameters:
- warnings.warn(
- PytestDeprecationWarning(
- f"{func.__name__} is asynchronous and explicitly "
- f'requests the "event_loop" fixture. Asynchronous fixtures and '
- f'test functions should use "asyncio.get_running_loop()" '
- f"instead."
- )
- )
+ _make_asyncio_fixture_function(func, loop_scope)
if "request" not in fixturedef.argnames:
fixturedef.argnames += ("request",)
_synchronize_async_fixture(fixturedef)
@@ -295,15 +274,12 @@ def _synchronize_async_fixture(fixturedef: FixtureDef) -> None:
def _add_kwargs(
func: Callable[..., Any],
kwargs: dict[str, Any],
- event_loop: asyncio.AbstractEventLoop,
request: FixtureRequest,
) -> dict[str, Any]:
sig = inspect.signature(func)
ret = kwargs.copy()
if "request" in sig.parameters:
ret["request"] = request
- if "event_loop" in sig.parameters:
- ret["event_loop"] = event_loop
return ret
@@ -334,7 +310,7 @@ def _asyncgen_fixture_wrapper(request: FixtureRequest, **kwargs: Any):
)
event_loop = request.getfixturevalue(event_loop_fixture_id)
kwargs.pop(event_loop_fixture_id, None)
- gen_obj = func(**_add_kwargs(func, kwargs, event_loop, request))
+ gen_obj = func(**_add_kwargs(func, kwargs, request))
async def setup():
res = await gen_obj.__anext__() # type: ignore[union-attr]
@@ -383,7 +359,7 @@ def _async_fixture_wrapper(request: FixtureRequest, **kwargs: Any):
kwargs.pop(event_loop_fixture_id, None)
async def setup():
- res = await func(**_add_kwargs(func, kwargs, event_loop, request))
+ res = await func(**_add_kwargs(func, kwargs, request))
return res
context = contextvars.copy_context()
@@ -410,21 +386,13 @@ async def setup():
def _get_event_loop_fixture_id_for_async_fixture(
request: FixtureRequest, func: Any
) -> str:
- default_loop_scope = request.config.getini("asyncio_default_fixture_loop_scope")
+ default_loop_scope = cast(
+ _ScopeName, request.config.getini("asyncio_default_fixture_loop_scope")
+ )
loop_scope = (
getattr(func, "_loop_scope", None) or default_loop_scope or request.scope
)
- if loop_scope == "function":
- event_loop_fixture_id = "event_loop"
- else:
- event_loop_node = _retrieve_scope_root(request._pyfuncitem, loop_scope)
- event_loop_fixture_id = event_loop_node.stash.get(
- # Type ignored because of non-optimal mypy inference.
- _event_loop_fixture_id, # type: ignore[arg-type]
- "",
- )
- assert event_loop_fixture_id
- return event_loop_fixture_id
+ return f"_{loop_scope}_event_loop"
def _create_task_in_context(
@@ -516,15 +484,6 @@ def _from_function(cls, function: Function, /) -> Function:
)
subclass_instance.own_markers = function.own_markers
assert subclass_instance.own_markers == function.own_markers
- subclassed_function_signature = inspect.signature(subclass_instance.obj)
- if "event_loop" in subclassed_function_signature.parameters:
- subclass_instance.warn(
- PytestDeprecationWarning(
- f"{subclass_instance.name} is asynchronous and explicitly "
- f'requests the "event_loop" fixture. Asynchronous fixtures and '
- f'test functions should use "asyncio.get_running_loop()" instead.'
- )
- )
return subclass_instance
@staticmethod
@@ -671,151 +630,19 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
hook_result.force_result(updated_node_collection)
-_event_loop_fixture_id = StashKey[str]()
-_fixture_scope_by_collector_type: Mapping[type[pytest.Collector], _ScopeName] = {
- Class: "class",
- # Package is a subclass of module and the dict is used in isinstance checks
- # Therefore, the order matters and Package needs to appear before Module
- Package: "package",
- Module: "module",
- Session: "session",
-}
-
-# A stack used to push package-scoped loops during collection of a package
-# and pop those loops during collection of a Module
-__package_loop_stack: list[Callable[..., Any]] = []
-
-
-@pytest.hookimpl
-def pytest_collectstart(collector: pytest.Collector) -> None:
- try:
- collector_scope = next(
- scope
- for cls, scope in _fixture_scope_by_collector_type.items()
- if isinstance(collector, cls)
- )
- except StopIteration:
- return
- # Session is not a PyCollector type, so it doesn't have a corresponding
- # "obj" attribute to attach a dynamic fixture function to.
- # However, there's only one session per pytest run, so there's no need to
- # create the fixture dynamically. We can simply define a session-scoped
- # event loop fixture once in the plugin code.
- if collector_scope == "session":
- event_loop_fixture_id = _session_event_loop.__name__
- collector.stash[_event_loop_fixture_id] = event_loop_fixture_id
- return
- # There seem to be issues when a fixture is shadowed by another fixture
- # and both differ in their params.
- # https://github.com/pytest-dev/pytest/issues/2043
- # https://github.com/pytest-dev/pytest/issues/11350
- # As such, we assign a unique name for each event_loop fixture.
- # The fixture name is stored in the collector's Stash, so it can
- # be injected when setting up the test
- event_loop_fixture_id = f"{collector.nodeid}::"
- collector.stash[_event_loop_fixture_id] = event_loop_fixture_id
-
- @pytest.fixture(
- scope=collector_scope,
- name=event_loop_fixture_id,
- )
- def scoped_event_loop(
- *args, # Function needs to accept "cls" when collected by pytest.Class
- event_loop_policy,
- ) -> Iterator[asyncio.AbstractEventLoop]:
- new_loop_policy = event_loop_policy
- with (
- _temporary_event_loop_policy(new_loop_policy),
- _provide_event_loop() as loop,
- ):
- asyncio.set_event_loop(loop)
- yield loop
-
- # @pytest.fixture does not register the fixture anywhere, so pytest doesn't
- # know it exists. We work around this by attaching the fixture function to the
- # collected Python object, where it will be picked up by pytest.Class.collect()
- # or pytest.Module.collect(), respectively
- if type(collector) is Package:
- # Packages do not have a corresponding Python object. Therefore, the fixture
- # for the package-scoped event loop is added to a stack. When a module inside
- # the package is collected, the module will attach the fixture to its
- # Python object.
- __package_loop_stack.append(scoped_event_loop)
- elif isinstance(collector, Module):
- # Accessing Module.obj triggers a module import executing module-level
- # statements. A module-level pytest.skip statement raises the "Skipped"
- # OutcomeException or a Collector.CollectError, if the "allow_module_level"
- # kwargs is missing. These cases are handled correctly when they happen inside
- # Collector.collect(), but this hook runs before the actual collect call.
- # Therefore, we monkey patch Module.collect to add the scoped fixture to the
- # module before it runs the actual collection.
- def _patched_collect():
- # If the collected module is a DoctestTextfile, collector.obj is None
- module = collector.obj
- if module is not None:
- module.__pytest_asyncio_scoped_event_loop = scoped_event_loop
- try:
- package_loop = __package_loop_stack.pop()
- module.__pytest_asyncio_package_scoped_event_loop = package_loop
- except IndexError:
- pass
- return collector.__original_collect()
-
- collector.__original_collect = collector.collect # type: ignore[attr-defined]
- collector.collect = _patched_collect # type: ignore[method-assign]
- elif isinstance(collector, Class):
- collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop
-
-
@contextlib.contextmanager
def _temporary_event_loop_policy(policy: AbstractEventLoopPolicy) -> Iterator[None]:
- old_loop_policy = asyncio.get_event_loop_policy()
+ old_loop_policy = _get_event_loop_policy()
try:
old_loop = _get_event_loop_no_warn()
except RuntimeError:
old_loop = None
- asyncio.set_event_loop_policy(policy)
+ _set_event_loop_policy(policy)
try:
yield
finally:
- # Try detecting user-created event loops that were left unclosed
- # at the end of a test.
- try:
- current_loop: AbstractEventLoop | None = _get_event_loop_no_warn()
- except RuntimeError:
- current_loop = None
- if current_loop is not None and not current_loop.is_closed():
- warnings.warn(
- _UNCLOSED_EVENT_LOOP_WARNING % current_loop,
- DeprecationWarning,
- )
- current_loop.close()
-
- asyncio.set_event_loop_policy(old_loop_policy)
- # When a test uses both a scoped event loop and the event_loop fixture,
- # the "_provide_clean_event_loop" finalizer of the event_loop fixture
- # will already have installed a fresh event loop, in order to shield
- # subsequent tests from side-effects. We close this loop before restoring
- # the old loop to avoid ResourceWarnings.
- try:
- _get_event_loop_no_warn().close()
- except RuntimeError:
- pass
- asyncio.set_event_loop(old_loop)
-
-
-_REDEFINED_EVENT_LOOP_FIXTURE_WARNING = dedent(
- """\
- The event_loop fixture provided by pytest-asyncio has been redefined in
- %s:%d
- Replacing the event_loop fixture with a custom implementation is deprecated
- and will lead to errors in the future.
- If you want to request an asyncio event loop with a scope other than function
- scope, use the "loop_scope" argument to the asyncio mark when marking the tests.
- If you want to return different types of event loops, use the event_loop_policy
- fixture.
- """
-)
+ _set_event_loop_policy(old_loop_policy)
+ _set_event_loop(old_loop)
@pytest.hookimpl(tryfirst=True)
@@ -824,165 +651,36 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
if not marker:
return
default_loop_scope = _get_default_test_loop_scope(metafunc.config)
- scope = _get_marked_loop_scope(marker, default_loop_scope)
- if scope == "function":
+ loop_scope = _get_marked_loop_scope(marker, default_loop_scope)
+ event_loop_fixture_id = f"_{loop_scope}_event_loop"
+ # This specific fixture name may already be in metafunc.argnames, if this
+ # test indirectly depends on the fixture. For example, this is the case
+ # when the test depends on an async fixture, both of which share the same
+ # event loop fixture mark.
+ if event_loop_fixture_id in metafunc.fixturenames:
return
- event_loop_node = _retrieve_scope_root(metafunc.definition, scope)
- event_loop_fixture_id = event_loop_node.stash.get(_event_loop_fixture_id, None)
-
- if event_loop_fixture_id:
- # This specific fixture name may already be in metafunc.argnames, if this
- # test indirectly depends on the fixture. For example, this is the case
- # when the test depends on an async fixture, both of which share the same
- # event loop fixture mark.
- if event_loop_fixture_id in metafunc.fixturenames:
- return
- fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage")
- assert fixturemanager is not None
- if "event_loop" in metafunc.fixturenames:
- raise MultipleEventLoopsRequestedError(
- _MULTIPLE_LOOPS_REQUESTED_ERROR.format(
- test_name=metafunc.definition.nodeid,
- scope=scope,
- scoped_loop_node=event_loop_node.nodeid,
- ),
- )
- # Add the scoped event loop fixture to Metafunc's list of fixture names and
- # fixturedefs and leave the actual parametrization to pytest
- # The fixture needs to be appended to avoid messing up the fixture evaluation
- # order
- metafunc.fixturenames.append(event_loop_fixture_id)
- metafunc._arg2fixturedefs[event_loop_fixture_id] = (
- fixturemanager._arg2fixturedefs[event_loop_fixture_id]
- )
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_fixture_setup(
- fixturedef: FixtureDef,
-) -> Generator[None, pluggy.Result, None]:
- """Adjust the event loop policy when an event loop is produced."""
- if fixturedef.argname == "event_loop":
- # The use of a fixture finalizer is preferred over the
- # pytest_fixture_post_finalizer hook. The fixture finalizer is invoked once
- # for each fixture, whereas the hook may be invoked multiple times for
- # any specific fixture.
- # see https://github.com/pytest-dev/pytest/issues/5848
- _add_finalizers(
- fixturedef,
- _close_event_loop,
- _restore_event_loop_policy(asyncio.get_event_loop_policy()),
- _provide_clean_event_loop,
- )
- outcome = yield
- loop: asyncio.AbstractEventLoop = outcome.get_result()
- # Weird behavior was observed when checking for an attribute of FixtureDef.func
- # Instead, we now check for a special attribute of the returned event loop
- fixture_filename = inspect.getsourcefile(fixturedef.func)
- if not _is_pytest_asyncio_loop(loop):
- _, fixture_line_number = inspect.getsourcelines(fixturedef.func)
- warnings.warn(
- _REDEFINED_EVENT_LOOP_FIXTURE_WARNING
- % (fixture_filename, fixture_line_number),
- DeprecationWarning,
- )
- policy = asyncio.get_event_loop_policy()
- try:
- old_loop = _get_event_loop_no_warn(policy)
- if old_loop is not loop and not _is_pytest_asyncio_loop(old_loop):
- old_loop.close()
- except RuntimeError:
- # Either the current event loop has been set to None
- # or the loop policy doesn't specify to create new loops
- # or we're not in the main thread
- pass
- policy.set_event_loop(loop)
- return
-
- yield
-
-
-def _make_pytest_asyncio_loop(loop: AbstractEventLoop) -> AbstractEventLoop:
- loop.__pytest_asyncio = True # type: ignore[attr-defined]
- return loop
-
-
-def _is_pytest_asyncio_loop(loop: AbstractEventLoop) -> bool:
- return getattr(loop, "__pytest_asyncio", False)
-
-
-def _add_finalizers(fixturedef: FixtureDef, *finalizers: Callable[[], object]) -> None:
- """
- Registers the specified fixture finalizers in the fixture.
-
- Finalizers need to be specified in the exact order in which they should be invoked.
-
- :param fixturedef: Fixture definition which finalizers should be added to
- :param finalizers: Finalizers to be added
- """
- for finalizer in reversed(finalizers):
- fixturedef.addfinalizer(finalizer)
-
-
-_UNCLOSED_EVENT_LOOP_WARNING = dedent(
- """\
- pytest-asyncio detected an unclosed event loop when tearing down the event_loop
- fixture: %r
- pytest-asyncio will close the event loop for you, but future versions of the
- library will no longer do so. In order to ensure compatibility with future
- versions, please make sure that:
- 1. Any custom "event_loop" fixture properly closes the loop after yielding it
- 2. The scopes of your custom "event_loop" fixtures do not overlap
- 3. Your code does not modify the event loop in async fixtures or tests
- """
-)
+ fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage")
+ assert fixturemanager is not None
+ # Add the scoped event loop fixture to Metafunc's list of fixture names and
+ # fixturedefs and leave the actual parametrization to pytest
+ # The fixture needs to be appended to avoid messing up the fixture evaluation
+ # order
+ metafunc.fixturenames.append(event_loop_fixture_id)
+ metafunc._arg2fixturedefs[event_loop_fixture_id] = fixturemanager._arg2fixturedefs[
+ event_loop_fixture_id
+ ]
-def _close_event_loop() -> None:
- policy = asyncio.get_event_loop_policy()
- try:
- loop = policy.get_event_loop()
- except RuntimeError:
- loop = None
- if loop is not None and not _is_pytest_asyncio_loop(loop):
- if not loop.is_closed():
- warnings.warn(
- _UNCLOSED_EVENT_LOOP_WARNING % loop,
- DeprecationWarning,
- )
- loop.close()
+def _get_event_loop_policy() -> AbstractEventLoopPolicy:
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ return asyncio.get_event_loop_policy()
-def _restore_event_loop_policy(previous_policy) -> Callable[[], None]:
- def _restore_policy():
- # Close any event loop associated with the old loop policy
- # to avoid ResourceWarnings in the _provide_clean_event_loop finalizer
- try:
- loop = _get_event_loop_no_warn(previous_policy)
- except RuntimeError:
- loop = None
- if loop and not _is_pytest_asyncio_loop(loop):
- loop.close()
- asyncio.set_event_loop_policy(previous_policy)
-
- return _restore_policy
-
-
-def _provide_clean_event_loop() -> None:
- # At this point, the event loop for the current thread is closed.
- # When a user calls asyncio.get_event_loop(), they will get a closed loop.
- # In order to avoid this side effect from pytest-asyncio, we need to replace
- # the current loop with a fresh one.
- # Note that we cannot set the loop to None, because get_event_loop only creates
- # a new loop, when set_event_loop has not been called.
- policy = asyncio.get_event_loop_policy()
- try:
- old_loop = _get_event_loop_no_warn(policy)
- except RuntimeError:
- old_loop = None
- if old_loop is not None and not _is_pytest_asyncio_loop(old_loop):
- new_loop = policy.new_event_loop()
- policy.set_event_loop(new_loop)
+def _set_event_loop_policy(policy: AbstractEventLoopPolicy) -> None:
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ asyncio.set_event_loop_policy(policy)
def _get_event_loop_no_warn(
@@ -996,6 +694,12 @@ def _get_event_loop_no_warn(
return asyncio.get_event_loop()
+def _set_event_loop(loop: AbstractEventLoop | None) -> None:
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ asyncio.set_event_loop(loop)
+
+
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
"""
@@ -1079,29 +783,13 @@ def inner(*args, **kwargs):
return inner
-_MULTIPLE_LOOPS_REQUESTED_ERROR = dedent(
- """\
- Multiple asyncio event loops with different scopes have been requested
- by {test_name}. The test explicitly requests the event_loop fixture, while
- another event loop with {scope} scope is provided by {scoped_loop_node}.
- Remove "event_loop" from the requested fixture in your test to run the test
- in a {scope}-scoped event loop or remove the scope argument from the "asyncio"
- mark to run the test in a function-scoped event loop.
- """
-)
-
-
def pytest_runtest_setup(item: pytest.Item) -> None:
marker = item.get_closest_marker("asyncio")
if marker is None:
return
default_loop_scope = _get_default_test_loop_scope(item.config)
- scope = _get_marked_loop_scope(marker, default_loop_scope)
- if scope != "function":
- parent_node = _retrieve_scope_root(item, scope)
- event_loop_fixture_id = parent_node.stash[_event_loop_fixture_id]
- else:
- event_loop_fixture_id = "event_loop"
+ loop_scope = _get_marked_loop_scope(marker, default_loop_scope)
+ event_loop_fixture_id = f"_{loop_scope}_event_loop"
fixturenames = item.fixturenames # type: ignore[attr-defined]
if event_loop_fixture_id not in fixturenames:
fixturenames.append(event_loop_fixture_id)
@@ -1151,42 +839,36 @@ def _get_default_test_loop_scope(config: Config) -> _ScopeName:
return config.getini("asyncio_default_test_loop_scope")
-def _retrieve_scope_root(item: Collector | Item, scope: str) -> Collector:
- node_type_by_scope = {
- "class": Class,
- "module": Module,
- "package": Package,
- "session": Session,
- }
- scope_root_type = node_type_by_scope[scope]
- for node in reversed(item.listchain()):
- if isinstance(node, scope_root_type):
- assert isinstance(node, pytest.Collector)
- return node
- error_message = (
- f"{item.name} is marked to be run in an event loop with scope {scope}, "
- f"but is not part of any {scope}."
+def _create_scoped_event_loop_fixture(scope: _ScopeName) -> Callable:
+ @pytest.fixture(
+ scope=scope,
+ name=f"_{scope}_event_loop",
)
- raise pytest.UsageError(error_message)
+ def _scoped_event_loop(
+ *args, # Function needs to accept "cls" when collected by pytest.Class
+ event_loop_policy,
+ ) -> Iterator[asyncio.AbstractEventLoop]:
+ new_loop_policy = event_loop_policy
+ with (
+ _temporary_event_loop_policy(new_loop_policy),
+ _provide_event_loop() as loop,
+ ):
+ _set_event_loop(loop)
+ yield loop
+ return _scoped_event_loop
-@pytest.fixture
-def event_loop(request: FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]:
- """Create an instance of the default event loop for each test case."""
- new_loop_policy = request.getfixturevalue(event_loop_policy.__name__)
- with _temporary_event_loop_policy(new_loop_policy), _provide_event_loop() as loop:
- yield loop
+
+for scope in Scope:
+ globals()[f"_{scope.value}_event_loop"] = _create_scoped_event_loop_fixture(
+ scope.value
+ )
@contextlib.contextmanager
def _provide_event_loop() -> Iterator[asyncio.AbstractEventLoop]:
- loop = asyncio.get_event_loop_policy().new_event_loop()
- # Add a magic value to the event loop, so pytest-asyncio can determine if the
- # event_loop fixture was overridden. Other implementations of event_loop don't
- # set this value.
- # The magic value must be set as part of the function definition, because pytest
- # seems to have multiple instances of the same FixtureDef or fixture function
- loop = _make_pytest_asyncio_loop(loop)
+ policy = _get_event_loop_policy()
+ loop = policy.new_event_loop()
try:
yield loop
finally:
@@ -1200,20 +882,10 @@ def _provide_event_loop() -> Iterator[asyncio.AbstractEventLoop]:
loop.close()
-@pytest.fixture(scope="session")
-def _session_event_loop(
- request: FixtureRequest, event_loop_policy: AbstractEventLoopPolicy
-) -> Iterator[asyncio.AbstractEventLoop]:
- new_loop_policy = event_loop_policy
- with _temporary_event_loop_policy(new_loop_policy), _provide_event_loop() as loop:
- asyncio.set_event_loop(loop)
- yield loop
-
-
@pytest.fixture(scope="session", autouse=True)
def event_loop_policy() -> AbstractEventLoopPolicy:
"""Return an instance of the policy used to create asyncio event loops."""
- return asyncio.get_event_loop_policy()
+ return _get_event_loop_policy()
def is_async_test(item: Item) -> bool:
diff --git a/tests/async_fixtures/test_async_fixtures_scope.py b/tests/async_fixtures/test_async_fixtures_scope.py
deleted file mode 100644
index 7fbed781..00000000
--- a/tests/async_fixtures/test_async_fixtures_scope.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
-We support module-scoped async fixtures, but only if the event loop is
-module-scoped too.
-"""
-
-from __future__ import annotations
-
-import asyncio
-
-import pytest
-
-
-@pytest.fixture(scope="module")
-def event_loop():
- """A module-scoped event loop."""
- loop = asyncio.new_event_loop()
- yield loop
- loop.close()
-
-
-@pytest.fixture(scope="module")
-async def async_fixture():
- await asyncio.sleep(0.1)
- return 1
-
-
-@pytest.mark.asyncio
-async def test_async_fixture_scope(async_fixture):
- assert async_fixture == 1
- await asyncio.sleep(0.1)
diff --git a/tests/async_fixtures/test_async_fixtures_with_finalizer.py b/tests/async_fixtures/test_async_fixtures_with_finalizer.py
deleted file mode 100644
index 199ecbca..00000000
--- a/tests/async_fixtures/test_async_fixtures_with_finalizer.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-import functools
-
-import pytest
-
-import pytest_asyncio
-
-
-@pytest.mark.asyncio(loop_scope="module")
-async def test_module_with_event_loop_finalizer(port_with_event_loop_finalizer):
- await asyncio.sleep(0.01)
- assert port_with_event_loop_finalizer
-
-
-@pytest.mark.asyncio(loop_scope="module")
-async def test_module_with_get_event_loop_finalizer(port_with_get_event_loop_finalizer):
- await asyncio.sleep(0.01)
- assert port_with_get_event_loop_finalizer
-
-
-@pytest.fixture(scope="module")
-def event_loop():
- """Change event_loop fixture to module level."""
- policy = asyncio.get_event_loop_policy()
- loop = policy.new_event_loop()
- yield loop
- loop.close()
-
-
-@pytest_asyncio.fixture(loop_scope="module", scope="module")
-async def port_with_event_loop_finalizer(request):
- def port_finalizer(finalizer):
- async def port_afinalizer():
- # await task using loop provided by event_loop fixture
- # RuntimeError is raised if task is created on a different loop
- await finalizer
-
- asyncio.run(port_afinalizer())
-
- worker = asyncio.ensure_future(asyncio.sleep(0.2))
- request.addfinalizer(functools.partial(port_finalizer, worker))
- return True
-
-
-@pytest_asyncio.fixture(loop_scope="module", scope="module")
-async def port_with_get_event_loop_finalizer(request):
- def port_finalizer(finalizer):
- async def port_afinalizer():
- # await task using current loop retrieved from the event loop policy
- # RuntimeError is raised if task is created on a different loop.
- # This can happen when pytest_fixture_setup
- # does not set up the loop correctly,
- # for example when policy.set_event_loop() is called with a wrong argument
- await finalizer
-
- current_loop = asyncio.get_event_loop_policy().get_event_loop()
- current_loop.run_until_complete(port_afinalizer())
-
- worker = asyncio.ensure_future(asyncio.sleep(0.2))
- request.addfinalizer(functools.partial(port_finalizer, worker))
- return True
diff --git a/tests/async_fixtures/test_async_gen_fixtures.py b/tests/async_fixtures/test_async_gen_fixtures.py
deleted file mode 100644
index ddc2f5be..00000000
--- a/tests/async_fixtures/test_async_gen_fixtures.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from __future__ import annotations
-
-import unittest.mock
-
-import pytest
-
-START = object()
-END = object()
-RETVAL = object()
-
-
-@pytest.fixture(scope="module")
-def mock():
- return unittest.mock.Mock(return_value=RETVAL)
-
-
-@pytest.fixture
-async def async_gen_fixture(mock):
- try:
- yield mock(START)
- except Exception as e:
- mock(e)
- else:
- mock(END)
-
-
-@pytest.mark.asyncio
-async def test_async_gen_fixture(async_gen_fixture, mock):
- assert mock.called
- assert mock.call_args_list[-1] == unittest.mock.call(START)
- assert async_gen_fixture is RETVAL
-
-
-@pytest.mark.asyncio
-async def test_async_gen_fixture_finalized(mock):
- try:
- assert mock.called
- assert mock.call_args_list[-1] == unittest.mock.call(END)
- finally:
- mock.reset_mock()
-
-
-class TestAsyncGenFixtureMethod:
- is_same_instance = False
-
- @pytest.fixture(autouse=True)
- async def async_gen_fixture_method(self):
- self.is_same_instance = True
- yield None
-
- @pytest.mark.asyncio
- async def test_async_gen_fixture_method(self):
- assert self.is_same_instance
diff --git a/tests/async_fixtures/test_parametrized_loop.py b/tests/async_fixtures/test_parametrized_loop.py
deleted file mode 100644
index ca2cb5c7..00000000
--- a/tests/async_fixtures/test_parametrized_loop.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from __future__ import annotations
-
-from textwrap import dedent
-
-from pytest import Pytester
-
-
-def test_event_loop_parametrization(pytester: Pytester):
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
-
- import pytest
- import pytest_asyncio
-
- TESTS_COUNT = 0
-
-
- def teardown_module():
- # parametrized 2 * 2 times: 2 for 'event_loop' and 2 for 'fix'
- assert TESTS_COUNT == 4
-
-
- @pytest.fixture(scope="module", params=[1, 2])
- def event_loop(request):
- request.param
- loop = asyncio.new_event_loop()
- yield loop
- loop.close()
-
-
- @pytest_asyncio.fixture(params=["a", "b"])
- async def fix(request):
- await asyncio.sleep(0)
- return request.param
-
-
- @pytest.mark.asyncio
- async def test_parametrized_loop(fix):
- await asyncio.sleep(0)
- global TESTS_COUNT
- TESTS_COUNT += 1
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict")
- result.assert_outcomes(passed=4)
diff --git a/tests/conftest.py b/tests/conftest.py
index 76e2026f..eecab735 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,34 +1,3 @@
from __future__ import annotations
-import asyncio
-
-import pytest
-
pytest_plugins = "pytester"
-
-
-@pytest.fixture
-def dependent_fixture(event_loop):
- """A fixture dependent on the event_loop fixture, doing some cleanup."""
- counter = 0
-
- async def just_a_sleep():
- """Just sleep a little while."""
- nonlocal event_loop
- await asyncio.sleep(0.1)
- nonlocal counter
- counter += 1
-
- event_loop.run_until_complete(just_a_sleep())
- yield
- event_loop.run_until_complete(just_a_sleep())
-
- assert counter == 2
-
-
-@pytest.fixture(scope="session", name="factory_involving_factories")
-def factory_involving_factories_fixture(unused_tcp_port_factory):
- def factory():
- return unused_tcp_port_factory()
-
- return factory
diff --git a/tests/hypothesis/test_base.py b/tests/hypothesis/test_base.py
index 4b185f62..487b05fe 100644
--- a/tests/hypothesis/test_base.py
+++ b/tests/hypothesis/test_base.py
@@ -45,43 +45,6 @@ async def test_mark_and_parametrize(x, y):
assert y in (1, 2)
-def test_can_use_explicit_event_loop_fixture(pytester: Pytester):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = module")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
- from hypothesis import given
- import hypothesis.strategies as st
-
- pytest_plugins = 'pytest_asyncio'
-
- @pytest.fixture(scope="module")
- def event_loop():
- loop = asyncio.get_event_loop_policy().new_event_loop()
- yield loop
- loop.close()
-
- @given(st.integers())
- @pytest.mark.asyncio
- async def test_explicit_fixture_request(event_loop, n):
- semaphore = asyncio.Semaphore(value=0)
- event_loop.call_soon(semaphore.release)
- await semaphore.acquire()
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=2)
- result.stdout.fnmatch_lines(
- [
- '*is asynchronous and explicitly requests the "event_loop" fixture*',
- "*event_loop fixture provided by pytest-asyncio has been redefined*",
- ]
- )
-
-
def test_async_auto_marked(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
diff --git a/tests/loop_fixture_scope/conftest.py b/tests/loop_fixture_scope/conftest.py
deleted file mode 100644
index 4e8b06de..00000000
--- a/tests/loop_fixture_scope/conftest.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-
-import pytest
-
-
-class CustomSelectorLoop(asyncio.SelectorEventLoop):
- """A subclass with no overrides, just to test for presence."""
-
-
-@pytest.fixture(scope="module")
-def event_loop():
- """Create an instance of the default event loop for each test case."""
- loop = CustomSelectorLoop()
- yield loop
- loop.close()
diff --git a/tests/loop_fixture_scope/test_loop_fixture_scope.py b/tests/loop_fixture_scope/test_loop_fixture_scope.py
deleted file mode 100644
index eb1bae58..00000000
--- a/tests/loop_fixture_scope/test_loop_fixture_scope.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""Unit tests for overriding the event loop with a larger scoped one."""
-
-from __future__ import annotations
-
-import asyncio
-
-import pytest
-
-
-@pytest.mark.asyncio
-async def test_for_custom_loop():
- """This test should be executed using the custom loop."""
- await asyncio.sleep(0.01)
- assert type(asyncio.get_event_loop()).__name__ == "CustomSelectorLoop"
-
-
-@pytest.mark.asyncio
-async def test_dependent_fixture(dependent_fixture):
- await asyncio.sleep(0.1)
diff --git a/tests/markers/test_class_scope.py b/tests/markers/test_class_scope.py
index 4bddb4b8..e8732e86 100644
--- a/tests/markers/test_class_scope.py
+++ b/tests/markers/test_class_scope.py
@@ -82,29 +82,6 @@ async def test_this_runs_in_same_loop(self):
result.assert_outcomes(passed=2)
-def test_asyncio_mark_raises_when_class_scoped_is_request_without_class(
- pytester: pytest.Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- @pytest.mark.asyncio(loop_scope="class")
- async def test_has_no_surrounding_class():
- pass
- """
- )
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(errors=1)
- result.stdout.fnmatch_lines(
- "*is marked to be run in an event loop with scope*",
- )
-
-
def test_asyncio_mark_is_inherited_to_subclasses(pytester: pytest.Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
diff --git a/tests/markers/test_function_scope.py b/tests/markers/test_function_scope.py
index c17a6225..f750ba58 100644
--- a/tests/markers/test_function_scope.py
+++ b/tests/markers/test_function_scope.py
@@ -92,29 +92,6 @@ async def test_warns():
result.stdout.fnmatch_lines("*DeprecationWarning*")
-def test_function_scope_supports_explicit_event_loop_fixture_request(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import pytest
-
- pytestmark = pytest.mark.asyncio
-
- async def test_remember_loop(event_loop):
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines(
- '*is asynchronous and explicitly requests the "event_loop" fixture*'
- )
-
-
def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
diff --git a/tests/markers/test_module_scope.py b/tests/markers/test_module_scope.py
index 7dbdbb7f..a050f503 100644
--- a/tests/markers/test_module_scope.py
+++ b/tests/markers/test_module_scope.py
@@ -5,59 +5,6 @@
from pytest import Pytester
-def test_asyncio_mark_works_on_module_level(pytester: Pytester):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
-
- import pytest
-
- pytestmark = pytest.mark.asyncio
-
-
- class TestPyTestMark:
- async def test_is_asyncio(self, event_loop, sample_fixture):
- assert asyncio.get_event_loop()
-
- counter = 1
-
- async def inc():
- nonlocal counter
- counter += 1
- await asyncio.sleep(0)
-
- await asyncio.ensure_future(inc())
- assert counter == 2
-
-
- async def test_is_asyncio(event_loop, sample_fixture):
- assert asyncio.get_event_loop()
- counter = 1
-
- async def inc():
- nonlocal counter
- counter += 1
- await asyncio.sleep(0)
-
- await asyncio.ensure_future(inc())
- assert counter == 2
-
-
- @pytest.fixture
- def sample_fixture():
- return None
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=2, warnings=2)
- result.stdout.fnmatch_lines(
- '*is asynchronous and explicitly requests the "event_loop" fixture*'
- )
-
-
def test_asyncio_mark_provides_module_scoped_loop_strict_mode(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(
@@ -89,28 +36,6 @@ async def test_this_runs_in_same_loop(self):
result.assert_outcomes(passed=3)
-def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- pytestmark = pytest.mark.asyncio(loop_scope="module")
-
- async def test_remember_loop(event_loop):
- pass
- """
- )
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(errors=1)
- result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *")
-
-
def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
diff --git a/tests/markers/test_package_scope.py b/tests/markers/test_package_scope.py
index 204238a4..3e41459b 100644
--- a/tests/markers/test_package_scope.py
+++ b/tests/markers/test_package_scope.py
@@ -69,28 +69,6 @@ async def test_subpackage_runs_in_different_loop():
result.assert_outcomes(passed=4)
-def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- __init__="",
- test_raises=dedent(
- """\
- import asyncio
- import pytest
-
- @pytest.mark.asyncio(loop_scope="package")
- async def test_remember_loop(event_loop):
- pass
- """
- ),
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(errors=1)
- result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *")
-
-
def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
@@ -361,23 +339,3 @@ async def test_does_not_fail(sets_event_loop_to_none, n):
)
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(passed=2)
-
-
-def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- __init__="",
- test_module=dedent(
- """\
- import pytest
-
- @pytest.mark.asyncio(loop_scope="package")
- async def test_anything():
- pass
- """
- ),
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict")
- result.assert_outcomes(warnings=0, passed=1)
diff --git a/tests/markers/test_session_scope.py b/tests/markers/test_session_scope.py
index 70e191b2..2d3a4993 100644
--- a/tests/markers/test_session_scope.py
+++ b/tests/markers/test_session_scope.py
@@ -70,28 +70,6 @@ async def test_subpackage_runs_in_same_loop():
result.assert_outcomes(passed=4)
-def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- __init__="",
- test_raises=dedent(
- """\
- import asyncio
- import pytest
-
- @pytest.mark.asyncio(loop_scope="session")
- async def test_remember_loop(event_loop):
- pass
- """
- ),
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(errors=1)
- result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *")
-
-
def test_asyncio_mark_respects_the_loop_policy(
pytester: Pytester,
):
diff --git a/tests/test_dependent_fixtures.py b/tests/test_dependent_fixtures.py
deleted file mode 100644
index 2e53700a..00000000
--- a/tests/test_dependent_fixtures.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-
-import pytest
-
-
-@pytest.mark.asyncio
-async def test_dependent_fixture(dependent_fixture):
- """Test a dependent fixture."""
- await asyncio.sleep(0.1)
-
-
-@pytest.mark.asyncio
-async def test_factory_involving_factories(factory_involving_factories):
- factory_involving_factories()
diff --git a/tests/test_event_loop_fixture_finalizer.py b/tests/test_event_loop_fixture_finalizer.py
deleted file mode 100644
index 1e378643..00000000
--- a/tests/test_event_loop_fixture_finalizer.py
+++ /dev/null
@@ -1,149 +0,0 @@
-from __future__ import annotations
-
-from textwrap import dedent
-
-from pytest import Pytester
-
-
-def test_event_loop_fixture_finalizer_returns_fresh_loop_after_test(pytester: Pytester):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
-
- import pytest
-
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
-
- @pytest.mark.asyncio
- async def test_1():
- # This async test runs in its own event loop
- global loop
- running_loop = asyncio.get_event_loop_policy().get_event_loop()
- # Make sure this test case received a different loop
- assert running_loop is not loop
-
- def test_2():
- # Code outside of pytest-asyncio should not receive a "used" event loop
- current_loop = asyncio.get_event_loop_policy().get_event_loop()
- assert not current_loop.is_running()
- assert not current_loop.is_closed()
- """
- )
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(passed=2)
-
-
-def test_event_loop_fixture_finalizer_handles_loop_set_to_none_sync(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
-
- def test_sync(event_loop):
- asyncio.get_event_loop_policy().set_event_loop(None)
- """
- )
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(passed=1)
-
-
-def test_event_loop_fixture_finalizer_handles_loop_set_to_none_async_without_fixture(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- @pytest.mark.asyncio
- async def test_async_without_explicit_fixture_request():
- asyncio.get_event_loop_policy().set_event_loop(None)
- """
- )
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(passed=1)
-
-
-def test_event_loop_fixture_finalizer_handles_loop_set_to_none_async_with_fixture(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- @pytest.mark.asyncio
- async def test_async_with_explicit_fixture_request(event_loop):
- asyncio.get_event_loop_policy().set_event_loop(None)
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines(
- '*is asynchronous and explicitly requests the "event_loop" fixture*'
- )
-
-
-def test_event_loop_fixture_finalizer_raises_warning_when_fixture_leaves_loop_unclosed(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- pytest_plugins = 'pytest_asyncio'
-
- @pytest.fixture
- def event_loop():
- loop = asyncio.get_event_loop_policy().new_event_loop()
- yield loop
-
- @pytest.mark.asyncio
- async def test_ends_with_unclosed_loop():
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
- result.assert_outcomes(passed=1, warnings=2)
- result.stdout.fnmatch_lines("*unclosed event loop*")
-
-
-def test_event_loop_fixture_finalizer_raises_warning_when_test_leaves_loop_unclosed(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- pytest_plugins = 'pytest_asyncio'
-
- @pytest.mark.asyncio
- async def test_ends_with_unclosed_loop():
- asyncio.set_event_loop(asyncio.new_event_loop())
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines("*unclosed event loop*")
diff --git a/tests/test_event_loop_fixture_override_deprecation.py b/tests/test_event_loop_fixture_override_deprecation.py
deleted file mode 100644
index 04859ef7..00000000
--- a/tests/test_event_loop_fixture_override_deprecation.py
+++ /dev/null
@@ -1,113 +0,0 @@
-from __future__ import annotations
-
-from textwrap import dedent
-
-from pytest import Pytester
-
-
-def test_emit_warning_when_event_loop_fixture_is_redefined(pytester: Pytester):
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- @pytest.fixture
- def event_loop():
- loop = asyncio.new_event_loop()
- yield loop
- loop.close()
-
- @pytest.mark.asyncio
- async def test_emits_warning():
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines(
- ["*event_loop fixture provided by pytest-asyncio has been redefined*"]
- )
-
-
-def test_emit_warning_when_event_loop_fixture_is_redefined_explicit_request(
- pytester: Pytester,
-):
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- @pytest.fixture
- def event_loop():
- loop = asyncio.new_event_loop()
- yield loop
- loop.close()
-
- @pytest.mark.asyncio
- async def test_emits_warning_when_requested_explicitly(event_loop):
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=2)
- result.stdout.fnmatch_lines(
- ["*event_loop fixture provided by pytest-asyncio has been redefined*"]
- )
- result.stdout.fnmatch_lines(
- ['*is asynchronous and explicitly requests the "event_loop" fixture*']
- )
-
-
-def test_does_not_emit_warning_when_no_test_uses_the_event_loop_fixture(
- pytester: Pytester,
-):
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
-
- @pytest.fixture
- def event_loop():
- loop = asyncio.new_event_loop()
- yield loop
- loop.close()
-
- def test_emits_no_warning():
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict")
- result.assert_outcomes(passed=1, warnings=0)
-
-
-def test_emit_warning_when_redefined_event_loop_is_used_by_fixture(pytester: Pytester):
- pytester.makepyfile(
- dedent(
- """\
- import asyncio
- import pytest
- import pytest_asyncio
-
- @pytest.fixture
- def event_loop():
- loop = asyncio.new_event_loop()
- yield loop
- loop.close()
-
- @pytest_asyncio.fixture
- async def uses_event_loop():
- pass
-
- def test_emits_warning(uses_event_loop):
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
diff --git a/tests/test_explicit_event_loop_fixture_request.py b/tests/test_explicit_event_loop_fixture_request.py
deleted file mode 100644
index c685ad84..00000000
--- a/tests/test_explicit_event_loop_fixture_request.py
+++ /dev/null
@@ -1,168 +0,0 @@
-from __future__ import annotations
-
-from textwrap import dedent
-
-from pytest import Pytester
-
-
-def test_emit_warning_when_event_loop_is_explicitly_requested_in_coroutine(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import pytest
-
- @pytest.mark.asyncio
- async def test_coroutine_emits_warning(event_loop):
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines(
- ['*is asynchronous and explicitly requests the "event_loop" fixture*']
- )
-
-
-def test_emit_warning_when_event_loop_is_explicitly_requested_in_coroutine_method(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import pytest
-
- class TestEmitsWarning:
- @pytest.mark.asyncio
- async def test_coroutine_emits_warning(self, event_loop):
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines(
- ['*is asynchronous and explicitly requests the "event_loop" fixture*']
- )
-
-
-def test_emit_warning_when_event_loop_is_explicitly_requested_in_coroutine_staticmethod(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import pytest
-
- class TestEmitsWarning:
- @staticmethod
- @pytest.mark.asyncio
- async def test_coroutine_emits_warning(event_loop):
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines(
- ['*is asynchronous and explicitly requests the "event_loop" fixture*']
- )
-
-
-def test_emit_warning_when_event_loop_is_explicitly_requested_in_coroutine_fixture(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import pytest
- import pytest_asyncio
-
- @pytest_asyncio.fixture
- async def emits_warning(event_loop):
- pass
-
- @pytest.mark.asyncio
- async def test_uses_fixture(emits_warning):
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines(
- ['*is asynchronous and explicitly requests the "event_loop" fixture*']
- )
-
-
-def test_emit_warning_when_event_loop_is_explicitly_requested_in_async_gen_fixture(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import pytest
- import pytest_asyncio
-
- @pytest_asyncio.fixture
- async def emits_warning(event_loop):
- yield
-
- @pytest.mark.asyncio
- async def test_uses_fixture(emits_warning):
- pass
- """
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W default")
- result.assert_outcomes(passed=1, warnings=1)
- result.stdout.fnmatch_lines(
- ['*is asynchronous and explicitly requests the "event_loop" fixture*']
- )
-
-
-def test_does_not_emit_warning_when_event_loop_is_explicitly_requested_in_sync_function(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import pytest
-
- def test_uses_fixture(event_loop):
- pass
- """
- )
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(passed=1)
-
-
-def test_does_not_emit_warning_when_event_loop_is_explicitly_requested_in_sync_fixture(
- pytester: Pytester,
-):
- pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
- pytester.makepyfile(
- dedent(
- """\
- import pytest
-
- @pytest.fixture
- def any_fixture(event_loop):
- pass
-
- def test_uses_fixture(any_fixture):
- pass
- """
- )
- )
- result = pytester.runpytest("--asyncio-mode=strict")
- result.assert_outcomes(passed=1)
diff --git a/tests/test_multiloop.py b/tests/test_multiloop.py
deleted file mode 100644
index e6c852b9..00000000
--- a/tests/test_multiloop.py
+++ /dev/null
@@ -1,72 +0,0 @@
-from __future__ import annotations
-
-from textwrap import dedent
-
-from pytest import Pytester
-
-
-def test_event_loop_override(pytester: Pytester):
- pytester.makeconftest(
- dedent(
- '''\
- import asyncio
-
- import pytest
-
-
- @pytest.fixture
- def dependent_fixture(event_loop):
- """A fixture dependent on the event_loop fixture, doing some cleanup."""
- counter = 0
-
- async def just_a_sleep():
- """Just sleep a little while."""
- nonlocal event_loop
- await asyncio.sleep(0.1)
- nonlocal counter
- counter += 1
-
- event_loop.run_until_complete(just_a_sleep())
- yield
- event_loop.run_until_complete(just_a_sleep())
-
- assert counter == 2
-
-
- class CustomSelectorLoop(asyncio.SelectorEventLoop):
- """A subclass with no overrides, just to test for presence."""
-
-
- @pytest.fixture
- def event_loop():
- """Create an instance of the default event loop for each test case."""
- loop = CustomSelectorLoop()
- yield loop
- loop.close()
- '''
- )
- )
- pytester.makepyfile(
- dedent(
- '''\
- """Unit tests for overriding the event loop."""
- import asyncio
-
- import pytest
-
-
- @pytest.mark.asyncio
- async def test_for_custom_loop():
- """This test should be executed using the custom loop."""
- await asyncio.sleep(0.01)
- assert type(asyncio.get_event_loop()).__name__ == "CustomSelectorLoop"
-
-
- @pytest.mark.asyncio
- async def test_dependent_fixture(dependent_fixture):
- await asyncio.sleep(0.1)
- '''
- )
- )
- result = pytester.runpytest_subprocess("--asyncio-mode=strict")
- result.assert_outcomes(passed=2, warnings=2)
diff --git a/tests/test_simple.py b/tests/test_simple.py
index b8a34fb2..f92ef4e7 100644
--- a/tests/test_simple.py
+++ b/tests/test_simple.py
@@ -14,13 +14,6 @@ async def async_coro():
return "ok"
-def test_event_loop_fixture(event_loop):
- """Test the injection of the event_loop fixture."""
- assert event_loop
- ret = event_loop.run_until_complete(async_coro())
- assert ret == "ok"
-
-
@pytest.mark.asyncio
async def test_asyncio_marker():
"""Test the asyncio pytest marker."""
diff --git a/tests/test_subprocess.py b/tests/test_subprocess.py
index c32ba964..438f49f2 100644
--- a/tests/test_subprocess.py
+++ b/tests/test_subprocess.py
@@ -7,16 +7,6 @@
import pytest
-if sys.platform == "win32":
- # The default asyncio event loop implementation on Windows does not
- # support subprocesses. Subprocesses are available for Windows if a
- # ProactorEventLoop is used.
- @pytest.fixture()
- def event_loop():
- loop = asyncio.ProactorEventLoop()
- yield loop
- loop.close()
-
@pytest.mark.asyncio
async def test_subprocess():
diff --git a/tox.ini b/tox.ini
index 9a0cf93b..e6457f56 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
minversion = 4.9.0
-envlist = py39, py310, py311, py312, py313, pytest-min, docs
+envlist = py39, py310, py311, py312, py313, py314, pytest-min, docs
isolated_build = true
passenv =
CI
@@ -76,4 +76,5 @@ python =
3.11: py311
3.12: py312
3.13: py313
+ 3.14-dev: py314
pypy3: pypy3