def test_percent_empty_value_handling(self): # Test empty deviation cases--should pass without error. with AcceptedPercent(0): # <- Accepts empty deviations only. raise ValidationError([ Invalid(None, 0), Invalid('', 0), ]) # Test diffs that can not be accepted as percentages. with self.assertRaises(ValidationError) as cm: with AcceptedPercent(2.00): # <- Accepts +/- 200%. raise ValidationError([ Invalid(None, 0), # 0% Invalid(0, None), # 0% Deviation(+2, 0), # Can not be accepted by percent. Invalid(+2, None), # Can not be accepted by percent. Deviation(float('nan'), 16), # Not a number. ]) actual = cm.exception.differences expected = [ Deviation(+2, 0), # Can not be accepted by percent. Invalid(2, None), # Can not be accepted by percent. Deviation(float('nan'), 16), # Not a number. ] self.assertEqual(actual, expected)
def test_none_vs_numeric(self): diff = _getdiff(None, 6) # For None vs non-zero, self.assertEqual(diff, Deviation(-6, 6)) # difference is calculated # as 0 - other. diff = _getdiff(None, 0) # For None vs zero, self.assertEqual(diff, Deviation(None, 0)) # difference remains None.
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_eval(self): diff = Deviation(+1, 100) self.assertEqual(diff, eval(repr(diff))) diff = Deviation(-1, 100) self.assertEqual(diff, eval(repr(diff))) diff = Deviation(float('nan'), 100) self.assertEqual(diff, eval(repr(diff)))
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(float('nan'), 100) # None reference. self.assertEqual(repr(diff), "Deviation(float('nan'), 100)")
def test_keywords(self): """Keywords should be passed to diff objet.""" diff = _getdiff(5, 6, col1='AAA') self.assertEqual(diff, Deviation(-1, 6, col1='AAA')) diff = _getdiff('a', 6, col1='AAA') self.assertEqual(diff, Invalid('a', 6, col1='AAA')) diff = _getdiff(_NOTFOUND, 6, col1='AAA') self.assertEqual(diff, Deviation(-6, 6, col1='AAA'))
def test_repr(self): diff = Deviation(1, 100) # Simple. self.assertEqual("Deviation(+1, 100)", repr(diff)) diff = Deviation(-1, 100) # Simple negative. self.assertEqual("Deviation(-1, 100)", repr(diff)) diff = Deviation(3, 50, col1='a', col2='b') # Using kwds. self.assertRegex(repr(diff), "Deviation\(\+3, 50, col1=u?'a', col2=u?'b'\)") diff = Deviation(1, None, col1='a') # None reference. self.assertRegex(repr(diff), "Deviation\(\+1, None, col1=u?'a'\)")
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_multiarg_predicate(self): with self.assertRaises(ValidationError) as cm: def func(diff): return diff < 2 with AcceptedArgs((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_function_predicate(self): with self.assertRaises(ValidationError) as cm: def function(args): diff, expected = args return diff < 2 and expected == 5 with AcceptedArgs(function): # <- Acceptance! 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_is_common(self): """If requirement is common it should be omitted from Invalid difference (but not from Deviation differences). """ diff = _getdiff('a', 6, is_common=True) self.assertEqual(diff, Invalid('a')) diff = _getdiff(_NOTFOUND, 6, is_common=True) self.assertEqual(diff, Deviation(-6, 6))
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_datetime_vs_datetime(self): diff = _make_difference( datetime.datetime(1989, 2, 24, hour=10, minute=30), datetime.datetime(1989, 2, 24, hour=11, minute=30), ) self.assertEqual( diff, Deviation( datetime.timedelta(hours=-1), datetime.datetime(1989, 2, 24, hour=11, minute=30), ), )
def test_object_vs_object(self): """Non-numeric comparisons return Invalid type.""" diff = _make_difference('a', 'b') self.assertEqual(diff, Invalid('a', 'b')) diff = _make_difference(5, 'b') self.assertEqual(diff, Invalid(5, 'b')) diff = _make_difference('a', 6) self.assertEqual(diff, Invalid('a', 6)) diff = _make_difference(float('nan'), 6) self.assertEqual(diff, Deviation(float('nan'), 6)) diff = _make_difference(5, float('nan')) self.assertEqual(diff, Deviation(float('nan'), float('nan'))) fn = lambda x: True diff = _make_difference('a', fn) self.assertEqual(diff, Invalid('a', fn)) regex = re.compile('^test$') diff = _make_difference('a', regex) self.assertEqual(diff, Invalid('a', re.compile('^test$')))
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_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_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 test_repr(self): diff = Deviation(1, 100) # Simple. self.assertEqual("Deviation(+1, 100)", repr(diff)) diff = Deviation(-1, 100) # Simple negative. self.assertEqual("Deviation(-1, 100)", repr(diff)) diff = Deviation(3, 50, col1='a', col2='b') # Using kwds. self.assertRegex(repr(diff), "Deviation\(\+3, 50, col1=u?'a', col2=u?'b'\)") with self.assertRaises(ValueError): diff = Deviation(0, 100) # Zero diff. with self.assertRaises(ValueError): diff = Deviation(None, 100) # None diff. diff = Deviation(1, None, col1='a') # None reference. self.assertRegex(repr(diff), "Deviation\(\+1, None, col1=u?'a'\)")
def test_repr_eval(self): diff = Deviation(+1, 100) self.assertEqual(diff, eval(repr(diff))) # Test __repr__ eval diff = Deviation(-1, 100, col4='foo', col5='bar') self.assertEqual(diff, eval(repr(diff))) # Test __repr__ eval
def test_eq(self): diff1 = Deviation(1, 100) diff2 = Deviation(1, 100) self.assertEqual(diff1, diff2) diff1 = Deviation(1.0, 100.0) diff2 = Deviation(1.0, 100.0) self.assertEqual(diff1, diff2) diff1 = Deviation(1.0, 100) diff2 = Deviation(1, 100) self.assertEqual(diff1, diff2) diff1 = Deviation(1, 100.0) diff2 = Deviation(1, 100) self.assertEqual(diff1, diff2) diff1 = Deviation(1, 100, foo='aaa', bar='bbb') diff2 = Deviation(1, 100, bar='bbb', foo='aaa') self.assertEqual(diff1, diff2) diff1 = Deviation(1, 100) diff2 = Deviation(1, 250) self.assertNotEqual(diff1, diff2) diff1 = Deviation(+1, 100) diff2 = "Deviation(+1, 100)" self.assertNotEqual(diff1, diff2)
def test_hash(self): diff = Deviation(1, 100, col1='a', col2='b') self.assertIsInstance(hash(diff), int)
def test_str(self): diff = Deviation(5, 75, col1='a') self.assertEqual(str(diff), repr(diff))
def test_instantiation(self): Deviation(1, 100) # Pass without error.
def test_zero_and_empty_value_handling(self): """Empty values receive special handling.""" # Expected 0 (pass without error). Deviation(+5, 0) Deviation(-5, 0) Deviation(float('nan'), 0) with self.assertRaises(ValueError): Deviation(0, 0) # Expected numeric value (pass without error). Deviation(+1, 5) Deviation(-1, 5) Deviation(float('nan'), 5) # Expected non-zero, with empty or zero deviation. with self.assertRaises(ValueError): Deviation(0, 5) with self.assertRaises(TypeError): Deviation(None, 5) with self.assertRaises(TypeError): Deviation('', 5) with self.assertRaises(TypeError): Deviation(5, None) with self.assertRaises(TypeError): Deviation(5, '') # NaN handling. Deviation(float('nan'), 0) Deviation(0, float('nan'))
def test_numeric_vs_numeric(self): diff = _make_difference(5, 6) self.assertEqual(diff, Deviation(-1, 6))
def test_empty_value_handling(self): with self.assertRaises(ValueError): Deviation(0, 100) # Zero diff. Deviation(0, None) Deviation(+5, None) Deviation(None, 0) with self.assertRaises(ValueError): Deviation(None, 5) # Should be Deviation(-5, 5) Deviation(0, '') Deviation(+5, '') Deviation('', 0) with self.assertRaises(ValueError): Deviation('', 5) # Should be Deviation(-5, 5) Deviation(0, float('nan')) Deviation(+5, float('nan')) Deviation(float('nan'), 0) with self.assertRaises(ValueError): Deviation(float('nan'), 5) # Should be Deviation(-5, 5) with self.assertRaises(ValueError): Deviation(0, 1) # Just no. # False is treated the same as zero. Deviation(+5, 0) Deviation(+5, False) with self.assertRaises(ValueError): Deviation(0, 0) with self.assertRaises(ValueError): Deviation(0, False) with self.assertRaises(ValueError): Deviation(False, 0) with self.assertRaises(ValueError): Deviation(False, 5) # Should be Deviation(-5, 5)
def test_numeric_vs_none(self): diff = _getdiff(5, None) self.assertEqual(diff, Deviation(+5, None)) diff = _getdiff(0, None) self.assertEqual(diff, Deviation(+0, None))
def test_numeric_vs_numeric(self): diff = _getdiff(5, 6) self.assertEqual(diff, Deviation(-1, 6))