diff options
-rw-r--r-- | coverage/test_helpers.py | 49 | ||||
-rw-r--r-- | tests/test_testing.py | 43 |
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`.""" |