Whole file
testing-cabal/mock
The author described this change as “Fix for _spec_signature and builtin types”. It counts as a record because the check below fails on the code as it stood at 288d23581 and passes on 669d5c84e, with nothing else changed between the two runs.
Projecttesting-cabal/mock
Fix saved2011-05-17
Sharing licenceBSD-2-Clause · LICENSE.txt
Change size+796 −781
What the code was meant to do, written into the code itself as a save note
Fix for _spec_signature and builtin types
The change
| 359 | 359 | self.call_count = 0 | |
| 360 | 360 | self.call_args_list = [] | |
| 361 | 361 | self.method_calls = [] | |
| 362 | - | for child in self._mock_children.values(): | |
| 363 | - | child.reset_mock() | |
| 364 | - | ||
| 365 | - | ret = self._mock_return_value | |
| 366 | - | if isinstance(ret, Mock) and ret is not self: | |
| 367 | - | ret.reset_mock() | |
| 368 | - | ||
| 369 | - | ||
| 370 | - | def __get_return_value(self): | |
| 371 | - | ret = self._mock_return_value | |
| 372 | - | if self._mock_signature is not None: | |
| 373 | - | ret = self._mock_signature.return_value | |
| 374 | - | ||
| 375 | - | if ret is DEFAULT: | |
| 376 | - | ret = self._get_child_mock() | |
| 377 | - | self.return_value = ret | |
| 378 | - | return ret | |
| 379 | - | ||
| 380 | - | def __set_return_value(self, value): | |
| 381 | - | if self._mock_signature is not None: | |
| 382 | - | self._mock_signature.return_value = value | |
| 383 | - | else: | |
| 384 | - | self._mock_return_value = value | |
| 385 | - | ||
| 386 | - | __return_value_doc = "The value to be returned when the mock is called." | |
| 387 | - | return_value = property(__get_return_value, __set_return_value, | |
| 388 | - | __return_value_doc) | |
| 389 | - | ||
| 390 | - | ||
| 391 | - | def __call__(self, *args, **kwargs): | |
| 392 | - | self.called = True | |
| 393 | - | self.call_count += 1 | |
| 394 | - | self.call_args = callargs((args, kwargs)) | |
| 395 | - | self.call_args_list.append(callargs((args, kwargs))) | |
| 396 | - | ||
| 397 | - | parent = self._mock_parent | |
| 398 | - | name = self._mock_name | |
| 399 | - | while parent is not None: | |
| 400 | - | parent.method_calls.append(callargs((name, args, kwargs))) | |
| 401 | - | if parent._mock_parent is None: | |
| 402 | - | break | |
| 403 | - | name = parent._mock_name + '.' + name | |
| 404 | - | parent = parent._mock_parent | |
| 405 | - | ||
| 406 | - | ret_val = DEFAULT | |
| 407 | - | if self.side_effect is not None: | |
| 408 | - | if (isinstance(self.side_effect, BaseException) or | |
| 409 | - | isinstance(self.side_effect, ClassTypes) and | |
| 410 | - | issubclass(self.side_effect, BaseException)): | |
| 411 | - | raise self.side_effect | |
| 412 | - | ||
| 413 | - | ret_val = self.side_effect(*args, **kwargs) | |
| 414 | - | if ret_val is DEFAULT: | |
| 415 | - | ret_val = self.return_value | |
| 416 | - | ||
| 417 | - | if self._mock_wraps is not None and self._mock_return_value is DEFAULT: | |
| 418 | - | return self._mock_wraps(*args, **kwargs) | |
| 419 | - | if ret_val is DEFAULT: | |
| 420 | - | ret_val = self.return_value | |
| 421 | - | return ret_val | |
| 422 | - | ||
| 423 | - | ||
| 424 | - | def __getattr__(self, name): | |
| 425 | - | if name == '_mock_methods': | |
| 426 | - | raise AttributeError(name) | |
| 427 | - | elif self._mock_methods is not None: | |
| 428 | - | if name not in self._mock_methods or name in _all_magics: | |
| 429 | - | raise AttributeError("Mock object has no attribute %r" % name) | |
| 430 | - | elif _is_magic(name): | |
| 431 | - | raise AttributeError(name) | |
| 432 | - | ||
| 433 | - | if name not in self._mock_children: | |
| 434 | - | wraps = None | |
| 435 | - | if self._mock_wraps is not None: | |
| 436 | - | # XXXX should we get the attribute without triggering code | |
| 437 | - | # execution? | |
| 438 | - | wraps = getattr(self._mock_wraps, name) | |
| 439 | - | self._mock_children[name] = self._get_child_mock(parent=self, | |
| 440 | - | name=name, | |
| 441 | - | wraps=wraps) | |
| 442 | - | ||
| 443 | - | return self._mock_children[name] | |
| 444 | - | ||
| 445 | - | ||
| 446 | - | def __repr__(self): | |
| 447 | - | if self._mock_name is None and self._spec_class is None: | |
| 448 | - | return object.__repr__(self) | |
| 449 | - | ||
| 450 | - | name_string = '' | |
| 451 | - | spec_string = '' | |
| 452 | - | if self._mock_name is not None: | |
| 453 | - | def get_name(name): | |
| 454 | - | if name is None: | |
| 455 | - | return 'mock' | |
| 456 | - | return name | |
| 457 | - | parent = self._mock_parent | |
| 458 | - | name = self._mock_name | |
| 459 | - | while parent is not None: | |
| 460 | - | name = get_name(parent._mock_name) + '.' + name | |
| 461 | - | parent = parent._mock_parent | |
| 462 | - | name_string = ' name=%r' % name | |
| 463 | - | if self._spec_class is not None: | |
| 464 | - | spec_string = ' spec=%r' | |
| 465 | - | if self._spec_set: | |
| 466 | - | spec_string = ' spec_set=%r' | |
| 467 | - | spec_string = spec_string % self._spec_class.__name__ | |
| 468 | - | return "<%s%s%s id='%s'>" % (type(self).__name__, | |
| 469 | - | name_string, | |
| 470 | - | spec_string, | |
| 471 | - | id(self)) | |
| 472 | - | ||
| 473 | - | ||
| 474 | - | def __setattr__(self, name, value): | |
| 475 | - | if not 'method_calls' in self.__dict__: | |
| 476 | - | # allow all attribute setting until initialisation is complete | |
| 477 | - | return object.__setattr__(self, name, value) | |
| 478 | - | if (self._spec_set and self._mock_methods is not None and name not in | |
| 479 | - | self._mock_methods and name not in self.__dict__ and | |
| 480 | - | name != 'return_value'): | |
| 481 | - | raise AttributeError("Mock object has no attribute '%s'" % name) | |
| 482 | - | if name in _unsupported_magics: | |
| 483 | - | msg = 'Attempting to set unsupported magic method %r.' % name | |
| 484 | - | raise AttributeError(msg) | |
| 485 | - | elif name in _all_magics: | |
| 486 | - | if self._mock_methods is not None and name not in self._mock_methods: | |
| 487 | - | raise AttributeError("Mock object has no attribute '%s'" % name) | |
| 488 | - | ||
| 489 | - | if isinstance(value, MagicProxy): | |
| 490 | - | setattr(type(self), name, value) | |
| 491 | - | return | |
| 492 | - | ||
| 493 | - | if not isinstance(value, Mock): | |
| 494 | - | setattr(type(self), name, _get_method(name, value)) | |
| 495 | - | original = value | |
| 496 | - | real = lambda *args, **kw: original(self, *args, **kw) | |
| 497 | - | value = mocksignature(value, real, skipfirst=True) | |
| 498 | - | else: | |
| 499 | - | setattr(type(self), name, value) | |
| 500 | - | return object.__setattr__(self, name, value) | |
| 501 | - | ||
| 502 | - | ||
| 503 | - | def __delattr__(self, name): | |
| 504 | - | if name in _all_magics and name in type(self).__dict__: | |
| 505 | - | delattr(type(self), name) | |
| 506 | - | return object.__delattr__(self, name) | |
| 507 | - | ||
| 508 | - | ||
| 509 | - | def assert_called_with(self, *args, **kwargs): | |
| 510 | - | """ | |
| 511 | - | assert that the mock was called with the specified arguments. | |
| 512 | - | ||
| 513 | - | Raises an AssertionError if the args and keyword args passed in are | |
| 514 | - | different to the last call to the mock. | |
| 515 | - | """ | |
| 516 | - | if self.call_args is None: | |
| 517 | - | raise AssertionError('Expected: %s\nNot called' % ((args, kwargs),)) | |
| 518 | - | if not self.call_args == (args, kwargs): | |
| 519 | - | raise AssertionError( | |
| 520 | - | 'Expected: %s\nCalled with: %s' % ((args, kwargs), self.call_args) | |
| 521 | - | ) | |
| 522 | - | ||
| 523 | - | ||
| 524 | - | def assert_called_once_with(self, *args, **kwargs): | |
| 525 | - | """ | |
| 526 | - | assert that the mock was called exactly once and with the specified | |
| 527 | - | arguments. | |
| 528 | - | """ | |
| 529 | - | if not self.call_count == 1: | |
| 530 | - | msg = ("Expected to be called once. Called %s times." % | |
| 531 | - | self.call_count) | |
| 532 | - | raise AssertionError(msg) | |
| 533 | - | return self.assert_called_with(*args, **kwargs) | |
| 534 | - | ||
| 535 | - | ||
| 536 | - | def _get_child_mock(self, **kw): | |
| 537 | - | klass = type(self).__mro__[1] | |
| 538 | - | return klass(**kw) | |
| 539 | - | ||
| 540 | - | ||
| 541 | - | ||
| 542 | - | class callargs(tuple): | |
| 543 | - | """ | |
| 544 | - | A tuple for holding the results of a call to a mock, either in the form | |
| 545 | - | `(args, kwargs)` or `(name, args, kwargs)`. | |
| 546 | - | ||
| 547 | - | If args or kwargs are empty then a callargs tuple will compare equal to | |
| 548 | - | a tuple without those values. This makes comparisons less verbose:: | |
| 549 | - | ||
| 550 | - | callargs('name', (), {}) == ('name',) | |
| 551 | - | callargs('name', (1,), {}) == ('name', (1,)) | |
| 552 | - | callargs((), {'a': 'b'}) == ({'a': 'b'},) | |
| 553 | - | """ | |
| 554 | - | def __eq__(self, other): | |
| 555 | - | if len(self) == 3: | |
| 556 | - | if other[0] != self[0]: | |
| 557 | - | return False | |
| 558 | - | args_kwargs = self[1:] | |
| 559 | - | other_args_kwargs = other[1:] | |
| 560 | - | else: | |
| 561 | - | args_kwargs = tuple(self) | |
| 562 | - | other_args_kwargs = other | |
| 563 | - | ||
| 564 | - | if len(other_args_kwargs) == 0: | |
| 565 | - | other_args, other_kwargs = (), {} | |
| 566 | - | elif len(other_args_kwargs) == 1: | |
| 567 | - | if isinstance(other_args_kwargs[0], tuple): | |
| 568 | - | other_args = other_args_kwargs[0] | |
| 569 | - | other_kwargs = {} | |
| 570 | - | else: | |
| 571 | - | other_args = () | |
| 572 | - | other_kwargs = other_args_kwargs[0] | |
| 573 | - | else: | |
| 574 | - | other_args, other_kwargs = other_args_kwargs | |
| 575 | - | ||
| 576 | - | return tuple(args_kwargs) == (other_args, other_kwargs) | |
| 577 | - | ||
| 578 | - | ||
| 579 | - | def _dot_lookup(thing, comp, import_path): | |
| 580 | - | try: | |
| 581 | - | return getattr(thing, comp) | |
| 582 | - | except AttributeError: | |
| 583 | - | __import__(import_path) | |
| 584 | - | return getattr(thing, comp) | |
| 585 | - | ||
| 586 | - | ||
| 587 | - | def _importer(target): | |
| 588 | - | components = target.split('.') | |
| 589 | - | import_path = components.pop(0) | |
| 590 | - | thing = __import__(import_path) | |
| 591 | - | ||
| 592 | - | for comp in components: | |
| 593 | - | import_path += ".%s" % comp | |
| 594 | - | thing = _dot_lookup(thing, comp, import_path) | |
| 595 | - | return thing | |
| 596 | - | ||
| 597 | - | ||
| 598 | - | class _patch(object): | |
| 599 | - | def __init__(self, target, attribute, new, spec, create, | |
| 600 | - | mocksignature, spec_set): | |
| 601 | - | self.target = target | |
| 602 | - | self.attribute = attribute | |
| 603 | - | self.new = new | |
| 604 | - | self.spec = spec | |
| 605 | - | self.create = create | |
| 606 | - | self.has_local = False | |
| 607 | - | self.mocksignature = mocksignature | |
| 608 | - | self.spec_set = spec_set | |
| 609 | - | ||
| 610 | - | ||
| 611 | - | def copy(self): | |
| 612 | - | return _patch(self.target, self.attribute, self.new, self.spec, | |
| 613 | - | self.create, self.mocksignature, self.spec_set) | |
| 614 | - | ||
| 615 | - | ||
| 616 | - | def __call__(self, func): | |
| 617 | - | if isinstance(func, ClassTypes): | |
| 618 | - | return self.decorate_class(func) | |
| 619 | - | return self.decorate_callable(func) | |
| 620 | - | ||
| 621 | - | ||
| 622 | - | def decorate_class(self, klass): | |
| 623 | - | for attr in dir(klass): | |
| 624 | - | attr_value = getattr(klass, attr) | |
| 625 | - | if attr.startswith("test") and hasattr(attr_value, "__call__"): | |
| 626 | - | setattr(klass, attr, self.copy()(attr_value)) | |
| 627 | - | return klass | |
| 628 | - | ||
| 629 | - | ||
| 630 | - | def decorate_callable(self, func): | |
| 631 | - | if hasattr(func, 'patchings'): | |
| 632 | - | func.patchings.append(self) | |
| 633 | - | return func | |
| 634 | - | ||
| 635 | - | @wraps(func) | |
| 636 | - | def patched(*args, **keywargs): | |
| 637 | - | # don't use a with here (backwards compatability with 2.5) | |
| 638 | - | extra_args = [] | |
| 639 | - | for patching in patched.patchings: | |
| 640 | - | arg = patching.__enter__() | |
| 641 | - | if patching.new is DEFAULT: | |
| 642 | - | extra_args.append(arg) | |
| 643 | - | args += tuple(extra_args) | |
| 644 | - | try: | |
| 645 | - | return func(*args, **keywargs) | |
| 646 | - | finally: | |
| 647 | - | for patching in reversed(getattr(patched, 'patchings', [])): | |
| 648 | - | patching.__exit__() | |
| 649 | - | ||
| 650 | - | patched.patchings = [self] | |
| 651 | - | if hasattr(func, 'func_code'): | |
| 652 | - | # not in Python 3 | |
| 653 | - | patched.compat_co_firstlineno = getattr(func, "compat_co_firstlineno", | |
| 654 | - | func.func_code.co_firstlineno) | |
| 655 | - | return patched | |
| 656 | - | ||
| 657 | - | ||
| 658 | - | def get_original(self): | |
| 659 | - | target = self.target | |
| 660 | - | name = self.attribute | |
| 661 | - | ||
| 662 | - | original = DEFAULT | |
| 663 | - | local = False | |
| 664 | - | ||
| 665 | - | try: | |
| 666 | - | original = target.__dict__[name] | |
| 667 | - | except (AttributeError, KeyError): | |
| 668 | - | original = getattr(target, name, DEFAULT) | |
| 669 | - | else: | |
| 670 | - | local = True | |
| 671 | - | ||
| 672 | - | if not self.create and original is DEFAULT: | |
| 673 | - | raise AttributeError("%s does not have the attribute %r" % (target, name)) | |
| 674 | - | return original, local | |
| 675 | - | ||
| 676 | - | ||
| 677 | - | def __enter__(self): | |
| 678 | - | """Perform the patch.""" | |
| 679 | - | new, spec, spec_set = self.new, self.spec, self.spec_set | |
| 680 | - | original, local = self.get_original() | |
| 681 | - | if new is DEFAULT: | |
| 682 | - | # XXXX what if original is DEFAULT - shouldn't use it as a spec | |
| 683 | - | inherit = False | |
| 684 | - | if spec_set == True: | |
| 685 | - | spec_set = original | |
| 686 | - | if isinstance(spec_set, ClassTypes): | |
| 687 | - | inherit = True | |
| 688 | - | elif spec == True: | |
| 689 | - | # set spec to the object we are replacing | |
| 690 | - | spec = original | |
| 691 | - | if isinstance(spec, ClassTypes): | |
| 692 | - | inherit = True | |
| 693 | - | new = Mock(spec=spec, spec_set=spec_set) | |
| 694 | - | if inherit: | |
| 695 | - | new.return_value = Mock(spec=spec, spec_set=spec_set) | |
| 696 | - | new_attr = new | |
| 697 | - | if self.mocksignature: | |
| 698 | - | new_attr = mocksignature(original, new) | |
| 699 | - | ||
| 700 | - | self.temp_original = original | |
| 701 | - | self.is_local = local | |
| 702 | - | setattr(self.target, self.attribute, new_attr) | |
| 703 | - | return new | |
| 704 | - | ||
| 705 | - | ||
| 706 | - | def __exit__(self, *_): | |
| 707 | - | """Undo the patch.""" | |
| 708 | - | if self.is_local and self.temp_original is not DEFAULT: | |
| 709 | - | setattr(self.target, self.attribute, self.temp_original) | |
| 710 | - | else: | |
| 711 | - | delattr(self.target, self.attribute) | |
| 712 | - | if not self.create and not hasattr(self.target, self.attribute): | |
| 713 | - | # needed for proxy objects like django settings | |
| 714 | - | setattr(self.target, self.attribute, self.temp_original) | |
| 715 | - | ||
| 716 | - | del self.temp_original | |
| 717 | - | del self.is_local | |
| 718 | - | ||
| 719 | - | start = __enter__ | |
| 720 | - | stop = __exit__ | |
| 721 | - | ||
| 722 | - | ||
| 723 | - | def _patch_object(target, attribute, new=DEFAULT, spec=None, create=False, | |
| 724 | - | mocksignature=False, spec_set=None): | |
| 725 | - | """ | |
| 726 | - | patch.object(target, attribute, new=DEFAULT, spec=None, create=False, | |
| 727 | - | mocksignature=False, spec_set=None) | |
| 728 | - | ||
| 729 | - | patch the named member (`attribute`) on an object (`target`) with a mock | |
| 730 | - | object. | |
| 731 | - | ||
| 732 | - | Arguments new, spec, create, mocksignature and spec_set have the same | |
| 733 | - | meaning as for patch. | |
| 734 | - | """ | |
| 735 | - | return _patch(target, attribute, new, spec, create, mocksignature, | |
| 736 | - | spec_set) | |
| 737 | - | ||
| 738 | - | ||
| 739 | - | def patch(target, new=DEFAULT, spec=None, create=False, | |
| 740 | - | mocksignature=False, spec_set=None): | |
| 741 | - | """ | |
| 742 | - | ``patch`` acts as a function decorator, class decorator or a context | |
| 743 | - | manager. Inside the body of the function or with statement, the ``target`` | |
| 744 | - | (specified in the form `'PackageName.ModuleName.ClassName'`) is patched | |
| 745 | - | with a ``new`` object. When the function/with statement exits the patch is | |
| 746 | - | undone. | |
| 747 | - | ||
| 748 | - | The ``target`` is imported and the specified attribute patched with the new | |
| 749 | - | object, so it must be importable from the environment you are calling the | |
| 750 | - | decorator from. | |
| 751 | - | ||
| 752 | - | If ``new`` is omitted, then a new ``Mock`` is created and passed in as an | |
| 753 | - | extra argument to the decorated function. | |
| 754 | - | ||
| 755 | - | The ``spec`` and ``spec_set`` keyword arguments are passed to the ``Mock`` | |
| 756 | - | if patch is creating one for you. | |
| 757 | - | ||
| 758 | - | In addition you can pass ``spec=True`` or ``spec_set=True``, which causes | |
| 1183 further changed lines not shown | |||
The check that tells the two apart
fail→pass·tests/testhelpers.py::SpecSignatureTest::test_builtins
Check file tests/testhelpers.py, taken without changes from the fix and copied onto the older code, so the exact same check runs against both versions.
Origin and history
The code before it288d23581bcb4596e3d618a2074baa8bc6f2aebd
Broken version dated2011-05-17
Modulemock
Units changedMock, _spec_signature
Fingerprint4edf6ead449771b6
Checked2026-08-18 by goldset/0.1
Every field above is generated by our program. None of it is written by hand.
Other bugs found in testing-cabal/mock
- 2023-04-16_get_signature_object
- 2022-12-28create_autospec
- 2020-01-29_patch_dict
- 2020-01-29_patch_object
- 2012-01-22_Call
- 2011-07-19Fix callargs call comparisons