summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_descr.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_descr.py')
-rw-r--r--Lib/test/test_descr.py962
1 files changed, 762 insertions, 200 deletions
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 1a891a8..64a4a36 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -1,8 +1,11 @@
import builtins
+import copyreg
import gc
+import itertools
+import math
+import pickle
import sys
import types
-import math
import unittest
import weakref
@@ -18,7 +21,8 @@ class OperatorsTest(unittest.TestCase):
'add': '+',
'sub': '-',
'mul': '*',
- 'div': '/',
+ 'truediv': '/',
+ 'floordiv': '//',
'divmod': 'divmod',
'pow': '**',
'lshift': '<<',
@@ -49,8 +53,6 @@ class OperatorsTest(unittest.TestCase):
'invert': '~',
'int': 'int',
'float': 'float',
- 'oct': 'oct',
- 'hex': 'hex',
}
for name, expr in list(self.unops.items()):
@@ -79,12 +81,6 @@ class OperatorsTest(unittest.TestCase):
def binop_test(self, a, b, res, expr="a+b", meth="__add__"):
d = {'a': a, 'b': b}
- # XXX Hack so this passes before 2.3 when -Qnew is specified.
- if meth == "__div__" and 1/2 == 0.5:
- meth = "__truediv__"
-
- if meth == '__divmod__': pass
-
self.assertEqual(eval(expr, d), res)
t = type(a)
m = getattr(t, meth)
@@ -218,7 +214,7 @@ class OperatorsTest(unittest.TestCase):
def number_operators(self, a, b, skip=[]):
dict = {'a': a, 'b': b}
- for name, expr in list(self.binops.items()):
+ for name, expr in self.binops.items():
if name not in skip:
name = "__%s__" % name
if hasattr(a, name):
@@ -258,7 +254,7 @@ class OperatorsTest(unittest.TestCase):
# Testing complex operations...
self.number_operators(100.0j, 3.0j, skip=['lt', 'le', 'gt', 'ge',
'int', 'float',
- 'divmod', 'mod'])
+ 'floordiv', 'divmod', 'mod'])
class Number(complex):
__slots__ = ['prec']
@@ -1146,7 +1142,7 @@ order (MRO) for bases """
except (TypeError, UnicodeEncodeError):
pass
else:
- raise TestFailed("[chr(128)] slots not caught")
+ self.fail("[chr(128)] slots not caught")
# Test leaks
class Counted(object):
@@ -1797,6 +1793,7 @@ order (MRO) for bases """
("__trunc__", int, zero, set(), {}),
("__ceil__", math.ceil, zero, set(), {}),
("__dir__", dir, empty_seq, set(), {}),
+ ("__round__", round, zero, set(), {}),
]
class Checker(object):
@@ -2258,7 +2255,9 @@ order (MRO) for bases """
minstance = M("m")
minstance.b = 2
minstance.a = 1
- names = [x for x in dir(minstance) if x not in ["__name__", "__doc__"]]
+ default_attributes = ['__name__', '__doc__', '__package__',
+ '__loader__', '__spec__']
+ names = [x for x in dir(minstance) if x not in default_attributes]
self.assertEqual(names, ['a', 'b'])
class M2(M):
@@ -3151,176 +3150,6 @@ order (MRO) for bases """
self.assertEqual(e.a, 1)
self.assertEqual(can_delete_dict(e), can_delete_dict(ValueError()))
- def test_pickles(self):
- # Testing pickling and copying new-style classes and objects...
- import pickle
-
- def sorteditems(d):
- L = list(d.items())
- L.sort()
- return L
-
- global C
- class C(object):
- def __init__(self, a, b):
- super(C, self).__init__()
- self.a = a
- self.b = b
- def __repr__(self):
- return "C(%r, %r)" % (self.a, self.b)
-
- global C1
- class C1(list):
- def __new__(cls, a, b):
- return super(C1, cls).__new__(cls)
- def __getnewargs__(self):
- return (self.a, self.b)
- def __init__(self, a, b):
- self.a = a
- self.b = b
- def __repr__(self):
- return "C1(%r, %r)<%r>" % (self.a, self.b, list(self))
-
- global C2
- class C2(int):
- def __new__(cls, a, b, val=0):
- return super(C2, cls).__new__(cls, val)
- def __getnewargs__(self):
- return (self.a, self.b, int(self))
- def __init__(self, a, b, val=0):
- self.a = a
- self.b = b
- def __repr__(self):
- return "C2(%r, %r)<%r>" % (self.a, self.b, int(self))
-
- global C3
- class C3(object):
- def __init__(self, foo):
- self.foo = foo
- def __getstate__(self):
- return self.foo
- def __setstate__(self, foo):
- self.foo = foo
-
- global C4classic, C4
- class C4classic: # classic
- pass
- class C4(C4classic, object): # mixed inheritance
- pass
-
- for bin in 0, 1:
- for cls in C, C1, C2:
- s = pickle.dumps(cls, bin)
- cls2 = pickle.loads(s)
- self.assertIs(cls2, cls)
-
- a = C1(1, 2); a.append(42); a.append(24)
- b = C2("hello", "world", 42)
- s = pickle.dumps((a, b), bin)
- x, y = pickle.loads(s)
- self.assertEqual(x.__class__, a.__class__)
- self.assertEqual(sorteditems(x.__dict__), sorteditems(a.__dict__))
- self.assertEqual(y.__class__, b.__class__)
- self.assertEqual(sorteditems(y.__dict__), sorteditems(b.__dict__))
- self.assertEqual(repr(x), repr(a))
- self.assertEqual(repr(y), repr(b))
- # Test for __getstate__ and __setstate__ on new style class
- u = C3(42)
- s = pickle.dumps(u, bin)
- v = pickle.loads(s)
- self.assertEqual(u.__class__, v.__class__)
- self.assertEqual(u.foo, v.foo)
- # Test for picklability of hybrid class
- u = C4()
- u.foo = 42
- s = pickle.dumps(u, bin)
- v = pickle.loads(s)
- self.assertEqual(u.__class__, v.__class__)
- self.assertEqual(u.foo, v.foo)
-
- # Testing copy.deepcopy()
- import copy
- for cls in C, C1, C2:
- cls2 = copy.deepcopy(cls)
- self.assertIs(cls2, cls)
-
- a = C1(1, 2); a.append(42); a.append(24)
- b = C2("hello", "world", 42)
- x, y = copy.deepcopy((a, b))
- self.assertEqual(x.__class__, a.__class__)
- self.assertEqual(sorteditems(x.__dict__), sorteditems(a.__dict__))
- self.assertEqual(y.__class__, b.__class__)
- self.assertEqual(sorteditems(y.__dict__), sorteditems(b.__dict__))
- self.assertEqual(repr(x), repr(a))
- self.assertEqual(repr(y), repr(b))
-
- def test_pickle_slots(self):
- # Testing pickling of classes with __slots__ ...
- import pickle
- # Pickling of classes with __slots__ but without __getstate__ should fail
- # (if using protocol 0 or 1)
- global B, C, D, E
- class B(object):
- pass
- for base in [object, B]:
- class C(base):
- __slots__ = ['a']
- class D(C):
- pass
- try:
- pickle.dumps(C(), 0)
- except TypeError:
- pass
- else:
- self.fail("should fail: pickle C instance - %s" % base)
- try:
- pickle.dumps(C(), 0)
- except TypeError:
- pass
- else:
- self.fail("should fail: pickle D instance - %s" % base)
- # Give C a nice generic __getstate__ and __setstate__
- class C(base):
- __slots__ = ['a']
- def __getstate__(self):
- try:
- d = self.__dict__.copy()
- except AttributeError:
- d = {}
- for cls in self.__class__.__mro__:
- for sn in cls.__dict__.get('__slots__', ()):
- try:
- d[sn] = getattr(self, sn)
- except AttributeError:
- pass
- return d
- def __setstate__(self, d):
- for k, v in list(d.items()):
- setattr(self, k, v)
- class D(C):
- pass
- # Now it should work
- x = C()
- y = pickle.loads(pickle.dumps(x))
- self.assertNotHasAttr(y, 'a')
- x.a = 42
- y = pickle.loads(pickle.dumps(x))
- self.assertEqual(y.a, 42)
- x = D()
- x.a = 42
- x.b = 100
- y = pickle.loads(pickle.dumps(x))
- self.assertEqual(y.a + y.b, 142)
- # A subclass that adds a slot should also work
- class E(C):
- __slots__ = ['b']
- x = E()
- x.a = 42
- x.b = "foo"
- y = pickle.loads(pickle.dumps(x))
- self.assertEqual(y.a, x.a)
- self.assertEqual(y.b, x.b)
-
def test_binary_operator_override(self):
# Testing overrides of binary operations...
class I(int):
@@ -3745,18 +3574,8 @@ order (MRO) for bases """
# bug).
del c
- # If that didn't blow up, it's also interesting to see whether clearing
- # the last container slot works: that will attempt to delete c again,
- # which will cause c to get appended back to the container again
- # "during" the del. (On non-CPython implementations, however, __del__
- # is typically not called again.)
support.gc_collect()
self.assertEqual(len(C.container), 1)
- del C.container[-1]
- if support.check_impl_detail():
- support.gc_collect()
- self.assertEqual(len(C.container), 1)
- self.assertEqual(C.container[-1].attr, 42)
# Make c mortal again, so that the test framework with -l doesn't report
# it as a leak.
@@ -3916,6 +3735,37 @@ order (MRO) for bases """
else:
assert 0, "best_base calculation found wanting"
+ def test_unsubclassable_types(self):
+ with self.assertRaises(TypeError):
+ class X(type(None)):
+ pass
+ with self.assertRaises(TypeError):
+ class X(object, type(None)):
+ pass
+ with self.assertRaises(TypeError):
+ class X(type(None), object):
+ pass
+ class O(object):
+ pass
+ with self.assertRaises(TypeError):
+ class X(O, type(None)):
+ pass
+ with self.assertRaises(TypeError):
+ class X(type(None), O):
+ pass
+
+ class X(object):
+ pass
+ with self.assertRaises(TypeError):
+ X.__bases__ = type(None),
+ with self.assertRaises(TypeError):
+ X.__bases__ = object, type(None)
+ with self.assertRaises(TypeError):
+ X.__bases__ = type(None), object
+ with self.assertRaises(TypeError):
+ X.__bases__ = O, type(None)
+ with self.assertRaises(TypeError):
+ X.__bases__ = type(None), O
def test_mutable_bases_with_failing_mro(self):
# Testing mutable bases with failing mro...
@@ -4334,9 +4184,8 @@ order (MRO) for bases """
('__add__', 'x + y', 'x += y'),
('__sub__', 'x - y', 'x -= y'),
('__mul__', 'x * y', 'x *= y'),
- ('__truediv__', 'operator.truediv(x, y)', None),
- ('__floordiv__', 'operator.floordiv(x, y)', None),
- ('__div__', 'x / y', 'x /= y'),
+ ('__truediv__', 'x / y', 'x /= y'),
+ ('__floordiv__', 'x // y', 'x //= y'),
('__mod__', 'x % y', 'x %= y'),
('__divmod__', 'divmod(x, y)', None),
('__pow__', 'x ** y', 'x **= y'),
@@ -4398,8 +4247,8 @@ order (MRO) for bases """
# Also check type_getattro for correctness.
class Meta(type):
pass
- class X(object):
- __metaclass__ = Meta
+ class X(metaclass=Meta):
+ pass
X.a = 42
Meta.a = Descr("a")
self.assertEqual(X.a, 42)
@@ -4536,6 +4385,13 @@ order (MRO) for bases """
self.assertRaises(TypeError, type.__dict__['__qualname__'].__set__,
str, 'Oink')
+ global Y
+ class Y:
+ class Inside:
+ pass
+ self.assertEqual(Y.__qualname__, 'Y')
+ self.assertEqual(Y.Inside.__qualname__, 'Y.Inside')
+
def test_qualname_dict(self):
ns = {'__qualname__': 'some.name'}
tp = type('Foo', (), ns)
@@ -4581,6 +4437,14 @@ order (MRO) for bases """
self.assertRaises(TypeError, case, 1, 2, 3)
self.assertRaises(TypeError, case, 1, 2, foo=3)
+ def test_subclassing_does_not_duplicate_dict_descriptors(self):
+ class Base:
+ pass
+ class Sub(Base):
+ pass
+ self.assertIn("__dict__", Base.__dict__)
+ self.assertNotIn("__dict__", Sub.__dict__)
+
class DictProxyTests(unittest.TestCase):
def setUp(self):
@@ -4691,11 +4555,709 @@ class MiscTests(unittest.TestCase):
self.assertEqual(X.mykey2, 'from Base2')
+class PicklingTests(unittest.TestCase):
+
+ def _check_reduce(self, proto, obj, args=(), kwargs={}, state=None,
+ listitems=None, dictitems=None):
+ if proto >= 4:
+ reduce_value = obj.__reduce_ex__(proto)
+ self.assertEqual(reduce_value[:3],
+ (copyreg.__newobj_ex__,
+ (type(obj), args, kwargs),
+ state))
+ if listitems is not None:
+ self.assertListEqual(list(reduce_value[3]), listitems)
+ else:
+ self.assertIsNone(reduce_value[3])
+ if dictitems is not None:
+ self.assertDictEqual(dict(reduce_value[4]), dictitems)
+ else:
+ self.assertIsNone(reduce_value[4])
+ elif proto >= 2:
+ reduce_value = obj.__reduce_ex__(proto)
+ self.assertEqual(reduce_value[:3],
+ (copyreg.__newobj__,
+ (type(obj),) + args,
+ state))
+ if listitems is not None:
+ self.assertListEqual(list(reduce_value[3]), listitems)
+ else:
+ self.assertIsNone(reduce_value[3])
+ if dictitems is not None:
+ self.assertDictEqual(dict(reduce_value[4]), dictitems)
+ else:
+ self.assertIsNone(reduce_value[4])
+ else:
+ base_type = type(obj).__base__
+ reduce_value = (copyreg._reconstructor,
+ (type(obj),
+ base_type,
+ None if base_type is object else base_type(obj)))
+ if state is not None:
+ reduce_value += (state,)
+ self.assertEqual(obj.__reduce_ex__(proto), reduce_value)
+ self.assertEqual(obj.__reduce__(), reduce_value)
+
+ def test_reduce(self):
+ protocols = range(pickle.HIGHEST_PROTOCOL + 1)
+ args = (-101, "spam")
+ kwargs = {'bacon': -201, 'fish': -301}
+ state = {'cheese': -401}
+
+ class C1:
+ def __getnewargs__(self):
+ return args
+ obj = C1()
+ for proto in protocols:
+ self._check_reduce(proto, obj, args)
+
+ for name, value in state.items():
+ setattr(obj, name, value)
+ for proto in protocols:
+ self._check_reduce(proto, obj, args, state=state)
+
+ class C2:
+ def __getnewargs__(self):
+ return "bad args"
+ obj = C2()
+ for proto in protocols:
+ if proto >= 2:
+ with self.assertRaises(TypeError):
+ obj.__reduce_ex__(proto)
+
+ class C3:
+ def __getnewargs_ex__(self):
+ return (args, kwargs)
+ obj = C3()
+ for proto in protocols:
+ if proto >= 4:
+ self._check_reduce(proto, obj, args, kwargs)
+ elif proto >= 2:
+ with self.assertRaises(ValueError):
+ obj.__reduce_ex__(proto)
+
+ class C4:
+ def __getnewargs_ex__(self):
+ return (args, "bad dict")
+ class C5:
+ def __getnewargs_ex__(self):
+ return ("bad tuple", kwargs)
+ class C6:
+ def __getnewargs_ex__(self):
+ return ()
+ class C7:
+ def __getnewargs_ex__(self):
+ return "bad args"
+ for proto in protocols:
+ for cls in C4, C5, C6, C7:
+ obj = cls()
+ if proto >= 2:
+ with self.assertRaises((TypeError, ValueError)):
+ obj.__reduce_ex__(proto)
+
+ class C9:
+ def __getnewargs_ex__(self):
+ return (args, {})
+ obj = C9()
+ for proto in protocols:
+ self._check_reduce(proto, obj, args)
+
+ class C10:
+ def __getnewargs_ex__(self):
+ raise IndexError
+ obj = C10()
+ for proto in protocols:
+ if proto >= 2:
+ with self.assertRaises(IndexError):
+ obj.__reduce_ex__(proto)
+
+ class C11:
+ def __getstate__(self):
+ return state
+ obj = C11()
+ for proto in protocols:
+ self._check_reduce(proto, obj, state=state)
+
+ class C12:
+ def __getstate__(self):
+ return "not dict"
+ obj = C12()
+ for proto in protocols:
+ self._check_reduce(proto, obj, state="not dict")
+
+ class C13:
+ def __getstate__(self):
+ raise IndexError
+ obj = C13()
+ for proto in protocols:
+ with self.assertRaises(IndexError):
+ obj.__reduce_ex__(proto)
+ if proto < 2:
+ with self.assertRaises(IndexError):
+ obj.__reduce__()
+
+ class C14:
+ __slots__ = tuple(state)
+ def __init__(self):
+ for name, value in state.items():
+ setattr(self, name, value)
+
+ obj = C14()
+ for proto in protocols:
+ if proto >= 2:
+ self._check_reduce(proto, obj, state=(None, state))
+ else:
+ with self.assertRaises(TypeError):
+ obj.__reduce_ex__(proto)
+ with self.assertRaises(TypeError):
+ obj.__reduce__()
+
+ class C15(dict):
+ pass
+ obj = C15({"quebec": -601})
+ for proto in protocols:
+ self._check_reduce(proto, obj, dictitems=dict(obj))
+
+ class C16(list):
+ pass
+ obj = C16(["yukon"])
+ for proto in protocols:
+ self._check_reduce(proto, obj, listitems=list(obj))
+
+ def test_special_method_lookup(self):
+ protocols = range(pickle.HIGHEST_PROTOCOL + 1)
+ class Picky:
+ def __getstate__(self):
+ return {}
+
+ def __getattr__(self, attr):
+ if attr in ("__getnewargs__", "__getnewargs_ex__"):
+ raise AssertionError(attr)
+ return None
+ for protocol in protocols:
+ state = {} if protocol >= 2 else None
+ self._check_reduce(protocol, Picky(), state=state)
+
+ def _assert_is_copy(self, obj, objcopy, msg=None):
+ """Utility method to verify if two objects are copies of each others.
+ """
+ if msg is None:
+ msg = "{!r} is not a copy of {!r}".format(obj, objcopy)
+ if type(obj).__repr__ is object.__repr__:
+ # We have this limitation for now because we use the object's repr
+ # to help us verify that the two objects are copies. This allows
+ # us to delegate the non-generic verification logic to the objects
+ # themselves.
+ raise ValueError("object passed to _assert_is_copy must " +
+ "override the __repr__ method.")
+ self.assertIsNot(obj, objcopy, msg=msg)
+ self.assertIs(type(obj), type(objcopy), msg=msg)
+ if hasattr(obj, '__dict__'):
+ self.assertDictEqual(obj.__dict__, objcopy.__dict__, msg=msg)
+ self.assertIsNot(obj.__dict__, objcopy.__dict__, msg=msg)
+ if hasattr(obj, '__slots__'):
+ self.assertListEqual(obj.__slots__, objcopy.__slots__, msg=msg)
+ for slot in obj.__slots__:
+ self.assertEqual(
+ hasattr(obj, slot), hasattr(objcopy, slot), msg=msg)
+ self.assertEqual(getattr(obj, slot, None),
+ getattr(objcopy, slot, None), msg=msg)
+ self.assertEqual(repr(obj), repr(objcopy), msg=msg)
+
+ @staticmethod
+ def _generate_pickle_copiers():
+ """Utility method to generate the many possible pickle configurations.
+ """
+ class PickleCopier:
+ "This class copies object using pickle."
+ def __init__(self, proto, dumps, loads):
+ self.proto = proto
+ self.dumps = dumps
+ self.loads = loads
+ def copy(self, obj):
+ return self.loads(self.dumps(obj, self.proto))
+ def __repr__(self):
+ # We try to be as descriptive as possible here since this is
+ # the string which we will allow us to tell the pickle
+ # configuration we are using during debugging.
+ return ("PickleCopier(proto={}, dumps={}.{}, loads={}.{})"
+ .format(self.proto,
+ self.dumps.__module__, self.dumps.__qualname__,
+ self.loads.__module__, self.loads.__qualname__))
+ return (PickleCopier(*args) for args in
+ itertools.product(range(pickle.HIGHEST_PROTOCOL + 1),
+ {pickle.dumps, pickle._dumps},
+ {pickle.loads, pickle._loads}))
+
+ def test_pickle_slots(self):
+ # Tests pickling of classes with __slots__.
+
+ # Pickling of classes with __slots__ but without __getstate__ should
+ # fail (if using protocol 0 or 1)
+ global C
+ class C:
+ __slots__ = ['a']
+ with self.assertRaises(TypeError):
+ pickle.dumps(C(), 0)
+
+ global D
+ class D(C):
+ pass
+ with self.assertRaises(TypeError):
+ pickle.dumps(D(), 0)
+
+ class C:
+ "A class with __getstate__ and __setstate__ implemented."
+ __slots__ = ['a']
+ def __getstate__(self):
+ state = getattr(self, '__dict__', {}).copy()
+ for cls in type(self).__mro__:
+ for slot in cls.__dict__.get('__slots__', ()):
+ try:
+ state[slot] = getattr(self, slot)
+ except AttributeError:
+ pass
+ return state
+ def __setstate__(self, state):
+ for k, v in state.items():
+ setattr(self, k, v)
+ def __repr__(self):
+ return "%s()<%r>" % (type(self).__name__, self.__getstate__())
+
+ class D(C):
+ "A subclass of a class with slots."
+ pass
+
+ global E
+ class E(C):
+ "A subclass with an extra slot."
+ __slots__ = ['b']
+
+ # Now it should work
+ for pickle_copier in self._generate_pickle_copiers():
+ with self.subTest(pickle_copier=pickle_copier):
+ x = C()
+ y = pickle_copier.copy(x)
+ self._assert_is_copy(x, y)
+
+ x.a = 42
+ y = pickle_copier.copy(x)
+ self._assert_is_copy(x, y)
+
+ x = D()
+ x.a = 42
+ x.b = 100
+ y = pickle_copier.copy(x)
+ self._assert_is_copy(x, y)
+
+ x = E()
+ x.a = 42
+ x.b = "foo"
+ y = pickle_copier.copy(x)
+ self._assert_is_copy(x, y)
+
+ def test_reduce_copying(self):
+ # Tests pickling and copying new-style classes and objects.
+ global C1
+ class C1:
+ "The state of this class is copyable via its instance dict."
+ ARGS = (1, 2)
+ NEED_DICT_COPYING = True
+ def __init__(self, a, b):
+ super().__init__()
+ self.a = a
+ self.b = b
+ def __repr__(self):
+ return "C1(%r, %r)" % (self.a, self.b)
+
+ global C2
+ class C2(list):
+ "A list subclass copyable via __getnewargs__."
+ ARGS = (1, 2)
+ NEED_DICT_COPYING = False
+ def __new__(cls, a, b):
+ self = super().__new__(cls)
+ self.a = a
+ self.b = b
+ return self
+ def __init__(self, *args):
+ super().__init__()
+ # This helps testing that __init__ is not called during the
+ # unpickling process, which would cause extra appends.
+ self.append("cheese")
+ @classmethod
+ def __getnewargs__(cls):
+ return cls.ARGS
+ def __repr__(self):
+ return "C2(%r, %r)<%r>" % (self.a, self.b, list(self))
+
+ global C3
+ class C3(list):
+ "A list subclass copyable via __getstate__."
+ ARGS = (1, 2)
+ NEED_DICT_COPYING = False
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+ # This helps testing that __init__ is not called during the
+ # unpickling process, which would cause extra appends.
+ self.append("cheese")
+ @classmethod
+ def __getstate__(cls):
+ return cls.ARGS
+ def __setstate__(self, state):
+ a, b = state
+ self.a = a
+ self.b = b
+ def __repr__(self):
+ return "C3(%r, %r)<%r>" % (self.a, self.b, list(self))
+
+ global C4
+ class C4(int):
+ "An int subclass copyable via __getnewargs__."
+ ARGS = ("hello", "world", 1)
+ NEED_DICT_COPYING = False
+ def __new__(cls, a, b, value):
+ self = super().__new__(cls, value)
+ self.a = a
+ self.b = b
+ return self
+ @classmethod
+ def __getnewargs__(cls):
+ return cls.ARGS
+ def __repr__(self):
+ return "C4(%r, %r)<%r>" % (self.a, self.b, int(self))
+
+ global C5
+ class C5(int):
+ "An int subclass copyable via __getnewargs_ex__."
+ ARGS = (1, 2)
+ KWARGS = {'value': 3}
+ NEED_DICT_COPYING = False
+ def __new__(cls, a, b, *, value=0):
+ self = super().__new__(cls, value)
+ self.a = a
+ self.b = b
+ return self
+ @classmethod
+ def __getnewargs_ex__(cls):
+ return (cls.ARGS, cls.KWARGS)
+ def __repr__(self):
+ return "C5(%r, %r)<%r>" % (self.a, self.b, int(self))
+
+ test_classes = (C1, C2, C3, C4, C5)
+ # Testing copying through pickle
+ pickle_copiers = self._generate_pickle_copiers()
+ for cls, pickle_copier in itertools.product(test_classes, pickle_copiers):
+ with self.subTest(cls=cls, pickle_copier=pickle_copier):
+ kwargs = getattr(cls, 'KWARGS', {})
+ obj = cls(*cls.ARGS, **kwargs)
+ proto = pickle_copier.proto
+ if 2 <= proto < 4 and hasattr(cls, '__getnewargs_ex__'):
+ with self.assertRaises(ValueError):
+ pickle_copier.dumps(obj, proto)
+ continue
+ objcopy = pickle_copier.copy(obj)
+ self._assert_is_copy(obj, objcopy)
+ # For test classes that supports this, make sure we didn't go
+ # around the reduce protocol by simply copying the attribute
+ # dictionary. We clear attributes using the previous copy to
+ # not mutate the original argument.
+ if proto >= 2 and not cls.NEED_DICT_COPYING:
+ objcopy.__dict__.clear()
+ objcopy2 = pickle_copier.copy(objcopy)
+ self._assert_is_copy(obj, objcopy2)
+
+ # Testing copying through copy.deepcopy()
+ for cls in test_classes:
+ with self.subTest(cls=cls):
+ kwargs = getattr(cls, 'KWARGS', {})
+ obj = cls(*cls.ARGS, **kwargs)
+ # XXX: We need to modify the copy module to support PEP 3154's
+ # reduce protocol 4.
+ if hasattr(cls, '__getnewargs_ex__'):
+ continue
+ objcopy = deepcopy(obj)
+ self._assert_is_copy(obj, objcopy)
+ # For test classes that supports this, make sure we didn't go
+ # around the reduce protocol by simply copying the attribute
+ # dictionary. We clear attributes using the previous copy to
+ # not mutate the original argument.
+ if not cls.NEED_DICT_COPYING:
+ objcopy.__dict__.clear()
+ objcopy2 = deepcopy(objcopy)
+ self._assert_is_copy(obj, objcopy2)
+
+ def test_issue24097(self):
+ # Slot name is freed inside __getattr__ and is later used.
+ class S(str): # Not interned
+ pass
+ class A:
+ __slotnames__ = [S('spam')]
+ def __getattr__(self, attr):
+ if attr == 'spam':
+ A.__slotnames__[:] = [S('spam')]
+ return 42
+ else:
+ raise AttributeError
+
+ import copyreg
+ expected = (copyreg.__newobj__, (A,), (None, {'spam': 42}), None, None)
+ self.assertEqual(A().__reduce__(2), expected) # Shouldn't crash
+
+
+class SharedKeyTests(unittest.TestCase):
+
+ @support.cpython_only
+ def test_subclasses(self):
+ # Verify that subclasses can share keys (per PEP 412)
+ class A:
+ pass
+ class B(A):
+ pass
+
+ a, b = A(), B()
+ self.assertEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(b)))
+ self.assertLess(sys.getsizeof(vars(a)), sys.getsizeof({}))
+ a.x, a.y, a.z, a.w = range(4)
+ self.assertNotEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(b)))
+ a2 = A()
+ self.assertEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(a2)))
+ self.assertLess(sys.getsizeof(vars(a)), sys.getsizeof({}))
+ b.u, b.v, b.w, b.t = range(4)
+ self.assertLess(sys.getsizeof(vars(b)), sys.getsizeof({}))
+
+
+class DebugHelperMeta(type):
+ """
+ Sets default __doc__ and simplifies repr() output.
+ """
+ def __new__(mcls, name, bases, attrs):
+ if attrs.get('__doc__') is None:
+ attrs['__doc__'] = name # helps when debugging with gdb
+ return type.__new__(mcls, name, bases, attrs)
+ def __repr__(cls):
+ return repr(cls.__name__)
+
+
+class MroTest(unittest.TestCase):
+ """
+ Regressions for some bugs revealed through
+ mcsl.mro() customization (typeobject.c: mro_internal()) and
+ cls.__bases__ assignment (typeobject.c: type_set_bases()).
+ """
+
+ def setUp(self):
+ self.step = 0
+ self.ready = False
+
+ def step_until(self, limit):
+ ret = (self.step < limit)
+ if ret:
+ self.step += 1
+ return ret
+
+ def test_incomplete_set_bases_on_self(self):
+ """
+ type_set_bases must be aware that type->tp_mro can be NULL.
+ """
+ class M(DebugHelperMeta):
+ def mro(cls):
+ if self.step_until(1):
+ assert cls.__mro__ is None
+ cls.__bases__ += ()
+
+ return type.mro(cls)
+
+ class A(metaclass=M):
+ pass
+
+ def test_reent_set_bases_on_base(self):
+ """
+ Deep reentrancy must not over-decref old_mro.
+ """
+ class M(DebugHelperMeta):
+ def mro(cls):
+ if cls.__mro__ is not None and cls.__name__ == 'B':
+ # 4-5 steps are usually enough to make it crash somewhere
+ if self.step_until(10):
+ A.__bases__ += ()
+
+ return type.mro(cls)
+
+ class A(metaclass=M):
+ pass
+ class B(A):
+ pass
+ B.__bases__ += ()
+
+ def test_reent_set_bases_on_direct_base(self):
+ """
+ Similar to test_reent_set_bases_on_base, but may crash differently.
+ """
+ class M(DebugHelperMeta):
+ def mro(cls):
+ base = cls.__bases__[0]
+ if base is not object:
+ if self.step_until(5):
+ base.__bases__ += ()
+
+ return type.mro(cls)
+
+ class A(metaclass=M):
+ pass
+ class B(A):
+ pass
+ class C(B):
+ pass
+
+ def test_reent_set_bases_tp_base_cycle(self):
+ """
+ type_set_bases must check for an inheritance cycle not only through
+ MRO of the type, which may be not yet updated in case of reentrance,
+ but also through tp_base chain, which is assigned before diving into
+ inner calls to mro().
+
+ Otherwise, the following snippet can loop forever:
+ do {
+ // ...
+ type = type->tp_base;
+ } while (type != NULL);
+
+ Functions that rely on tp_base (like solid_base and PyType_IsSubtype)
+ would not be happy in that case, causing a stack overflow.
+ """
+ class M(DebugHelperMeta):
+ def mro(cls):
+ if self.ready:
+ if cls.__name__ == 'B1':
+ B2.__bases__ = (B1,)
+ if cls.__name__ == 'B2':
+ B1.__bases__ = (B2,)
+ return type.mro(cls)
+
+ class A(metaclass=M):
+ pass
+ class B1(A):
+ pass
+ class B2(A):
+ pass
+
+ self.ready = True
+ with self.assertRaises(TypeError):
+ B1.__bases__ += ()
+
+ def test_tp_subclasses_cycle_in_update_slots(self):
+ """
+ type_set_bases must check for reentrancy upon finishing its job
+ by updating tp_subclasses of old/new bases of the type.
+ Otherwise, an implicit inheritance cycle through tp_subclasses
+ can break functions that recurse on elements of that field
+ (like recurse_down_subclasses and mro_hierarchy) eventually
+ leading to a stack overflow.
+ """
+ class M(DebugHelperMeta):
+ def mro(cls):
+ if self.ready and cls.__name__ == 'C':
+ self.ready = False
+ C.__bases__ = (B2,)
+ return type.mro(cls)
+
+ class A(metaclass=M):
+ pass
+ class B1(A):
+ pass
+ class B2(A):
+ pass
+ class C(A):
+ pass
+
+ self.ready = True
+ C.__bases__ = (B1,)
+ B1.__bases__ = (C,)
+
+ self.assertEqual(C.__bases__, (B2,))
+ self.assertEqual(B2.__subclasses__(), [C])
+ self.assertEqual(B1.__subclasses__(), [])
+
+ self.assertEqual(B1.__bases__, (C,))
+ self.assertEqual(C.__subclasses__(), [B1])
+
+ def test_tp_subclasses_cycle_error_return_path(self):
+ """
+ The same as test_tp_subclasses_cycle_in_update_slots, but tests
+ a code path executed on error (goto bail).
+ """
+ class E(Exception):
+ pass
+ class M(DebugHelperMeta):
+ def mro(cls):
+ if self.ready and cls.__name__ == 'C':
+ if C.__bases__ == (B2,):
+ self.ready = False
+ else:
+ C.__bases__ = (B2,)
+ raise E
+ return type.mro(cls)
+
+ class A(metaclass=M):
+ pass
+ class B1(A):
+ pass
+ class B2(A):
+ pass
+ class C(A):
+ pass
+
+ self.ready = True
+ with self.assertRaises(E):
+ C.__bases__ = (B1,)
+ B1.__bases__ = (C,)
+
+ self.assertEqual(C.__bases__, (B2,))
+ self.assertEqual(C.__mro__, tuple(type.mro(C)))
+
+ def test_incomplete_extend(self):
+ """
+ Extending an unitialized type with type->tp_mro == NULL must
+ throw a reasonable TypeError exception, instead of failing
+ with PyErr_BadInternalCall.
+ """
+ class M(DebugHelperMeta):
+ def mro(cls):
+ if cls.__mro__ is None and cls.__name__ != 'X':
+ with self.assertRaises(TypeError):
+ class X(cls):
+ pass
+
+ return type.mro(cls)
+
+ class A(metaclass=M):
+ pass
+
+ def test_incomplete_super(self):
+ """
+ Attrubute lookup on a super object must be aware that
+ its target type can be uninitialized (type->tp_mro == NULL).
+ """
+ class M(DebugHelperMeta):
+ def mro(cls):
+ if cls.__mro__ is None:
+ with self.assertRaises(AttributeError):
+ super(cls, cls).xxx
+
+ return type.mro(cls)
+
+ class A(metaclass=M):
+ pass
+
+
def test_main():
# Run all local test cases, with PTypesLongInitTest first.
support.run_unittest(PTypesLongInitTest, OperatorsTest,
ClassPropertiesAndMethods, DictProxyTests,
- MiscTests)
+ MiscTests, PicklingTests, SharedKeyTests,
+ MroTest)
if __name__ == "__main__":
test_main()