diff options
-rw-r--r-- | lib/testtools/MANUAL | 5 | ||||
-rw-r--r-- | lib/testtools/NEWS | 4 | ||||
-rw-r--r-- | lib/testtools/testtools/__init__.py | 2 | ||||
-rw-r--r-- | lib/testtools/testtools/testcase.py | 19 | ||||
-rw-r--r-- | lib/testtools/testtools/tests/test_testtools.py | 22 |
5 files changed, 49 insertions, 3 deletions
diff --git a/lib/testtools/MANUAL b/lib/testtools/MANUAL index db213669c9..1a43e70f23 100644 --- a/lib/testtools/MANUAL +++ b/lib/testtools/MANUAL @@ -42,6 +42,11 @@ logic in a try/finally block or tearDown method. e.g.:: self.addCleanup(foo.unlock) ... +Cleanups can also report multiple errors, if appropriate by wrapping them in +a testtools.MultipleExceptions object:: + + raise MultipleExceptions(exc_info1, exc_info2) + TestCase.addOnException ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/lib/testtools/NEWS b/lib/testtools/NEWS index 596df0d6a6..89b942fdbe 100644 --- a/lib/testtools/NEWS +++ b/lib/testtools/NEWS @@ -7,6 +7,10 @@ NEXT Improvements ------------ +* Cleanups can raise ``testtools.MultipleExceptions`` if they have multiple + exceptions to report. For instance, a cleanup which is itself responsible for + running several different internal cleanup routines might use this. + * Code duplication between assertEqual and the matcher Equals has been removed. * In normal circumstances, a TestCase will no longer share details with clones diff --git a/lib/testtools/testtools/__init__.py b/lib/testtools/testtools/__init__.py index b1c9b661ed..2b76a5eef7 100644 --- a/lib/testtools/testtools/__init__.py +++ b/lib/testtools/testtools/__init__.py @@ -8,6 +8,7 @@ __all__ = [ 'ErrorHolder', 'ExtendedToOriginalDecorator', 'iterate_tests', + 'MultipleExceptions', 'MultiTestResult', 'PlaceHolder', 'TestCase', @@ -28,6 +29,7 @@ from testtools.runtest import ( ) from testtools.testcase import ( ErrorHolder, + MultipleExceptions, PlaceHolder, TestCase, clone_test_with_new_id, diff --git a/lib/testtools/testtools/testcase.py b/lib/testtools/testtools/testcase.py index 959c129691..573cd84dc2 100644 --- a/lib/testtools/testtools/testcase.py +++ b/lib/testtools/testtools/testcase.py @@ -5,6 +5,7 @@ __metaclass__ = type __all__ = [ 'clone_test_with_new_id', + 'MultipleExceptions', 'TestCase', 'skip', 'skipIf', @@ -60,6 +61,13 @@ except ImportError: """ +class MultipleExceptions(Exception): + """Represents many exceptions raised from some operation. + + :ivar args: The sys.exc_info() tuples for each exception. + """ + + class TestCase(unittest.TestCase): """Extensions to the basic TestCase. @@ -188,9 +196,14 @@ class TestCase(unittest.TestCase): except KeyboardInterrupt: raise except: - exc_info = sys.exc_info() - self._report_traceback(exc_info) - last_exception = exc_info[1] + exceptions = [sys.exc_info()] + while exceptions: + exc_info = exceptions.pop() + if exc_info[0] is MultipleExceptions: + exceptions.extend(exc_info[1].args) + continue + self._report_traceback(exc_info) + last_exception = exc_info[1] return last_exception def addCleanup(self, function, *arguments, **keywordArguments): diff --git a/lib/testtools/testtools/tests/test_testtools.py b/lib/testtools/testtools/tests/test_testtools.py index 5dfb355990..8e253e6311 100644 --- a/lib/testtools/testtools/tests/test_testtools.py +++ b/lib/testtools/testtools/tests/test_testtools.py @@ -8,6 +8,7 @@ import unittest from testtools import ( ErrorHolder, + MultipleExceptions, PlaceHolder, TestCase, clone_test_with_new_id, @@ -608,6 +609,27 @@ class TestAddCleanup(TestCase): self.assertRaises( KeyboardInterrupt, self.test.run, self.logging_result) + def test_all_errors_from_MultipleExceptions_reported(self): + # When a MultipleExceptions exception is caught, all the errors are + # reported. + def raiseMany(): + try: + 1/0 + except Exception: + exc_info1 = sys.exc_info() + try: + 1/0 + except Exception: + exc_info2 = sys.exc_info() + raise MultipleExceptions(exc_info1, exc_info2) + self.test.addCleanup(raiseMany) + self.logging_result = ExtendedTestResult() + self.test.run(self.logging_result) + self.assertEqual(['startTest', 'addError', 'stopTest'], + [event[0] for event in self.logging_result._events]) + self.assertEqual(set(['traceback', 'traceback-1']), + set(self.logging_result._events[1][2].keys())) + def test_multipleCleanupErrorsReported(self): # Errors from all failing cleanups are reported as separate backtraces. self.test.addCleanup(lambda: 1/0) |