Skip to content

Commit c8792bd

Browse files
committed
python,unittest: replace obj fixture patching with FixtureManager._register_fixture
Instead of modifying user objects like modules and classes that we really shouldn't be touching, use the new `_register_fixture` internal API to do it directly.
1 parent 3234c79 commit c8792bd

File tree

2 files changed

+91
-75
lines changed

2 files changed

+91
-75
lines changed

src/_pytest/python.py

+58-50
Original file line numberDiff line numberDiff line change
@@ -582,13 +582,13 @@ def _getobj(self):
582582
return importtestmodule(self.path, self.config)
583583

584584
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
585-
self._inject_setup_module_fixture()
586-
self._inject_setup_function_fixture()
585+
self._register_setup_module_fixture()
586+
self._register_setup_function_fixture()
587587
self.session._fixturemanager.parsefactories(self)
588588
return super().collect()
589589

590-
def _inject_setup_module_fixture(self) -> None:
591-
"""Inject a hidden autouse, module scoped fixture into the collected module object
590+
def _register_setup_module_fixture(self) -> None:
591+
"""Register an autouse, module-scoped fixture for the collected module object
592592
that invokes setUpModule/tearDownModule if either or both are available.
593593
594594
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
@@ -604,23 +604,25 @@ def _inject_setup_module_fixture(self) -> None:
604604
if setup_module is None and teardown_module is None:
605605
return
606606

607-
@fixtures.fixture(
608-
autouse=True,
609-
scope="module",
610-
# Use a unique name to speed up lookup.
611-
name=f"_xunit_setup_module_fixture_{self.obj.__name__}",
612-
)
613607
def xunit_setup_module_fixture(request) -> Generator[None, None, None]:
608+
module = request.module
614609
if setup_module is not None:
615-
_call_with_optional_argument(setup_module, request.module)
610+
_call_with_optional_argument(setup_module, module)
616611
yield
617612
if teardown_module is not None:
618-
_call_with_optional_argument(teardown_module, request.module)
613+
_call_with_optional_argument(teardown_module, module)
619614

620-
self.obj.__pytest_setup_module = xunit_setup_module_fixture
615+
self.session._fixturemanager._register_fixture(
616+
# Use a unique name to speed up lookup.
617+
name=f"_xunit_setup_module_fixture_{self.obj.__name__}",
618+
func=xunit_setup_module_fixture,
619+
nodeid=self.nodeid,
620+
scope="module",
621+
autouse=True,
622+
)
621623

622-
def _inject_setup_function_fixture(self) -> None:
623-
"""Inject a hidden autouse, function scoped fixture into the collected module object
624+
def _register_setup_function_fixture(self) -> None:
625+
"""Register an autouse, function-scoped fixture for the collected module object
624626
that invokes setup_function/teardown_function if either or both are available.
625627
626628
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
@@ -633,25 +635,27 @@ def _inject_setup_function_fixture(self) -> None:
633635
if setup_function is None and teardown_function is None:
634636
return
635637

636-
@fixtures.fixture(
637-
autouse=True,
638-
scope="function",
639-
# Use a unique name to speed up lookup.
640-
name=f"_xunit_setup_function_fixture_{self.obj.__name__}",
641-
)
642638
def xunit_setup_function_fixture(request) -> Generator[None, None, None]:
643639
if request.instance is not None:
644640
# in this case we are bound to an instance, so we need to let
645641
# setup_method handle this
646642
yield
647643
return
644+
function = request.function
648645
if setup_function is not None:
649-
_call_with_optional_argument(setup_function, request.function)
646+
_call_with_optional_argument(setup_function, function)
650647
yield
651648
if teardown_function is not None:
652-
_call_with_optional_argument(teardown_function, request.function)
649+
_call_with_optional_argument(teardown_function, function)
653650

654-
self.obj.__pytest_setup_function = xunit_setup_function_fixture
651+
self.session._fixturemanager._register_fixture(
652+
# Use a unique name to speed up lookup.
653+
name=f"_xunit_setup_function_fixture_{self.obj.__name__}",
654+
func=xunit_setup_function_fixture,
655+
nodeid=self.nodeid,
656+
scope="function",
657+
autouse=True,
658+
)
655659

656660

657661
class Package(nodes.Directory):
@@ -795,15 +799,15 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
795799
)
796800
return []
797801

798-
self._inject_setup_class_fixture()
799-
self._inject_setup_method_fixture()
802+
self._register_setup_class_fixture()
803+
self._register_setup_method_fixture()
800804

801805
self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid)
802806

803807
return super().collect()
804808

805-
def _inject_setup_class_fixture(self) -> None:
806-
"""Inject a hidden autouse, class scoped fixture into the collected class object
809+
def _register_setup_class_fixture(self) -> None:
810+
"""Register an autouse, class scoped fixture into the collected class object
807811
that invokes setup_class/teardown_class if either or both are available.
808812
809813
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
@@ -814,25 +818,27 @@ def _inject_setup_class_fixture(self) -> None:
814818
if setup_class is None and teardown_class is None:
815819
return
816820

817-
@fixtures.fixture(
818-
autouse=True,
819-
scope="class",
820-
# Use a unique name to speed up lookup.
821-
name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}",
822-
)
823-
def xunit_setup_class_fixture(cls) -> Generator[None, None, None]:
821+
def xunit_setup_class_fixture(request) -> Generator[None, None, None]:
822+
cls = request.cls
824823
if setup_class is not None:
825824
func = getimfunc(setup_class)
826-
_call_with_optional_argument(func, self.obj)
825+
_call_with_optional_argument(func, cls)
827826
yield
828827
if teardown_class is not None:
829828
func = getimfunc(teardown_class)
830-
_call_with_optional_argument(func, self.obj)
829+
_call_with_optional_argument(func, cls)
831830

832-
self.obj.__pytest_setup_class = xunit_setup_class_fixture
831+
self.session._fixturemanager._register_fixture(
832+
# Use a unique name to speed up lookup.
833+
name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}",
834+
func=xunit_setup_class_fixture,
835+
nodeid=self.nodeid,
836+
scope="class",
837+
autouse=True,
838+
)
833839

834-
def _inject_setup_method_fixture(self) -> None:
835-
"""Inject a hidden autouse, function scoped fixture into the collected class object
840+
def _register_setup_method_fixture(self) -> None:
841+
"""Register an autouse, function scoped fixture into the collected class object
836842
that invokes setup_method/teardown_method if either or both are available.
837843
838844
Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with
@@ -845,23 +851,25 @@ def _inject_setup_method_fixture(self) -> None:
845851
if setup_method is None and teardown_method is None:
846852
return
847853

848-
@fixtures.fixture(
849-
autouse=True,
850-
scope="function",
851-
# Use a unique name to speed up lookup.
852-
name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}",
853-
)
854-
def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]:
854+
def xunit_setup_method_fixture(request) -> Generator[None, None, None]:
855+
instance = request.instance
855856
method = request.function
856857
if setup_method is not None:
857-
func = getattr(self, setup_name)
858+
func = getattr(instance, setup_name)
858859
_call_with_optional_argument(func, method)
859860
yield
860861
if teardown_method is not None:
861-
func = getattr(self, teardown_name)
862+
func = getattr(instance, teardown_name)
862863
_call_with_optional_argument(func, method)
863864

864-
self.obj.__pytest_setup_method = xunit_setup_method_fixture
865+
self.session._fixturemanager._register_fixture(
866+
# Use a unique name to speed up lookup.
867+
name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}",
868+
func=xunit_setup_method_fixture,
869+
nodeid=self.nodeid,
870+
scope="function",
871+
autouse=True,
872+
)
865873

866874

867875
def hasinit(obj: object) -> bool:

src/_pytest/unittest.py

+33-25
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ def collect(self) -> Iterable[Union[Item, Collector]]:
7070

7171
skipped = _is_skipped(cls)
7272
if not skipped:
73-
self._inject_unittest_setup_method_fixture(cls)
74-
self._inject_unittest_setup_class_fixture(cls)
75-
self._inject_setup_class_fixture()
73+
self._register_unittest_setup_method_fixture(cls)
74+
self._register_unittest_setup_class_fixture(cls)
75+
self._register_setup_class_fixture()
7676

7777
self.session._fixturemanager.parsefactories(self, unittest=True)
7878
loader = TestLoader()
@@ -93,24 +93,21 @@ def collect(self) -> Iterable[Union[Item, Collector]]:
9393
if ut is None or runtest != ut.TestCase.runTest: # type: ignore
9494
yield TestCaseFunction.from_parent(self, name="runTest")
9595

96-
def _inject_unittest_setup_class_fixture(self, cls: type) -> None:
97-
"""Injects a hidden auto-use fixture to invoke setUpClass and
96+
def _register_unittest_setup_class_fixture(self, cls: type) -> None:
97+
"""Register an auto-use fixture to invoke setUpClass and
9898
tearDownClass (#517)."""
9999
setup = getattr(cls, "setUpClass", None)
100100
teardown = getattr(cls, "tearDownClass", None)
101101
if setup is None and teardown is None:
102102
return None
103103
cleanup = getattr(cls, "doClassCleanups", lambda: None)
104104

105-
@pytest.fixture(
106-
scope="class",
107-
autouse=True,
108-
# Use a unique name to speed up lookup.
109-
name=f"_unittest_setUpClass_fixture_{cls.__qualname__}",
110-
)
111-
def fixture(self) -> Generator[None, None, None]:
112-
if _is_skipped(self):
113-
reason = self.__unittest_skip_why__
105+
def unittest_setup_class_fixture(
106+
request: FixtureRequest,
107+
) -> Generator[None, None, None]:
108+
cls = request.cls
109+
if _is_skipped(cls):
110+
reason = cls.__unittest_skip_why__
114111
raise pytest.skip.Exception(reason, _use_item_location=True)
115112
if setup is not None:
116113
try:
@@ -127,23 +124,27 @@ def fixture(self) -> Generator[None, None, None]:
127124
finally:
128125
cleanup()
129126

130-
cls.__pytest_class_setup = fixture # type: ignore[attr-defined]
127+
self.session._fixturemanager._register_fixture(
128+
# Use a unique name to speed up lookup.
129+
name=f"_unittest_setUpClass_fixture_{cls.__qualname__}",
130+
func=unittest_setup_class_fixture,
131+
nodeid=self.nodeid,
132+
scope="class",
133+
autouse=True,
134+
)
131135

132-
def _inject_unittest_setup_method_fixture(self, cls: type) -> None:
133-
"""Injects a hidden auto-use fixture to invoke setup_method and
136+
def _register_unittest_setup_method_fixture(self, cls: type) -> None:
137+
"""Register an auto-use fixture to invoke setup_method and
134138
teardown_method (#517)."""
135139
setup = getattr(cls, "setup_method", None)
136140
teardown = getattr(cls, "teardown_method", None)
137141
if setup is None and teardown is None:
138142
return None
139143

140-
@pytest.fixture(
141-
scope="function",
142-
autouse=True,
143-
# Use a unique name to speed up lookup.
144-
name=f"_unittest_setup_method_fixture_{cls.__qualname__}",
145-
)
146-
def fixture(self, request: FixtureRequest) -> Generator[None, None, None]:
144+
def unittest_setup_method_fixture(
145+
request: FixtureRequest,
146+
) -> Generator[None, None, None]:
147+
self = request.instance
147148
if _is_skipped(self):
148149
reason = self.__unittest_skip_why__
149150
raise pytest.skip.Exception(reason, _use_item_location=True)
@@ -153,7 +154,14 @@ def fixture(self, request: FixtureRequest) -> Generator[None, None, None]:
153154
if teardown is not None:
154155
teardown(self, request.function)
155156

156-
cls.__pytest_method_setup = fixture # type: ignore[attr-defined]
157+
self.session._fixturemanager._register_fixture(
158+
# Use a unique name to speed up lookup.
159+
name=f"_unittest_setup_method_fixture_{cls.__qualname__}",
160+
func=unittest_setup_method_fixture,
161+
nodeid=self.nodeid,
162+
scope="function",
163+
autouse=True,
164+
)
157165

158166

159167
class TestCaseFunction(Function):

0 commit comments

Comments
 (0)