Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests fail with Python 3.9 #160

Closed
mgorny opened this issue May 28, 2020 · 10 comments
Closed

Tests fail with Python 3.9 #160

mgorny opened this issue May 28, 2020 · 10 comments

Comments

@mgorny
Copy link

mgorny commented May 28, 2020

This is via running tox -e py39 on git master, with Python 3.9.0b1.

Full log: py39.log

==================================================================== FAILURES =====================================================================
______________________________________________ TestCallingOuterClassMethod.test_class_call_function _______________________________________________

self = <test_outer_classmethod.TestCallingOuterClassMethod testMethod=test_class_call_function>

    def test_class_call_function(self):
        # Test calling classmethod. The instance and class passed to the
        # wrapper will both be None because our decorator is surrounded
        # by the classmethod decorator. The classmethod decorator
        # doesn't bind the method and treats it like a normal function,
        # explicitly passing the class as the first argument with the
        # actual arguments following that.
    
        _args = (1, 2)
        _kwargs = {'one': 1, 'two': 2}
    
        @wrapt.decorator
        def _decorator(wrapped, instance, args, kwargs):
            self.assertEqual(instance, None)
            self.assertEqual(args, (Class,)+_args)
            self.assertEqual(kwargs, _kwargs)
            self.assertEqual(wrapped.__module__, _function.__module__)
            self.assertEqual(wrapped.__name__, _function.__name__)
            return wrapped(*args, **kwargs)
    
        @_decorator
        def _function(*args, **kwargs):
            return args, kwargs
    
        class Class(object):
            @classmethod
            @_decorator
            def _function(cls, *args, **kwargs):
                return (args, kwargs)
    
>       result = Class._function(*_args, **_kwargs)

tests/test_outer_classmethod.py:153: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/test_outer_classmethod.py:136: in _decorator
    self.assertEqual(instance, None)
E   AssertionError: <class 'test_outer_classmethod.TestCallin[54 chars]ass'> != None
_____________________________________________ TestCallingOuterClassMethod.test_instance_call_function _____________________________________________

self = <test_outer_classmethod.TestCallingOuterClassMethod testMethod=test_instance_call_function>

    def test_instance_call_function(self):
        # Test calling classmethod via class instance. The instance
        # and class passed to the wrapper will both be None because our
        # decorator is surrounded by the classmethod decorator. The
        # classmethod decorator doesn't bind the method and treats it
        # like a normal function, explicitly passing the class as the
        # first argument with the actual arguments following that.
    
        _args = (1, 2)
        _kwargs = {'one': 1, 'two': 2}
    
        @wrapt.decorator
        def _decorator(wrapped, instance, args, kwargs):
            self.assertEqual(instance, None)
            self.assertEqual(args, (Class,)+_args)
            self.assertEqual(kwargs, _kwargs)
            self.assertEqual(wrapped.__module__, _function.__module__)
            self.assertEqual(wrapped.__name__, _function.__name__)
            return wrapped(*args, **kwargs)
    
        @_decorator
        def _function(*args, **kwargs):
            return args, kwargs
    
        class Class(object):
            @classmethod
            @_decorator
            def _function(cls, *args, **kwargs):
                return (args, kwargs)
    
>       result = Class()._function(*_args, **_kwargs)

tests/test_outer_classmethod.py:187: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/test_outer_classmethod.py:170: in _decorator
    self.assertEqual(instance, None)
E   AssertionError: <class 'test_outer_classmethod.TestCallin[57 chars]ass'> != None
______________________________________________ TestSynchronized.test_synchronized_outer_classmethod _______________________________________________

self = <test_synchronized_lock.TestSynchronized testMethod=test_synchronized_outer_classmethod>

    def test_synchronized_outer_classmethod(self):
        # XXX If all was good, this would be detected as a class
        # method call, but the classmethod decorator doesn't bind
        # the wrapped function to the class before calling and
        # just calls it direct, explicitly passing the class as
        # first argument. This screws things up. Would be nice if
        # Python were fixed, but that isn't likely to happen.
    
        #_lock0 = getattr(C4, '_synchronized_lock', None)
        _lock0 = getattr(C4.function2, '_synchronized_lock', None)
        self.assertEqual(_lock0, None)
    
        c4.function2()
    
        #_lock1 = getattr(C4, '_synchronized_lock', None)
        _lock1 = getattr(C4.function2, '_synchronized_lock', None)
>       self.assertNotEqual(_lock1, None)
E       AssertionError: None == None

tests/test_synchronized_lock.py:175: AssertionError
-------------------------------------------------------------- Captured stdout call ---------------------------------------------------------------
function2
@GrahamDumpleton
Copy link
Owner

Both tests seem to relate to what has been a long standing bug in Python that I reported many many years ago related to the classmethod decorator. The tests would be expecting the broken behaviour. It is possible that the bug have finally been fixed.

@mgorny
Copy link
Author

mgorny commented May 28, 2020

bpo-19072: The classmethod decorator can now wrap other descriptors such as property objects. Adapted from a patch written by Graham Dumpleton.

I guess that's you ;-).

@GrahamDumpleton
Copy link
Owner

First reported in 2013. Patch was even provided. But as a non Python core contributor who knew nothing about what was required to submit patches to core, the response I got was very off putting. Never wanted to get involved with Python core work after that.

@mgorny
Copy link
Author

mgorny commented May 28, 2020

Yeah, from what I've seen a lot of very old patches have suddenly made it to 3.9, when their authors haven't looked at them in years (and might have not agreed with the final solution after all).

@mgorny
Copy link
Author

mgorny commented May 28, 2020

Does this mean that the test failure is safe to ignore?

@GrahamDumpleton
Copy link
Owner

I don't know. It was late at night when you reported it. I need to make time to look properly at it.

@mgorny
Copy link
Author

mgorny commented May 29, 2020

Well, the way I read https://wrapt.readthedocs.io/en/latest/decorators.html#decorating-class-methods it says that it might be fixed one day and that users shouldn't do this. I'm going to make a PR with the assumption that Py3.9 behavior is correct.

@mgorny mgorny closed this as completed May 29, 2020
@mgorny
Copy link
Author

mgorny commented May 29, 2020

Sorry, clicked the wrong button.

@WildCard65
Copy link

@GrahamDumpleton @mgorny Bumping this to inject when the issue was marked resolved: python/cpython#8405

@GrahamDumpleton
Copy link
Owner

These tests are fixed up with version 1.13.0 of wrapt.

hroncok added a commit to hroncok/wrapt that referenced this issue Mar 7, 2024
Fixes GrahamDumpleton#259

The failures were:

    =================================== FAILURES ===================================
    _____________ TestCallingOuterClassMethod.test_class_call_function _____________

    self = <test_outer_classmethod.TestCallingOuterClassMethod testMethod=test_class_call_function>

        def test_class_call_function(self):
            # Test calling classmethod. Prior to Python 3.9, the instance
            # and class passed to the wrapper will both be None because our
            # decorator is surrounded by the classmethod decorator. The
            # classmethod decorator doesn't bind the method and treats it
            # like a normal function, explicitly passing the class as the
            # first argument with the actual arguments following that. This
            # was only finally fixed in Python 3.9. For more details see:
            # https://bugs.python.org/issue19072

            _args = (1, 2)
            _kwargs = {'one': 1, 'two': 2}

            @wrapt.decorator
            def _decorator(wrapped, instance, args, kwargs):
                if PYXY < (3, 9):
                    self.assertEqual(instance, None)
                    self.assertEqual(args, (Class,)+_args)
                else:
                    self.assertEqual(instance, Class)
                    self.assertEqual(args, _args)

                self.assertEqual(kwargs, _kwargs)
                self.assertEqual(wrapped.__module__, _function.__module__)
                self.assertEqual(wrapped.__name__, _function.__name__)

                return wrapped(*args, **kwargs)

            @_decorator
            def _function(*args, **kwargs):
                return args, kwargs

            class Class(object):
                @classmethod
                @_decorator
                def _function(cls, *args, **kwargs):
                    return (args, kwargs)

    >       result = Class._function(*_args, **_kwargs)

    tests/test_outer_classmethod.py:160:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    tests/test_outer_classmethod.py:141: in _decorator
        self.assertEqual(instance, Class)
    E   AssertionError: None != <class 'test_outer_classmethod.TestCallin[54 chars]ass'>
    ___________ TestCallingOuterClassMethod.test_instance_call_function ____________

    self = <test_outer_classmethod.TestCallingOuterClassMethod testMethod=test_instance_call_function>

        def test_instance_call_function(self):
            # Test calling classmethod via class instance. Prior to Python
            # 3.9, the instance and class passed to the wrapper will both be
            # None because our decorator is surrounded by the classmethod
            # decorator. The classmethod decorator doesn't bind the method
            # and treats it like a normal function, explicitly passing the
            # class as the first argument with the actual arguments
            # following that. This was only finally fixed in Python 3.9. For
            # more details see: https://bugs.python.org/issue19072

            _args = (1, 2)
            _kwargs = {'one': 1, 'two': 2}

            @wrapt.decorator
            def _decorator(wrapped, instance, args, kwargs):
                if PYXY < (3, 9):
                    self.assertEqual(instance, None)
                    self.assertEqual(args, (Class,)+_args)
                else:
                    self.assertEqual(instance, Class)
                    self.assertEqual(args, _args)

                self.assertEqual(kwargs, _kwargs)
                self.assertEqual(wrapped.__module__, _function.__module__)
                self.assertEqual(wrapped.__name__, _function.__name__)

                return wrapped(*args, **kwargs)

            @_decorator
            def _function(*args, **kwargs):
                return args, kwargs

            class Class(object):
                @classmethod
                @_decorator
                def _function(cls, *args, **kwargs):
                    return (args, kwargs)

    >       result = Class()._function(*_args, **_kwargs)

    tests/test_outer_classmethod.py:202:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    tests/test_outer_classmethod.py:183: in _decorator
        self.assertEqual(instance, Class)
    E   AssertionError: None != <class 'test_outer_classmethod.TestCallin[57 chars]ass'>
    _____________ TestSynchronized.test_synchronized_outer_classmethod _____________

    self = <test_synchronized_lock.TestSynchronized testMethod=test_synchronized_outer_classmethod>

        def test_synchronized_outer_classmethod(self):
            # Prior to Python 3.9 this isn't detected as a class method
            # call, as the classmethod decorator doesn't bind the wrapped
            # function to the class before calling and just calls it direct,
            # explicitly passing the class as first argument. For more
            # details see: https://bugs.python.org/issue19072

            if PYXY < (3, 9):
                _lock0 = getattr(C4.function2, '_synchronized_lock', None)
            else:
                _lock0 = getattr(C4, '_synchronized_lock', None)
            self.assertEqual(_lock0, None)

            c4.function2()

            if PYXY < (3, 9):
                _lock1 = getattr(C4.function2, '_synchronized_lock', None)
            else:
                _lock1 = getattr(C4, '_synchronized_lock', None)
    >       self.assertNotEqual(_lock1, None)
    E       AssertionError: None == None

    tests/test_synchronized_lock.py:181: AssertionError
    ----------------------------- Captured stdout call -----------------------------
    function2
    =========================== short test summary info ============================
    FAILED tests/test_outer_classmethod.py::TestCallingOuterClassMethod::test_class_call_function
    FAILED tests/test_outer_classmethod.py::TestCallingOuterClassMethod::test_instance_call_function
    FAILED tests/test_synchronized_lock.py::TestSynchronized::test_synchronized_outer_classmethod
    ======================== 3 failed, 435 passed in 0.83s =========================

To fix the same failures on Python 3.9,
they were adjusted in the past. For details see
GrahamDumpleton#160

However, Python 3.13 reverted the change from 3.9,
so this adds an upper bound for the conditionals.

To make the conditionals easier to read, the if-else branches were switched.
FFY00 pushed a commit to FFY00/wrapt that referenced this issue Sep 19, 2024
Fixes GrahamDumpleton#259

The failures were:

    =================================== FAILURES ===================================
    _____________ TestCallingOuterClassMethod.test_class_call_function _____________

    self = <test_outer_classmethod.TestCallingOuterClassMethod testMethod=test_class_call_function>

        def test_class_call_function(self):
            # Test calling classmethod. Prior to Python 3.9, the instance
            # and class passed to the wrapper will both be None because our
            # decorator is surrounded by the classmethod decorator. The
            # classmethod decorator doesn't bind the method and treats it
            # like a normal function, explicitly passing the class as the
            # first argument with the actual arguments following that. This
            # was only finally fixed in Python 3.9. For more details see:
            # https://bugs.python.org/issue19072

            _args = (1, 2)
            _kwargs = {'one': 1, 'two': 2}

            @wrapt.decorator
            def _decorator(wrapped, instance, args, kwargs):
                if PYXY < (3, 9):
                    self.assertEqual(instance, None)
                    self.assertEqual(args, (Class,)+_args)
                else:
                    self.assertEqual(instance, Class)
                    self.assertEqual(args, _args)

                self.assertEqual(kwargs, _kwargs)
                self.assertEqual(wrapped.__module__, _function.__module__)
                self.assertEqual(wrapped.__name__, _function.__name__)

                return wrapped(*args, **kwargs)

            @_decorator
            def _function(*args, **kwargs):
                return args, kwargs

            class Class(object):
                @classmethod
                @_decorator
                def _function(cls, *args, **kwargs):
                    return (args, kwargs)

    >       result = Class._function(*_args, **_kwargs)

    tests/test_outer_classmethod.py:160:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    tests/test_outer_classmethod.py:141: in _decorator
        self.assertEqual(instance, Class)
    E   AssertionError: None != <class 'test_outer_classmethod.TestCallin[54 chars]ass'>
    ___________ TestCallingOuterClassMethod.test_instance_call_function ____________

    self = <test_outer_classmethod.TestCallingOuterClassMethod testMethod=test_instance_call_function>

        def test_instance_call_function(self):
            # Test calling classmethod via class instance. Prior to Python
            # 3.9, the instance and class passed to the wrapper will both be
            # None because our decorator is surrounded by the classmethod
            # decorator. The classmethod decorator doesn't bind the method
            # and treats it like a normal function, explicitly passing the
            # class as the first argument with the actual arguments
            # following that. This was only finally fixed in Python 3.9. For
            # more details see: https://bugs.python.org/issue19072

            _args = (1, 2)
            _kwargs = {'one': 1, 'two': 2}

            @wrapt.decorator
            def _decorator(wrapped, instance, args, kwargs):
                if PYXY < (3, 9):
                    self.assertEqual(instance, None)
                    self.assertEqual(args, (Class,)+_args)
                else:
                    self.assertEqual(instance, Class)
                    self.assertEqual(args, _args)

                self.assertEqual(kwargs, _kwargs)
                self.assertEqual(wrapped.__module__, _function.__module__)
                self.assertEqual(wrapped.__name__, _function.__name__)

                return wrapped(*args, **kwargs)

            @_decorator
            def _function(*args, **kwargs):
                return args, kwargs

            class Class(object):
                @classmethod
                @_decorator
                def _function(cls, *args, **kwargs):
                    return (args, kwargs)

    >       result = Class()._function(*_args, **_kwargs)

    tests/test_outer_classmethod.py:202:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    tests/test_outer_classmethod.py:183: in _decorator
        self.assertEqual(instance, Class)
    E   AssertionError: None != <class 'test_outer_classmethod.TestCallin[57 chars]ass'>
    _____________ TestSynchronized.test_synchronized_outer_classmethod _____________

    self = <test_synchronized_lock.TestSynchronized testMethod=test_synchronized_outer_classmethod>

        def test_synchronized_outer_classmethod(self):
            # Prior to Python 3.9 this isn't detected as a class method
            # call, as the classmethod decorator doesn't bind the wrapped
            # function to the class before calling and just calls it direct,
            # explicitly passing the class as first argument. For more
            # details see: https://bugs.python.org/issue19072

            if PYXY < (3, 9):
                _lock0 = getattr(C4.function2, '_synchronized_lock', None)
            else:
                _lock0 = getattr(C4, '_synchronized_lock', None)
            self.assertEqual(_lock0, None)

            c4.function2()

            if PYXY < (3, 9):
                _lock1 = getattr(C4.function2, '_synchronized_lock', None)
            else:
                _lock1 = getattr(C4, '_synchronized_lock', None)
    >       self.assertNotEqual(_lock1, None)
    E       AssertionError: None == None

    tests/test_synchronized_lock.py:181: AssertionError
    ----------------------------- Captured stdout call -----------------------------
    function2
    =========================== short test summary info ============================
    FAILED tests/test_outer_classmethod.py::TestCallingOuterClassMethod::test_class_call_function
    FAILED tests/test_outer_classmethod.py::TestCallingOuterClassMethod::test_instance_call_function
    FAILED tests/test_synchronized_lock.py::TestSynchronized::test_synchronized_outer_classmethod
    ======================== 3 failed, 435 passed in 0.83s =========================

To fix the same failures on Python 3.9,
they were adjusted in the past. For details see
GrahamDumpleton#160

However, Python 3.13 reverted the change from 3.9,
so this adds an upper bound for the conditionals.

To make the conditionals easier to read, the if-else branches were switched.

Signed-off-by: Filipe Laíns <lains@riseup.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants