summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/testtools/NEWS45
-rw-r--r--lib/testtools/testtools/matchers.py9
-rw-r--r--lib/testtools/testtools/testcase.py29
-rw-r--r--lib/testtools/testtools/tests/test_testtools.py32
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):