mirror of https://github.com/Qiskit/qiskit.git
534 lines
19 KiB
Python
534 lines
19 KiB
Python
# This code is part of Qiskit.
|
|
#
|
|
# (C) Copyright IBM 2022.
|
|
#
|
|
# This code is licensed under the Apache License, Version 2.0. You may
|
|
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
#
|
|
# Any modifications or derivative works of this code must retain this
|
|
# copyright notice, and modified files need to carry a notice indicating
|
|
# that they have been altered from the originals.
|
|
|
|
"""Tests for the methods in ``utils.classtools``."""
|
|
|
|
# If `utils.wrap_method` is horrendously broken, then the test suite probably won't even have been
|
|
# able to collect, because it's used in `QiskitTestCase`.
|
|
#
|
|
# Throughout this file, we use ``this`` as an argument name to refer to a general reference, which
|
|
# may be an instance or the type of the instance. This is just for clarity when we are using the
|
|
# same function to wrap both instance and class methods.
|
|
#
|
|
# We use a lot of dummy classes in test cases, for which there is absolutely no point defining
|
|
# docstrings.
|
|
# pylint: disable=missing-class-docstring,missing-function-docstring
|
|
|
|
import unittest
|
|
import sys
|
|
|
|
from qiskit.utils import wrap_method
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
|
|
def call_first_argument_with(*args, **kwargs):
|
|
"""Create a function that calls its first and only argument with the signature given to this
|
|
function."""
|
|
|
|
def out(mock):
|
|
return mock(*args, **kwargs)
|
|
|
|
return out
|
|
|
|
|
|
def call_second_argument_with(*args, **kwargs):
|
|
"""Create a function that calls its second argument with the first, and the signature given to
|
|
this function."""
|
|
|
|
def out(this, mock):
|
|
mock(this, *args, **kwargs)
|
|
|
|
return out
|
|
|
|
|
|
class TestWrapMethod(QiskitTestCase):
|
|
"""Tests of ``utils.classtools.wrap_method``. These can be rather tricky, because there's a lot
|
|
of messing around with descriptors and some special cases necessary to support builtin Python
|
|
functions, so we sometimes have rather funny access patterns."""
|
|
|
|
def test_called_with(self):
|
|
"""Test the basic call patterns are correct. We use regular Python functions rather than
|
|
mocks to make the instances and callbacks in this simplest case, because the low-level
|
|
descriptor use means that there might be side-effects to binding mocks directly."""
|
|
|
|
class Dummy:
|
|
def instance(self, mock):
|
|
mock(self, "method")
|
|
|
|
@classmethod
|
|
def class_(cls, mock):
|
|
mock(cls, "method")
|
|
|
|
@staticmethod
|
|
def static(mock):
|
|
mock("method")
|
|
|
|
wrap_method(
|
|
Dummy,
|
|
"instance",
|
|
before=call_second_argument_with("before"),
|
|
after=call_second_argument_with("after"),
|
|
)
|
|
wrap_method(
|
|
Dummy,
|
|
"class_",
|
|
before=call_second_argument_with("before"),
|
|
after=call_second_argument_with("after"),
|
|
)
|
|
wrap_method(
|
|
Dummy,
|
|
"static",
|
|
before=call_first_argument_with("before"),
|
|
after=call_first_argument_with("after"),
|
|
)
|
|
|
|
with self.subTest("from instance"):
|
|
source = Dummy()
|
|
with self.subTest("instance"):
|
|
mock = unittest.mock.Mock()
|
|
|
|
# Test that the before/after are not called when the method is accessed.
|
|
caller = source.instance
|
|
mock.assert_not_called()
|
|
caller(mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call(source, "before"),
|
|
unittest.mock.call(source, "method"),
|
|
unittest.mock.call(source, "after"),
|
|
],
|
|
any_order=False,
|
|
)
|
|
with self.subTest("class"):
|
|
mock = unittest.mock.Mock()
|
|
caller = source.class_
|
|
mock.assert_not_called()
|
|
caller(mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call(Dummy, "before"),
|
|
unittest.mock.call(Dummy, "method"),
|
|
unittest.mock.call(Dummy, "after"),
|
|
],
|
|
any_order=False,
|
|
)
|
|
with self.subTest("static"):
|
|
mock = unittest.mock.Mock()
|
|
caller = source.static
|
|
mock.assert_not_called()
|
|
caller(mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call("before"),
|
|
unittest.mock.call("method"),
|
|
unittest.mock.call("after"),
|
|
],
|
|
any_order=False,
|
|
)
|
|
|
|
with self.subTest("from type"):
|
|
with self.subTest("instance"):
|
|
mock = unittest.mock.Mock()
|
|
|
|
# Test that the before/after are not called when the method is accessed.
|
|
caller = Dummy.instance
|
|
mock.assert_not_called()
|
|
caller("this", mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call("this", "before"),
|
|
unittest.mock.call("this", "method"),
|
|
unittest.mock.call("this", "after"),
|
|
],
|
|
any_order=False,
|
|
)
|
|
with self.subTest("class"):
|
|
mock = unittest.mock.Mock()
|
|
caller = Dummy.class_
|
|
mock.assert_not_called()
|
|
caller(mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call(Dummy, "before"),
|
|
unittest.mock.call(Dummy, "method"),
|
|
unittest.mock.call(Dummy, "after"),
|
|
],
|
|
any_order=False,
|
|
)
|
|
with self.subTest("static"):
|
|
mock = unittest.mock.Mock()
|
|
caller = Dummy.static
|
|
mock.assert_not_called()
|
|
caller(mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call("before"),
|
|
unittest.mock.call("method"),
|
|
unittest.mock.call("after"),
|
|
],
|
|
any_order=False,
|
|
)
|
|
|
|
def test_can_wrap_with_lambda(self):
|
|
"""Test that lambda functions can be used as the callbacks."""
|
|
|
|
class Dummy:
|
|
def instance(self, mock):
|
|
mock(self, "method")
|
|
|
|
wrap_method(Dummy, "instance", after=lambda self, mock: mock(self, "after"))
|
|
with self.subTest("from instance"):
|
|
mock = unittest.mock.Mock()
|
|
source = Dummy()
|
|
caller = source.instance
|
|
mock.assert_not_called()
|
|
caller(mock)
|
|
mock.assert_has_calls(
|
|
[unittest.mock.call(source, "method"), unittest.mock.call(source, "after")],
|
|
any_order=False,
|
|
)
|
|
|
|
with self.subTest("from type"):
|
|
mock = unittest.mock.Mock()
|
|
caller = Dummy.instance
|
|
mock.assert_not_called()
|
|
caller("this", mock)
|
|
mock.assert_has_calls(
|
|
[unittest.mock.call("this", "method"), unittest.mock.call("this", "after")],
|
|
any_order=False,
|
|
)
|
|
|
|
def test_can_wrap_with_callable_class(self):
|
|
"""Test that a class with a ``__call__`` but no descriptor protocol can be used as the
|
|
callbacks."""
|
|
|
|
class Dummy:
|
|
def instance(self, mock):
|
|
mock(self, "method")
|
|
|
|
class Callback:
|
|
# Note that this class does not implement the descriptor protocol, unlike normal Python
|
|
# functions. ``__call__`` itself implements ``__get__``, but that just bounds the
|
|
# ``Callback`` instance to ``self``.
|
|
def __call__(self, this, mock):
|
|
mock(this, "after")
|
|
|
|
wrap_method(Dummy, "instance", after=Callback())
|
|
with self.subTest("from instance"):
|
|
mock = unittest.mock.Mock()
|
|
source = Dummy()
|
|
caller = source.instance
|
|
mock.assert_not_called()
|
|
caller(mock)
|
|
mock.assert_has_calls(
|
|
[unittest.mock.call(source, "method"), unittest.mock.call(source, "after")],
|
|
any_order=False,
|
|
)
|
|
|
|
with self.subTest("from type"):
|
|
mock = unittest.mock.Mock()
|
|
caller = Dummy.instance
|
|
mock.assert_not_called()
|
|
caller("this", mock)
|
|
mock.assert_has_calls(
|
|
[unittest.mock.call("this", "method"), unittest.mock.call("this", "after")],
|
|
any_order=False,
|
|
)
|
|
|
|
def test_can_wrap_with_builtin(self):
|
|
"""Test that builtin functions can be used a callback. Many CPython builtins don't
|
|
implement the desriptor protocol that all functions defined with ``def`` or ``lambda`` do,
|
|
which means we need to take special care that they work. This is most relevant for
|
|
C-extension functions created via pybind11, Cython or similar, rather than actual Python
|
|
builtins."""
|
|
|
|
class Dummy:
|
|
def instance(self, mock):
|
|
mock(self, "method")
|
|
|
|
# We don't want to add additional compilation requirements for one simple test in the suite,
|
|
# so instead we need a CPython builtin function (of type ``types.BuiltinFunctionType``) that
|
|
# has side effects when being called with an arbitrary object as the first positional
|
|
# argument. ``breakpoint`` is a good choice, because it's designed to support dependency
|
|
# injection with arbitrary arguments; it calls out to the overridable ``sys.breakpointhook``
|
|
# with all its arguments. We can't use ``eval`` to test the bound-method calls because the
|
|
# first argument would be the instance, not the string with a side-effect-y programme.
|
|
with unittest.mock.patch.object(sys, "breakpointhook") as mock:
|
|
wrap_method(Dummy, "instance", before=breakpoint)
|
|
|
|
with self.subTest("from instance"):
|
|
mock.reset_mock()
|
|
source = Dummy()
|
|
source.instance(mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call(source, mock),
|
|
unittest.mock.call(source, "method"),
|
|
]
|
|
)
|
|
|
|
with self.subTest("from type"):
|
|
mock.reset_mock()
|
|
Dummy.instance("this", mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call("this", mock),
|
|
unittest.mock.call("this", "method"),
|
|
]
|
|
)
|
|
|
|
def test_can_wrap_with_mock(self):
|
|
"""This is kind of a meta test, to check that we can use a ``unittest.mock.Mock`` instance
|
|
as the callback."""
|
|
|
|
class Dummy:
|
|
def instance(self, x):
|
|
pass
|
|
|
|
mock = unittest.mock.Mock()
|
|
wrap_method(Dummy, "instance", before=mock)
|
|
|
|
with self.subTest("from instance"):
|
|
mock.reset_mock()
|
|
source = Dummy()
|
|
source.instance("hello, world")
|
|
mock.assert_called_once_with(source, "hello, world")
|
|
with self.subTest("from type"):
|
|
mock.reset_mock()
|
|
Dummy.instance("this", "hello, world")
|
|
mock.assert_called_once_with("this", "hello, world")
|
|
|
|
def test_wrapping_inherited_method(self):
|
|
"""Test that ``wrap_method`` will correctly find a method defined only on a parent class."""
|
|
|
|
class Parent:
|
|
def instance(self, mock):
|
|
mock(self, "method")
|
|
|
|
class Child(Parent):
|
|
pass
|
|
|
|
wrap_method(Child, "instance", before=call_second_argument_with("before"))
|
|
|
|
with self.subTest("from instance"):
|
|
mock = unittest.mock.Mock()
|
|
source = Child()
|
|
caller = source.instance
|
|
mock.assert_not_called()
|
|
caller(mock)
|
|
mock.assert_has_calls(
|
|
[unittest.mock.call(source, "before"), unittest.mock.call(source, "method")],
|
|
any_order=False,
|
|
)
|
|
|
|
with self.subTest("from type"):
|
|
mock = unittest.mock.Mock()
|
|
caller = Child.instance
|
|
mock.assert_not_called()
|
|
caller("this", mock)
|
|
mock.assert_has_calls(
|
|
[unittest.mock.call("this", "before"), unittest.mock.call("this", "method")],
|
|
any_order=False,
|
|
)
|
|
|
|
def test_wrapping___init__(self):
|
|
"""Test that wrapping the magic __init__ method works."""
|
|
|
|
class Dummy:
|
|
def __init__(self, mock):
|
|
mock("__init__")
|
|
self.mock = mock
|
|
|
|
def add_extra_property(self, _):
|
|
mock("extra")
|
|
self.extra = "hello, world"
|
|
|
|
wrap_method(Dummy, "__init__", after=add_extra_property)
|
|
|
|
mock = unittest.mock.Mock()
|
|
dummy = Dummy(mock)
|
|
self.assertIs(dummy.mock, mock)
|
|
self.assertEqual(dummy.extra, "hello, world") # pylint: disable=no-member
|
|
mock.assert_has_calls(
|
|
[unittest.mock.call("__init__"), unittest.mock.call("extra")],
|
|
any_order=False,
|
|
)
|
|
|
|
def test_wrapping___add__(self):
|
|
"""Test that wrapping an arithmetic operator works. There is nothing particularly special
|
|
about ``__add__`` that (say) ``__init__`` doesn't also do, but this is just a further check
|
|
that the magic methods can work. Note that ``__add__`` must be defined on the type; all
|
|
magic methods ignore re-definitions in instance dictionaries."""
|
|
|
|
mock = unittest.mock.Mock()
|
|
|
|
class Dummy:
|
|
def __init__(self, n):
|
|
self.n = n
|
|
|
|
def __add__(self, other):
|
|
return type(self)(self.n + other.n)
|
|
|
|
wrap_method(Dummy, "__add__", before=mock)
|
|
|
|
left = Dummy(1)
|
|
right = Dummy(2)
|
|
out = left + right
|
|
self.assertIsInstance(out, Dummy)
|
|
self.assertEqual(out.n, 3)
|
|
mock.assert_has_calls([unittest.mock.call(left, right)])
|
|
|
|
def test_wrapping___new__(self):
|
|
"""Test that wrapping the magic __new__ method works. This method is implicitly made into a
|
|
static method with no decorator, but still gets called with the class in the first
|
|
position. Note that ``type`` implements ``__new__``, so the getter needs to ensure that it
|
|
doesn't accidentally"""
|
|
|
|
class Dummy:
|
|
def __new__(cls, mock):
|
|
mock(cls, "__new__")
|
|
return super().__new__(cls)
|
|
|
|
wrap_method(Dummy, "__new__", before=call_second_argument_with("extra"))
|
|
|
|
mock = unittest.mock.Mock()
|
|
dummy = Dummy(mock)
|
|
mock.assert_has_calls(
|
|
[unittest.mock.call(Dummy, "extra"), unittest.mock.call(Dummy, "__new__")],
|
|
any_order=False,
|
|
)
|
|
|
|
def test_wrapping_object___new__(self):
|
|
"""Test that wrapping the magic __new__ method works when it is inherited. This is a very
|
|
special case, because by inheritance ``A.__new__`` is ``type.__new__``, but that's not we
|
|
want to wrap; we need to have used ``type.__getattribute__`` to make sure that we're getting
|
|
the default implementation ``object.__new__``, and not the ``__new__`` method that literally
|
|
constructs new types.
|
|
"""
|
|
|
|
class Dummy:
|
|
pass
|
|
|
|
mock = unittest.mock.Mock()
|
|
wrap_method(Dummy, "__new__", before=mock)
|
|
dummy = Dummy()
|
|
mock.assert_called_once_with(Dummy)
|
|
|
|
def test_wrapping_object___eq__(self):
|
|
"""Test that wrapping equality works. ``type`` also implements ``__eq__`` in a way that
|
|
returns ``NotImplemented`` if one of the operands is not a ``type``, so this tests that we
|
|
are successfully finding ``object.__eq__`` in the resolution of the wrapped method."""
|
|
|
|
class Dummy:
|
|
def __init__(self, n):
|
|
self.n = n
|
|
|
|
def __eq__(self, other):
|
|
return self.n == other.n
|
|
|
|
mock = unittest.mock.Mock()
|
|
wrap_method(Dummy, "__eq__", before=mock)
|
|
|
|
left = Dummy(1)
|
|
right = Dummy(2)
|
|
self.assertNotEqual(left, right)
|
|
mock.assert_has_calls([unittest.mock.call(left, right)])
|
|
|
|
def test_wrapping_object___init_subclass__(self):
|
|
"""Test that wrapping the magic ``__init_subclass__`` method works. This method is
|
|
implicitly made into a class method without needing a decorator."""
|
|
|
|
class Dummy:
|
|
pass
|
|
|
|
mock = unittest.mock.Mock()
|
|
wrap_method(Dummy, "__init_subclass__", before=mock)
|
|
|
|
class Child(Dummy):
|
|
pass
|
|
|
|
mock.assert_called_once_with(Child)
|
|
|
|
def test_wrapping_already_wrapped_method(self):
|
|
"""Test that a chain of wrapped methods evaluate correctly, in the right order. Methods
|
|
that are explicitly overridden in child class definitions do not create chains of wrapped
|
|
methods in the same way, even if they call ``super().method`` because the actual object in
|
|
the child definition would be a regular function. This tests the case that method we're
|
|
wrapping is the exact output of a previous wrapping."""
|
|
|
|
class Grandparent:
|
|
def method(self, mock):
|
|
mock(self, "grandparent")
|
|
|
|
wrap_method(
|
|
Grandparent,
|
|
"method",
|
|
before=call_second_argument_with("before 1"),
|
|
after=call_second_argument_with("after 1"),
|
|
)
|
|
|
|
class Parent(Grandparent):
|
|
pass
|
|
|
|
wrap_method(
|
|
Parent,
|
|
"method",
|
|
before=call_second_argument_with("before 2"),
|
|
after=call_second_argument_with("after 2"),
|
|
)
|
|
|
|
class Child(Parent):
|
|
pass
|
|
|
|
wrap_method(
|
|
Child,
|
|
"method",
|
|
before=call_second_argument_with("before 3"),
|
|
after=call_second_argument_with("after 3"),
|
|
)
|
|
|
|
mock = unittest.mock.Mock()
|
|
child = Child()
|
|
child.method(mock)
|
|
mock.assert_has_calls(
|
|
[
|
|
unittest.mock.call(child, "before 3"),
|
|
unittest.mock.call(child, "before 2"),
|
|
unittest.mock.call(child, "before 1"),
|
|
unittest.mock.call(child, "grandparent"),
|
|
unittest.mock.call(child, "after 1"),
|
|
unittest.mock.call(child, "after 2"),
|
|
unittest.mock.call(child, "after 3"),
|
|
],
|
|
any_order=False,
|
|
)
|
|
|
|
def test_docstring_inherited(self):
|
|
"""Test that the docstring of a method is correctly passed through, to avoid clobbering
|
|
documentation."""
|
|
|
|
class Dummy:
|
|
def method(self):
|
|
"""This is documentation."""
|
|
|
|
wrap_method(Dummy, "method", before=lambda self: None)
|
|
self.assertEqual(Dummy.method.__doc__, "This is documentation.")
|
|
|
|
def test_raises_on_invalid_name(self):
|
|
"""Test that a suitable error is raised if the method doesn't exist."""
|
|
|
|
class Dummy:
|
|
pass
|
|
|
|
with self.assertRaisesRegex(AttributeError, "bad"):
|
|
wrap_method(Dummy, "bad", before=lambda self: None)
|