From 695bffc83d5bc5e89cdf1b20667f7846c979c750 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 23 Nov 2010 15:42:23 +0100 Subject: [PATCH] refine unittest support to also work with twisted trial test cases better by introducing a slightly hackish way to report a failure upstream --- _pytest/python.py | 3 ++ _pytest/runner.py | 8 ++++-- _pytest/unittest.py | 36 +++++++++++++++++++++-- testing/test_runner.py | 11 +++++++ testing/test_unittest.py | 62 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 5 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index d6560d57a..3c4374f43 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -329,6 +329,9 @@ class FunctionMixin(PyobjMixin): def repr_failure(self, excinfo, outerr=None): assert outerr is None, "XXX outerr usage is deprecated" + if excinfo.errisinstance(pytest.fail.Exception): + if not excinfo.value.pytrace: + return str(excinfo.value) return self._repr_failure_py(excinfo, style=self.config.option.tbstyle) diff --git a/_pytest/runner.py b/_pytest/runner.py index d65087ae8..5002ba7ae 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -142,6 +142,7 @@ class BaseReport(object): def pytest_runtest_makereport(item, call): when = call.when keywords = dict([(x,1) for x in item.keywords]) + excinfo = call.excinfo if not call.excinfo: outcome = "passed" longrepr = None @@ -312,8 +313,9 @@ class OutcomeException(Exception): """ OutcomeException and its subclass instances indicate and contain info about test and collection outcomes. """ - def __init__(self, msg=None): + def __init__(self, msg=None, pytrace=True): self.msg = msg + self.pytrace = pytrace def __repr__(self): if self.msg: @@ -355,10 +357,10 @@ def skip(msg=""): raise Skipped(msg=msg) skip.Exception = Skipped -def fail(msg=""): +def fail(msg="", pytrace=True): """ explicitely fail an currently-executing test with the given Message. """ __tracebackhide__ = True - raise Failed(msg=msg) + raise Failed(msg=msg, pytrace=pytrace) fail.Exception = Failed diff --git a/_pytest/unittest.py b/_pytest/unittest.py index f61e03bb6..3f059bf12 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -33,16 +33,40 @@ class UnitTestCase(pytest.Class): meth() class TestCaseFunction(pytest.Function): + _excinfo = None def setup(self): pass def teardown(self): pass def startTest(self, testcase): pass + + def _addexcinfo(self, rawexcinfo): + #__tracebackhide__ = True + assert rawexcinfo + try: + self._excinfo = py.code.ExceptionInfo(rawexcinfo) + except TypeError: + try: + try: + l = py.std.traceback.format_exception(*rawexcinfo) + l.insert(0, "NOTE: Incompatible Exception Representation, " + "displaying natively:\n\n") + pytest.fail("".join(l), pytrace=False) + except (pytest.fail.Exception, KeyboardInterrupt): + raise + except: + pytest.fail("ERROR: Unknown Incompatible Exception " + "representation:\n%r" %(rawexcinfo,), pytrace=False) + except pytest.fail.Exception: + self._excinfo = py.code.ExceptionInfo() + except KeyboardInterrupt: + raise + def addError(self, testcase, rawexcinfo): - py.builtin._reraise(*rawexcinfo) + self._addexcinfo(rawexcinfo) def addFailure(self, testcase, rawexcinfo): - py.builtin._reraise(*rawexcinfo) + self._addexcinfo(rawexcinfo) def addSuccess(self, testcase): pass def stopTest(self, testcase): @@ -50,3 +74,11 @@ class TestCaseFunction(pytest.Function): def runtest(self): testcase = self.parent.obj(self.name) testcase(result=self) + +@pytest.mark.tryfirst +def pytest_runtest_makereport(item, call): + if isinstance(item, TestCaseFunction): + if item._excinfo: + call.excinfo = item._excinfo + item._excinfo = None + del call.result diff --git a/testing/test_runner.py b/testing/test_runner.py index 1cf062d0b..379476d40 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -334,6 +334,17 @@ def test_pytest_fail(): s = excinfo.exconly(tryshort=True) assert s.startswith("Failed") +def test_pytest_fail_notrace(testdir): + testdir.makepyfile(""" + import pytest + def test_hello(): + pytest.fail("hello", pytrace=False) + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "hello" + ]) + def test_exception_printing_skip(): try: pytest.skip("hello") diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 852cb6098..cd6c6295c 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -103,3 +103,65 @@ def test_class_setup(testdir): """) reprec = testdir.inline_run(testpath) reprec.assertoutcome(passed=3) + + +@pytest.mark.multi(type=['Error', 'Failure']) +def test_testcase_adderrorandfailure_defers(testdir, type): + testdir.makepyfile(""" + from unittest import TestCase + import pytest + class MyTestCase(TestCase): + def run(self, result): + excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0) + try: + result.add%s(self, excinfo._excinfo) + except KeyboardInterrupt: + raise + except: + pytest.fail("add%s should not raise") + def test_hello(self): + pass + """ % (type, type)) + result = testdir.runpytest() + assert 'should not raise' not in result.stdout.str() + +@pytest.mark.multi(type=['Error', 'Failure']) +def test_testcase_custom_exception_info(testdir, type): + testdir.makepyfile(""" + from unittest import TestCase + import py, pytest + class MyTestCase(TestCase): + def run(self, result): + excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0) + # we fake an incompatible exception info + from _pytest.monkeypatch import monkeypatch + mp = monkeypatch() + def t(*args): + mp.undo() + raise TypeError() + mp.setattr(py.code, 'ExceptionInfo', t) + try: + excinfo = excinfo._excinfo + result.add%(type)s(self, excinfo) + finally: + mp.undo() + def test_hello(self): + pass + """ % locals()) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "NOTE: Incompatible Exception Representation*", + "*ZeroDivisionError*", + "*1 failed*", + ]) + +def test_testcase_totally_incompatible_exception_info(testdir): + item, = testdir.getitems(""" + from unittest import TestCase + class MyTestCase(TestCase): + def test_hello(self): + pass + """) + item.addError(None, 42) + excinfo = item._excinfo + assert 'ERROR: Unknown Incompatible' in str(excinfo.getrepr())