1"""
2Tests for `attr.validators`.
3"""
4
5from __future__ import absolute_import, division, print_function
6
7import pytest
8import zope.interface
9
10import attr
11
12from attr import has
13from attr import validators as validator_module
14from attr._compat import TYPE
15from attr.validators import and_, in_, instance_of, optional, provides
16
17from .utils import simple_attr
18
19
20class TestInstanceOf(object):
21    """
22    Tests for `instance_of`.
23    """
24    def test_success(self):
25        """
26        Nothing happens if types match.
27        """
28        v = instance_of(int)
29        v(None, simple_attr("test"), 42)
30
31    def test_subclass(self):
32        """
33        Subclasses are accepted too.
34        """
35        v = instance_of(int)
36        # yep, bools are a subclass of int :(
37        v(None, simple_attr("test"), True)
38
39    def test_fail(self):
40        """
41        Raises `TypeError` on wrong types.
42        """
43        v = instance_of(int)
44        a = simple_attr("test")
45        with pytest.raises(TypeError) as e:
46            v(None, a, "42")
47        assert (
48            "'test' must be <{type} 'int'> (got '42' that is a <{type} "
49            "'str'>).".format(type=TYPE),
50            a, int, "42",
51
52        ) == e.value.args
53
54    def test_repr(self):
55        """
56        Returned validator has a useful `__repr__`.
57        """
58        v = instance_of(int)
59        assert (
60            "<instance_of validator for type <{type} 'int'>>"
61            .format(type=TYPE)
62        ) == repr(v)
63
64
65def always_pass(_, __, ___):
66    """
67    Toy validator that always passses.
68    """
69
70
71def always_fail(_, __, ___):
72    """
73    Toy validator that always fails.
74    """
75    0/0
76
77
78class TestAnd(object):
79    def test_success(self):
80        """
81        Succeeds if all wrapped validators succeed.
82        """
83        v = and_(instance_of(int), always_pass)
84
85        v(None, simple_attr("test"), 42)
86
87    def test_fail(self):
88        """
89        Fails if any wrapped validator fails.
90        """
91        v = and_(instance_of(int), always_fail)
92
93        with pytest.raises(ZeroDivisionError):
94            v(None, simple_attr("test"), 42)
95
96    def test_sugar(self):
97        """
98        `and_(v1, v2, v3)` and `[v1, v2, v3]` are equivalent.
99        """
100        @attr.s
101        class C(object):
102            a1 = attr.ib("a1", validator=and_(
103                instance_of(int),
104            ))
105            a2 = attr.ib("a2", validator=[
106                instance_of(int),
107            ])
108
109        assert C.__attrs_attrs__[0].validator == C.__attrs_attrs__[1].validator
110
111
112class IFoo(zope.interface.Interface):
113    """
114    An interface.
115    """
116    def f():
117        """
118        A function called f.
119        """
120
121
122class TestProvides(object):
123    """
124    Tests for `provides`.
125    """
126    def test_success(self):
127        """
128        Nothing happens if value provides requested interface.
129        """
130        @zope.interface.implementer(IFoo)
131        class C(object):
132            def f(self):
133                pass
134
135        v = provides(IFoo)
136        v(None, simple_attr("x"), C())
137
138    def test_fail(self):
139        """
140        Raises `TypeError` if interfaces isn't provided by value.
141        """
142        value = object()
143        a = simple_attr("x")
144
145        v = provides(IFoo)
146        with pytest.raises(TypeError) as e:
147            v(None, a, value)
148        assert (
149            "'x' must provide {interface!r} which {value!r} doesn't."
150            .format(interface=IFoo, value=value),
151            a, IFoo, value,
152        ) == e.value.args
153
154    def test_repr(self):
155        """
156        Returned validator has a useful `__repr__`.
157        """
158        v = provides(IFoo)
159        assert (
160            "<provides validator for interface {interface!r}>"
161            .format(interface=IFoo)
162        ) == repr(v)
163
164
165@pytest.mark.parametrize("validator", [
166    instance_of(int),
167    [always_pass, instance_of(int)],
168])
169class TestOptional(object):
170    """
171    Tests for `optional`.
172    """
173    def test_success(self, validator):
174        """
175        Nothing happens if validator succeeds.
176        """
177        v = optional(validator)
178        v(None, simple_attr("test"), 42)
179
180    def test_success_with_none(self, validator):
181        """
182        Nothing happens if None.
183        """
184        v = optional(validator)
185        v(None, simple_attr("test"), None)
186
187    def test_fail(self, validator):
188        """
189        Raises `TypeError` on wrong types.
190        """
191        v = optional(validator)
192        a = simple_attr("test")
193        with pytest.raises(TypeError) as e:
194            v(None, a, "42")
195        assert (
196            "'test' must be <{type} 'int'> (got '42' that is a <{type} "
197            "'str'>).".format(type=TYPE),
198            a, int, "42",
199
200        ) == e.value.args
201
202    def test_repr(self, validator):
203        """
204        Returned validator has a useful `__repr__`.
205        """
206        v = optional(validator)
207
208        if isinstance(validator, list):
209            assert (
210                ("<optional validator for _AndValidator(_validators=[{func}, "
211                 "<instance_of validator for type <{type} 'int'>>]) or None>")
212                .format(func=repr(always_pass), type=TYPE)
213            ) == repr(v)
214        else:
215            assert (
216                ("<optional validator for <instance_of validator for type "
217                 "<{type} 'int'>> or None>")
218                .format(type=TYPE)
219            ) == repr(v)
220
221
222class TestIn_(object):
223    """
224    Tests for `in_`.
225    """
226    def test_success_with_value(self):
227        """
228        If the value is in our options, nothing happens.
229        """
230        v = in_([1, 2, 3])
231        a = simple_attr("test")
232        v(1, a, 3)
233
234    def test_fail(self):
235        """
236        Raise ValueError if the value is outside our options.
237        """
238        v = in_([1, 2, 3])
239        a = simple_attr("test")
240        with pytest.raises(ValueError) as e:
241            v(None, a, None)
242        assert (
243            "'test' must be in [1, 2, 3] (got None)",
244        ) == e.value.args
245
246    def test_repr(self):
247        """
248        Returned validator has a useful `__repr__`.
249        """
250        v = in_([3, 4, 5])
251        assert(
252            ("<in_ validator with options [3, 4, 5]>")
253        ) == repr(v)
254
255
256def test_hashability():
257    """
258    Validator classes are hashable.
259    """
260    for obj_name in dir(validator_module):
261        obj = getattr(validator_module, obj_name)
262        if not has(obj):
263            continue
264        hash_func = getattr(obj, '__hash__', None)
265        assert hash_func is not None
266        assert hash_func is not object.__hash__
267