def test_special_values(self): # Test empty deviation cases--should pass without error. with allowed_percent(0): # <- Allows empty deviations only. raise ValidationError([ Deviation(None, 0), Deviation('', 0), ]) # Test diffs that can not be allowed as percentages. with self.assertRaises(ValidationError) as cm: with allowed_percent(2.00): # <- Allows +/- 200%. raise ValidationError([ Deviation(None, 0), # 0% Deviation(0, None), # 0% Deviation(+2, 0), # Can not be allowed by percent. Deviation(+2, None), # Can not be allowed by percent. Deviation(float('nan'), 16), # Not a number. ]) actual = cm.exception.differences expected = [ Deviation(+2, 0), # Can not be allowed by percent. Deviation(+2, None), # Can not be allowed by percent. Deviation(float('nan'), 16), # Not a number. ] self.assertEqual(actual, expected)
def test_none_vs_numeric(self): diff = _make_difference(None, 6) # For None vs non-zero, self.assertEqual(diff, Deviation(-6, 6)) # difference is calculated # as 0 - other. diff = _make_difference(None, 0) # For None vs zero, self.assertEqual(diff, Deviation(None, 0)) # difference remains None.
def test_some_differences(self): # Sequence order. data = {'a': ['x', 'y']} result = _apply_mapping_requirement(data, {'a': ['x', 'z']}) result = dict(result) self.assertTrue(len(result) == 1) self.assertEqual(result, {'a': {(1, 2): [Invalid('y', 'z')]}}) # Set membership. data = {'a': ['x', 'x'], 'b': ['x', 'y', 'z']} result = _apply_mapping_requirement(data, { 'a': set(['x', 'y']), 'b': set(['x', 'y']) }) expected = {'a': [Missing('y')], 'b': [Extra('z')]} self.assertEqual(dict(result), expected) # Equality of single values. data = {'a': 'x', 'b': 10} result = _apply_mapping_requirement(data, {'a': 'j', 'b': 9}) expected = {'a': Invalid('x', expected='j'), 'b': Deviation(+1, 9)} self.assertEqual(dict(result), expected) # Equality of multiple values. data = {'a': ['x', 'j'], 'b': [10, 9]} result = _apply_mapping_requirement(data, {'a': 'j', 'b': 9}) expected = {'a': [Invalid('x')], 'b': [Deviation(+1, 9)]} self.assertEqual(dict(result), expected) # Equality of single tuples. data = {'a': (1, 'x'), 'b': (9, 10)} result = _apply_mapping_requirement(data, {'a': (1, 'j'), 'b': (9, 9)}) expected = { 'a': Invalid((1, 'x'), expected=(1, 'j')), 'b': Invalid((9, 10), expected=(9, 9)) } self.assertEqual(dict(result), expected) # Equality of multiple tuples. data = {'a': [(1, 'j'), (1, 'x')], 'b': [(9, 9), (9, 10)]} result = _apply_mapping_requirement(data, {'a': (1, 'j'), 'b': (9, 9)}) expected = {'a': [Invalid((1, 'x'))], 'b': [Invalid((9, 10))]} self.assertEqual(dict(result), expected) # Equality of multiple values, missing key with single item. data = {'a': ['x', 'j'], 'b': [10, 9]} result = _apply_mapping_requirement(data, {'a': 'j', 'b': 9, 'c': 'z'}) expected = { 'a': [Invalid('x')], 'b': [Deviation(+1, 9)], 'c': Missing('z') } self.assertEqual(dict(result), expected) # Missing key, set membership. data = {'a': 'x'} result = _apply_mapping_requirement(data, {'a': 'x', 'b': set(['z'])}) expected = {'b': [Missing('z')]} self.assertEqual(dict(result), expected)
def test_deviation(self): diff = Deviation(+1, 100) with self.assertRaises(AttributeError): diff.expected = 101 with self.assertRaises(AttributeError): diff.new_attribute = 202
def test_repr(self): diff = Deviation(1, 100) # Simple positive. self.assertEqual(repr(diff), "Deviation(+1, 100)") diff = Deviation(-1, 100) # Simple negative. self.assertEqual(repr(diff), "Deviation(-1, 100)") diff = Deviation(1, None) # None reference. self.assertEqual(repr(diff), "Deviation(+1, None)")
def test_same_value_case(self): with self.assertRaises(ValidationError) as cm: with AcceptedPercent(0.25, 0.25): # <- Accepts +25% only. raise ValidationError(self.differences) result_diffs = cm.exception.differences self.assertEqual({ 'aaa': Deviation(-1, 16), 'ccc': Deviation(+2, 16) }, result_diffs)
def test_same_value_case(self): with self.assertRaises(ValidationError) as cm: with AcceptedTolerance(4, 4): # <- Accepts off-by-4 only. raise ValidationError(self.differences) result_diffs = cm.exception.differences self.assertEqual({ 'aaa': Deviation(-1, 16), 'ccc': Deviation(+2, 16) }, result_diffs)
def test_multiarg_predicate(self): with self.assertRaises(ValidationError) as cm: def func(diff): return diff < 2 with allowed_args((func, 5)): raise ValidationError([ Deviation(+1, 5), Deviation(+2, 5), ]) remaining_diffs = cm.exception.differences self.assertEqual(list(remaining_diffs), [Deviation(+2, 5)])
def test_percent_error(self): # Test "tolerance" syntax. with self.assertRaises(ValidationError) as cm: with AcceptedPercent(0.2): # <- Accepts +/- 20%. raise ValidationError(self.differences) remaining = cm.exception.differences self.assertEqual(remaining, {'bbb': Deviation(+4, 16)}) # Test "upper/lower" syntax. with self.assertRaises(ValidationError) as cm: with AcceptedPercent(0.0, 0.3): # <- Accepts from 0 to 30%. raise ValidationError(self.differences) result_diffs = cm.exception.differences self.assertEqual({'aaa': Deviation(-1, 16)}, result_diffs)
def test_function_predicate(self): with self.assertRaises(ValidationError) as cm: def function(args): diff, expected = args return diff < 2 and expected == 5 with allowed_args(function): # <- Allowance! raise ValidationError([ Deviation(+1, 5), Deviation(+2, 5), ]) remaining_diffs = cm.exception.differences self.assertEqual(list(remaining_diffs), [Deviation(+2, 5)])
def test_interval_method(self): data = {'A': 5, 'B': 7, 'C': 9} validate.interval(data, 5, 10) data = [5, 7, 9] validate.interval(data, 5, 10) data = {'A': [7, 8, 9], 'B': [5, 6]} validate.interval(data, 5, 10) with self.assertRaises(ValidationError) as cm: data = {'A': 3, 'B': 6, 'C': [6, 7], 'D': [9, 10]} validate.interval(data, 5, 9) actual = cm.exception.differences expected = {'A': Deviation(-2, 5), 'D': [Deviation(+1, 9)]} self.assertEqual(actual, expected)
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_some_false(self): data = ['x', 'x', 'y'] result = _require_predicate_from_iterable(data, 'x') self.assertEqual(list(result), [Invalid('y')]) data = [2, 2, 7] result = _require_predicate_from_iterable(data, 2) self.assertEqual(list(result), [Deviation(+5, 2)])
def test_novalue_comparisons(self): diff = _make_difference('a', NOVALUE) self.assertEqual(diff, Extra('a')) diff = _make_difference(NOVALUE, 'b') self.assertEqual(diff, Missing('b')) # For numeric comparisons, NOVALUE behaves like None. diff = _make_difference(5, NOVALUE) self.assertEqual(diff, Deviation(+5, None)) diff = _make_difference(0, NOVALUE) self.assertEqual(diff, Deviation(0, None)) diff = _make_difference(NOVALUE, 6) self.assertEqual(diff, Deviation(-6, 6)) # <- Asymmetric behavior # (see None vs numeric)! diff = _make_difference(NOVALUE, 0) self.assertEqual(diff, Deviation(None, 0))
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')) diff = _make_difference(NOVALUE, 6, show_expected=False) self.assertEqual(diff, Deviation(-6, 6))
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_required_sequence(self): """When *required* is a sequence, should compare predicates by position. """ with self.assertRaises(ValidationError) as cm: data = ['a', 2, 'x', 3] required = ['a', 2, 'c', 4] self.assertValid(data, required) error = cm.exception expected = [ Invalid('x', expected='c'), Deviation(-1, 4), ] self.assertEqual(error.differences, expected) self.assertEqual(error.args[1], 'does not match required sequence')
def test_approx_method(self): data = {'A': 5.00000001, 'B': 10.00000001} requirement = Query.from_object({'A': 5, 'B': 10}) validate.approx(data, requirement) data = [5.00000001, 10.00000001] requirement = Query.from_object([5, 10]) validate.approx(data, requirement) data = {'A': [5.00000001, 10.00000001], 'B': [5.00000001, 10.00000001]} requirement = Query.from_object({'A': [5, 10], 'B': [5, 10]}) validate.approx(data, requirement) with self.assertRaises(ValidationError) as cm: data = {'A': 3, 'B': 10.00000001} requirement = {'A': 5, 'B': 10} validate.approx(data, requirement) actual = cm.exception.differences expected = {'A': Deviation(-2, 5)} self.assertEqual(actual, expected)
def setUp(self): self.differences = { 'aaa': Deviation(-1, 16), # -6.25% 'bbb': Deviation(+4, 16), # 25.0% 'ccc': Deviation(+2, 16), # 12.5% }
def test_NaN_values(self): with self.assertRaises(ValidationError): # <- NaN values should not be caught! with allowed_deviation(0): raise ValidationError(Deviation(float('nan'), 0))
def test_empty_string(self): with allowed_deviation(0): # <- Pass without failure. raise ValidationError(Deviation('', 0)) with allowed_deviation(0): # <- Pass without failure. raise ValidationError(Deviation(0, ''))
def test_same_value_case(self): with self.assertRaises(ValidationError) as cm: with allowed_deviation(3, 3): # <- Allows off-by-3 only. raise ValidationError(self.differences) result_diffs = cm.exception.differences self.assertEqual({'aaa': Deviation(-1, 10), 'ccc': Deviation(+2, 10)}, result_diffs)
def setUp(self): self.differences = { 'aaa': Deviation(-1, 10), 'bbb': Deviation(+3, 10), 'ccc': Deviation(+2, 10), }
def test_numeric_vs_none(self): diff = _make_difference(5, None) self.assertEqual(diff, Deviation(+5, None)) diff = _make_difference(0, None) self.assertEqual(diff, Deviation(+0, None))
def test_deviation(self): result = _require_predicate(11, 10, True) self.assertEqual(result, Deviation(+1, 10))
def test_tolerance_syntax(self): with self.assertRaises(ValidationError) as cm: with allowed_percent(0.2): # <- Allows +/- 20%. raise ValidationError(self.differences) remaining = cm.exception.differences self.assertEqual(remaining, {'bbb': Deviation(+4, 16)})
def test_lower_upper_syntax(self): with self.assertRaises(ValidationError) as cm: with allowed_percent(0.0, 0.3): # <- Allows from 0 to 30%. raise ValidationError(self.differences) result_diffs = cm.exception.differences self.assertEqual({'aaa': Deviation(-1, 16)}, result_diffs)
def test_numeric_vs_numeric(self): diff = _make_difference(5, 6) self.assertEqual(diff, Deviation(-1, 6))
def test_integration_examples(self): # Test allowance of +/- 2 OR +/- 6%. with self.assertRaises(ValidationError) as cm: differences = [ Deviation(+2, 1), # 200% Deviation(+4, 8), # 50% Deviation(+8, 32), # 25% ] with allowed_deviation(2) | allowed_percent(0.25): raise ValidationError(differences) remaining = cm.exception.differences self.assertEqual(remaining, [Deviation(+4, 8)]) # Test missing-type AND matching-value. with self.assertRaises(ValidationError) as cm: differences = [ Missing('A'), Missing('B'), Extra('C'), ] with allowed_missing() & allowed_args(lambda x: x == 'A'): raise ValidationError(differences) remaining = cm.exception.differences self.assertEqual(remaining, [Missing('B'), Extra('C')]) # Test missing-type OR allowed-limit. with self.assertRaises(ValidationError) as cm: differences = [ Extra('A'), Missing('B'), Extra('C'), Missing('D'), ] with allowed_limit(1) | allowed_missing(): raise ValidationError(differences) remaining = cm.exception.differences self.assertEqual(remaining, [Extra('C')]) # Test missing-type AND allowed-limit. with self.assertRaises(ValidationError) as cm: differences = [ Extra('A'), Missing('B'), Missing('C'), ] with allowed_limit(1) & allowed_missing(): # Allows only 1 missing. raise ValidationError(differences) remaining = cm.exception.differences self.assertEqual(remaining, [Extra('A'), Missing('C')]) # Test missing-type OR allowed-limit. with self.assertRaises(ValidationError) as cm: differences = [ Extra('A'), Missing('B'), Extra('C'), Missing('D'), ] with allowed_limit(1) | allowed_specific(Extra('A')): raise ValidationError(differences) remaining = cm.exception.differences self.assertEqual(remaining, [Extra('C'), Missing('D')])
def test_repr_eval(self): diff = Deviation(+1, 100) self.assertEqual(diff, eval(repr(diff))) diff = Deviation(-1, 100) self.assertEqual(diff, eval(repr(diff)))