diff -r 768234f5c246 Lib/unittest/case.py --- a/Lib/unittest/case.py Sat Jun 25 15:03:52 2011 +0200 +++ b/Lib/unittest/case.py Sun Jun 26 17:12:16 2011 +0200 @@ -1034,9 +1034,20 @@ 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)) + diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines)) + # Get the maxsize to truncate to, which is the index when + # the two strings first, second start to differ. + seq = difflib.SequenceMatcher(a=repr(first), b=repr(second)) + truncate_size = seq.find_longest_match( + 0, len(repr(first)), 0, len(repr(second))).size + # If a diff was found, the trancate should include the next char. + if truncate_size != 0: + truncate_size += 1 + standardMsg = '%s != %s' % ( + safe_repr(first, True, max_length=truncate_size), + safe_repr(second, True, max_length=truncate_size)) + standardMsg = self._truncateMessage(standardMsg, diff) self.fail(self._formatMessage(msg, standardMsg)) diff -r 768234f5c246 Lib/unittest/test/test_case.py --- a/Lib/unittest/test/test_case.py Sat Jun 25 15:03:52 2011 +0200 +++ b/Lib/unittest/test/test_case.py Sun Jun 26 17:12:16 2011 +0200 @@ -756,6 +756,16 @@ self.assertEqual(str(cm.exception), '%r != %r' % (s1, s2)) self.assertEqual(s + 'a', s + 'a') + def testAssertEqualHumanReadableOutput(self): + # Issue #12038: Make the assertion error message more human redable + # for long strings. + s1 = "aaaaaa" + s2 = "abbbbb" + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s1, s2) + self.assertIn("aa [truncated]... != 'ab [truncated]...", + str(cm.exception)) + def testAssertCountEqual(self): a = object() self.assertCountEqual([1, 2, 3], [3, 2, 1]) diff -r 768234f5c246 Lib/unittest/util.py --- a/Lib/unittest/util.py Sat Jun 25 15:03:52 2011 +0200 +++ b/Lib/unittest/util.py Sun Jun 26 17:12:16 2011 +0200 @@ -5,14 +5,31 @@ __unittest = True _MAX_LENGTH = 80 -def safe_repr(obj, short=False): +def safe_repr(obj, short=False, max_length=None): try: result = repr(obj) except Exception: result = object.__repr__(obj) - if not short or len(result) < _MAX_LENGTH: + + len_result = len(result) + truncate_size = max_length or _MAX_LENGTH + if not short or len_result <= truncate_size: return result - return result[:_MAX_LENGTH] + ' [truncated]...' + + if truncate_size <= _MAX_LENGTH: + result = result[:truncate_size] + else: + # 0 _MAX_LENGTH truncate_size len(result) + # begin trunc end + # <-----------------------------------------------> + # begin ... trunc + # <--------------> + result = (result[:_MAX_LENGTH // 2] + '...' + + result[-_MAX_LENGTH // 2:truncate_size]) + + if truncate_size < len_result: + return result + ' [truncated]...' + return result def strclass(cls): return "%s.%s" % (cls.__module__, cls.__name__)