From 47c76694f8d4c76a2d7af49c3bb74769f1a9196e Mon Sep 17 00:00:00 2001 From: Aleksandr Brodin Date: Tue, 8 Aug 2023 17:20:27 +0700 Subject: [PATCH 1/3] support usefixtures with parametrize --- src/_pytest/python.py | 14 +++++++++++++- testing/python/metafunc.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index cbb82e39090..e2c81e7eb9c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -507,7 +507,7 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: for callspec in metafunc._calls: subname = f"{name}[{callspec.id}]" - yield Function.from_parent( + node = Function.from_parent( self, name=subname, callspec=callspec, @@ -515,6 +515,18 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: keywords={callspec.id: True}, originalname=name, ) + # if usefixtures is added via a parameter, then there will be + # fixtures missing from the node.fixturenames + callspec_usefixtures = tuple( + arg + for mark in node.iter_markers(name="usefixtures") + for arg in mark.args + if arg not in node.fixturenames + ) + if callspec_usefixtures: + # node.fixturenames must be unique for this parameter + node.fixturenames = [*node.fixturenames, *callspec_usefixtures] + yield node def importtestmodule( diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 4ee9e32d6e9..fa9f445368a 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -2112,3 +2112,31 @@ def test_converted_to_str(a, b): "*= 6 passed in *", ] ) + + def test_simple_usefixtures_single_argname(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def some_fixture(): + pytest.skip() + + @pytest.mark.parametrize("val", [ + 1, + pytest.param(2, marks=pytest.mark.usefixtures('some_fixture')), + 3, + ]) + def test_foo(request, val): + assert val + """ + ) + result = pytester.runpytest("-vv", "-s") + result.assert_outcomes(passed=2, skipped=1) + result.stdout.fnmatch_lines( + [ + "test_simple_usefixtures_single_argname.py::test_foo[1] PASSED", + "test_simple_usefixtures_single_argname.py::test_foo[2] SKIPPED", + "test_simple_usefixtures_single_argname.py::test_foo[3] PASSED", + ] + ) From b1a51f98e270dd4889c8ab29d685e304927ae77a Mon Sep 17 00:00:00 2001 From: Aleksandr Brodin Date: Tue, 8 Aug 2023 17:42:18 +0700 Subject: [PATCH 2/3] add changelog and update authors --- AUTHORS | 1 + changelog/4112.feature.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog/4112.feature.rst diff --git a/AUTHORS b/AUTHORS index e9e033c73f0..ff41614954e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,6 +14,7 @@ Ahn Ki-Wook Akhilesh Ramakrishnan Akiomi Kamakura Alan Velasco +Aleksandr Brodin Alessio Izzo Alex Jones Alex Lambson diff --git a/changelog/4112.feature.rst b/changelog/4112.feature.rst new file mode 100644 index 00000000000..5ce12d38e8f --- /dev/null +++ b/changelog/4112.feature.rst @@ -0,0 +1 @@ +Support use `pytest.mark.usefixtures` with `pytest.mark.parametrize`. From aa9bc87110054d8746e3b5d36fce7d8c66afefc7 Mon Sep 17 00:00:00 2001 From: Aleksandr Brodin Date: Tue, 5 Sep 2023 17:53:37 +0700 Subject: [PATCH 3/3] set unique fixtureinfo if it was changed from params marks --- src/_pytest/compat.py | 1 + src/_pytest/python.py | 22 +++++++--------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 73d77f978f7..118e7b72b6b 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -163,6 +163,7 @@ def getfuncargnames( and not isinstance( inspect.getattr_static(cls, name, default=None), staticmethod ) + and not hasattr(function, "__self__") ): arg_names = arg_names[1:] # Remove any names that will be replaced with mocks. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index e2c81e7eb9c..02300a0aa64 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -507,7 +507,7 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: for callspec in metafunc._calls: subname = f"{name}[{callspec.id}]" - node = Function.from_parent( + yield Function.from_parent( self, name=subname, callspec=callspec, @@ -515,18 +515,6 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: keywords={callspec.id: True}, originalname=name, ) - # if usefixtures is added via a parameter, then there will be - # fixtures missing from the node.fixturenames - callspec_usefixtures = tuple( - arg - for mark in node.iter_markers(name="usefixtures") - for arg in mark.args - if arg not in node.fixturenames - ) - if callspec_usefixtures: - # node.fixturenames must be unique for this parameter - node.fixturenames = [*node.fixturenames, *callspec_usefixtures] - yield node def importtestmodule( @@ -1811,9 +1799,13 @@ def __init__( if keywords: self.keywords.update(keywords) + fm = self.session._fixturemanager + fixtureinfo_ = fm.getfixtureinfo(self, self.obj, self.cls) if fixtureinfo is None: - fm = self.session._fixturemanager - fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls) + fixtureinfo = fixtureinfo_ + elif set(fixtureinfo_.names_closure) != set(fixtureinfo.names_closure): + fixtureinfo_.name2fixturedefs.update(fixtureinfo.name2fixturedefs) + fixtureinfo = fixtureinfo_ self._fixtureinfo: FuncFixtureInfo = fixtureinfo self.fixturenames = fixtureinfo.names_closure self._initrequest()