Skip to content

Instantly share code, notes, and snippets.

@bukzor
Created March 16, 2013 19:12
Show Gist options
  • Select an option

  • Save bukzor/5177848 to your computer and use it in GitHub Desktop.

Select an option

Save bukzor/5177848 to your computer and use it in GitHub Desktop.
Some code which will pinpoint differences in nested structures. Possibly useful for `testfixtutres.compare()`.
class TestAssertEqualDeep(T.TestCase):
def test_numbers(self):
assert_equal_deep(1, 1)
with assert_raises_with_args(AssertionError, '\nUnequal at / : 1 != 2'):
assert_equal_deep(1, 2)
def test_strings(self):
assert_equal_deep('foo', 'foo')
with assert_raises_with_args(AssertionError, "\nUnequal at / : 'foo' != 'bar'"):
assert_equal_deep('foo', 'bar')
def test_list(self):
assert_equal_deep([1,2,3], [1,2,3])
with assert_raises_with_args(AssertionError, '\nUnequal at [2] : 3 != 4'):
assert_equal_deep([1,2,3], [1,2,4])
with assert_raises_with_args(AssertionError, '\nUnequal at [2] : 3 != undefined'):
assert_equal_deep([1,2,3], [1,2])
with assert_raises_with_args(AssertionError, '\nUnequal at [2] : undefined != 4'):
assert_equal_deep([1,2], [1,2,4])
def test_list_order(self):
with assert_raises_with_args(
AssertionError,
"\nUnequal at [2] : undefined != 3"
"\nUnequal at [3] : undefined != 4",
):
assert_equal_deep([1,2], [1,2,3,4])
def test_dict(self):
assert_equal_deep({'a':2}, {'a':2})
with assert_raises_with_args(AssertionError, '\nUnequal at /a : 1 != 2'):
assert_equal_deep({'a':1}, {'a':2})
with assert_raises_with_args(AssertionError, '\nUnequal at /a : 1 != undefined'):
assert_equal_deep({'a':1}, {})
with assert_raises_with_args(AssertionError, '\nUnequal at /a : undefined != 2'):
assert_equal_deep({}, {'a':2})
def test_complex(self):
complex = {
'letters': ['a', 'b', 'c'],
'numbers': {1:'one', 2:'two'},
}
assert_equal_deep(complex, deepcopy(complex))
complex2 = deepcopy(complex)
complex2['numbers'][3] = 'three'
with assert_raises_with_args(
AssertionError,
"\nUnequal at /numbers/3 : undefined != 'three'"
):
assert_equal_deep(complex, complex2)
complex2['letters'].append('d')
with assert_raises_with_args(
AssertionError,
"\nUnequal at /letters[3] : undefined != 'd'"
"\nUnequal at /numbers/3 : undefined != 'three'"
):
assert_equal_deep(complex, complex2)
with assert_raises_with_args(
AssertionError,
"\nUnequal at /letters[3] : undefined != 'd'"
"\nAnd more..."
):
assert_equal_deep(complex, complex2, max_diffs_printed=1)
with assert_raises_with_args(
AssertionError,
"\nUnequal at /letters[3] : undefined != 'd'"
"\nUnequal at /numbers/3 : undefined != 'three'"
):
assert_equal_deep(complex, complex2, max_diffs_printed=2)
complex2['symbols'] = '!@#%^'
with assert_raises_with_args(
AssertionError,
"\nUnequal at /symbols : undefined != '!@#%^'"
"\nUnequal at /letters[3] : undefined != 'd'"
"\nAnd more..."
):
assert_equal_deep(complex, complex2, max_diffs_printed=2)
def test_disparate(self):
with T.assert_raises_such_that(
AssertionError,
lambda e: T.assert_equal(
e.args[0].startswith('\nUnequal at / : ',),
True
)
):
assert_equal_deep(['f', 'o', 'o', 'o'], 'fooo')
with T.assert_raises_such_that(
AssertionError,
lambda e: T.assert_equal(
e.args[0].startswith('\nUnequal at / : ',),
True
)
):
assert_equal_deep({0:'f', 1:'o', 2:'o', 3:'o'}, 'fooo')
def assert_equal_deep(jsonish1, jsonish2, max_diffs_printed=10):
difference_count = 0
differences = []
stack = [(jsonish1,jsonish2,'')]
while stack:
jsonish1, jsonish2, xpath = stack.pop()
if hasattr(jsonish1, 'items') and hasattr(jsonish2, 'items'):
# These things are dict-like.
items1 = sorted(jsonish1.items(), reverse=True)
items2 = sorted(jsonish2.items(), reverse=True)
format = '/%s'
elif hasattr(jsonish1, '__iter__') and hasattr(jsonish2, '__iter__'):
# These things are list-like.
items1 = reversed(tuple(enumerate(jsonish1)))
items2 = reversed(tuple(enumerate(jsonish2)))
format = '[%i]'
elif jsonish1 != jsonish2:
if difference_count < max_diffs_printed:
if xpath == '':
xpath = '/'
from pprint import pformat
differences.append("\nUnequal at %s : %s != %s" % (xpath, pformat(jsonish1), pformat(jsonish2)))
difference_count += 1
continue
else:
differences.append("\nAnd more...")
break
else:
continue
seen_keys = set()
for self, other, order in (
(items1, jsonish2, lambda self, other, msg: (self, other, msg)),
(items2, jsonish1, lambda self, other, msg: (other, self, msg)),
):
for key, val in self:
if key in seen_keys:
continue
try:
other_val = other[key]
except (IndexError, KeyError):
other_val = undefined
stack.append(order(val, other_val, xpath + (format % key)))
seen_keys.add(key)
if difference_count:
raise AssertionError("".join(differences))
class undefined(object):
"""Singleton object to represent a value that was never assigned."""
def __repr__(self):
return 'undefined'
# This is a singleton.
undefined = undefined()
@contextmanager
def assert_raises_with_args(exception_type, *args):
with T.assert_raises_such_that(
exception_type,
lambda e: T.assert_equal(e.args, args)
):
yield
class TypeNameMatcher(object):
def __init__(self, type, name, module=None):
self.type = type
self.name = name
self.module = module
def __eq__(self, other):
return (
self.type is type(other) and
self.name == other.__name__ and
(
self.module is None or
self.module == other.__module__
)
)
def __ne__(self, other):
return not (self == other)
def __repr__(self):
if self.module is not None:
optional_module = ', %r' % self.module
else:
optional_module = ''
return '%s(%r, %r%s)' % (
self.__class__.__name__,
self.type,
self.name,
optional_module,
)
# A useful constant
lambda_matcher = TypeNameMatcher(types.FunctionType, '<lambda>')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment