[Python-checkins] cpython: Issue #18996: TestCase.assertEqual() now more cleverly shorten differing

serhiy.storchaka python-checkins at python.org
Mon Sep 23 22:07:17 CEST 2013


http://hg.python.org/cpython/rev/5bb83faa8818
changeset:   85792:5bb83faa8818
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Mon Sep 23 23:07:00 2013 +0300
summary:
  Issue #18996: TestCase.assertEqual() now more cleverly shorten differing
strings in error report.

files:
  Lib/unittest/case.py           |  20 ++++-------
  Lib/unittest/test/test_case.py |  37 ++++++++++++++++++++-
  Lib/unittest/util.py           |  37 ++++++++++++++++++++++
  Misc/NEWS                      |   3 +
  4 files changed, 81 insertions(+), 16 deletions(-)


diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -12,7 +12,7 @@
 
 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 @@
     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 @@
             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 @@
         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 @@
             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
--- a/Lib/unittest/test/test_case.py
+++ b/Lib/unittest/test/test_case.py
@@ -829,18 +829,18 @@
 
         # 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 @@
         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
--- 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)
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@
 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

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list