diff --git a/docs/source/reference/changelog.rst b/docs/source/reference/changelog.rst index f06157e4..834d65e5 100644 --- a/docs/source/reference/changelog.rst +++ b/docs/source/reference/changelog.rst @@ -6,6 +6,7 @@ Changelog =================== - 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 `_ - Fixes a bug that caused an internal pytest error when using unittest.SkipTest in a module `#711 `_ +- Fixes a bug that caused an internal pytest error when an ImportWarning is emitted in a module `#713 `_ 0.23.2 (2023-12-04) diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 4af34fec..eb013f46 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -26,10 +26,8 @@ Union, overload, ) -from unittest import SkipTest import pytest -from _pytest.outcomes import OutcomeException from pytest import ( Class, Collector, @@ -608,25 +606,28 @@ def scoped_event_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 class, where it will be picked up by pytest.Class.collect() + # collected Python object, where it will be picked up by pytest.Class.collect() # or pytest.Module.collect(), respectively - try: - pyobject = collector.obj - # If the collected module is a DoctestTextfile, collector.obj is None - if pyobject is None: - return - pyobject.__pytest_asyncio_scoped_event_loop = scoped_event_loop - except (OutcomeException, Collector.CollectError): + if type(collector) is 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. - return - except SkipTest: - # Users may also have a unittest suite that they run with pytest. - # Therefore, we need to handle SkipTest to avoid breaking test collection. - return + # Therefore, we monkey patch Module.collect to add the scoped fixture to the + # module before it runs the actual collection. + def _patched_collect(): + collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop + return collector.__original_collect() + + collector.__original_collect = collector.collect + collector.collect = _patched_collect + else: + pyobject = collector.obj + # If the collected module is a DoctestTextfile, collector.obj is None + if pyobject is None: + return + pyobject.__pytest_asyncio_scoped_event_loop = scoped_event_loop # When collector is a package, collector.obj is the package's __init__.py. # pytest doesn't seem to collect fixtures in __init__.py. # Using parsefactories to collect fixtures in __init__.py their baseid will end diff --git a/tests/test_import.py b/tests/test_import.py new file mode 100644 index 00000000..77352150 --- /dev/null +++ b/tests/test_import.py @@ -0,0 +1,18 @@ +from textwrap import dedent + +from pytest import Pytester + + +def test_import_warning(pytester: Pytester): + pytester.makepyfile( + dedent( + """\ + raise ImportWarning() + + async def test_errors_out(): + pass + """ + ) + ) + result = pytester.runpytest("--asyncio-mode=auto") + result.assert_outcomes(errors=1)