diff options
| -rw-r--r-- | Lib/unittest/case.py | 20 | ||||
| -rw-r--r-- | Lib/unittest/test/test_case.py | 37 | ||||
| -rw-r--r-- | Lib/unittest/util.py | 37 | ||||
| -rw-r--r-- | Misc/NEWS | 3 | 
4 files changed, 81 insertions, 16 deletions
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 2909610367..7ed932fafd 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -12,7 +12,7 @@ import contextlib  from . import result  from .util import (strclass, safe_repr, _count_diff_all_purpose, -                   _count_diff_hashable) +                   _count_diff_hashable, _common_shorten_repr)  __unittest = True @@ -770,7 +770,7 @@ class TestCase(object):      def _baseAssertEqual(self, first, second, msg=None):          """The default assertEqual implementation, not type specific."""          if not first == second: -            standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second)) +            standardMsg = '%s != %s' % _common_shorten_repr(first, second)              msg = self._formatMessage(msg, standardMsg)              raise self.failureException(msg) @@ -905,14 +905,9 @@ class TestCase(object):              if seq1 == seq2:                  return -            seq1_repr = safe_repr(seq1) -            seq2_repr = safe_repr(seq2) -            if len(seq1_repr) > 30: -                seq1_repr = seq1_repr[:30] + '...' -            if len(seq2_repr) > 30: -                seq2_repr = seq2_repr[:30] + '...' -            elements = (seq_type_name.capitalize(), seq1_repr, seq2_repr) -            differing = '%ss differ: %s != %s\n' % elements +            differing = '%ss differ: %s != %s\n' % ( +                    (seq_type_name.capitalize(),) + +                    _common_shorten_repr(seq1, seq2))              for i in range(min(len1, len2)):                  try: @@ -1070,7 +1065,7 @@ class TestCase(object):          self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')          if d1 != d2: -            standardMsg = '%s != %s' % (safe_repr(d1, True), safe_repr(d2, True)) +            standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)              diff = ('\n' + '\n'.join(difflib.ndiff(                             pprint.pformat(d1).splitlines(),                             pprint.pformat(d2).splitlines()))) @@ -1154,8 +1149,7 @@ class TestCase(object):              if len(firstlines) == 1 and first.strip('\r\n') == first:                  firstlines = [first + '\n']                  secondlines = [second + '\n'] -            standardMsg = '%s != %s' % (safe_repr(first, True), -                                        safe_repr(second, True)) +            standardMsg = '%s != %s' % _common_shorten_repr(first, second)              diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines))              standardMsg = self._truncateMessage(standardMsg, diff)              self.fail(self._formatMessage(msg, standardMsg)) diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py index f08668f6ec..9aa9fd1312 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -829,18 +829,18 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):          # set a lower threshold value and add a cleanup to restore it          old_threshold = self._diffThreshold -        self._diffThreshold = 2**8 +        self._diffThreshold = 2**5          self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold))          # under the threshold: diff marker (^) in error message -        s = 'x' * (2**7) +        s = 'x' * (2**4)          with self.assertRaises(self.failureException) as cm:              self.assertEqual(s + 'a', s + 'b')          self.assertIn('^', str(cm.exception))          self.assertEqual(s + 'a', s + 'a')          # over the threshold: diff not used and marker (^) not in error message -        s = 'x' * (2**9) +        s = 'x' * (2**6)          # if the path that uses difflib is taken, _truncateMessage will be          # called -- replace it with explodingTruncation to verify that this          # doesn't happen @@ -857,6 +857,37 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):          self.assertEqual(str(cm.exception), '%r != %r' % (s1, s2))          self.assertEqual(s + 'a', s + 'a') +    def testAssertEqual_shorten(self): +        # set a lower threshold value and add a cleanup to restore it +        old_threshold = self._diffThreshold +        self._diffThreshold = 0 +        self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold)) + +        s = 'x' * 100 +        s1, s2 = s + 'a', s + 'b' +        with self.assertRaises(self.failureException) as cm: +            self.assertEqual(s1, s2) +        c = 'xxxx[35 chars]' + 'x' * 61 +        self.assertEqual(str(cm.exception), "'%sa' != '%sb'" % (c, c)) +        self.assertEqual(s + 'a', s + 'a') + +        p = 'y' * 50 +        s1, s2 = s + 'a' + p, s + 'b' + p +        with self.assertRaises(self.failureException) as cm: +            self.assertEqual(s1, s2) +        c = 'xxxx[85 chars]xxxxxxxxxxx' +        #print() +        #print(str(cm.exception)) +        self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, p, c, p)) + +        p = 'y' * 100 +        s1, s2 = s + 'a' + p, s + 'b' + p +        with self.assertRaises(self.failureException) as cm: +            self.assertEqual(s1, s2) +        c = 'xxxx[91 chars]xxxxx' +        d = 'y' * 40 + '[56 chars]yyyy' +        self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, d, c, d)) +      def testAssertCountEqual(self):          a = object()          self.assertCountEqual([1, 2, 3], [3, 2, 1]) diff --git a/Lib/unittest/util.py b/Lib/unittest/util.py index ccdf0b81fa..aee498fd0b 100644 --- a/Lib/unittest/util.py +++ b/Lib/unittest/util.py @@ -1,10 +1,47 @@  """Various utility functions."""  from collections import namedtuple, OrderedDict +from os.path import commonprefix  __unittest = True  _MAX_LENGTH = 80 +_PLACEHOLDER_LEN = 12 +_MIN_BEGIN_LEN = 5 +_MIN_END_LEN = 5 +_MIN_COMMON_LEN = 5 +_MIN_DIFF_LEN = _MAX_LENGTH - \ +               (_MIN_BEGIN_LEN + _PLACEHOLDER_LEN + _MIN_COMMON_LEN + +                _PLACEHOLDER_LEN + _MIN_END_LEN) +assert _MIN_DIFF_LEN >= 0 + +def _shorten(s, prefixlen, suffixlen): +    skip = len(s) - prefixlen - suffixlen +    if skip > _PLACEHOLDER_LEN: +        s = '%s[%d chars]%s' % (s[:prefixlen], skip, s[len(s) - suffixlen:]) +    return s + +def _common_shorten_repr(*args): +    args = tuple(map(safe_repr, args)) +    maxlen = max(map(len, args)) +    if maxlen <= _MAX_LENGTH: +        return args + +    prefix = commonprefix(args) +    prefixlen = len(prefix) + +    common_len = _MAX_LENGTH - \ +                 (maxlen - prefixlen + _MIN_BEGIN_LEN + _PLACEHOLDER_LEN) +    if common_len > _MIN_COMMON_LEN: +        assert _MIN_BEGIN_LEN + _PLACEHOLDER_LEN + _MIN_COMMON_LEN + \ +               (maxlen - prefixlen) < _MAX_LENGTH +        prefix = _shorten(prefix, _MIN_BEGIN_LEN, common_len) +        return tuple(prefix + s[prefixlen:] for s in args) + +    prefix = _shorten(prefix, _MIN_BEGIN_LEN, _MIN_COMMON_LEN) +    return tuple(prefix + _shorten(s[prefixlen:], _MIN_DIFF_LEN, _MIN_END_LEN) +                 for s in args) +  def safe_repr(obj, short=False):      try:          result = repr(obj) @@ -12,6 +12,9 @@ Core and Builtins  Library  ------- +- Issue #18996: TestCase.assertEqual() now more cleverly shorten differing +  strings in error report. +  - Issue #19034: repr() for tkinter.Tcl_Obj now exposes string reperesentation.  - Issue #18978: ``urllib.request.Request`` now allows the method to be  | 
