1""" 2Tests for dunder methods from `attrib._make`. 3""" 4 5from __future__ import absolute_import, division, print_function 6 7import copy 8 9import pytest 10 11from hypothesis import given 12from hypothesis.strategies import booleans 13 14import attr 15 16from attr._make import ( 17 NOTHING, Factory, _add_init, _add_repr, _Nothing, fields, make_class 18) 19from attr.validators import instance_of 20 21from .utils import simple_attr, simple_class 22 23 24CmpC = simple_class(cmp=True) 25CmpCSlots = simple_class(cmp=True, slots=True) 26ReprC = simple_class(repr=True) 27ReprCSlots = simple_class(repr=True, slots=True) 28 29# HashC is hashable by explicit definition while HashCSlots is hashable 30# implicitly. 31HashC = simple_class(hash=True) 32HashCSlots = simple_class(hash=None, cmp=True, frozen=True, slots=True) 33 34 35class InitC(object): 36 __attrs_attrs__ = [simple_attr("a"), simple_attr("b")] 37 38 39InitC = _add_init(InitC, False) 40 41 42class TestAddCmp(object): 43 """ 44 Tests for `_add_cmp`. 45 """ 46 @given(booleans()) 47 def test_cmp(self, slots): 48 """ 49 If `cmp` is False, ignore that attribute. 50 """ 51 C = make_class("C", { 52 "a": attr.ib(cmp=False), 53 "b": attr.ib() 54 }, slots=slots) 55 56 assert C(1, 2) == C(2, 2) 57 58 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 59 def test_equal(self, cls): 60 """ 61 Equal objects are detected as equal. 62 """ 63 assert cls(1, 2) == cls(1, 2) 64 assert not (cls(1, 2) != cls(1, 2)) 65 66 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 67 def test_unequal_same_class(self, cls): 68 """ 69 Unequal objects of correct type are detected as unequal. 70 """ 71 assert cls(1, 2) != cls(2, 1) 72 assert not (cls(1, 2) == cls(2, 1)) 73 74 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 75 def test_unequal_different_class(self, cls): 76 """ 77 Unequal objects of different type are detected even if their attributes 78 match. 79 """ 80 class NotCmpC(object): 81 a = 1 82 b = 2 83 assert cls(1, 2) != NotCmpC() 84 assert not (cls(1, 2) == NotCmpC()) 85 86 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 87 def test_lt(self, cls): 88 """ 89 __lt__ compares objects as tuples of attribute values. 90 """ 91 for a, b in [ 92 ((1, 2), (2, 1)), 93 ((1, 2), (1, 3)), 94 (("a", "b"), ("b", "a")), 95 ]: 96 assert cls(*a) < cls(*b) 97 98 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 99 def test_lt_unordable(self, cls): 100 """ 101 __lt__ returns NotImplemented if classes differ. 102 """ 103 assert NotImplemented == (cls(1, 2).__lt__(42)) 104 105 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 106 def test_le(self, cls): 107 """ 108 __le__ compares objects as tuples of attribute values. 109 """ 110 for a, b in [ 111 ((1, 2), (2, 1)), 112 ((1, 2), (1, 3)), 113 ((1, 1), (1, 1)), 114 (("a", "b"), ("b", "a")), 115 (("a", "b"), ("a", "b")), 116 ]: 117 assert cls(*a) <= cls(*b) 118 119 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 120 def test_le_unordable(self, cls): 121 """ 122 __le__ returns NotImplemented if classes differ. 123 """ 124 assert NotImplemented == (cls(1, 2).__le__(42)) 125 126 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 127 def test_gt(self, cls): 128 """ 129 __gt__ compares objects as tuples of attribute values. 130 """ 131 for a, b in [ 132 ((2, 1), (1, 2)), 133 ((1, 3), (1, 2)), 134 (("b", "a"), ("a", "b")), 135 ]: 136 assert cls(*a) > cls(*b) 137 138 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 139 def test_gt_unordable(self, cls): 140 """ 141 __gt__ returns NotImplemented if classes differ. 142 """ 143 assert NotImplemented == (cls(1, 2).__gt__(42)) 144 145 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 146 def test_ge(self, cls): 147 """ 148 __ge__ compares objects as tuples of attribute values. 149 """ 150 for a, b in [ 151 ((2, 1), (1, 2)), 152 ((1, 3), (1, 2)), 153 ((1, 1), (1, 1)), 154 (("b", "a"), ("a", "b")), 155 (("a", "b"), ("a", "b")), 156 ]: 157 assert cls(*a) >= cls(*b) 158 159 @pytest.mark.parametrize("cls", [CmpC, CmpCSlots]) 160 def test_ge_unordable(self, cls): 161 """ 162 __ge__ returns NotImplemented if classes differ. 163 """ 164 assert NotImplemented == (cls(1, 2).__ge__(42)) 165 166 167class TestAddRepr(object): 168 """ 169 Tests for `_add_repr`. 170 """ 171 @pytest.mark.parametrize("slots", [True, False]) 172 def test_repr(self, slots): 173 """ 174 If `repr` is False, ignore that attribute. 175 """ 176 C = make_class("C", { 177 "a": attr.ib(repr=False), 178 "b": attr.ib() 179 }, slots=slots) 180 181 assert "C(b=2)" == repr(C(1, 2)) 182 183 @pytest.mark.parametrize("cls", [ReprC, ReprCSlots]) 184 def test_repr_works(self, cls): 185 """ 186 repr returns a sensible value. 187 """ 188 assert "C(a=1, b=2)" == repr(cls(1, 2)) 189 190 def test_infinite_recursion(self): 191 """ 192 In the presence of a cyclic graph, repr will emit an ellipsis and not 193 raise an exception. 194 """ 195 @attr.s 196 class Cycle(object): 197 value = attr.ib(default=7) 198 cycle = attr.ib(default=None) 199 200 cycle = Cycle() 201 cycle.cycle = cycle 202 assert "Cycle(value=7, cycle=...)" == repr(cycle) 203 204 def test_underscores(self): 205 """ 206 repr does not strip underscores. 207 """ 208 class C(object): 209 __attrs_attrs__ = [simple_attr("_x")] 210 211 C = _add_repr(C) 212 i = C() 213 i._x = 42 214 215 assert "C(_x=42)" == repr(i) 216 217 def test_repr_uninitialized_member(self): 218 """ 219 repr signals unset attributes 220 """ 221 C = make_class("C", { 222 "a": attr.ib(init=False), 223 }) 224 225 assert "C(a=NOTHING)" == repr(C()) 226 227 @given(add_str=booleans(), slots=booleans()) 228 def test_str(self, add_str, slots): 229 """ 230 If str is True, it returns the same as repr. 231 232 This only makes sense when subclassing a class with an poor __str__ 233 (like Exceptions). 234 """ 235 @attr.s(str=add_str, slots=slots) 236 class Error(Exception): 237 x = attr.ib() 238 239 e = Error(42) 240 241 assert (str(e) == repr(e)) is add_str 242 243 def test_str_no_repr(self): 244 """ 245 Raises a ValueError if repr=False and str=True. 246 """ 247 with pytest.raises(ValueError) as e: 248 simple_class(repr=False, str=True) 249 250 assert ( 251 "__str__ can only be generated if a __repr__ exists." 252 ) == e.value.args[0] 253 254 255class TestAddHash(object): 256 """ 257 Tests for `_add_hash`. 258 """ 259 def test_enforces_type(self): 260 """ 261 The `hash` argument to both attrs and attrib must be None, True, or 262 False. 263 """ 264 exc_args = ("Invalid value for hash. Must be True, False, or None.",) 265 266 with pytest.raises(TypeError) as e: 267 make_class("C", {}, hash=1), 268 269 assert exc_args == e.value.args 270 271 with pytest.raises(TypeError) as e: 272 make_class("C", {"a": attr.ib(hash=1)}), 273 274 assert exc_args == e.value.args 275 276 @given(booleans()) 277 def test_hash_attribute(self, slots): 278 """ 279 If `hash` is False on an attribute, ignore that attribute. 280 """ 281 C = make_class("C", {"a": attr.ib(hash=False), "b": attr.ib()}, 282 slots=slots, hash=True) 283 284 assert hash(C(1, 2)) == hash(C(2, 2)) 285 286 @given(booleans()) 287 def test_hash_attribute_mirrors_cmp(self, cmp): 288 """ 289 If `hash` is None, the hash generation mirrors `cmp`. 290 """ 291 C = make_class("C", {"a": attr.ib(cmp=cmp)}, cmp=True, frozen=True) 292 293 if cmp: 294 assert C(1) != C(2) 295 assert hash(C(1)) != hash(C(2)) 296 assert hash(C(1)) == hash(C(1)) 297 else: 298 assert C(1) == C(2) 299 assert hash(C(1)) == hash(C(2)) 300 301 @given(booleans()) 302 def test_hash_mirrors_cmp(self, cmp): 303 """ 304 If `hash` is None, the hash generation mirrors `cmp`. 305 """ 306 C = make_class("C", {"a": attr.ib()}, cmp=cmp, frozen=True) 307 308 i = C(1) 309 310 assert i == i 311 assert hash(i) == hash(i) 312 313 if cmp: 314 assert C(1) == C(1) 315 assert hash(C(1)) == hash(C(1)) 316 else: 317 assert C(1) != C(1) 318 assert hash(C(1)) != hash(C(1)) 319 320 @pytest.mark.parametrize("cls", [HashC, HashCSlots]) 321 def test_hash_works(self, cls): 322 """ 323 __hash__ returns different hashes for different values. 324 """ 325 assert hash(cls(1, 2)) != hash(cls(1, 1)) 326 327 def test_hash_default(self): 328 """ 329 Classes are not hashable by default. 330 """ 331 C = make_class("C", {}) 332 333 with pytest.raises(TypeError) as e: 334 hash(C()) 335 336 assert e.value.args[0] in ( 337 "'C' objects are unhashable", # PyPy 338 "unhashable type: 'C'", # CPython 339 ) 340 341 342class TestAddInit(object): 343 """ 344 Tests for `_add_init`. 345 """ 346 @given(booleans(), booleans()) 347 def test_init(self, slots, frozen): 348 """ 349 If `init` is False, ignore that attribute. 350 """ 351 C = make_class("C", {"a": attr.ib(init=False), "b": attr.ib()}, 352 slots=slots, frozen=frozen) 353 with pytest.raises(TypeError) as e: 354 C(a=1, b=2) 355 356 assert ( 357 "__init__() got an unexpected keyword argument 'a'" == 358 e.value.args[0] 359 ) 360 361 @given(booleans(), booleans()) 362 def test_no_init_default(self, slots, frozen): 363 """ 364 If `init` is False but a Factory is specified, don't allow passing that 365 argument but initialize it anyway. 366 """ 367 C = make_class("C", { 368 "_a": attr.ib(init=False, default=42), 369 "_b": attr.ib(init=False, default=Factory(list)), 370 "c": attr.ib() 371 }, slots=slots, frozen=frozen) 372 with pytest.raises(TypeError): 373 C(a=1, c=2) 374 with pytest.raises(TypeError): 375 C(b=1, c=2) 376 377 i = C(23) 378 assert (42, [], 23) == (i._a, i._b, i.c) 379 380 @given(booleans(), booleans()) 381 def test_no_init_order(self, slots, frozen): 382 """ 383 If an attribute is `init=False`, it's legal to come after a mandatory 384 attribute. 385 """ 386 make_class("C", { 387 "a": attr.ib(default=Factory(list)), 388 "b": attr.ib(init=False), 389 }, slots=slots, frozen=frozen) 390 391 def test_sets_attributes(self): 392 """ 393 The attributes are initialized using the passed keywords. 394 """ 395 obj = InitC(a=1, b=2) 396 assert 1 == obj.a 397 assert 2 == obj.b 398 399 def test_default(self): 400 """ 401 If a default value is present, it's used as fallback. 402 """ 403 class C(object): 404 __attrs_attrs__ = [ 405 simple_attr(name="a", default=2), 406 simple_attr(name="b", default="hallo"), 407 simple_attr(name="c", default=None), 408 ] 409 410 C = _add_init(C, False) 411 i = C() 412 assert 2 == i.a 413 assert "hallo" == i.b 414 assert None is i.c 415 416 def test_factory(self): 417 """ 418 If a default factory is present, it's used as fallback. 419 """ 420 class D(object): 421 pass 422 423 class C(object): 424 __attrs_attrs__ = [ 425 simple_attr(name="a", default=Factory(list)), 426 simple_attr(name="b", default=Factory(D)), 427 ] 428 C = _add_init(C, False) 429 i = C() 430 431 assert [] == i.a 432 assert isinstance(i.b, D) 433 434 def test_validator(self): 435 """ 436 If a validator is passed, call it with the preliminary instance, the 437 Attribute, and the argument. 438 """ 439 class VException(Exception): 440 pass 441 442 def raiser(*args): 443 raise VException(*args) 444 445 C = make_class("C", {"a": attr.ib("a", validator=raiser)}) 446 with pytest.raises(VException) as e: 447 C(42) 448 449 assert (fields(C).a, 42,) == e.value.args[1:] 450 assert isinstance(e.value.args[0], C) 451 452 def test_validator_slots(self): 453 """ 454 If a validator is passed, call it with the preliminary instance, the 455 Attribute, and the argument. 456 """ 457 class VException(Exception): 458 pass 459 460 def raiser(*args): 461 raise VException(*args) 462 463 C = make_class("C", {"a": attr.ib("a", validator=raiser)}, slots=True) 464 with pytest.raises(VException) as e: 465 C(42) 466 467 assert (fields(C)[0], 42,) == e.value.args[1:] 468 assert isinstance(e.value.args[0], C) 469 470 @given(booleans()) 471 def test_validator_others(self, slots): 472 """ 473 Does not interfere when setting non-attrs attributes. 474 """ 475 C = make_class("C", { 476 "a": attr.ib("a", validator=instance_of(int)) 477 }, slots=slots) 478 i = C(1) 479 480 assert 1 == i.a 481 482 if not slots: 483 i.b = "foo" 484 assert "foo" == i.b 485 else: 486 with pytest.raises(AttributeError): 487 i.b = "foo" 488 489 def test_underscores(self): 490 """ 491 The argument names in `__init__` are without leading and trailing 492 underscores. 493 """ 494 class C(object): 495 __attrs_attrs__ = [simple_attr("_private")] 496 497 C = _add_init(C, False) 498 i = C(private=42) 499 assert 42 == i._private 500 501 502class TestNothing(object): 503 """ 504 Tests for `_Nothing`. 505 """ 506 def test_copy(self): 507 """ 508 __copy__ returns the same object. 509 """ 510 n = _Nothing() 511 assert n is copy.copy(n) 512 513 def test_deepcopy(self): 514 """ 515 __deepcopy__ returns the same object. 516 """ 517 n = _Nothing() 518 assert n is copy.deepcopy(n) 519 520 def test_eq(self): 521 """ 522 All instances are equal. 523 """ 524 assert _Nothing() == _Nothing() == NOTHING 525 assert not (_Nothing() != _Nothing()) 526 assert 1 != _Nothing() 527