Skip to content

Commit b20fef5

Browse files
authored
Run teardown once after reruns (#205)
1 parent ae55e1a commit b20fef5

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Changelog
44
11.1 (unreleased)
55
-----------------
66

7+
Bug fixes
8+
+++++++++
9+
10+
- Run teardown of session, class, ... scoped fixtures only once after rerunning tests
11+
712
Features
813
++++++++
914

pytest_rerunfailures.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import pytest
1414
from _pytest.outcomes import fail
15+
from _pytest.python import Function
1516
from _pytest.runner import runtestprotocol
1617
from packaging.version import parse as parse_version
1718

@@ -505,6 +506,40 @@ def _get(self, i: str, k: str) -> int:
505506
return int(self._sock_recv(self.sock))
506507

507508

509+
def pytest_runtest_teardown(item, nextitem):
510+
reruns = get_reruns_count(item)
511+
if reruns is None:
512+
# global setting is not specified, and this test is not marked with
513+
# flaky
514+
return
515+
516+
# teardown when test not failed or rerun limit exceeded
517+
if item.execution_count > reruns or getattr(item, "test_failed", None) is False:
518+
item.teardown()
519+
else:
520+
# clean cashed results from any level of setups
521+
_remove_cached_results_from_failed_fixtures(item)
522+
523+
if PYTEST_GTE_63:
524+
for key in list(item.session._setupstate.stack.keys()):
525+
if type(key) != Function:
526+
del item.session._setupstate.stack[key]
527+
else:
528+
for node in list(item.session._setupstate.stack):
529+
if type(node) != Function:
530+
item.session._setupstate.stack.remove(node)
531+
532+
item.teardown()
533+
534+
535+
@pytest.hookimpl(hookwrapper=True)
536+
def pytest_runtest_makereport(item, call):
537+
outcome = yield
538+
result = outcome.get_result()
539+
if call.when == "call":
540+
item.test_failed = result.failed
541+
542+
508543
def pytest_runtest_protocol(item, nextitem):
509544
"""
510545
Run the test protocol.

test_pytest_rerunfailures.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,3 +701,91 @@ def test_foo():
701701

702702
time.sleep.assert_called_with(5)
703703
assert_outcomes(result, passed=0, failed=1, rerun=4)
704+
705+
706+
def test_run_session_teardown_once_after_reruns(testdir):
707+
testdir.makepyfile(
708+
"""
709+
import logging
710+
import pytest
711+
712+
@pytest.fixture(scope='session')
713+
def session_fixture():
714+
logging.info('session setup')
715+
yield
716+
logging.info('session teardown')
717+
718+
@pytest.fixture(scope='class')
719+
def class_fixture():
720+
logging.info('class setup')
721+
yield
722+
logging.info('class teardown')
723+
724+
@pytest.fixture(scope='function')
725+
def function_fixture():
726+
logging.info('function setup')
727+
yield
728+
logging.info('function teardown')
729+
730+
class TestFoo:
731+
@staticmethod
732+
def test_foo_1(session_fixture, class_fixture, function_fixture):
733+
pass
734+
735+
@staticmethod
736+
def test_foo_2(session_fixture, class_fixture, function_fixture):
737+
assert False
738+
739+
class TestBar:
740+
@staticmethod
741+
def test_bar_1(session_fixture, class_fixture, function_fixture):
742+
assert False
743+
744+
@staticmethod
745+
def test_bar_2(session_fixture, class_fixture, function_fixture):
746+
assert False
747+
748+
@staticmethod
749+
def test_bar_3(session_fixture, class_fixture, function_fixture):
750+
pass"""
751+
)
752+
import logging
753+
754+
logging.info = mock.MagicMock()
755+
756+
result = testdir.runpytest("--reruns", "2")
757+
expected_calls = [
758+
mock.call("session setup"),
759+
# class TestFoo
760+
mock.call("class setup"),
761+
mock.call("function setup"),
762+
mock.call("function teardown"),
763+
mock.call("function setup"),
764+
mock.call("function teardown"),
765+
mock.call("function setup"),
766+
mock.call("function teardown"),
767+
mock.call("function setup"),
768+
mock.call("function teardown"),
769+
mock.call("class teardown"),
770+
# class TestBar
771+
mock.call("class setup"),
772+
mock.call("function setup"),
773+
mock.call("function teardown"),
774+
mock.call("function setup"),
775+
mock.call("function teardown"),
776+
mock.call("function setup"),
777+
mock.call("function teardown"),
778+
mock.call("function setup"),
779+
mock.call("function teardown"),
780+
mock.call("function setup"),
781+
mock.call("function teardown"),
782+
mock.call("function setup"),
783+
mock.call("function teardown"),
784+
mock.call("function setup"),
785+
mock.call("function teardown"),
786+
mock.call("class teardown"),
787+
mock.call("session teardown"),
788+
]
789+
790+
logging.info.assert_has_calls(expected_calls, any_order=False)
791+
assert_outcomes(result, failed=3, passed=2, rerun=6)

0 commit comments

Comments
 (0)