Bug 2196752 - python-mockito fails to build with Python 3.12: SystemError: <class 'functools.partial'> returned a result with an exception set
Summary: python-mockito fails to build with Python 3.12: SystemError: <class 'functool...
Keywords:
Status: CLOSED RAWHIDE
Alias: None
Product: Fedora
Classification: Fedora
Component: python3.12
Version: rawhide
Hardware: Unspecified
OS: Unspecified
unspecified
unspecified
Target Milestone: ---
Assignee: Python Maintainers
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks: PYTHON3.12
TreeView+ depends on / blocked
 
Reported: 2023-05-10 06:27 UTC by Tomáš Hrnčiar
Modified: 2023-08-02 12:19 UTC (History)
9 users (show)

Fixed In Version:
Doc Type: If docs needed, set a value
Doc Text:
Clone Of:
Environment:
Last Closed: 2023-08-02 12:19:05 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)

Description Tomáš Hrnčiar 2023-05-10 06:27:00 UTC
python-mockito fails to build with Python 3.12.0a7.

=================================== FAILURES ===================================
______________________________ test_deprecated_a _______________________________

args = (<Dummy id=140649672338192>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kwargs = {}

    def new_mocked_method(*args, **kwargs):
        # we throw away the first argument, if it's either self or cls
        if (
            inspect.ismethod(new_mocked_method)
            or inspect.isclass(self.mocked_obj)
            and not isinstance(new_mocked_method, staticmethod)
        ):
            args = args[1:]
    
>       return remembered_invocation_builder(
            self, method_name, *args, **kwargs)

mockito/mocking.py:108: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

mock = <mockito.mocking.Mock object at 0x7feba286d340>, method_name = '__get__'
args = (<Dummy id=140649672338192>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kwargs = {}
invoc = __get__(<Dummy id=140649672338192>, <class 'mockito.mocking.mock.<locals>.Dummy'>)

    def remembered_invocation_builder(mock, method_name, *args, **kwargs):
        invoc = invocation.RememberedInvocation(mock, method_name)
>       return invoc(*args, **kwargs)

mockito/mocking.py:48: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = __get__(<Dummy id=140649672338192>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
params = (<Dummy id=140649672338192>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
named_params = {}, matching_invocation = __get__(...)

    def __call__(self, *params, **named_params):
        if self.strict:
            self.ensure_mocked_object_has_method(self.method_name)
            self.ensure_signature_matches(
                self.method_name, params, named_params)
    
        self._remember_params(params, named_params)
        self.mock.remember(self)
    
        for matching_invocation in self.mock.stubbed_invocations:
            if matching_invocation.matches(self):
                matching_invocation.should_answer(self)
>               return matching_invocation.answer_first(
                    *params, **named_params)

mockito/invocation.py:95: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = __get__(...)
args = (<Dummy id=140649672338192>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kwargs = {}

    def answer_first(self, *args, **kwargs):
        self.used += 1
>       return self.answers.answer(*args, **kwargs)

mockito/invocation.py:331: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <mockito.invocation.CompositeAnswer object at 0x7feb8dc5c470>
args = (<Dummy id=140649672338192>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kwargs = {}
a = functools.partial(<function raise_ at 0x7feba288f880>, <class 'ValueError'>)

    def answer(self, *args, **kwargs):
        if len(self.answers) == 0:
            return None
    
        if len(self.answers) == 1:
            a = self.answers[0]
        else:
            a = self.answers.popleft()
    
>       return a(*args, **kwargs)

mockito/invocation.py:440: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

exception = <class 'ValueError'>
a = (<Dummy id=140649672338192>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kw = {}

    def raise_(exception, *a, **kw):
>       raise exception
E       ValueError

mockito/invocation.py:383: ValueError

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

unstub = None

    def test_deprecated_a(unstub):
        # Setting on `__class__` is confusing for users
        m = mock()
    
        prop = mock()
        when(prop).__get__(Ellipsis).thenRaise(ValueError)
        m.__class__.tx = prop
    
        with pytest.raises(ValueError):
>           m.tx

tests/mocking_properties_test.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Dummy id=140649672338192>, method_name = 'tx'

    def __getattr__(self, method_name):
        if strict:
            __tracebackhide__ = operator.methodcaller(
                "errisinstance", AttributeError
            )
    
            raise AttributeError(
                "'Dummy' has no attribute %r configured" % method_name)
>       return functools.partial(
            remembered_invocation_builder, theMock, method_name)
E       SystemError: <class 'functools.partial'> returned a result with an exception set

mockito/mocking.py:281: SystemError
______________________________ test_deprecated_b _______________________________

a = (<Dummy id=140649669444192>,)

    def _raise(*a):
        print(a)
>       raise ValueError('Boom')
E       ValueError: Boom

tests/mocking_properties_test.py:22: ValueError

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

unstub = None

    def test_deprecated_b(unstub):
        # Setting on `__class__` is confusing for users
        m = mock()
    
        def _raise(*a):
            print(a)
            raise ValueError('Boom')
    
        m.__class__.tx = property(_raise)
    
        with pytest.raises(ValueError):
>           m.tx

tests/mocking_properties_test.py:27: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Dummy id=140649669444192>, method_name = 'tx'

    def __getattr__(self, method_name):
        if strict:
            __tracebackhide__ = operator.methodcaller(
                "errisinstance", AttributeError
            )
    
            raise AttributeError(
                "'Dummy' has no attribute %r configured" % method_name)
>       return functools.partial(
            remembered_invocation_builder, theMock, method_name)
E       SystemError: <class 'functools.partial'> returned a result with an exception set

mockito/mocking.py:281: SystemError
----------------------------- Captured stdout call -----------------------------
(<Dummy id=140649669444192>,)
______________________________ test_deprecated_c _______________________________

args = (<Dummy id=140649673126160>,), kwargs = {}

    def new_mocked_method(*args, **kwargs):
        # we throw away the first argument, if it's either self or cls
        if (
            inspect.ismethod(new_mocked_method)
            or inspect.isclass(self.mocked_obj)
            and not isinstance(new_mocked_method, staticmethod)
        ):
            args = args[1:]
    
>       return remembered_invocation_builder(
            self, method_name, *args, **kwargs)

mockito/mocking.py:108: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

mock = <mockito.mocking.Mock object at 0x7feb8dcdfcb0>, method_name = '__call__'
args = (<Dummy id=140649673126160>,), kwargs = {}
invoc = __call__(<Dummy id=140649673126160>)

    def remembered_invocation_builder(mock, method_name, *args, **kwargs):
        invoc = invocation.RememberedInvocation(mock, method_name)
>       return invoc(*args, **kwargs)

mockito/mocking.py:48: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = __call__(<Dummy id=140649673126160>)
params = (<Dummy id=140649673126160>,), named_params = {}
matching_invocation = __call__(...)

    def __call__(self, *params, **named_params):
        if self.strict:
            self.ensure_mocked_object_has_method(self.method_name)
            self.ensure_signature_matches(
                self.method_name, params, named_params)
    
        self._remember_params(params, named_params)
        self.mock.remember(self)
    
        for matching_invocation in self.mock.stubbed_invocations:
            if matching_invocation.matches(self):
                matching_invocation.should_answer(self)
>               return matching_invocation.answer_first(
                    *params, **named_params)

mockito/invocation.py:95: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = __call__(...), args = (<Dummy id=140649673126160>,), kwargs = {}

    def answer_first(self, *args, **kwargs):
        self.used += 1
>       return self.answers.answer(*args, **kwargs)

mockito/invocation.py:331: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <mockito.invocation.CompositeAnswer object at 0x7feb8dcddcd0>
args = (<Dummy id=140649673126160>,), kwargs = {}
a = functools.partial(<function raise_ at 0x7feba288f880>, <class 'ValueError'>)

    def answer(self, *args, **kwargs):
        if len(self.answers) == 0:
            return None
    
        if len(self.answers) == 1:
            a = self.answers[0]
        else:
            a = self.answers.popleft()
    
>       return a(*args, **kwargs)

mockito/invocation.py:440: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

exception = <class 'ValueError'>, a = (<Dummy id=140649673126160>,), kw = {}

    def raise_(exception, *a, **kw):
>       raise exception
E       ValueError

mockito/invocation.py:383: ValueError

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

unstub = None

    def test_deprecated_c(unstub):
        # Setting on `__class__` is confusing for users
        # Wrapping explicitly with `property` as well
        m = mock()
    
        prop = mock(strict=True)
        when(prop).__call__(Ellipsis).thenRaise(ValueError)
        m.__class__.tx = property(prop)
    
        with pytest.raises(ValueError):
>           m.tx

tests/mocking_properties_test.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Dummy id=140649673126160>, method_name = 'tx'

    def __getattr__(self, method_name):
        if strict:
            __tracebackhide__ = operator.methodcaller(
                "errisinstance", AttributeError
            )
    
            raise AttributeError(
                "'Dummy' has no attribute %r configured" % method_name)
>       return functools.partial(
            remembered_invocation_builder, theMock, method_name)
E       SystemError: <class 'functools.partial'> returned a result with an exception set

mockito/mocking.py:281: SystemError
__________________________ test_recommended_approach ___________________________

args = (<Dummy id=140649669573280>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kwargs = {}

    def new_mocked_method(*args, **kwargs):
        # we throw away the first argument, if it's either self or cls
        if (
            inspect.ismethod(new_mocked_method)
            or inspect.isclass(self.mocked_obj)
            and not isinstance(new_mocked_method, staticmethod)
        ):
            args = args[1:]
    
>       return remembered_invocation_builder(
            self, method_name, *args, **kwargs)

mockito/mocking.py:108: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

mock = <mockito.mocking.Mock object at 0x7feb8d97bda0>, method_name = '__get__'
args = (<Dummy id=140649669573280>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kwargs = {}
invoc = __get__(<Dummy id=140649669573280>, <class 'mockito.mocking.mock.<locals>.Dummy'>)

    def remembered_invocation_builder(mock, method_name, *args, **kwargs):
        invoc = invocation.RememberedInvocation(mock, method_name)
>       return invoc(*args, **kwargs)

mockito/mocking.py:48: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = __get__(<Dummy id=140649669573280>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
params = (<Dummy id=140649669573280>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
named_params = {}, matching_invocation = __get__(...)

    def __call__(self, *params, **named_params):
        if self.strict:
            self.ensure_mocked_object_has_method(self.method_name)
            self.ensure_signature_matches(
                self.method_name, params, named_params)
    
        self._remember_params(params, named_params)
        self.mock.remember(self)
    
        for matching_invocation in self.mock.stubbed_invocations:
            if matching_invocation.matches(self):
                matching_invocation.should_answer(self)
>               return matching_invocation.answer_first(
                    *params, **named_params)

mockito/invocation.py:95: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = __get__(...)
args = (<Dummy id=140649669573280>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kwargs = {}

    def answer_first(self, *args, **kwargs):
        self.used += 1
>       return self.answers.answer(*args, **kwargs)

mockito/invocation.py:331: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <mockito.invocation.CompositeAnswer object at 0x7feb8d97bc80>
args = (<Dummy id=140649669573280>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kwargs = {}
a = functools.partial(<function raise_ at 0x7feba288f880>, <class 'ValueError'>)

    def answer(self, *args, **kwargs):
        if len(self.answers) == 0:
            return None
    
        if len(self.answers) == 1:
            a = self.answers[0]
        else:
            a = self.answers.popleft()
    
>       return a(*args, **kwargs)

mockito/invocation.py:440: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

exception = <class 'ValueError'>
a = (<Dummy id=140649669573280>, <class 'mockito.mocking.mock.<locals>.Dummy'>)
kw = {}

    def raise_(exception, *a, **kw):
>       raise exception
E       ValueError

mockito/invocation.py:383: ValueError

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

    def test_recommended_approach():
        prop = mock(strict=True)
        when(prop).__get__(Ellipsis).thenRaise(ValueError)
    
        m = mock({'tx': prop})
        with pytest.raises(ValueError):
>           m.tx

tests/mocking_properties_test.py:49: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Dummy id=140649669573280>, method_name = 'tx'

    def __getattr__(self, method_name):
        if strict:
            __tracebackhide__ = operator.methodcaller(
                "errisinstance", AttributeError
            )
    
            raise AttributeError(
                "'Dummy' has no attribute %r configured" % method_name)
>       return functools.partial(
            remembered_invocation_builder, theMock, method_name)
E       SystemError: <class 'functools.partial'> returned a result with an exception set

mockito/mocking.py:281: SystemError
=========================== short test summary info ============================
FAILED tests/mocking_properties_test.py::test_deprecated_a - SystemError: <cl...
FAILED tests/mocking_properties_test.py::test_deprecated_b - SystemError: <cl...
FAILED tests/mocking_properties_test.py::test_deprecated_c - SystemError: <cl...
FAILED tests/mocking_properties_test.py::test_recommended_approach - SystemEr...
================== 4 failed, 1136 passed, 3 xfailed in 1.10s ===================

https://docs.python.org/3.12/whatsnew/3.12.html

For the build logs, see:
https://copr-be.cloud.fedoraproject.org/results/@python/python3.12/fedora-rawhide-x86_64/05902284-python-mockito/

For all our attempts to build python-mockito with Python 3.12, see:
https://copr.fedorainfracloud.org/coprs/g/python/python3.12/package/python-mockito/

Testing and mass rebuild of packages is happening in copr. You can follow these instructions to test locally in mock if your package builds with Python 3.12:
https://copr.fedorainfracloud.org/coprs/g/python/python3.12/

Let us know here if you have any questions.

Python 3.12 is planned to be included in Fedora 39. To make that update smoother, we're building Fedora packages with all pre-releases of Python 3.12.
A build failure prevents us from testing all dependent packages (transitive [Build]Requires), so if this package is required a lot, it's important for us to get it fixed soon.
We'd appreciate help from the people who know this package best, but if you don't want to work on this now, let us know so we can try to work around it on our side.

Comment 1 Fabrice BAUZAC-STEHLY 2023-05-15 20:02:43 UTC
Hello,

I can reproduce this error:
SystemError: <class 'functools.partial'> returned a result with an exception set

According to
https://pythonextensionpatterns.readthedocs.io/en/latest/exceptions.html
the correct way for C code to raise an exception is to use both
PyErr_* and to return NULL.  The error message seems to say that a C
function returned non-NULL but on the other hand an exception has been
set.  And indeed, partial() seems to be implemented in a C file:
cpython-3.12.0a7/Modules/_functoolsmodule.c

I guess this is a CPython bug, not a python-mockito one.  Either the C
code wants to raise an exception, and therefore return NULL, or it
doesn't, in which case it should not set it.

I think this bug should be redirected to CPython rather than
python-mockito.

Thanks!

Best regards
Fabrice

Comment 2 Fabrice BAUZAC-STEHLY 2023-05-15 20:18:26 UTC
As explained above, reassigning to the python3.12 component.

Comment 3 Petr Viktorin 2023-05-16 08:08:41 UTC
If anyone gets to this before I do: normally Python checks this after a builtin function returns (when the check is cheap), but not before it's called. It's possible that the exception was set before the partial() call.
(But it does look like a CPython bug -- AFAIK Mockito is a pure Python library with minimal dependencies.)

To turn on additional asserts, BR /usr/bin/python3-debug and run the tests with that.

Comment 4 Tomas Orsava 2023-05-24 12:29:47 UTC
This is a leaf package, not blocking any other packages during the Python 3.12 rebuild.

Comment 5 Petr Viktorin 2023-05-26 08:36:11 UTC
This is indeed an issue in CPython, and should be fixed in beta 1 (https://github.com/python/cpython/pull/103332).

Comment 6 Tomas Orsava 2023-08-02 12:19:05 UTC
Package now builds.


Note You need to log in before you can comment on or make changes to this bug.