def test_exit_context(self): """The __exit__() method should re-raise exceptions that are not accepted and it should return True when there are no errors or if all differences have been accepted (see PEP 343 for context manager protocol). """ try: raise ValidationError([Missing('A'), Extra('B')], 'error description') except ValidationError: type, value, traceback = sys.exc_info() # Get exception info. with self.assertRaises(ValidationError) as cm: acceptance = MinimalAcceptance('acceptance message') acceptance.__exit__(type, value, traceback) description = cm.exception.description self.assertEqual(description, 'acceptance message: error description') # Test with no error description. try: raise ValidationError([Missing('A'), Extra('B')]) # <- No description. except ValidationError: type, value, traceback = sys.exc_info() # Get exception info. with self.assertRaises(ValidationError) as cm: acceptance = MinimalAcceptance('acceptance message') acceptance.__exit__(type, value, traceback) description = cm.exception.description self.assertEqual(description, 'acceptance message')
def test_get_deviation_expected_extra_or_single_arg_invalid(self): """Extra and single-argument Invalid differences should be treated the same. """ func = AcceptedTolerance._get_deviation_expected self.assertEqual(func(Extra(2)), (2, 0)) self.assertEqual(func(Invalid(2)), (2, 0)) self.assertEqual(func(Extra(-2)), (-2, 0)) self.assertEqual(func(Invalid(-2)), (-2, 0)) self.assertEqual(func(Extra(0)), (0, 0)) self.assertEqual(func(Invalid(0)), (0, 0)) with self.assertRaises(TypeError): func(Extra((1, 2))) with self.assertRaises(TypeError): func(Invalid((1, 2))) with self.assertRaises(TypeError): func(Extra('abc')) with self.assertRaises(TypeError): func(Invalid('abc'))
def test_accepted_invalid(self): differences = [Invalid('X'), Invalid('Y'), Extra('Z')] with self.assertRaises(ValidationError) as cm: with AcceptedDifferences(Invalid): # <- Apply acceptance! raise ValidationError(differences) remaining_diffs = cm.exception.differences self.assertEqual(list(remaining_diffs), [Extra('Z')])
def test_extra(self): diff = Extra('foo') with self.assertRaises(AttributeError): diff.attr = ('bar',) with self.assertRaises(AttributeError): diff.new_attribute = 'baz'
def test_mapping_vs_type(self): differences = { 'a': [Missing('X')], 'b': [Missing('Y'), Extra('X')], 'c': Missing('X'), } acceptance = AcceptedDifferences(Missing) expected = {'b': Extra('X')} self.assertAcceptance(differences, acceptance, expected)
def test_extra_deviation_percent(self): with self.assertRaises(ValidationError) as cm: with AcceptedPercent(2.0): # <- Accepts +/- 200%. raise ValidationError([ Extra(-1), # <- Rejected: Can not be accepted by percent. Extra(0), # <- ACCEPTED! Extra(2), # <- Rejected: Can not be accepted by percent. ]) remaining = cm.exception.differences self.assertEqual(remaining, [Extra(-1), Extra(2)])
def test_nonmapping_vs_mapping(self): """A mapping accpetance will not accept any non-mapping differences.""" differences = [Missing('Y'), Extra('X')] acceptance = AcceptedDifferences({'a': Extra('X')}) self.assertAcceptance(differences, acceptance, differences) differences = Extra('X') acceptance = AcceptedDifferences({'a': Extra('X')}) self.assertAcceptance(differences, acceptance, [differences])
def test_filterfalse(self): class accepted_missing(MinimalAcceptance): def call_predicate(_self, item): return isinstance(item[1], Missing) acceptance = accepted_missing() result = acceptance._filterfalse([ (None, Missing('A')), (None, Extra('B')), ]) self.assertEqual(list(result), [(None, Extra('B'))])
def test_scope(self): with self.assertRaises(ValidationError) as cm: with AcceptedCount(2, scope='group'): # <- Accepts 2 per group. raise ValidationError({ 'foo': [Extra('xxx'), Extra('yyy')], 'bar': [Missing('xxx'), Missing('yyy')], 'baz': [Invalid('xxx'), Invalid('yyy'), Invalid('zzz')], }) remaining = cm.exception.differences self.assertEqual(remaining, {'baz': Invalid('zzz')})
def test_nonmapping_container(self): """When differences container is not a mapping, the keys that AcceptedKeys() sees are all None. """ with self.assertRaises(ValidationError) as cm: with AcceptedKeys('foo'): # <- Accept keys that equal 'foo'. differences = [Missing(1), Extra(2)] # <- List has no keys! raise ValidationError(differences) remaining_diffs = cm.exception.differences self.assertEqual(list(remaining_diffs), [Missing(1), Extra(2)])
def test_over_limit(self): with self.assertRaises(ValidationError) as cm: with AcceptedCount(1): # <- Accepts 1 but there are 2. raise ValidationError([Extra('xxx'), Missing('yyy')]) remaining = list(cm.exception.differences) self.assertEqual(remaining, [Missing('yyy')]) with self.assertRaises(ValidationError) as cm: with AcceptedCount(1): # <- Accepts 1 and there are 2. raise ValidationError({'foo': Extra('xxx'), 'bar': Missing('yyy')}) remaining = cm.exception.differences self.assertIsInstance(remaining, Mapping) self.assertEqual(len(remaining), 1)
def test_UnionedAcceptance(self): original_diffs = [Missing('a'), Extra('a'), Missing('b'), Extra('b')] with self.assertRaises(ValidationError) as cm: with UnionedAcceptance(self.accepted_missing, self.accepted_letter_a): raise ValidationError(original_diffs) differences = cm.exception.differences self.assertEqual(list(differences), [Extra('b')]) # Test with acceptances in reverse-order (should give same result). with self.assertRaises(ValidationError) as cm: with UnionedAcceptance(self.accepted_letter_a, self.accepted_missing): raise ValidationError(original_diffs) differences = cm.exception.differences self.assertEqual(list(differences), [Extra('b')])
def test_non_deviation_diffs(self): diffs = [Missing('foo'), Extra('bar'), Invalid('baz')] with self.assertRaises(ValidationError) as cm: with AcceptedPercent(0.05): raise ValidationError(diffs) uncaught_diffs = cm.exception.differences self.assertEqual(diffs, uncaught_diffs)
def test_data_mapping(self): with self.assertRaises(ValidationError) as cm: data = {'a': set([1, 2]), 'b': set([1]), 'c': set([1, 2, 3])} required = set([1, 2]) self.assertValid(data, required) differences = cm.exception.differences self.assertEqual(differences, {'b': [Missing(2)], 'c': [Extra(3)]})
def test_unique_method(self): validate.unique([1, 2, 3, 4]) with self.assertRaises(ValidationError) as cm: validate.unique([1, 2, 3, 3]) actual = cm.exception.differences expected = [Extra(3)] self.assertEqual(actual, expected)
def test_nonmapping(self): with self.assertRaises(ValidationError) as cm: data = set([1, 2, 3]) required = set([1, 2, 4]) self.assertValid(data, required) differences = cm.exception.differences self.assertEqual(differences, [Missing(4), Extra(3)])
def test_hashable(self): """Differences with hashable *args should be hashable.""" # Following should all pass without error. hash(Missing('foo')) hash(Extra('bar')) hash(Invalid('baz')) hash(Invalid('baz', 'qux')) hash(Deviation(-1, 10))
def test_novalue_comparisons(self): diff = _make_difference('a', NOVALUE) self.assertEqual(diff, Extra('a')) diff = _make_difference(5, NOVALUE) self.assertEqual(diff, Extra(5)) diff = _make_difference(0, NOVALUE) self.assertEqual(diff, Extra(0)) diff = _make_difference(NOVALUE, 'a') self.assertEqual(diff, Missing('a')) diff = _make_difference(NOVALUE, 5) self.assertEqual(diff, Missing(5)) diff = _make_difference(NOVALUE, 0) self.assertEqual(diff, Missing(0))
def test_repr(self): acceptance = AcceptedDifferences(Extra) self.assertEqual(repr(acceptance), 'AcceptedDifferences(Extra)') acceptance = AcceptedDifferences(Extra('foo')) self.assertEqual(repr(acceptance), "AcceptedDifferences(Extra('foo'))") acceptance = AcceptedDifferences([Extra('foo')], scope='element') self.assertEqual(repr(acceptance), "AcceptedDifferences([Extra('foo')], scope='element')") acceptance = AcceptedDifferences([Extra('foo')]) # Defaults to 'group' scope. self.assertEqual(repr(acceptance), "AcceptedDifferences([Extra('foo')])") acceptance = AcceptedDifferences([Extra('foo')], scope='whole') self.assertEqual(repr(acceptance), "AcceptedDifferences([Extra('foo')], scope='whole')") acceptance = AcceptedDifferences({'a': Extra('foo')}) self.assertEqual(repr(acceptance), "AcceptedDifferences({'a': Extra('foo')})")
def test_scope(self): acceptance = AcceptedDifferences(Extra) self.assertEqual(acceptance.scope, set(['element'])) acceptance = AcceptedDifferences(Extra('foo')) self.assertEqual(acceptance.scope, set(['element'])) acceptance = AcceptedDifferences([Extra('foo')], scope='element') self.assertEqual(acceptance.scope, set(['element'])) acceptance = AcceptedDifferences([Extra('foo')]) # Defaults to 'group' scope. self.assertEqual(acceptance.scope, set(['group'])) acceptance = AcceptedDifferences([Extra('foo')], scope='whole') self.assertEqual(acceptance.scope, set(['whole'])) # Mapping of differences defaults to 'group' scope, too. acceptance = AcceptedDifferences({'a': Extra('foo')}) self.assertEqual(acceptance.scope, set(['group']))
def test_set_method(self): data = [1, 2, 3, 4] requirement = Query.from_object([1, 2, 3, 4]) validate.set(data, requirement) with self.assertRaises(ValidationError) as cm: data = [1, 2, 3, 5] requirement = set([1, 2, 3, 4]) validate.set(data, requirement) actual = cm.exception.differences expected = [Missing(4), Extra(5)] self.assertEqual(actual, expected) with self.assertRaises(ValidationError) as cm: data = {'A': [1, 2, 3], 'B': [3]} requirement = {'A': iter([1, 2]), 'B': iter([3, 4])} validate.set(data, requirement) actual = cm.exception.differences expected = {'A': [Extra(3)], 'B': [Missing(4)]} self.assertEqual(actual, expected)
def test_superset_method(self): data = [1, 2, 3] superset = Query.from_object([1, 2, 3, 4]) validate.superset(data, superset) with self.assertRaises(ValidationError) as cm: data = {'A': [1, 2, 3], 'B': [3, 4, 5]} superset = {'A': set([1, 2, 3]), 'B': set([2, 3, 4])} validate.superset(data, superset) actual = cm.exception.differences expected = {'B': [Extra(5)]} self.assertEqual(actual, expected)
def test_string_predicate(self): with self.assertRaises(ValidationError) as cm: with AcceptedArgs('bbb'): # <- Acceptance! raise ValidationError([ Missing('aaa'), Missing('bbb'), Extra('bbb'), ]) remaining_diffs = cm.exception.differences self.assertEqual(list(remaining_diffs), [Missing('aaa')])
def test_order_method(self): data = ['A', 'B', 'C', 'C'] requirement = iter(['A', 'B', 'C', 'C']) validate.order(data, requirement) data = ['A', 'B', 'C', 'D'] requirement = Query.from_object(['A', 'B', 'C', 'D']) validate.order(data, requirement) with self.assertRaises(ValidationError) as cm: data = ['A', 'C', 'D', 'F'] requirement = Query.from_object(iter(['A', 'B', 'C', 'D'])) validate.order(data, requirement) actual = cm.exception.differences expected = [Missing((1, 'B')), Extra((3, 'F'))] self.assertEqual(actual, expected) with self.assertRaises(ValidationError) as cm: data = {'x': ['A'], 'y': ['B', 'C', 'D']} requirement = Query.from_object({'x': ['A', 'B'], 'y': ['C', 'D']}) validate.order(data, requirement) actual = cm.exception.differences expected = {'x': [Missing((1, 'B'))], 'y': [Extra((0, 'B'))]} self.assertEqual(actual, expected)
def test_incompatible_diffs(self): """Test differences that cannot be fuzzy matched.""" incompatible_diffs = [ Missing('foo'), Extra('bar'), Invalid('baz'), # <- Cannot accept if there's no expected value. Deviation(1, 10), ] differences = incompatible_diffs + self.differences with self.assertRaises(ValidationError) as cm: with AcceptedFuzzy(cutoff=0.5): raise ValidationError(differences) remaining = cm.exception.differences self.assertEqual(remaining, incompatible_diffs)
def test_show_expected(self): """If requirement is common it should be omitted from Invalid difference (but not from Deviation differences). """ diff = _make_difference('a', 6, show_expected=True) self.assertEqual(diff, Invalid('a', expected=6)) diff = _make_difference('a', 6, show_expected=False) self.assertEqual(diff, Invalid('a')) # Show expected has no value with Missing or Extra differences. diff = _make_difference(NOVALUE, 6, show_expected=False) self.assertEqual(diff, Missing(6)) diff = _make_difference(6, NOVALUE, show_expected=False) self.assertEqual(diff, Extra(6))
def test_unhashable_contents(self): """The hash behavior of differences should act like tuples do. When a difference's contents are unhashable, the difference itself becomes unhashable too. """ with self.assertRaises(TypeError): hash(Missing(['foo'])) with self.assertRaises(TypeError): hash(Extra(['bar'])) with self.assertRaises(TypeError): hash(Invalid(['baz'])) with self.assertRaises(TypeError): hash(Invalid('baz', ['qux']))
def test_notfound_comparisons(self): diff = _getdiff('a', _NOTFOUND) self.assertEqual(diff, Extra('a')) diff = _getdiff(_NOTFOUND, 'b') self.assertEqual(diff, Missing('b')) # For numeric comparisons, _NOTFOUND behaves like None. diff = _getdiff(5, _NOTFOUND) self.assertEqual(diff, Deviation(+5, None)) diff = _getdiff(0, _NOTFOUND) self.assertEqual(diff, Deviation(0, None)) diff = _getdiff(_NOTFOUND, 6) self.assertEqual(diff, Deviation(-6, 6)) # <- Assymetric behavior # (see None vs numeric)! diff = _getdiff(_NOTFOUND, 0) self.assertEqual(diff, Deviation(None, 0))
def test_combination_of_cases(self): """This is a bit of an integration test.""" differences = { 'foo': [Extra('xxx'), Missing('yyy')], 'bar': [Extra('xxx')], 'baz': [Extra('xxx'), Missing('yyy'), Extra('zzz')], } #accepted = {Ellipsis: [Extra('xxx'), Missing('yyy')]} accepted = [Extra('xxx'), Missing('yyy')] with self.assertRaises(ValidationError) as cm: with AcceptedDifferences(accepted): raise ValidationError(differences) actual = cm.exception.differences self.assertEqual(actual, {'baz': Extra('zzz')})
def test_acceptance_protocol(self): accepted = self.LoggingAcceptance() result = accepted._filterfalse([ ('foo', Missing('A')), ('foo', Extra('B')), ('bar', Missing('C')), ]) list(result) # Evaluate entire iterator, discarding result. expected = [ 'start_collection()', "start_group('foo')", "call_predicate(('foo', Missing('A')))", "call_predicate(('foo', Extra('B')))", "end_group('foo')", "start_group('bar')", "call_predicate(('bar', Missing('C')))", "end_group('bar')", 'end_collection()', ] self.assertEqual(accepted.log, expected)