summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/testtools/MANUAL5
-rw-r--r--lib/testtools/NEWS4
-rw-r--r--lib/testtools/testtools/__init__.py2
-rw-r--r--lib/testtools/testtools/testcase.py19
-rw-r--r--lib/testtools/testtools/tests/test_testtools.py22
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)