Skip to content

Commit 0c522bf

Browse files
committed
[fix] Fixes a bug that caused an internal pytest error when using ImportWarning in a module.
The fix monkey patches the pytest.Module.collect to attach the scoped event loop fixture to the module, rather than directly accessing Module.obj. This allows dropping all error handling related to module imports that has been added, because pytest_collectstart isn't meant to deal with those errors. Signed-off-by: Michael Seifert <[email protected]>
1 parent 31c7e6f commit 0c522bf

File tree

3 files changed

+35
-15
lines changed

3 files changed

+35
-15
lines changed

docs/source/reference/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Changelog
66
===================
77
- Fixes a bug that caused event loops to be closed prematurely when using async generator fixtures with class scope or wider in a function-scoped test `#708 <https://github.com/pytest-dev/pytest-asyncio/issues/708>`_
88
- Fixes a bug that caused an internal pytest error when using unittest.SkipTest in a module `#711 <https://github.com/pytest-dev/pytest-asyncio/issues/711>`_
9+
- Fixes a bug that caused an internal pytest error when an ImportWarning is emitted in a module `#713 <https://github.com/pytest-dev/pytest-asyncio/issues/713>`_
910

1011

1112
0.23.2 (2023-12-04)

pytest_asyncio/plugin.py

+16-15
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,8 @@
2626
Union,
2727
overload,
2828
)
29-
from unittest import SkipTest
3029

3130
import pytest
32-
from _pytest.outcomes import OutcomeException
3331
from pytest import (
3432
Class,
3533
Collector,
@@ -608,25 +606,28 @@ def scoped_event_loop(
608606

609607
# @pytest.fixture does not register the fixture anywhere, so pytest doesn't
610608
# know it exists. We work around this by attaching the fixture function to the
611-
# collected Python class, where it will be picked up by pytest.Class.collect()
609+
# collected Python object, where it will be picked up by pytest.Class.collect()
612610
# or pytest.Module.collect(), respectively
613-
try:
614-
pyobject = collector.obj
615-
# If the collected module is a DoctestTextfile, collector.obj is None
616-
if pyobject is None:
617-
return
618-
pyobject.__pytest_asyncio_scoped_event_loop = scoped_event_loop
619-
except (OutcomeException, Collector.CollectError):
611+
if type(collector) is Module:
620612
# Accessing Module.obj triggers a module import executing module-level
621613
# statements. A module-level pytest.skip statement raises the "Skipped"
622614
# OutcomeException or a Collector.CollectError, if the "allow_module_level"
623615
# kwargs is missing. These cases are handled correctly when they happen inside
624616
# Collector.collect(), but this hook runs before the actual collect call.
625-
return
626-
except SkipTest:
627-
# Users may also have a unittest suite that they run with pytest.
628-
# Therefore, we need to handle SkipTest to avoid breaking test collection.
629-
return
617+
# Therefore, we monkey patch Module.collect to add the scoped fixture to the
618+
# module before it runs the actual collection.
619+
def _patched_collect():
620+
collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop
621+
return collector.__original_collect()
622+
623+
collector.__original_collect = collector.collect
624+
collector.collect = _patched_collect
625+
else:
626+
pyobject = collector.obj
627+
# If the collected module is a DoctestTextfile, collector.obj is None
628+
if pyobject is None:
629+
return
630+
pyobject.__pytest_asyncio_scoped_event_loop = scoped_event_loop
630631
# When collector is a package, collector.obj is the package's __init__.py.
631632
# pytest doesn't seem to collect fixtures in __init__.py.
632633
# Using parsefactories to collect fixtures in __init__.py their baseid will end

tests/test_import.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from textwrap import dedent
2+
3+
from pytest import Pytester
4+
5+
6+
def test_import_warning(pytester: Pytester):
7+
pytester.makepyfile(
8+
dedent(
9+
"""\
10+
raise ImportWarning()
11+
12+
async def test_errors_out():
13+
pass
14+
"""
15+
)
16+
)
17+
result = pytester.runpytest("--asyncio-mode=auto")
18+
result.assert_outcomes(errors=1)

0 commit comments

Comments
 (0)