uber / doubles

Test doubles for Python.
MIT License
165 stars 17 forks source link

Error "doubles.exceptions.VerifyingBuiltinDoubleArgumentError: unsupported callable" #150

Open arnlen opened 11 months ago

arnlen commented 11 months ago

Context

Every time I try to mock a function from an external library (depthai in my case), I got the following error:

ValueError: no signature found for builtin <instancemethod getIrDrivers at 0x102b0b400>

/usr/local/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py:2328: ValueError

The above exception was the direct cause of the following exception: [...]

TypeError: unsupported callable

During handling of the above exception, another exception occurred: [...]

doubles.exceptions.VerifyingBuiltinDoubleArgumentError: unsupported callable

What I tried

In the FAQ, I read this section which explains how to bypass the inspection functionality, by using with no_builtin_verification().

This is what I tried, by it doesn't fix the issue.

My code

import depthai as dai

from models.device import Device
from doubles import InstanceDouble, allow, no_builtin_verification
import functools

class TestDevice:

    with no_builtin_verification():
        allow(functools).reduce

        def test_when_camera_has_no_ir_projector(self):
            dai_device = InstanceDouble('depthai.Device')
            allow(dai_device).getIrDrivers.and_return([])

            device = Device(dai_device, 300)
            assert device.dai_device == dai_device

Setup


Any idea? I'm even sure if I'm using the with no_builtin_verification() properly.' 🤔

arnlen commented 11 months ago

Full error stack:

$ pytest tests/models/device_test.py
============================================================================================================================= test session starts ==============================================================================================================================
platform darwin -- Python 3.11.5, pytest-7.4.2, pluggy-1.3.0
rootdir: /Users/[...]/my-project
plugins: doubles-1.5.3
collected 1 item

tests/models/device_test.py                                                                                                                                                                                                                                             [100%]

=================================================================================================================================== FAILURES ===================================================================================================================================
_______________________________________________________________________________________________________________ TestDevice.test_when_camera_has_no_ir_projector ________________________________________________________________________________________________________________

func = <instancemethod getIrDrivers at 0x102b0b400>

    def getfullargspec(func):
        """Get the names and default values of a callable object's parameters.

        A tuple of seven things is returned:
        (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations).
        'args' is a list of the parameter names.
        'varargs' and 'varkw' are the names of the * and ** parameters or None.
        'defaults' is an n-tuple of the default values of the last n parameters.
        'kwonlyargs' is a list of keyword-only parameter names.
        'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults.
        'annotations' is a dictionary mapping parameter names to annotations.

        Notable differences from inspect.signature():
          - the "self" parameter is always reported, even for bound methods
          - wrapper chains defined by __wrapped__ *not* unwrapped automatically
        """
        try:
            # Re: `skip_bound_arg=False`
            #
            # There is a notable difference in behaviour between getfullargspec
            # and Signature: the former always returns 'self' parameter for bound
            # methods, whereas the Signature always shows the actual calling
            # signature of the passed object.
            #
            # To simulate this behaviour, we "unbind" bound methods, to trick
            # inspect.signature to always return their first parameter ("self",
            # usually)

            # Re: `follow_wrapper_chains=False`
            #
            # getfullargspec() historically ignored __wrapped__ attributes,
            # so we ensure that remains the case in 3.3+

>           sig = _signature_from_callable(func,
                                           follow_wrapper_chains=False,
                                           skip_bound_arg=False,
                                           sigcls=Signature,
                                           eval_str=False)

/usr/local/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py:1369:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py:2521: in _signature_from_callable
    return _signature_from_builtin(sigcls, obj,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'inspect.Signature'>, func = <instancemethod getIrDrivers at 0x102b0b400>, skip_bound_arg = False

    def _signature_from_builtin(cls, func, skip_bound_arg=True):
        """Private helper function to get signature for
        builtin callables.
        """

        if not _signature_is_builtin(func):
            raise TypeError("{!r} is not a Python builtin "
                            "function".format(func))

        s = getattr(func, "__text_signature__", None)
        if not s:
>           raise ValueError("no signature found for builtin {!r}".format(func))
E           ValueError: no signature found for builtin <instancemethod getIrDrivers at 0x102b0b400>

/usr/local/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py:2328: ValueError

The above exception was the direct cause of the following exception:

method = <instancemethod getIrDrivers at 0x102b0b400>, method_name = 'getIrDrivers', args = ['self_or_cls'], kwargs = {}

    def _verify_arguments(method, method_name, args, kwargs):
        try:
>           getcallargs(method, *args, **kwargs)

../../../.venv/lib/python3.11/site-packages/doubles/verification.py:130:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py:1528: in getcallargs
    spec = getfullargspec(func)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

func = <instancemethod getIrDrivers at 0x102b0b400>

    def getfullargspec(func):
        """Get the names and default values of a callable object's parameters.

        A tuple of seven things is returned:
        (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations).
        'args' is a list of the parameter names.
        'varargs' and 'varkw' are the names of the * and ** parameters or None.
        'defaults' is an n-tuple of the default values of the last n parameters.
        'kwonlyargs' is a list of keyword-only parameter names.
        'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults.
        'annotations' is a dictionary mapping parameter names to annotations.

        Notable differences from inspect.signature():
          - the "self" parameter is always reported, even for bound methods
          - wrapper chains defined by __wrapped__ *not* unwrapped automatically
        """
        try:
            # Re: `skip_bound_arg=False`
            #
            # There is a notable difference in behaviour between getfullargspec
            # and Signature: the former always returns 'self' parameter for bound
            # methods, whereas the Signature always shows the actual calling
            # signature of the passed object.
            #
            # To simulate this behaviour, we "unbind" bound methods, to trick
            # inspect.signature to always return their first parameter ("self",
            # usually)

            # Re: `follow_wrapper_chains=False`
            #
            # getfullargspec() historically ignored __wrapped__ attributes,
            # so we ensure that remains the case in 3.3+

            sig = _signature_from_callable(func,
                                           follow_wrapper_chains=False,
                                           skip_bound_arg=False,
                                           sigcls=Signature,
                                           eval_str=False)
        except Exception as ex:
            # Most of the times 'signature' will raise ValueError.
            # But, it can also raise AttributeError, and, maybe something
            # else. So to be fully backwards compatible, we catch all
            # possible exceptions here, and reraise a TypeError.
>           raise TypeError('unsupported callable') from ex
E           TypeError: unsupported callable

/usr/local/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py:1379: TypeError

During handling of the above exception, another exception occurred:

self = <tests.models.device_test.TestDevice object at 0x102a69050>

    def test_when_camera_has_no_ir_projector(self):
        dai_device = InstanceDouble('depthai.Device')
        allow(dai_device).getIrDrivers.and_return([])

>       device = Device(dai_device = dai_device, ir_projector_power = 300)

tests/models/device_test.py:12:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
models/device.py:19: in __init__
    self.update_ir_projector_power(ir_projector_power)
models/device.py:33: in update_ir_projector_power
    if len(self.dai_device.getIrDrivers()) == 0:
../../../.venv/lib/python3.11/site-packages/doubles/proxy_method.py:77: in __call__
    expectation.verify_arguments(args, kwargs)
../../../.venv/lib/python3.11/site-packages/doubles/allowance.py:267: in verify_arguments
    verify_arguments(self._target, self._method_name, args, kwargs)
../../../.venv/lib/python3.11/site-packages/doubles/verification.py:125: in verify_arguments
    _verify_arguments(method, method_name, args, kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

method = <instancemethod getIrDrivers at 0x102b0b400>, method_name = 'getIrDrivers', args = ['self_or_cls'], kwargs = {}

    def _verify_arguments(method, method_name, args, kwargs):
        try:
            getcallargs(method, *args, **kwargs)
        except TypeError as e:
            if not _is_python_function(method):
>               raise VerifyingBuiltinDoubleArgumentError(str(e))
E               doubles.exceptions.VerifyingBuiltinDoubleArgumentError: unsupported callable

../../../.venv/lib/python3.11/site-packages/doubles/verification.py:133: VerifyingBuiltinDoubleArgumentError
--------------------------------------------------------------------------------------------------------------------------- Captured stdout teardown ---------------------------------------------------------------------------------------------------------------------------
F
=========================================================================================================================== short test summary info ============================================================================================================================
FAILED tests/models/device_test.py::TestDevice::test_when_camera_has_no_ir_projector - doubles.exceptions.VerifyingBuiltinDoubleArgumentError: unsupported callable
toddsifleet commented 10 months ago

You want to use the context manager within the test.

If you do:

` import depthai as dai

from models.device import Device
from doubles import InstanceDouble, allow, no_builtin_verification
import functools

class TestDevice:

    def test_when_camera_has_no_ir_projector(self):
        with no_builtin_verification():
            allow(functools).reduce

        dai_device = InstanceDouble('depthai.Device')
        allow(dai_device).getIrDrivers.and_return([])

        device = Device(dai_device, 300)
        assert device.dai_device == dai_device`

It should work as you're expecting it too.