diff --git a/docs/source/reference/changelog.rst b/docs/source/reference/changelog.rst index 103cea15..79440f45 100644 --- a/docs/source/reference/changelog.rst +++ b/docs/source/reference/changelog.rst @@ -2,6 +2,11 @@ Changelog ========= +0.23.1 (2023-12-03) +=================== +- Fixes a bug that caused an internal pytest error when using module-level skips `#701 `_ + + 0.23.0 (2023-12-03) =================== This release is backwards-compatible with v0.21. diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 892d8237..fb0ed226 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -28,6 +28,7 @@ ) import pytest +from _pytest.outcomes import OutcomeException from pytest import ( Class, Collector, @@ -607,7 +608,15 @@ def scoped_event_loop( # 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() # or pytest.Module.collect(), respectively - collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop + try: + collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop + except (OutcomeException, Collector.CollectError): + # 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 # 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_pytest_skip.py b/tests/test_pytest_skip.py index 3c669cfb..17d0befc 100644 --- a/tests/test_pytest_skip.py +++ b/tests/test_pytest_skip.py @@ -3,7 +3,7 @@ from pytest import Pytester -def test_asyncio_marker_compatibility_with_skip(pytester: Pytester): +def test_asyncio_strict_mode_skip(pytester: Pytester): pytester.makepyfile( dedent( """\ @@ -21,7 +21,7 @@ async def test_no_warning_on_skip(): result.assert_outcomes(skipped=1) -def test_asyncio_auto_mode_compatibility_with_skip(pytester: Pytester): +def test_asyncio_auto_mode_skip(pytester: Pytester): pytester.makepyfile( dedent( """\ @@ -36,3 +36,55 @@ async def test_no_warning_on_skip(): ) result = pytester.runpytest("--asyncio-mode=auto") result.assert_outcomes(skipped=1) + + +def test_asyncio_strict_mode_module_level_skip(pytester: Pytester): + pytester.makepyfile( + dedent( + """\ + import pytest + + pytest.skip("Skip all tests", allow_module_level=True) + + @pytest.mark.asyncio + async def test_is_skipped(): + pass + """ + ) + ) + result = pytester.runpytest("--asyncio-mode=strict") + result.assert_outcomes(skipped=1) + + +def test_asyncio_auto_mode_module_level_skip(pytester: Pytester): + pytester.makepyfile( + dedent( + """\ + import pytest + + pytest.skip("Skip all tests", allow_module_level=True) + + async def test_is_skipped(): + pass + """ + ) + ) + result = pytester.runpytest("--asyncio-mode=auto") + result.assert_outcomes(skipped=1) + + +def test_asyncio_auto_mode_wrong_skip_usage(pytester: Pytester): + pytester.makepyfile( + dedent( + """\ + import pytest + + pytest.skip("Skip all tests") + + async def test_is_skipped(): + pass + """ + ) + ) + result = pytester.runpytest("--asyncio-mode=auto") + result.assert_outcomes(errors=1)