Add remove_finalizer function

This commit is contained in:
RoniKish 2022-04-25 10:43:38 +03:00
parent eb8b3ad929
commit 791805984b
5 changed files with 54 additions and 1 deletions

View File

@ -285,6 +285,7 @@ Roberto Polli
Roland Puntaier
Romain Dorgueil
Roman Bolshakov
Roni Kishner
Ronny Pfannschmidt
Ross Lawley
Ruaridh Williamson

View File

@ -730,6 +730,11 @@ Here's how the previous example would look using the ``addfinalizer`` method:
It's a bit longer than yield fixtures and a bit more complex, but it
does offer some nuances for when you're in a pinch.
In addition you can use the remove_finalizer method to remove a finalizer you added
to the teardown stage.
The remove_finalizer method will remove the first finalizer match it finds and return True,
if no match was found the function will return None.
.. code-block:: pytest
$ pytest -q test_emaillib.py

View File

@ -760,6 +760,11 @@ class SubRequest(FixtureRequest):
within the requesting test context finished execution."""
self._fixturedef.addfinalizer(finalizer)
def remove_finalizer(self, finalizer: Callable[[], object]) -> None:
"""Remove finalizer/teardown function to be called after the last test
within the requesting test context finished execution."""
return self._fixturedef.remove_finalizer(finalizer)
def _schedule_finalizers(
self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
) -> None:
@ -1003,6 +1008,12 @@ class FixtureDef(Generic[FixtureValue]):
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
self._finalizers.append(finalizer)
def remove_finalizer(self, finalizer: Callable[[], object]) -> None:
for finalizer_index, finalizer_func in enumerate(self._finalizers):
if finalizer_func.__qualname__ == finalizer.__qualname__:
del self._finalizers[finalizer_index]
return True
def finish(self, request: SubRequest) -> None:
exc = None
try:

View File

@ -503,6 +503,20 @@ class SetupState:
assert node in self.stack, (node, self.stack)
self.stack[node][0].append(finalizer)
def remove_finalizer(self, finalizer: Callable[[], object], node: Node) -> None:
"""Remove a finalizer in the given node by name.
The node must be currently active in the stack.
The first finalizer by name will be removed.
"""
assert node and not isinstance(node, tuple)
assert callable(finalizer)
assert node in self.stack, (node, self.stack)
for finalizer_index, finalizer_func in enumerate(self.stack[node][0]):
if finalizer_func.__qualname__ == finalizer.__qualname__:
del self.stack[node][0][finalizer_index]
return True
def teardown_exact(self, nextitem: Optional[Item]) -> None:
"""Teardown the current stack up until reaching nodes that nextitem
also descends from.

View File

@ -857,11 +857,33 @@ class TestRequestBasic:
parent = item.getparent(pytest.Module)
assert parent is not None
teardownlist = parent.obj.teardownlist
item.session._setupstate.teardown_exact(None)
assert teardownlist == [1]
def test_request_remove_finalizer(self, pytester: Pytester) -> None:
item = pytester.getitem(
"""
import pytest
teardownlist = []
@pytest.fixture
def something(request):
request.addfinalizer(lambda: teardownlist.append(1))
request.remove_finalizer(lambda: teardownlist.append(1))
def test_func(something): pass
"""
)
assert isinstance(item, Function)
item.session._setupstate.setup(item)
item._request._fillfixtures()
# successively check finalization calls
parent = item.getparent(pytest.Module)
assert parent is not None
teardownlist = parent.obj.teardownlist
ss = item.session._setupstate
assert not teardownlist
ss.teardown_exact(None)
print(ss.stack)
assert teardownlist == [1]
assert teardownlist == []
def test_request_addfinalizer_failing_setup(self, pytester: Pytester) -> None:
pytester.makepyfile(