Skip to content

Commit

Permalink
[fix] Fixes a bug that caused an internal pytest error when using Imp…
Browse files Browse the repository at this point in the history
…ortWarning 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 <m.seifert@digitalernachschub.de>
  • Loading branch information
seifertm committed Jan 1, 2024
1 parent 31c7e6f commit 0c522bf
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/source/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/pytest-dev/pytest-asyncio/issues/708>`_
- 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>`_
- 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>`_


0.23.2 (2023-12-04)
Expand Down
31 changes: 16 additions & 15 deletions pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@
Union,
overload,
)
from unittest import SkipTest

import pytest
from _pytest.outcomes import OutcomeException
from pytest import (
Class,
Collector,
Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions tests/test_import.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 0c522bf

Please sign in to comment.