From 52ee148b098a5e3f4cba6e4e5aac878c008f195c Mon Sep 17 00:00:00 2001 From: Gupta Arpit Date: Sat, 16 Nov 2024 22:26:53 +0530 Subject: [PATCH 01/18] Remove _pytest.compat.is_generator() fix #12960 --- changelog/12960.improvement.rst | 1 + src/_pytest/compat.py | 5 ----- src/_pytest/fixtures.py | 3 +-- src/_pytest/python.py | 3 +-- testing/test_compat.py | 23 ++++++++++++----------- 5 files changed, 15 insertions(+), 20 deletions(-) create mode 100644 changelog/12960.improvement.rst diff --git a/changelog/12960.improvement.rst b/changelog/12960.improvement.rst new file mode 100644 index 00000000000..11767e022db --- /dev/null +++ b/changelog/12960.improvement.rst @@ -0,0 +1 @@ +Removed _pytest.compat.is_generator() and just using inspect.isgeneratorfunction() directly \ No newline at end of file diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 614848e0dba..82aea5e635e 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -43,11 +43,6 @@ class NotSetType(enum.Enum): # fmt: on -def is_generator(func: object) -> bool: - genfunc = inspect.isgeneratorfunction(func) - return genfunc and not iscoroutinefunction(func) - - def iscoroutinefunction(func: object) -> bool: """Return True if func is a coroutine function (a function defined with async def syntax, and doesn't contain yield), or a function decorated with diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 8f275e4d622..6407ae0c48f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -49,7 +49,6 @@ from _pytest.compat import getfuncargnames from _pytest.compat import getimfunc from _pytest.compat import getlocation -from _pytest.compat import is_generator from _pytest.compat import NOTSET from _pytest.compat import NotSetType from _pytest.compat import safe_getattr @@ -891,7 +890,7 @@ def toterminal(self, tw: TerminalWriter) -> None: def call_fixture_func( fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs ) -> FixtureValue: - if is_generator(fixturefunc): + if inspect.isgeneratorfunction(fixturefunc): fixturefunc = cast( Callable[..., Generator[FixtureValue, None, None]], fixturefunc ) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index d48a6c4a9fb..63d638e8a8c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -43,7 +43,6 @@ from _pytest.compat import get_real_func from _pytest.compat import getimfunc from _pytest.compat import is_async_function -from _pytest.compat import is_generator from _pytest.compat import LEGACY_PATH from _pytest.compat import NOTSET from _pytest.compat import safe_getattr @@ -231,7 +230,7 @@ def pytest_pycollect_makeitem( lineno=lineno + 1, ) elif getattr(obj, "__test__", True): - if is_generator(obj): + if inspect.isgeneratorfunction(obj): res = Function.from_parent(collector, name=name) reason = ( f"yield tests were removed in pytest 4.0 - {name} will be ignored" diff --git a/testing/test_compat.py b/testing/test_compat.py index 2c6b0269c27..32a8b771e08 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -6,12 +6,12 @@ from functools import partial from functools import wraps import sys +import inspect from typing import TYPE_CHECKING from _pytest.compat import _PytestWrapper from _pytest.compat import assert_never from _pytest.compat import get_real_func -from _pytest.compat import is_generator from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass from _pytest.outcomes import OutcomeException @@ -30,8 +30,8 @@ def zap(): def foo(): pass # pragma: no cover - assert is_generator(zap) - assert not is_generator(foo) + assert inspect.isgeneratorfunction(zap) + assert not inspect.isgeneratorfunction(foo) def test_real_func_loop_limit() -> None: @@ -99,14 +99,15 @@ def foo(x): def test_is_generator_asyncio(pytester: Pytester) -> None: pytester.makepyfile( """ - from _pytest.compat import is_generator import asyncio + import inspect + @asyncio.coroutine def baz(): yield from [1,2,3] def test_is_generator_asyncio(): - assert not is_generator(baz) + assert not inspect.isgeneratorfunction(baz) """ ) # avoid importing asyncio into pytest's own process, @@ -118,7 +119,7 @@ def test_is_generator_asyncio(): def test_is_generator_async_syntax(pytester: Pytester) -> None: pytester.makepyfile( """ - from _pytest.compat import is_generator + import inspect def test_is_generator_py35(): async def foo(): await foo() @@ -126,8 +127,8 @@ async def foo(): async def bar(): pass - assert not is_generator(foo) - assert not is_generator(bar) + assert not inspect.isgeneratorfunction(foo) + assert not inspect.isgeneratorfunction(bar) """ ) result = pytester.runpytest() @@ -137,7 +138,7 @@ async def bar(): def test_is_generator_async_gen_syntax(pytester: Pytester) -> None: pytester.makepyfile( """ - from _pytest.compat import is_generator + import inspect def test_is_generator(): async def foo(): yield @@ -146,8 +147,8 @@ async def foo(): async def bar(): yield - assert not is_generator(foo) - assert not is_generator(bar) + assert not inspect.isgeneratorfunction(foo) + assert not inspect.isgeneratorfunction(bar) """ ) result = pytester.runpytest() From 16cab96de4d1aed54cc4aefc120f7747942b6587 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:57:24 +0000 Subject: [PATCH 02/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/12960.improvement.rst | 2 +- testing/test_compat.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/12960.improvement.rst b/changelog/12960.improvement.rst index 11767e022db..ac9c9e431b1 100644 --- a/changelog/12960.improvement.rst +++ b/changelog/12960.improvement.rst @@ -1 +1 @@ -Removed _pytest.compat.is_generator() and just using inspect.isgeneratorfunction() directly \ No newline at end of file +Removed _pytest.compat.is_generator() and just using inspect.isgeneratorfunction() directly diff --git a/testing/test_compat.py b/testing/test_compat.py index 32a8b771e08..17f343ab5d2 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -5,8 +5,8 @@ from functools import cached_property from functools import partial from functools import wraps -import sys import inspect +import sys from typing import TYPE_CHECKING from _pytest.compat import _PytestWrapper From a44da2d1282baa8c8687cb2325d81693eee6c777 Mon Sep 17 00:00:00 2001 From: Gupta Arpit Date: Sun, 17 Nov 2024 01:33:53 +0530 Subject: [PATCH 03/18] Added suggested changes --- src/_pytest/python.py | 6 ++---- testing/test_compat.py | 39 --------------------------------------- 2 files changed, 2 insertions(+), 43 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 63d638e8a8c..bae770e2062 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -231,15 +231,13 @@ def pytest_pycollect_makeitem( ) elif getattr(obj, "__test__", True): if inspect.isgeneratorfunction(obj): - res = Function.from_parent(collector, name=name) reason = ( f"yield tests were removed in pytest 4.0 - {name} will be ignored" ) - res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) - res.warn(PytestCollectionWarning(reason)) - return res + raise RuntimeError(reason) # Raise a hard error instead of xfail else: return list(collector._genfunctions(name, obj)) + return None return None diff --git a/testing/test_compat.py b/testing/test_compat.py index 17f343ab5d2..65c4b10ff7f 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -116,45 +116,6 @@ def test_is_generator_asyncio(): result.stdout.fnmatch_lines(["*1 passed*"]) -def test_is_generator_async_syntax(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import inspect - def test_is_generator_py35(): - async def foo(): - await foo() - - async def bar(): - pass - - assert not inspect.isgeneratorfunction(foo) - assert not inspect.isgeneratorfunction(bar) - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines(["*1 passed*"]) - - -def test_is_generator_async_gen_syntax(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import inspect - def test_is_generator(): - async def foo(): - yield - await foo() - - async def bar(): - yield - - assert not inspect.isgeneratorfunction(foo) - assert not inspect.isgeneratorfunction(bar) - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines(["*1 passed*"]) - - class ErrorsHelper: @property def raise_baseexception(self): From 526529a85fbee376de740c76a1397ac43f0a6ab2 Mon Sep 17 00:00:00 2001 From: Gupta Arpit Date: Sat, 16 Nov 2024 22:26:53 +0530 Subject: [PATCH 04/18] Remove _pytest.compat.is_generator() fix #12960 --- changelog/12960.improvement.rst | 1 + src/_pytest/compat.py | 5 ----- src/_pytest/fixtures.py | 3 +-- src/_pytest/python.py | 3 +-- testing/test_compat.py | 23 ++++++++++++----------- 5 files changed, 15 insertions(+), 20 deletions(-) create mode 100644 changelog/12960.improvement.rst diff --git a/changelog/12960.improvement.rst b/changelog/12960.improvement.rst new file mode 100644 index 00000000000..11767e022db --- /dev/null +++ b/changelog/12960.improvement.rst @@ -0,0 +1 @@ +Removed _pytest.compat.is_generator() and just using inspect.isgeneratorfunction() directly \ No newline at end of file diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 614848e0dba..82aea5e635e 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -43,11 +43,6 @@ class NotSetType(enum.Enum): # fmt: on -def is_generator(func: object) -> bool: - genfunc = inspect.isgeneratorfunction(func) - return genfunc and not iscoroutinefunction(func) - - def iscoroutinefunction(func: object) -> bool: """Return True if func is a coroutine function (a function defined with async def syntax, and doesn't contain yield), or a function decorated with diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 8f275e4d622..6407ae0c48f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -49,7 +49,6 @@ from _pytest.compat import getfuncargnames from _pytest.compat import getimfunc from _pytest.compat import getlocation -from _pytest.compat import is_generator from _pytest.compat import NOTSET from _pytest.compat import NotSetType from _pytest.compat import safe_getattr @@ -891,7 +890,7 @@ def toterminal(self, tw: TerminalWriter) -> None: def call_fixture_func( fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs ) -> FixtureValue: - if is_generator(fixturefunc): + if inspect.isgeneratorfunction(fixturefunc): fixturefunc = cast( Callable[..., Generator[FixtureValue, None, None]], fixturefunc ) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index d48a6c4a9fb..63d638e8a8c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -43,7 +43,6 @@ from _pytest.compat import get_real_func from _pytest.compat import getimfunc from _pytest.compat import is_async_function -from _pytest.compat import is_generator from _pytest.compat import LEGACY_PATH from _pytest.compat import NOTSET from _pytest.compat import safe_getattr @@ -231,7 +230,7 @@ def pytest_pycollect_makeitem( lineno=lineno + 1, ) elif getattr(obj, "__test__", True): - if is_generator(obj): + if inspect.isgeneratorfunction(obj): res = Function.from_parent(collector, name=name) reason = ( f"yield tests were removed in pytest 4.0 - {name} will be ignored" diff --git a/testing/test_compat.py b/testing/test_compat.py index 2c6b0269c27..32a8b771e08 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -6,12 +6,12 @@ from functools import partial from functools import wraps import sys +import inspect from typing import TYPE_CHECKING from _pytest.compat import _PytestWrapper from _pytest.compat import assert_never from _pytest.compat import get_real_func -from _pytest.compat import is_generator from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass from _pytest.outcomes import OutcomeException @@ -30,8 +30,8 @@ def zap(): def foo(): pass # pragma: no cover - assert is_generator(zap) - assert not is_generator(foo) + assert inspect.isgeneratorfunction(zap) + assert not inspect.isgeneratorfunction(foo) def test_real_func_loop_limit() -> None: @@ -99,14 +99,15 @@ def foo(x): def test_is_generator_asyncio(pytester: Pytester) -> None: pytester.makepyfile( """ - from _pytest.compat import is_generator import asyncio + import inspect + @asyncio.coroutine def baz(): yield from [1,2,3] def test_is_generator_asyncio(): - assert not is_generator(baz) + assert not inspect.isgeneratorfunction(baz) """ ) # avoid importing asyncio into pytest's own process, @@ -118,7 +119,7 @@ def test_is_generator_asyncio(): def test_is_generator_async_syntax(pytester: Pytester) -> None: pytester.makepyfile( """ - from _pytest.compat import is_generator + import inspect def test_is_generator_py35(): async def foo(): await foo() @@ -126,8 +127,8 @@ async def foo(): async def bar(): pass - assert not is_generator(foo) - assert not is_generator(bar) + assert not inspect.isgeneratorfunction(foo) + assert not inspect.isgeneratorfunction(bar) """ ) result = pytester.runpytest() @@ -137,7 +138,7 @@ async def bar(): def test_is_generator_async_gen_syntax(pytester: Pytester) -> None: pytester.makepyfile( """ - from _pytest.compat import is_generator + import inspect def test_is_generator(): async def foo(): yield @@ -146,8 +147,8 @@ async def foo(): async def bar(): yield - assert not is_generator(foo) - assert not is_generator(bar) + assert not inspect.isgeneratorfunction(foo) + assert not inspect.isgeneratorfunction(bar) """ ) result = pytester.runpytest() From 42cd2965e59b110419e2f6ac91e35bcdfdb3e6a8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:57:24 +0000 Subject: [PATCH 05/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/12960.improvement.rst | 2 +- testing/test_compat.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/12960.improvement.rst b/changelog/12960.improvement.rst index 11767e022db..ac9c9e431b1 100644 --- a/changelog/12960.improvement.rst +++ b/changelog/12960.improvement.rst @@ -1 +1 @@ -Removed _pytest.compat.is_generator() and just using inspect.isgeneratorfunction() directly \ No newline at end of file +Removed _pytest.compat.is_generator() and just using inspect.isgeneratorfunction() directly diff --git a/testing/test_compat.py b/testing/test_compat.py index 32a8b771e08..17f343ab5d2 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -5,8 +5,8 @@ from functools import cached_property from functools import partial from functools import wraps -import sys import inspect +import sys from typing import TYPE_CHECKING from _pytest.compat import _PytestWrapper From 38a241671ccd56e6c6ff131f8e85bb5ef82fc701 Mon Sep 17 00:00:00 2001 From: Gupta Arpit Date: Sun, 17 Nov 2024 01:33:53 +0530 Subject: [PATCH 06/18] Added suggested changes --- src/_pytest/python.py | 6 ++---- testing/test_compat.py | 39 --------------------------------------- 2 files changed, 2 insertions(+), 43 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 63d638e8a8c..bae770e2062 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -231,15 +231,13 @@ def pytest_pycollect_makeitem( ) elif getattr(obj, "__test__", True): if inspect.isgeneratorfunction(obj): - res = Function.from_parent(collector, name=name) reason = ( f"yield tests were removed in pytest 4.0 - {name} will be ignored" ) - res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) - res.warn(PytestCollectionWarning(reason)) - return res + raise RuntimeError(reason) # Raise a hard error instead of xfail else: return list(collector._genfunctions(name, obj)) + return None return None diff --git a/testing/test_compat.py b/testing/test_compat.py index 17f343ab5d2..65c4b10ff7f 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -116,45 +116,6 @@ def test_is_generator_asyncio(): result.stdout.fnmatch_lines(["*1 passed*"]) -def test_is_generator_async_syntax(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import inspect - def test_is_generator_py35(): - async def foo(): - await foo() - - async def bar(): - pass - - assert not inspect.isgeneratorfunction(foo) - assert not inspect.isgeneratorfunction(bar) - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines(["*1 passed*"]) - - -def test_is_generator_async_gen_syntax(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import inspect - def test_is_generator(): - async def foo(): - yield - await foo() - - async def bar(): - yield - - assert not inspect.isgeneratorfunction(foo) - assert not inspect.isgeneratorfunction(bar) - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines(["*1 passed*"]) - - class ErrorsHelper: @property def raise_baseexception(self): From ecc26e04f41d9c60f94cc80d0b2f0c6cd77117dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 20:04:32 +0000 Subject: [PATCH 07/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/python.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index bae770e2062..23b65765a53 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -56,7 +56,6 @@ from _pytest.fixtures import FuncFixtureInfo from _pytest.fixtures import get_scope_node from _pytest.main import Session -from _pytest.mark import MARK_GEN from _pytest.mark import ParameterSet from _pytest.mark.structures import get_unpacked_marks from _pytest.mark.structures import Mark From 3ab4a77606652a4aa11f2ffba8dedf84b3474d99 Mon Sep 17 00:00:00 2001 From: Arpit Gupta <113178590+arpitgupta-it@users.noreply.github.com> Date: Sun, 17 Nov 2024 01:50:30 +0530 Subject: [PATCH 08/18] Removed stdlib tests as per review --- testing/test_compat.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/testing/test_compat.py b/testing/test_compat.py index 65c4b10ff7f..07c28cebdd1 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -23,17 +23,6 @@ from typing_extensions import Literal -def test_is_generator() -> None: - def zap(): - yield # pragma: no cover - - def foo(): - pass # pragma: no cover - - assert inspect.isgeneratorfunction(zap) - assert not inspect.isgeneratorfunction(foo) - - def test_real_func_loop_limit() -> None: class Evil: def __init__(self): @@ -95,27 +84,6 @@ def foo(x): assert get_real_func(partial(foo)) is foo -@pytest.mark.skipif(sys.version_info >= (3, 11), reason="coroutine removed") -def test_is_generator_asyncio(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import asyncio - import inspect - - @asyncio.coroutine - def baz(): - yield from [1,2,3] - - def test_is_generator_asyncio(): - assert not inspect.isgeneratorfunction(baz) - """ - ) - # avoid importing asyncio into pytest's own process, - # which in turn imports logging (#8) - result = pytester.runpytest_subprocess() - result.stdout.fnmatch_lines(["*1 passed*"]) - - class ErrorsHelper: @property def raise_baseexception(self): From 362a2179305139b3c877f0c2b2ed004a2f8ecf09 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 20:20:49 +0000 Subject: [PATCH 09/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_compat.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/testing/test_compat.py b/testing/test_compat.py index 07c28cebdd1..86868858956 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -5,8 +5,6 @@ from functools import cached_property from functools import partial from functools import wraps -import inspect -import sys from typing import TYPE_CHECKING from _pytest.compat import _PytestWrapper @@ -15,7 +13,6 @@ from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass from _pytest.outcomes import OutcomeException -from _pytest.pytester import Pytester import pytest From 78017f41313f5135f0afe00d9ad57937b4e41045 Mon Sep 17 00:00:00 2001 From: Gupta Arpit Date: Sun, 17 Nov 2024 13:45:51 +0530 Subject: [PATCH 10/18] Pushed review changes --- changelog/12960.improvement.rst | 2 +- src/_pytest/python.py | 8 ++------ testing/test_terminal.py | 5 ----- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/changelog/12960.improvement.rst b/changelog/12960.improvement.rst index ac9c9e431b1..e935f90704b 100644 --- a/changelog/12960.improvement.rst +++ b/changelog/12960.improvement.rst @@ -1 +1 @@ -Removed _pytest.compat.is_generator() and just using inspect.isgeneratorfunction() directly +Test functions containing a yield now cause an explicit error. They have not been run since Pytest 4.0, and were previously marked as an expected failure and deprecation warning. \ No newline at end of file diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 23b65765a53..d1e8d369e7c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -230,12 +230,8 @@ def pytest_pycollect_makeitem( ) elif getattr(obj, "__test__", True): if inspect.isgeneratorfunction(obj): - reason = ( - f"yield tests were removed in pytest 4.0 - {name} will be ignored" - ) - raise RuntimeError(reason) # Raise a hard error instead of xfail - else: - return list(collector._genfunctions(name, obj)) + raise RuntimeError("'yield' keyword is allowed in fixtures, but not in tests ({name})") + return list(collector._genfunctions(name, obj)) return None return None diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 14c152d6123..872703900cd 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1042,10 +1042,6 @@ def test_pass(): class TestClass(object): def test_skip(self): pytest.skip("hello") - def test_gen(): - def check(x): - assert x == 1 - yield check, 0 """ ) @@ -1058,7 +1054,6 @@ def test_verbose_reporting(self, verbose_testfile, pytester: Pytester) -> None: "*test_verbose_reporting.py::test_fail *FAIL*", "*test_verbose_reporting.py::test_pass *PASS*", "*test_verbose_reporting.py::TestClass::test_skip *SKIP*", - "*test_verbose_reporting.py::test_gen *XFAIL*", ] ) assert result.ret == 1 From a074630394daa767ea2bc700f3626a2533288268 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 Nov 2024 08:16:14 +0000 Subject: [PATCH 11/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/12960.improvement.rst | 2 +- src/_pytest/python.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/changelog/12960.improvement.rst b/changelog/12960.improvement.rst index e935f90704b..43e981dc648 100644 --- a/changelog/12960.improvement.rst +++ b/changelog/12960.improvement.rst @@ -1 +1 @@ -Test functions containing a yield now cause an explicit error. They have not been run since Pytest 4.0, and were previously marked as an expected failure and deprecation warning. \ No newline at end of file +Test functions containing a yield now cause an explicit error. They have not been run since Pytest 4.0, and were previously marked as an expected failure and deprecation warning. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index d1e8d369e7c..d117fa58d01 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -230,7 +230,9 @@ def pytest_pycollect_makeitem( ) elif getattr(obj, "__test__", True): if inspect.isgeneratorfunction(obj): - raise RuntimeError("'yield' keyword is allowed in fixtures, but not in tests ({name})") + raise RuntimeError( + "'yield' keyword is allowed in fixtures, but not in tests ({name})" + ) return list(collector._genfunctions(name, obj)) return None return None From 5398d7c15aeb1de7595e5ed4fad0d787feb3d0f2 Mon Sep 17 00:00:00 2001 From: Gupta Arpit Date: Sun, 17 Nov 2024 17:26:00 +0530 Subject: [PATCH 12/18] Add test for RuntimeError on 'yield' in tests --- testing/test_collection.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/testing/test_collection.py b/testing/test_collection.py index aba8f8ea48d..80c250027c6 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1878,3 +1878,21 @@ def test_respect_system_exceptions( result.stdout.fnmatch_lines([f"*{head}*"]) result.stdout.fnmatch_lines([msg]) result.stdout.no_fnmatch_line(f"*{tail}*") + + +def test_yield_disallowed_in_tests(pytester: Pytester): + """Ensure generator test functions with 'yield' raise a RuntimeError.""" + pytester.makepyfile( + """ + def test_with_yield(): + yield 1 + """ + ) + result = pytester.runpytest() + assert result.ret == 2 + result.stdout.fnmatch_lines( + ["*RuntimeError: 'yield' keyword is allowed in fixtures, but not in tests (*)*"] + ) + result.stdout.fnmatch_lines( + ["*collected 0 items*"] + ) \ No newline at end of file From bad14eec09c40370270d7fb428deb929c6e2718f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 Nov 2024 11:56:25 +0000 Subject: [PATCH 13/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_collection.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/testing/test_collection.py b/testing/test_collection.py index 80c250027c6..53463d81866 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1893,6 +1893,4 @@ def test_with_yield(): result.stdout.fnmatch_lines( ["*RuntimeError: 'yield' keyword is allowed in fixtures, but not in tests (*)*"] ) - result.stdout.fnmatch_lines( - ["*collected 0 items*"] - ) \ No newline at end of file + result.stdout.fnmatch_lines(["*collected 0 items*"]) From 1bcaf2c58d44e4426dd1ef6989206e9790710285 Mon Sep 17 00:00:00 2001 From: Gupta Arpit Date: Mon, 18 Nov 2024 00:37:00 +0530 Subject: [PATCH 14/18] Rename changelog/12960.breaking.rst --- changelog/{12960.improvement.rst => 12960.breaking.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog/{12960.improvement.rst => 12960.breaking.rst} (100%) diff --git a/changelog/12960.improvement.rst b/changelog/12960.breaking.rst similarity index 100% rename from changelog/12960.improvement.rst rename to changelog/12960.breaking.rst From 3de3d0d79ed2efe23a6e73d386a54b75a712fcf1 Mon Sep 17 00:00:00 2001 From: Gupta Arpit Date: Mon, 18 Nov 2024 15:54:42 +0530 Subject: [PATCH 15/18] Added final review requested changes --- changelog/12960.breaking.rst | 2 +- doc/en/deprecations.rst | 60 ++++++++++++++++++------------------ src/_pytest/python.py | 5 +-- testing/test_collection.py | 7 +++-- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/changelog/12960.breaking.rst b/changelog/12960.breaking.rst index 43e981dc648..942348d8245 100644 --- a/changelog/12960.breaking.rst +++ b/changelog/12960.breaking.rst @@ -1 +1 @@ -Test functions containing a yield now cause an explicit error. They have not been run since Pytest 4.0, and were previously marked as an expected failure and deprecation warning. +Test functions containing a yield now cause an explicit error. They have not been run since pytest 4.0, and were previously marked as an expected failure and deprecation warning. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 59f9d83451b..689ba96b615 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -374,6 +374,36 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +.. _yield tests deprecated: + +``yield`` tests +~~~~~~~~~~~~~~~ + +.. versionremoved:: 8.4 + +pytest no longer supports ``yield``-style tests, where a test function actually ``yield`` functions and values +that are then turned into proper test methods. Example: + +.. code-block:: python + + def check(x, y): + assert x**x == y + + + def test_squared(): + yield check, 2, 4 + yield check, 3, 9 + +This would result in two actual test functions being generated. + +This form of test function doesn't support fixtures properly, and users should switch to ``pytest.mark.parametrize``: + +.. code-block:: python + + @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)]) + def test_squared(x, y): + assert x**x == y + .. _nose-deprecation: Support for tests written for nose @@ -1270,36 +1300,6 @@ with the ``name`` parameter: return cell() -.. _yield tests deprecated: - -``yield`` tests -~~~~~~~~~~~~~~~ - -.. versionremoved:: 4.0 - -pytest supported ``yield``-style tests, where a test function actually ``yield`` functions and values -that are then turned into proper test methods. Example: - -.. code-block:: python - - def check(x, y): - assert x**x == y - - - def test_squared(): - yield check, 2, 4 - yield check, 3, 9 - -This would result into two actual test functions being generated. - -This form of test function doesn't support fixtures properly, and users should switch to ``pytest.mark.parametrize``: - -.. code-block:: python - - @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)]) - def test_squared(x, y): - assert x**x == y - .. _internal classes accessed through node deprecated: Internal classes accessed through ``Node`` diff --git a/src/_pytest/python.py b/src/_pytest/python.py index d117fa58d01..1456b5212d4 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -230,8 +230,9 @@ def pytest_pycollect_makeitem( ) elif getattr(obj, "__test__", True): if inspect.isgeneratorfunction(obj): - raise RuntimeError( - "'yield' keyword is allowed in fixtures, but not in tests ({name})" + fail( + f"'yield' keyword is allowed in fixtures, but not in tests ({name})", + pytrace=False, ) return list(collector._genfunctions(name, obj)) return None diff --git a/testing/test_collection.py b/testing/test_collection.py index 53463d81866..aed0c34091d 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1881,7 +1881,7 @@ def test_respect_system_exceptions( def test_yield_disallowed_in_tests(pytester: Pytester): - """Ensure generator test functions with 'yield' raise a RuntimeError.""" + """Ensure generator test functions with 'yield' fail collection (#12960).""" pytester.makepyfile( """ def test_with_yield(): @@ -1891,6 +1891,7 @@ def test_with_yield(): result = pytester.runpytest() assert result.ret == 2 result.stdout.fnmatch_lines( - ["*RuntimeError: 'yield' keyword is allowed in fixtures, but not in tests (*)*"] + ["*'yield' keyword is allowed in fixtures, but not in tests (test_with_yield)*"] ) - result.stdout.fnmatch_lines(["*collected 0 items*"]) + # Assert that no tests were collected + result.stdout.fnmatch_lines(["*collected 0 items*"]) \ No newline at end of file From 7c24a0ba1919c43e0f4ac8c48bd2126c8a4d64b2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:26:40 +0000 Subject: [PATCH 16/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/en/deprecations.rst | 2 +- testing/test_collection.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 689ba96b615..a6b7ed4beab 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -403,7 +403,7 @@ This form of test function doesn't support fixtures properly, and users should s @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)]) def test_squared(x, y): assert x**x == y - + .. _nose-deprecation: Support for tests written for nose diff --git a/testing/test_collection.py b/testing/test_collection.py index aed0c34091d..7d28610e015 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1894,4 +1894,4 @@ def test_with_yield(): ["*'yield' keyword is allowed in fixtures, but not in tests (test_with_yield)*"] ) # Assert that no tests were collected - result.stdout.fnmatch_lines(["*collected 0 items*"]) \ No newline at end of file + result.stdout.fnmatch_lines(["*collected 0 items*"]) From 1a5dbcb04d2677f46175d5b03dfd63f0debbf43e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 18 Nov 2024 09:30:53 -0300 Subject: [PATCH 17/18] Update changelog/12960.breaking.rst --- changelog/12960.breaking.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/12960.breaking.rst b/changelog/12960.breaking.rst index 942348d8245..3ab87e6fe23 100644 --- a/changelog/12960.breaking.rst +++ b/changelog/12960.breaking.rst @@ -1 +1,3 @@ Test functions containing a yield now cause an explicit error. They have not been run since pytest 4.0, and were previously marked as an expected failure and deprecation warning. + +See :ref:`the docs ` for more information. From 70e41cb2de00d19761033537574b318c9728fb45 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 18 Nov 2024 15:30:34 -0300 Subject: [PATCH 18/18] Update doc/en/deprecations.rst --- doc/en/deprecations.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index a6b7ed4beab..18df64c9204 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -379,8 +379,14 @@ Some breaking changes which could not be deprecated are also listed. ``yield`` tests ~~~~~~~~~~~~~~~ +.. versionremoved:: 4.0 + + ``yield`` tests ``xfail``. + .. versionremoved:: 8.4 + ``yield`` tests raise a collection error. + pytest no longer supports ``yield``-style tests, where a test function actually ``yield`` functions and values that are then turned into proper test methods. Example: