From a888bf182e20a11e2151853700b3b28da231b70f Mon Sep 17 00:00:00 2001 From: Guoqiang Zhang Date: Mon, 11 Jun 2018 16:12:47 -0400 Subject: [PATCH] Continue to call finalizers in the stack when a finalizer in a former scope raises an exception --- AUTHORS | 1 + changelog/3569.bugfix.rst | 1 + src/_pytest/runner.py | 11 ++++++++++- testing/test_runner.py | 17 +++++++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 changelog/3569.bugfix.rst diff --git a/AUTHORS b/AUTHORS index fff3207a3..3edfdcf85 100644 --- a/AUTHORS +++ b/AUTHORS @@ -82,6 +82,7 @@ Greg Price Grig Gheorghiu Grigorii Eremeev (budulianin) Guido Wesdorp +Guoqiang Zhang Harald Armin Massa Henk-Jaap Wagenaar Hugo van Kemenade diff --git a/changelog/3569.bugfix.rst b/changelog/3569.bugfix.rst new file mode 100644 index 000000000..586ecaf95 --- /dev/null +++ b/changelog/3569.bugfix.rst @@ -0,0 +1 @@ +Continue to call finalizers in the stack when a finalizer in a former scope raises an exception. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index ef1a0e694..18e925509 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -527,10 +527,19 @@ class SetupState(object): self._teardown_towards(needed_collectors) def _teardown_towards(self, needed_collectors): + exc = None while self.stack: if self.stack == needed_collectors[:len(self.stack)]: break - self._pop_and_teardown() + try: + self._pop_and_teardown() + except TEST_OUTCOME: + # XXX Only first exception will be seen by user, + # ideally all should be reported. + if exc is None: + exc = sys.exc_info() + if exc: + py.builtin._reraise(*exc) def prepare(self, colitem): """ setup objects along the collector chain to the test-method diff --git a/testing/test_runner.py b/testing/test_runner.py index 26493de6e..8146b2dcb 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -81,6 +81,23 @@ class TestSetupState(object): ss._callfinalizers(item) assert err.value.args == ("oops2",) + def test_teardown_multiple_scopes_one_fails(self, testdir): + module_teardown = [] + def fin_func(): + raise Exception("oops1") + + def fin_module(): + module_teardown.append("fin_module") + + item = testdir.getitem("def test_func(): pass") + ss = runner.SetupState() + ss.addfinalizer(fin_module, item.listchain()[-2]) + ss.addfinalizer(fin_func, item) + ss.prepare(item) + with pytest.raises(Exception): + ss.teardown_exact(item, None) + assert module_teardown + class BaseFunctionalTests(object):