summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/test_helpers.py49
-rw-r--r--tests/test_testing.py43
2 files changed, 91 insertions, 1 deletions
diff --git a/coverage/test_helpers.py b/coverage/test_helpers.py
index 092daa07..84e2f1cf 100644
--- a/coverage/test_helpers.py
+++ b/coverage/test_helpers.py
@@ -186,6 +186,55 @@ class StdStreamCapturingMixin(TestCase):
return self.captured_stderr.getvalue()
+class DelayedAssertionMixin(TestCase):
+ """A test case mixin that provides a `delayed_assertions` context manager.
+
+ Use it like this::
+
+ with self.delayed_assertions():
+ self.assertEqual(x, y)
+ self.assertEqual(z, w)
+
+ All of the assertions will run. The failures will be displayed at the end
+ of the with-statement.
+
+ NOTE: only works with some assert methods, I'm not sure which!
+
+ """
+ def __init__(self, *args, **kwargs):
+ super(DelayedAssertionMixin, self).__init__(*args, **kwargs)
+ # This mixin only works with assert methods that call `self.fail`. In
+ # Python 2.7, `assertEqual` didn't, but we can do what Python 3 does,
+ # and use `assertMultiLineEqual` for comparing strings.
+ self.addTypeEqualityFunc(str, 'assertMultiLineEqual')
+ self._delayed_assertions = None
+
+ @contextlib.contextmanager
+ def delayed_assertions(self):
+ """The context manager: assert that we didn't collect any assertions."""
+ self._delayed_assertions = []
+ old_fail = self.fail
+ self.fail = self._delayed_fail
+ try:
+ yield
+ finally:
+ self.fail = old_fail
+ if self._delayed_assertions:
+ if len(self._delayed_assertions) == 1:
+ self.fail(self._delayed_assertions[0])
+ else:
+ self.fail(
+ "{} failed assertions:\n{}".format(
+ len(self._delayed_assertions),
+ "\n".join(self._delayed_assertions),
+ )
+ )
+
+ def _delayed_fail(self, msg=None):
+ """The stand-in for TestCase.fail during delayed_assertions."""
+ self._delayed_assertions.append(msg)
+
+
class TempDirMixin(SysPathAwareMixin, ModuleAwareMixin, TestCase):
"""A test case mixin that creates a temp directory and files in it.
diff --git a/tests/test_testing.py b/tests/test_testing.py
index 9fc7f11d..1dafdd0d 100644
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -6,13 +6,15 @@
import datetime
import os
+import re
import sys
+import textwrap
import coverage
from coverage.backunittest import TestCase
from coverage.backward import to_bytes
from coverage.files import actual_path
-from coverage.test_helpers import EnvironmentAwareMixin, TempDirMixin
+from coverage.test_helpers import EnvironmentAwareMixin, TempDirMixin, DelayedAssertionMixin
from tests.coveragetest import CoverageTest
@@ -97,6 +99,45 @@ class EnvironmentAwareMixinTest(EnvironmentAwareMixin, TestCase):
self.assertNotIn("XYZZY_PLUGH", os.environ)
+class DelayedAssertionMixinTest(DelayedAssertionMixin, TestCase):
+ """Test the `delayed_assertions` method."""
+
+ def test_delayed_assertions(self):
+ # Two assertions can be shown at once:
+ msg = re.escape(textwrap.dedent("""\
+ 2 failed assertions:
+ 'x' != 'y'
+ - x
+ + y
+
+ 'w' != 'z'
+ - w
+ + z
+ """))
+ with self.assertRaisesRegex(AssertionError, msg):
+ with self.delayed_assertions():
+ self.assertEqual("x", "y")
+ self.assertEqual("w", "z")
+
+ # It's also OK if only one fails:
+ msg = re.escape(textwrap.dedent("""\
+ 'w' != 'z'
+ - w
+ + z
+ """))
+ with self.assertRaisesRegex(AssertionError, msg):
+ with self.delayed_assertions():
+ self.assertEqual("x", "x")
+ self.assertEqual("w", "z")
+
+ # If an error happens, it gets reported immediately, no special
+ # handling:
+ with self.assertRaises(ZeroDivisionError):
+ with self.delayed_assertions():
+ self.assertEqual("x", "y")
+ self.assertEqual("w", 1/0)
+
+
class CoverageTestTest(CoverageTest):
"""Test the methods in `CoverageTest`."""