This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author xtreak
Recipients michael.foord, nedbat, pablogsal, xtreak
Date 2019-04-11.11:14:05
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1554981246.39.0.636440730525.issue36593@roundup.psfhosted.org>
In-reply-to
Content
I think I got to this. See issue12370 (Use of super overwrites use of __class__ in class namespace) . msg138712 suggests a workaround to alias super at the module level. unittest.mock has the below line and NonCallableMock has __class__ defined as a property which is not set when set trace is used since the linked commit in 3.7.2 uses super() is somehow executed by settrace since it's in __delattr__ and not in a code path executed by user code?

Looking at the unittest.mock code super is not used anywhere but _safe_super is used which I hope is for this reason. So this is not a case with MagicMock only but rather a case with anything that inherits from NonCallableMock i.e. Mock, MagicMock etc. This is not a problem with import being made before the trace is set as you mentioned so set trace does something that exposes this issue

https://github.com/python/cpython/blob/536a35b3f14888999f1ffa5be7239d0c26b73d7a/Lib/unittest/mock.py#L48

# Workaround for issue #12370
# Without this, the __class__ properties wouldn't be set correctly
_safe_super = super

Sample program in issue12370. Add set trace SuperClass with using super() fails and passes without set trace for super(). Whereas SafeSuperClass that uses _safe_super (module level alias to super) passes both cases irrespective of usage of set trace. Nick coughlan added a message so that unittest.mock should use _safe_super in https://bugs.python.org/issue12370#msg161704

import sys

_safe_super = super

def trace(frame, event, arg):
    return trace

if len(sys.argv) > 1:
    sys.settrace(trace)

class SuperClass(object):

    def __init__(self):
        super().__init__()

    @property
    def __class__(self):
        return int

class SafeSuperClass(object):

    def __init__(self):
        _safe_super(SafeSuperClass, self).__init__()

    @property
    def __class__(self):
        return int

print(isinstance(SuperClass(), int))
print(isinstance(SafeSuperClass(), int))


Running above code with trace and without trace

➜  cpython git:(master) ✗ ./python.exe /tmp/buz.py
True
True
➜  cpython git:(master) ✗ ./python.exe /tmp/buz.py 1
False
True


There is a test for the above in Lib/test/test_super.py at https://github.com/python/cpython/blob/4c409beb4c360a73d054f37807d3daad58d1b567/Lib/test/test_super.py#L87

Add a trace as below in test_super.py at the top and the test case fails

import sys

def trace(frame, event, arg):
    return trace

sys.settrace(trace)


➜  cpython git:(master) ✗ ./python.exe Lib/test/test_super.py
....................F
======================================================================
FAIL: test_various___class___pathologies (__main__.TestSuper)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "Lib/test/test_super.py", line 100, in test_various___class___pathologies
    self.assertEqual(x.__class__, 413)
AssertionError: <class '__main__.TestSuper.test_various___class___pathologies.<locals>.X'> != 413

----------------------------------------------------------------------
Ran 21 tests in 0.058s

FAILED (failures=1)


The patch for this issue would be to use _safe_super instead of super() or to fallback to object() in the previous case that doesn't seemed to have suffered from this case. I can try to make a PR with tests. Added Michael for confirmation.


diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 8684f1dfa5..0e77f0e489 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -739,7 +739,7 @@ class NonCallableMock(Base):

         obj = self._mock_children.get(name, _missing)
         if name in self.__dict__:
-            super().__delattr__(name)
+            _safe_super(NonCallableMock, self).__delattr__(name)
         elif obj is _deleted:
             raise AttributeError(name)
         if obj is not _missing:
History
Date User Action Args
2019-04-11 11:14:06xtreaksetrecipients: + xtreak, nedbat, michael.foord, pablogsal
2019-04-11 11:14:06xtreaksetmessageid: <1554981246.39.0.636440730525.issue36593@roundup.psfhosted.org>
2019-04-11 11:14:06xtreaklinkissue36593 messages
2019-04-11 11:14:05xtreakcreate