Created
March 16, 2013 19:12
-
-
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()`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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