267 lines
6.4 KiB
Python
267 lines
6.4 KiB
Python
"""
|
|
Tests for `attr.validators`.
|
|
"""
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
import pytest
|
|
import zope.interface
|
|
|
|
import attr
|
|
|
|
from attr import has
|
|
from attr import validators as validator_module
|
|
from attr._compat import TYPE
|
|
from attr.validators import and_, in_, instance_of, optional, provides
|
|
|
|
from .utils import simple_attr
|
|
|
|
|
|
class TestInstanceOf(object):
|
|
"""
|
|
Tests for `instance_of`.
|
|
"""
|
|
def test_success(self):
|
|
"""
|
|
Nothing happens if types match.
|
|
"""
|
|
v = instance_of(int)
|
|
v(None, simple_attr("test"), 42)
|
|
|
|
def test_subclass(self):
|
|
"""
|
|
Subclasses are accepted too.
|
|
"""
|
|
v = instance_of(int)
|
|
# yep, bools are a subclass of int :(
|
|
v(None, simple_attr("test"), True)
|
|
|
|
def test_fail(self):
|
|
"""
|
|
Raises `TypeError` on wrong types.
|
|
"""
|
|
v = instance_of(int)
|
|
a = simple_attr("test")
|
|
with pytest.raises(TypeError) as e:
|
|
v(None, a, "42")
|
|
assert (
|
|
"'test' must be <{type} 'int'> (got '42' that is a <{type} "
|
|
"'str'>).".format(type=TYPE),
|
|
a, int, "42",
|
|
|
|
) == e.value.args
|
|
|
|
def test_repr(self):
|
|
"""
|
|
Returned validator has a useful `__repr__`.
|
|
"""
|
|
v = instance_of(int)
|
|
assert (
|
|
"<instance_of validator for type <{type} 'int'>>"
|
|
.format(type=TYPE)
|
|
) == repr(v)
|
|
|
|
|
|
def always_pass(_, __, ___):
|
|
"""
|
|
Toy validator that always passses.
|
|
"""
|
|
|
|
|
|
def always_fail(_, __, ___):
|
|
"""
|
|
Toy validator that always fails.
|
|
"""
|
|
0/0
|
|
|
|
|
|
class TestAnd(object):
|
|
def test_success(self):
|
|
"""
|
|
Succeeds if all wrapped validators succeed.
|
|
"""
|
|
v = and_(instance_of(int), always_pass)
|
|
|
|
v(None, simple_attr("test"), 42)
|
|
|
|
def test_fail(self):
|
|
"""
|
|
Fails if any wrapped validator fails.
|
|
"""
|
|
v = and_(instance_of(int), always_fail)
|
|
|
|
with pytest.raises(ZeroDivisionError):
|
|
v(None, simple_attr("test"), 42)
|
|
|
|
def test_sugar(self):
|
|
"""
|
|
`and_(v1, v2, v3)` and `[v1, v2, v3]` are equivalent.
|
|
"""
|
|
@attr.s
|
|
class C(object):
|
|
a1 = attr.ib("a1", validator=and_(
|
|
instance_of(int),
|
|
))
|
|
a2 = attr.ib("a2", validator=[
|
|
instance_of(int),
|
|
])
|
|
|
|
assert C.__attrs_attrs__[0].validator == C.__attrs_attrs__[1].validator
|
|
|
|
|
|
class IFoo(zope.interface.Interface):
|
|
"""
|
|
An interface.
|
|
"""
|
|
def f():
|
|
"""
|
|
A function called f.
|
|
"""
|
|
|
|
|
|
class TestProvides(object):
|
|
"""
|
|
Tests for `provides`.
|
|
"""
|
|
def test_success(self):
|
|
"""
|
|
Nothing happens if value provides requested interface.
|
|
"""
|
|
@zope.interface.implementer(IFoo)
|
|
class C(object):
|
|
def f(self):
|
|
pass
|
|
|
|
v = provides(IFoo)
|
|
v(None, simple_attr("x"), C())
|
|
|
|
def test_fail(self):
|
|
"""
|
|
Raises `TypeError` if interfaces isn't provided by value.
|
|
"""
|
|
value = object()
|
|
a = simple_attr("x")
|
|
|
|
v = provides(IFoo)
|
|
with pytest.raises(TypeError) as e:
|
|
v(None, a, value)
|
|
assert (
|
|
"'x' must provide {interface!r} which {value!r} doesn't."
|
|
.format(interface=IFoo, value=value),
|
|
a, IFoo, value,
|
|
) == e.value.args
|
|
|
|
def test_repr(self):
|
|
"""
|
|
Returned validator has a useful `__repr__`.
|
|
"""
|
|
v = provides(IFoo)
|
|
assert (
|
|
"<provides validator for interface {interface!r}>"
|
|
.format(interface=IFoo)
|
|
) == repr(v)
|
|
|
|
|
|
@pytest.mark.parametrize("validator", [
|
|
instance_of(int),
|
|
[always_pass, instance_of(int)],
|
|
])
|
|
class TestOptional(object):
|
|
"""
|
|
Tests for `optional`.
|
|
"""
|
|
def test_success(self, validator):
|
|
"""
|
|
Nothing happens if validator succeeds.
|
|
"""
|
|
v = optional(validator)
|
|
v(None, simple_attr("test"), 42)
|
|
|
|
def test_success_with_none(self, validator):
|
|
"""
|
|
Nothing happens if None.
|
|
"""
|
|
v = optional(validator)
|
|
v(None, simple_attr("test"), None)
|
|
|
|
def test_fail(self, validator):
|
|
"""
|
|
Raises `TypeError` on wrong types.
|
|
"""
|
|
v = optional(validator)
|
|
a = simple_attr("test")
|
|
with pytest.raises(TypeError) as e:
|
|
v(None, a, "42")
|
|
assert (
|
|
"'test' must be <{type} 'int'> (got '42' that is a <{type} "
|
|
"'str'>).".format(type=TYPE),
|
|
a, int, "42",
|
|
|
|
) == e.value.args
|
|
|
|
def test_repr(self, validator):
|
|
"""
|
|
Returned validator has a useful `__repr__`.
|
|
"""
|
|
v = optional(validator)
|
|
|
|
if isinstance(validator, list):
|
|
assert (
|
|
("<optional validator for _AndValidator(_validators=[{func}, "
|
|
"<instance_of validator for type <{type} 'int'>>]) or None>")
|
|
.format(func=repr(always_pass), type=TYPE)
|
|
) == repr(v)
|
|
else:
|
|
assert (
|
|
("<optional validator for <instance_of validator for type "
|
|
"<{type} 'int'>> or None>")
|
|
.format(type=TYPE)
|
|
) == repr(v)
|
|
|
|
|
|
class TestIn_(object):
|
|
"""
|
|
Tests for `in_`.
|
|
"""
|
|
def test_success_with_value(self):
|
|
"""
|
|
If the value is in our options, nothing happens.
|
|
"""
|
|
v = in_([1, 2, 3])
|
|
a = simple_attr("test")
|
|
v(1, a, 3)
|
|
|
|
def test_fail(self):
|
|
"""
|
|
Raise ValueError if the value is outside our options.
|
|
"""
|
|
v = in_([1, 2, 3])
|
|
a = simple_attr("test")
|
|
with pytest.raises(ValueError) as e:
|
|
v(None, a, None)
|
|
assert (
|
|
"'test' must be in [1, 2, 3] (got None)",
|
|
) == e.value.args
|
|
|
|
def test_repr(self):
|
|
"""
|
|
Returned validator has a useful `__repr__`.
|
|
"""
|
|
v = in_([3, 4, 5])
|
|
assert(
|
|
("<in_ validator with options [3, 4, 5]>")
|
|
) == repr(v)
|
|
|
|
|
|
def test_hashability():
|
|
"""
|
|
Validator classes are hashable.
|
|
"""
|
|
for obj_name in dir(validator_module):
|
|
obj = getattr(validator_module, obj_name)
|
|
if not has(obj):
|
|
continue
|
|
hash_func = getattr(obj, '__hash__', None)
|
|
assert hash_func is not None
|
|
assert hash_func is not object.__hash__
|