diff --git a/CHANGELOG b/CHANGELOG index 8275daed3..dd393842a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,8 +3,10 @@ Changes between 1.2.1 and 1.2.0 - fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value - fix issue78: always call python-level teardown functions even if the - according setup failed - but make sure that setup is called repeatedly - and no teardown if the setup raises a Skipped (as sone by py.test.skip()). + according setup failed. This includes refinements for calling setup_module/class functions + which will now only be called once instead of the previous behaviour where they'd be called + multiple times if they raise an exception (including a Skipped exception). Any exception + will be re-corded and associated with all tests in the according module/class scope. - fix issue63: assume <40 columns to be a bogus terminal width, default to 80 - update apipkg.py to fix an issue where recursive imports might unnecessarily break importing diff --git a/py/_plugin/pytest_runner.py b/py/_plugin/pytest_runner.py index 14fa38bb6..6814fb204 100644 --- a/py/_plugin/pytest_runner.py +++ b/py/_plugin/pytest_runner.py @@ -2,7 +2,7 @@ collect and run test items and create reports. """ -import py +import py, sys from py._test.outcome import Skipped # @@ -252,6 +252,10 @@ class SetupState(object): self.stack.append(col) try: col.setup() - except Skipped: - self.stack.pop() - raise + except Exception: + col._prepare_exc = sys.exc_info() + raise + # check if the last collection node has raised an error + for col in self.stack: + if hasattr(col, '_prepare_exc'): + py.builtin._reraise(*col._prepare_exc) diff --git a/testing/plugin/test_pytest_runner.py b/testing/plugin/test_pytest_runner.py index 4764de98d..963847d8f 100644 --- a/testing/plugin/test_pytest_runner.py +++ b/testing/plugin/test_pytest_runner.py @@ -34,6 +34,16 @@ class TestSetupState: ss.teardown_exact(item) ss.teardown_exact(item) + def test_setup_fails_and_failure_is_cached(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + raise ValueError(42) + def test_func(): pass + """) + ss = runner.SetupState() + py.test.raises(ValueError, "ss.prepare(item)") + py.test.raises(ValueError, "ss.prepare(item)") + class BaseFunctionalTests: def test_passfunction(self, testdir): reports = testdir.runitem(""" diff --git a/testing/plugin/test_pytest_runner_xunit.py b/testing/plugin/test_pytest_runner_xunit.py index f95915a89..77ed35e9a 100644 --- a/testing/plugin/test_pytest_runner_xunit.py +++ b/testing/plugin/test_pytest_runner_xunit.py @@ -150,7 +150,7 @@ def test_failing_setup_calls_teardown(testdir): "*2 error*" ]) -def test_setup_that_skips_calledagain_and_no_teardown(testdir): +def test_setup_that_skips_calledagain_and_teardown(testdir): p = testdir.makepyfile(""" import py def setup_module(mod): @@ -164,8 +164,27 @@ def test_setup_that_skips_calledagain_and_no_teardown(testdir): """) result = testdir.runpytest(p) result.stdout.fnmatch_lines([ - "*2 skipped*", + "*ValueError*43*", + "*2 skipped*1 error*", ]) - assert "43" not in result.stdout.str() + +def test_setup_fails_again_on_all_tests(testdir): + p = testdir.makepyfile(""" + import py + def setup_module(mod): + raise ValueError(42) + def test_function1(): + pass + def test_function2(): + pass + def teardown_module(mod): + raise ValueError(43) + """) + result = testdir.runpytest(p) + result.stdout.fnmatch_lines([ + "*3 error*" + ]) + assert "passed" not in result.stdout.str() +