diff --git a/docs/source/reference/changelog.rst b/docs/source/reference/changelog.rst index a528c07e..f72aad17 100644 --- a/docs/source/reference/changelog.rst +++ b/docs/source/reference/changelog.rst @@ -2,6 +2,11 @@ Changelog ========= +0.22.1 (UNRELEASED) +=================== +- Fixes a bug that caused an internal pytest error when using module-level skips `#655 `_ + + 0.22.0 (2023-10-31) =================== - Class-scoped and module-scoped event loops can be requested diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 18c86869..693c62a3 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -26,6 +26,7 @@ import pytest from _pytest.mark.structures import get_unpacked_marks +from _pytest.outcomes import OutcomeException from pytest import ( Collector, Config, @@ -549,7 +550,15 @@ def pytest_collectstart(collector: pytest.Collector): return # pytest.Collector.own_markers is empty at this point, # so we rely on _pytest.mark.structures.get_unpacked_marks - marks = get_unpacked_marks(collector.obj, consider_mro=True) + # The function imports the collected object if it's a module. + # This can lead to issues, when the module contains statements specifying + # test outcomes, such as "pytest.skip(allow_module_level=True)". These cases + # are handled correctly when they happen inside Collector.collect(), but this hook + # runs before the actual collect call. + try: + marks = get_unpacked_marks(collector.obj, consider_mro=True) + except (OutcomeException, Collector.CollectError): + marks = [] for mark in marks: if not mark.name == "asyncio_event_loop": continue 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 b6020c69..ec995dfb 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -260,41 +260,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> *")