diff options
-rw-r--r-- | lib/testtools/NEWS | 45 | ||||
-rw-r--r-- | lib/testtools/testtools/matchers.py | 9 | ||||
-rw-r--r-- | lib/testtools/testtools/testcase.py | 29 | ||||
-rw-r--r-- | lib/testtools/testtools/tests/test_testtools.py | 32 |
4 files changed, 78 insertions, 37 deletions
diff --git a/lib/testtools/NEWS b/lib/testtools/NEWS index dc5e6df8f1..596df0d6a6 100644 --- a/lib/testtools/NEWS +++ b/lib/testtools/NEWS @@ -4,6 +4,15 @@ testtools NEWS NEXT ~~~~ +Improvements +------------ + +* Code duplication between assertEqual and the matcher Equals has been removed. + +* In normal circumstances, a TestCase will no longer share details with clones + of itself. (Andrew Bennetts, bug #637725) + + 0.9.6 ~~~~~ @@ -17,32 +26,32 @@ patches and TestCase.assertEqual gives slightly nicer errors. Improvements ------------ - * 'TestCase.assertEqual' now formats errors a little more nicely, in the - style of bzrlib. +* 'TestCase.assertEqual' now formats errors a little more nicely, in the + style of bzrlib. - * Added `PlaceHolder` and `ErrorHolder`, TestCase-like objects that can be - used to add results to a `TestResult`. +* Added `PlaceHolder` and `ErrorHolder`, TestCase-like objects that can be + used to add results to a `TestResult`. - * 'Mismatch' now takes optional description and details parameters, so - custom Matchers aren't compelled to make their own subclass. +* 'Mismatch' now takes optional description and details parameters, so + custom Matchers aren't compelled to make their own subclass. - * jml added a built-in UTF8_TEXT ContentType to make it slightly easier to - add details to test results. See bug #520044. +* jml added a built-in UTF8_TEXT ContentType to make it slightly easier to + add details to test results. See bug #520044. - * Fix a bug in our built-in matchers where assertThat would blow up if any - of them failed. All built-in mismatch objects now provide get_details(). +* Fix a bug in our built-in matchers where assertThat would blow up if any + of them failed. All built-in mismatch objects now provide get_details(). - * New 'Is' matcher, which lets you assert that a thing is identical to - another thing. +* New 'Is' matcher, which lets you assert that a thing is identical to + another thing. - * New 'LessThan' matcher which lets you assert that a thing is less than - another thing. +* New 'LessThan' matcher which lets you assert that a thing is less than + another thing. - * TestCase now has a 'patch()' method to make it easier to monkey-patching - objects in tests. See the manual for more information. Fixes bug #310770. +* TestCase now has a 'patch()' method to make it easier to monkey-patching + objects in tests. See the manual for more information. Fixes bug #310770. - * MultiTestResult methods now pass back return values from the results it - forwards to. +* MultiTestResult methods now pass back return values from the results it + forwards to. 0.9.5 ~~~~~ diff --git a/lib/testtools/testtools/matchers.py b/lib/testtools/testtools/matchers.py index 6a4c82a2fe..61b5bd74f9 100644 --- a/lib/testtools/testtools/matchers.py +++ b/lib/testtools/testtools/matchers.py @@ -25,6 +25,7 @@ __all__ = [ import doctest import operator +from pprint import pformat class Matcher(object): @@ -178,6 +179,14 @@ class _BinaryMismatch(Mismatch): self.other = other def describe(self): + left = repr(self.expected) + right = repr(self.other) + if len(left) + len(right) > 70: + return "%s:\nreference = %s\nactual = %s\n" % ( + self._mismatch_string, pformat(self.expected), + pformat(self.other)) + else: + return "%s %s %s" % (left, self._mismatch_string,right) return "%r %s %r" % (self.expected, self._mismatch_string, self.other) diff --git a/lib/testtools/testtools/testcase.py b/lib/testtools/testtools/testcase.py index 48eec71d41..959c129691 100644 --- a/lib/testtools/testtools/testcase.py +++ b/lib/testtools/testtools/testcase.py @@ -17,13 +17,16 @@ try: except ImportError: wraps = None import itertools -from pprint import pformat import sys import types import unittest from testtools import content from testtools.compat import advance_iterator +from testtools.matchers import ( + Annotate, + Equals, + ) from testtools.monkey import patch from testtools.runtest import RunTest from testtools.testresult import TestResult @@ -81,7 +84,9 @@ class TestCase(unittest.TestCase): self._traceback_id_gen = itertools.count(0) self.__setup_called = False self.__teardown_called = False - self.__details = {} + # __details is lazy-initialized so that a constructed-but-not-run + # TestCase is safe to use with clone_test_with_new_id. + self.__details = None self.__RunTest = kwargs.get('runTest', RunTest) self.__exception_handlers = [] self.exception_handlers = [ @@ -114,6 +119,8 @@ class TestCase(unittest.TestCase): :param content_object: The content object for this detail. See testtools.content for more detail. """ + if self.__details is None: + self.__details = {} self.__details[name] = content_object def getDetails(self): @@ -121,6 +128,8 @@ class TestCase(unittest.TestCase): For more details see pydoc testtools.TestResult. """ + if self.__details is None: + self.__details = {} return self.__details def patch(self, obj, attribute, value): @@ -230,18 +239,10 @@ class TestCase(unittest.TestCase): :param observed: The observed value. :param message: An optional message to include in the error. """ - try: - return super(TestCase, self).assertEqual(expected, observed) - except self.failureException: - lines = [] - if message: - lines.append(message) - lines.extend( - ["not equal:", - "a = %s" % pformat(expected), - "b = %s" % pformat(observed), - '']) - self.fail('\n'.join(lines)) + matcher = Equals(expected) + if message: + matcher = Annotate(message, matcher) + self.assertThat(observed, matcher) failUnlessEqual = assertEquals = assertEqual diff --git a/lib/testtools/testtools/tests/test_testtools.py b/lib/testtools/testtools/tests/test_testtools.py index 9edc5a5176..5dfb355990 100644 --- a/lib/testtools/testtools/tests/test_testtools.py +++ b/lib/testtools/testtools/tests/test_testtools.py @@ -461,6 +461,15 @@ class TestAssertions(TestCase): 'a = %s' % pformat(a), 'b = %s' % pformat(b), '']) + expected_error = '\n'.join([ + 'Match failed. Matchee: "%r"' % b, + 'Matcher: Annotate(%r, Equals(%r))' % (message, a), + 'Difference: !=:', + 'reference = %s' % pformat(a), + 'actual = %s' % pformat(b), + ': ' + message, + '' + ]) self.assertFails(expected_error, self.assertEqual, a, b, message) self.assertFails(expected_error, self.assertEquals, a, b, message) self.assertFails(expected_error, self.failUnlessEqual, a, b, message) @@ -468,11 +477,12 @@ class TestAssertions(TestCase): def test_assertEqual_formatting_no_message(self): a = "cat" b = "dog" - expected_error = '\n'.join( - ['not equal:', - 'a = %s' % pformat(a), - 'b = %s' % pformat(b), - '']) + expected_error = '\n'.join([ + 'Match failed. Matchee: "dog"', + 'Matcher: Equals(\'cat\')', + 'Difference: \'cat\' != \'dog\'', + '' + ]) self.assertFails(expected_error, self.assertEqual, a, b) self.assertFails(expected_error, self.assertEquals, a, b) self.assertFails(expected_error, self.failUnlessEqual, a, b) @@ -760,6 +770,18 @@ class TestCloneTestWithNewId(TestCase): self.assertEqual(oldName, test.id(), "the original test instance should be unchanged.") + def test_cloned_testcase_does_not_share_details(self): + """A cloned TestCase does not share the details dict.""" + class Test(TestCase): + def test_foo(self): + self.addDetail( + 'foo', content.Content('text/plain', lambda: 'foo')) + orig_test = Test('test_foo') + cloned_test = clone_test_with_new_id(orig_test, self.getUniqueString()) + orig_test.run(unittest.TestResult()) + self.assertEqual('foo', orig_test.getDetails()['foo'].iter_bytes()) + self.assertEqual(None, cloned_test.getDetails().get('foo')) + class TestDetailsProvided(TestWithDetails): |