mirror of
https://github.com/python/cpython.git
synced 2025-11-27 13:45:25 +00:00
Issue #18996: TestCase.assertEqual() now more cleverly shorten differing
strings in error report.
This commit is contained in:
parent
463bd4b5c6
commit
77622f55c2
4 changed files with 81 additions and 16 deletions
|
|
@ -12,7 +12,7 @@ import contextlib
|
||||||
|
|
||||||
from . import result
|
from . import result
|
||||||
from .util import (strclass, safe_repr, _count_diff_all_purpose,
|
from .util import (strclass, safe_repr, _count_diff_all_purpose,
|
||||||
_count_diff_hashable)
|
_count_diff_hashable, _common_shorten_repr)
|
||||||
|
|
||||||
__unittest = True
|
__unittest = True
|
||||||
|
|
||||||
|
|
@ -770,7 +770,7 @@ class TestCase(object):
|
||||||
def _baseAssertEqual(self, first, second, msg=None):
|
def _baseAssertEqual(self, first, second, msg=None):
|
||||||
"""The default assertEqual implementation, not type specific."""
|
"""The default assertEqual implementation, not type specific."""
|
||||||
if not first == second:
|
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)
|
msg = self._formatMessage(msg, standardMsg)
|
||||||
raise self.failureException(msg)
|
raise self.failureException(msg)
|
||||||
|
|
||||||
|
|
@ -905,14 +905,9 @@ class TestCase(object):
|
||||||
if seq1 == seq2:
|
if seq1 == seq2:
|
||||||
return
|
return
|
||||||
|
|
||||||
seq1_repr = safe_repr(seq1)
|
differing = '%ss differ: %s != %s\n' % (
|
||||||
seq2_repr = safe_repr(seq2)
|
(seq_type_name.capitalize(),) +
|
||||||
if len(seq1_repr) > 30:
|
_common_shorten_repr(seq1, seq2))
|
||||||
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
|
|
||||||
|
|
||||||
for i in range(min(len1, len2)):
|
for i in range(min(len1, len2)):
|
||||||
try:
|
try:
|
||||||
|
|
@ -1070,7 +1065,7 @@ class TestCase(object):
|
||||||
self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
|
self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
|
||||||
|
|
||||||
if d1 != d2:
|
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(
|
diff = ('\n' + '\n'.join(difflib.ndiff(
|
||||||
pprint.pformat(d1).splitlines(),
|
pprint.pformat(d1).splitlines(),
|
||||||
pprint.pformat(d2).splitlines())))
|
pprint.pformat(d2).splitlines())))
|
||||||
|
|
@ -1154,8 +1149,7 @@ class TestCase(object):
|
||||||
if len(firstlines) == 1 and first.strip('\r\n') == first:
|
if len(firstlines) == 1 and first.strip('\r\n') == first:
|
||||||
firstlines = [first + '\n']
|
firstlines = [first + '\n']
|
||||||
secondlines = [second + '\n']
|
secondlines = [second + '\n']
|
||||||
standardMsg = '%s != %s' % (safe_repr(first, True),
|
standardMsg = '%s != %s' % _common_shorten_repr(first, second)
|
||||||
safe_repr(second, True))
|
|
||||||
diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines))
|
diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines))
|
||||||
standardMsg = self._truncateMessage(standardMsg, diff)
|
standardMsg = self._truncateMessage(standardMsg, diff)
|
||||||
self.fail(self._formatMessage(msg, standardMsg))
|
self.fail(self._formatMessage(msg, standardMsg))
|
||||||
|
|
|
||||||
|
|
@ -829,18 +829,18 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
|
||||||
|
|
||||||
# set a lower threshold value and add a cleanup to restore it
|
# set a lower threshold value and add a cleanup to restore it
|
||||||
old_threshold = self._diffThreshold
|
old_threshold = self._diffThreshold
|
||||||
self._diffThreshold = 2**8
|
self._diffThreshold = 2**5
|
||||||
self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold))
|
self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold))
|
||||||
|
|
||||||
# under the threshold: diff marker (^) in error message
|
# under the threshold: diff marker (^) in error message
|
||||||
s = 'x' * (2**7)
|
s = 'x' * (2**4)
|
||||||
with self.assertRaises(self.failureException) as cm:
|
with self.assertRaises(self.failureException) as cm:
|
||||||
self.assertEqual(s + 'a', s + 'b')
|
self.assertEqual(s + 'a', s + 'b')
|
||||||
self.assertIn('^', str(cm.exception))
|
self.assertIn('^', str(cm.exception))
|
||||||
self.assertEqual(s + 'a', s + 'a')
|
self.assertEqual(s + 'a', s + 'a')
|
||||||
|
|
||||||
# over the threshold: diff not used and marker (^) not in error message
|
# 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
|
# if the path that uses difflib is taken, _truncateMessage will be
|
||||||
# called -- replace it with explodingTruncation to verify that this
|
# called -- replace it with explodingTruncation to verify that this
|
||||||
# doesn't happen
|
# 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(str(cm.exception), '%r != %r' % (s1, s2))
|
||||||
self.assertEqual(s + 'a', s + 'a')
|
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):
|
def testAssertCountEqual(self):
|
||||||
a = object()
|
a = object()
|
||||||
self.assertCountEqual([1, 2, 3], [3, 2, 1])
|
self.assertCountEqual([1, 2, 3], [3, 2, 1])
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,47 @@
|
||||||
"""Various utility functions."""
|
"""Various utility functions."""
|
||||||
|
|
||||||
from collections import namedtuple, OrderedDict
|
from collections import namedtuple, OrderedDict
|
||||||
|
from os.path import commonprefix
|
||||||
|
|
||||||
__unittest = True
|
__unittest = True
|
||||||
|
|
||||||
_MAX_LENGTH = 80
|
_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):
|
def safe_repr(obj, short=False):
|
||||||
try:
|
try:
|
||||||
result = repr(obj)
|
result = repr(obj)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ Core and Builtins
|
||||||
Library
|
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 #19034: repr() for tkinter.Tcl_Obj now exposes string reperesentation.
|
||||||
|
|
||||||
- Issue #18978: ``urllib.request.Request`` now allows the method to be
|
- Issue #18978: ``urllib.request.Request`` now allows the method to be
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue