diff --git a/_pytest/runner.py b/_pytest/runner.py index 6792387db82..8b7b1d08224 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -294,6 +294,7 @@ def pytest_runtest_makereport(item, call): duration = call.stop - call.start keywords = dict([(x, 1) for x in item.keywords]) excinfo = call.excinfo + exception = None sections = [] if not call.excinfo: outcome = "passed" @@ -308,6 +309,7 @@ def pytest_runtest_makereport(item, call): longrepr = (str(r.path), r.lineno, r.message) else: outcome = "failed" + exception = excinfo.value if call.when == "call": longrepr = item.repr_failure(excinfo) else: # exception in setup or teardown @@ -317,7 +319,7 @@ def pytest_runtest_makereport(item, call): sections.append(("Captured %s %s" % (key, rwhen), content)) return TestReport(item.nodeid, item.location, keywords, outcome, longrepr, when, - sections, duration, user_properties=item.user_properties) + sections, duration, exception=exception, user_properties=item.user_properties) class TestReport(BaseReport): @@ -326,7 +328,7 @@ class TestReport(BaseReport): """ def __init__(self, nodeid, location, keywords, outcome, - longrepr, when, sections=(), duration=0, user_properties=(), **extra): + longrepr, when, sections=(), duration=0, user_properties=(), exception=None, **extra): #: normalized collection node id self.nodeid = nodeid @@ -345,6 +347,9 @@ def __init__(self, nodeid, location, keywords, outcome, #: None or a failure representation. self.longrepr = longrepr + #: None or the exception which caused the failure + self.exception = exception + #: one of 'setup', 'call', 'teardown' to indicate runtest phase. self.when = when diff --git a/changelog/3343.feature.rst b/changelog/3343.feature.rst new file mode 100644 index 00000000000..4dfa12c13d4 --- /dev/null +++ b/changelog/3343.feature.rst @@ -0,0 +1 @@ +Added the exception object to the ``TestReport``, for reference in hook functions. \ No newline at end of file diff --git a/testing/test_runner.py b/testing/test_runner.py index a3bd8ecb44f..1a747f33bd9 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -825,3 +825,61 @@ def test_func(): rep = reports[1] assert rep.capstdout == '' assert rep.capstderr == '' + + def test_exception(self, testdir): + reports = testdir.runitem(""" + def test_func(): + x = 1 + assert x == 4 + """) + rep = reports[1] + assert rep.failed + assert rep.when == "call" + assert isinstance(rep.exception, AssertionError) + assert str(rep.exception) == 'assert 1 == 4' + + def test_setup_exception(self, testdir): + reports = testdir.runitem(""" + import pytest + + @pytest.fixture + def fix(): + assert False + yield + + def test_func(fix): + pass + """) + rep = reports[0] + assert rep.failed + assert rep.when == "setup" + assert isinstance(rep.exception, AssertionError) + assert str(rep.exception) == 'assert False' + + def test_teardown_exception(self, testdir): + reports = testdir.runitem(""" + import pytest + + @pytest.fixture + def fix(): + yield + assert False + + def test_func(fix): + pass + """) + rep = reports[2] + assert rep.failed + assert rep.when == "teardown" + assert isinstance(rep.exception, AssertionError) + assert str(rep.exception) == 'assert False' + + def test_no_exception(self, testdir): + reports = testdir.runitem(""" + def test_func(): + x = 1 + assert x == 1 + """) + rep = reports[1] + assert not rep.failed + assert rep.exception is None