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 new file mode 100644 index 00000000..17d0befc --- /dev/null +++ b/tests/test_pytest_skip.py @@ -0,0 +1,90 @@ +from textwrap import dedent + +from pytest import Pytester + + +def test_asyncio_strict_mode_skip(pytester: Pytester): + pytester.makepyfile( + dedent( + """\ + import pytest + + pytest_plugins = "pytest_asyncio" + + @pytest.mark.asyncio + async def test_no_warning_on_skip(): + pytest.skip("Test a skip error inside asyncio") + """ + ) + ) + result = pytester.runpytest("--asyncio-mode=strict") + result.assert_outcomes(skipped=1) + + +def test_asyncio_auto_mode_skip(pytester: Pytester): + pytester.makepyfile( + dedent( + """\ + import pytest + + pytest_plugins = "pytest_asyncio" + + async def test_no_warning_on_skip(): + pytest.skip("Test a skip error inside asyncio") + """ + ) + ) + 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) diff --git a/tests/test_simple.py b/tests/test_simple.py index c448de92..05c92694 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -100,41 +100,6 @@ async def test_event_loop_before_fixture(self, loop): assert await loop.run_in_executor(None, self.foo) == 1 -def test_asyncio_marker_compatibility_with_skip(pytester: Pytester): - pytester.makepyfile( - dedent( - """\ - import pytest - - pytest_plugins = "pytest_asyncio" - - @pytest.mark.asyncio - async def test_no_warning_on_skip(): - pytest.skip("Test a skip error inside asyncio") - """ - ) - ) - result = pytester.runpytest("--asyncio-mode=strict") - result.assert_outcomes(skipped=1) - - -def test_asyncio_auto_mode_compatibility_with_skip(pytester: Pytester): - pytester.makepyfile( - dedent( - """\ - import pytest - - pytest_plugins = "pytest_asyncio" - - async def test_no_warning_on_skip(): - pytest.skip("Test a skip error inside asyncio") - """ - ) - ) - result = pytester.runpytest("--asyncio-mode=auto") - result.assert_outcomes(skipped=1) - - def test_invalid_asyncio_mode(testdir): result = testdir.runpytest("-o", "asyncio_mode=True") result.stderr.no_fnmatch_line("INTERNALERROR> *")