mirror of https://github.com/python/cpython
969 lines
26 KiB
Python
969 lines
26 KiB
Python
"Test the functionality of Python classes implementing operators."
|
|
|
|
import unittest
|
|
from test.support import cpython_only, import_helper, script_helper
|
|
|
|
testmeths = [
|
|
|
|
# Binary operations
|
|
"add",
|
|
"radd",
|
|
"sub",
|
|
"rsub",
|
|
"mul",
|
|
"rmul",
|
|
"matmul",
|
|
"rmatmul",
|
|
"truediv",
|
|
"rtruediv",
|
|
"floordiv",
|
|
"rfloordiv",
|
|
"mod",
|
|
"rmod",
|
|
"divmod",
|
|
"rdivmod",
|
|
"pow",
|
|
"rpow",
|
|
"rshift",
|
|
"rrshift",
|
|
"lshift",
|
|
"rlshift",
|
|
"and",
|
|
"rand",
|
|
"or",
|
|
"ror",
|
|
"xor",
|
|
"rxor",
|
|
|
|
# List/dict operations
|
|
"contains",
|
|
"getitem",
|
|
"setitem",
|
|
"delitem",
|
|
|
|
# Unary operations
|
|
"neg",
|
|
"pos",
|
|
"abs",
|
|
|
|
# generic operations
|
|
"init",
|
|
]
|
|
|
|
# These need to return something other than None
|
|
# "hash",
|
|
# "str",
|
|
# "repr",
|
|
# "int",
|
|
# "float",
|
|
|
|
# These are separate because they can influence the test of other methods.
|
|
# "getattr",
|
|
# "setattr",
|
|
# "delattr",
|
|
|
|
callLst = []
|
|
def trackCall(f):
|
|
def track(*args, **kwargs):
|
|
callLst.append((f.__name__, args))
|
|
return f(*args, **kwargs)
|
|
return track
|
|
|
|
statictests = """
|
|
@trackCall
|
|
def __hash__(self, *args):
|
|
return hash(id(self))
|
|
|
|
@trackCall
|
|
def __str__(self, *args):
|
|
return "AllTests"
|
|
|
|
@trackCall
|
|
def __repr__(self, *args):
|
|
return "AllTests"
|
|
|
|
@trackCall
|
|
def __int__(self, *args):
|
|
return 1
|
|
|
|
@trackCall
|
|
def __index__(self, *args):
|
|
return 1
|
|
|
|
@trackCall
|
|
def __float__(self, *args):
|
|
return 1.0
|
|
|
|
@trackCall
|
|
def __eq__(self, *args):
|
|
return True
|
|
|
|
@trackCall
|
|
def __ne__(self, *args):
|
|
return False
|
|
|
|
@trackCall
|
|
def __lt__(self, *args):
|
|
return False
|
|
|
|
@trackCall
|
|
def __le__(self, *args):
|
|
return True
|
|
|
|
@trackCall
|
|
def __gt__(self, *args):
|
|
return False
|
|
|
|
@trackCall
|
|
def __ge__(self, *args):
|
|
return True
|
|
"""
|
|
|
|
# Synthesize all the other AllTests methods from the names in testmeths.
|
|
|
|
method_template = """\
|
|
@trackCall
|
|
def __%s__(self, *args):
|
|
pass
|
|
"""
|
|
|
|
d = {}
|
|
exec(statictests, globals(), d)
|
|
for method in testmeths:
|
|
exec(method_template % method, globals(), d)
|
|
AllTests = type("AllTests", (object,), d)
|
|
del d, statictests, method, method_template
|
|
|
|
class ClassTests(unittest.TestCase):
|
|
def setUp(self):
|
|
callLst[:] = []
|
|
|
|
def assertCallStack(self, expected_calls):
|
|
actualCallList = callLst[:] # need to copy because the comparison below will add
|
|
# additional calls to callLst
|
|
if expected_calls != actualCallList:
|
|
self.fail("Expected call list:\n %s\ndoes not match actual call list\n %s" %
|
|
(expected_calls, actualCallList))
|
|
|
|
def testInit(self):
|
|
foo = AllTests()
|
|
self.assertCallStack([("__init__", (foo,))])
|
|
|
|
def testBinaryOps(self):
|
|
testme = AllTests()
|
|
# Binary operations
|
|
|
|
callLst[:] = []
|
|
testme + 1
|
|
self.assertCallStack([("__add__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 + testme
|
|
self.assertCallStack([("__radd__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme - 1
|
|
self.assertCallStack([("__sub__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 - testme
|
|
self.assertCallStack([("__rsub__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme * 1
|
|
self.assertCallStack([("__mul__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 * testme
|
|
self.assertCallStack([("__rmul__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme @ 1
|
|
self.assertCallStack([("__matmul__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 @ testme
|
|
self.assertCallStack([("__rmatmul__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme / 1
|
|
self.assertCallStack([("__truediv__", (testme, 1))])
|
|
|
|
|
|
callLst[:] = []
|
|
1 / testme
|
|
self.assertCallStack([("__rtruediv__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme // 1
|
|
self.assertCallStack([("__floordiv__", (testme, 1))])
|
|
|
|
|
|
callLst[:] = []
|
|
1 // testme
|
|
self.assertCallStack([("__rfloordiv__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme % 1
|
|
self.assertCallStack([("__mod__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 % testme
|
|
self.assertCallStack([("__rmod__", (testme, 1))])
|
|
|
|
|
|
callLst[:] = []
|
|
divmod(testme,1)
|
|
self.assertCallStack([("__divmod__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
divmod(1, testme)
|
|
self.assertCallStack([("__rdivmod__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme ** 1
|
|
self.assertCallStack([("__pow__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 ** testme
|
|
self.assertCallStack([("__rpow__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme >> 1
|
|
self.assertCallStack([("__rshift__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 >> testme
|
|
self.assertCallStack([("__rrshift__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme << 1
|
|
self.assertCallStack([("__lshift__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 << testme
|
|
self.assertCallStack([("__rlshift__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme & 1
|
|
self.assertCallStack([("__and__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 & testme
|
|
self.assertCallStack([("__rand__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme | 1
|
|
self.assertCallStack([("__or__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 | testme
|
|
self.assertCallStack([("__ror__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme ^ 1
|
|
self.assertCallStack([("__xor__", (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 ^ testme
|
|
self.assertCallStack([("__rxor__", (testme, 1))])
|
|
|
|
def testListAndDictOps(self):
|
|
testme = AllTests()
|
|
|
|
# List/dict operations
|
|
|
|
class Empty: pass
|
|
|
|
try:
|
|
1 in Empty()
|
|
self.fail('failed, should have raised TypeError')
|
|
except TypeError:
|
|
pass
|
|
|
|
callLst[:] = []
|
|
1 in testme
|
|
self.assertCallStack([('__contains__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme[1]
|
|
self.assertCallStack([('__getitem__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme[1] = 1
|
|
self.assertCallStack([('__setitem__', (testme, 1, 1))])
|
|
|
|
callLst[:] = []
|
|
del testme[1]
|
|
self.assertCallStack([('__delitem__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme[:42]
|
|
self.assertCallStack([('__getitem__', (testme, slice(None, 42)))])
|
|
|
|
callLst[:] = []
|
|
testme[:42] = "The Answer"
|
|
self.assertCallStack([('__setitem__', (testme, slice(None, 42),
|
|
"The Answer"))])
|
|
|
|
callLst[:] = []
|
|
del testme[:42]
|
|
self.assertCallStack([('__delitem__', (testme, slice(None, 42)))])
|
|
|
|
callLst[:] = []
|
|
testme[2:1024:10]
|
|
self.assertCallStack([('__getitem__', (testme, slice(2, 1024, 10)))])
|
|
|
|
callLst[:] = []
|
|
testme[2:1024:10] = "A lot"
|
|
self.assertCallStack([('__setitem__', (testme, slice(2, 1024, 10),
|
|
"A lot"))])
|
|
callLst[:] = []
|
|
del testme[2:1024:10]
|
|
self.assertCallStack([('__delitem__', (testme, slice(2, 1024, 10)))])
|
|
|
|
callLst[:] = []
|
|
testme[:42, ..., :24:, 24, 100]
|
|
self.assertCallStack([('__getitem__', (testme, (slice(None, 42, None),
|
|
Ellipsis,
|
|
slice(None, 24, None),
|
|
24, 100)))])
|
|
callLst[:] = []
|
|
testme[:42, ..., :24:, 24, 100] = "Strange"
|
|
self.assertCallStack([('__setitem__', (testme, (slice(None, 42, None),
|
|
Ellipsis,
|
|
slice(None, 24, None),
|
|
24, 100), "Strange"))])
|
|
callLst[:] = []
|
|
del testme[:42, ..., :24:, 24, 100]
|
|
self.assertCallStack([('__delitem__', (testme, (slice(None, 42, None),
|
|
Ellipsis,
|
|
slice(None, 24, None),
|
|
24, 100)))])
|
|
|
|
def testUnaryOps(self):
|
|
testme = AllTests()
|
|
|
|
callLst[:] = []
|
|
-testme
|
|
self.assertCallStack([('__neg__', (testme,))])
|
|
callLst[:] = []
|
|
+testme
|
|
self.assertCallStack([('__pos__', (testme,))])
|
|
callLst[:] = []
|
|
abs(testme)
|
|
self.assertCallStack([('__abs__', (testme,))])
|
|
callLst[:] = []
|
|
int(testme)
|
|
self.assertCallStack([('__int__', (testme,))])
|
|
callLst[:] = []
|
|
float(testme)
|
|
self.assertCallStack([('__float__', (testme,))])
|
|
callLst[:] = []
|
|
oct(testme)
|
|
self.assertCallStack([('__index__', (testme,))])
|
|
callLst[:] = []
|
|
hex(testme)
|
|
self.assertCallStack([('__index__', (testme,))])
|
|
|
|
|
|
def testMisc(self):
|
|
testme = AllTests()
|
|
|
|
callLst[:] = []
|
|
hash(testme)
|
|
self.assertCallStack([('__hash__', (testme,))])
|
|
|
|
callLst[:] = []
|
|
repr(testme)
|
|
self.assertCallStack([('__repr__', (testme,))])
|
|
|
|
callLst[:] = []
|
|
str(testme)
|
|
self.assertCallStack([('__str__', (testme,))])
|
|
|
|
callLst[:] = []
|
|
testme == 1
|
|
self.assertCallStack([('__eq__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme < 1
|
|
self.assertCallStack([('__lt__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme > 1
|
|
self.assertCallStack([('__gt__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
testme != 1
|
|
self.assertCallStack([('__ne__', (testme, 1))])
|
|
|
|
callLst[:] = []
|
|
1 == testme
|
|
self.assertCallStack([('__eq__', (1, testme))])
|
|
|
|
callLst[:] = []
|
|
1 < testme
|
|
self.assertCallStack([('__gt__', (1, testme))])
|
|
|
|
callLst[:] = []
|
|
1 > testme
|
|
self.assertCallStack([('__lt__', (1, testme))])
|
|
|
|
callLst[:] = []
|
|
1 != testme
|
|
self.assertCallStack([('__ne__', (1, testme))])
|
|
|
|
|
|
def testGetSetAndDel(self):
|
|
# Interfering tests
|
|
class ExtraTests(AllTests):
|
|
@trackCall
|
|
def __getattr__(self, *args):
|
|
return "SomeVal"
|
|
|
|
@trackCall
|
|
def __setattr__(self, *args):
|
|
pass
|
|
|
|
@trackCall
|
|
def __delattr__(self, *args):
|
|
pass
|
|
|
|
testme = ExtraTests()
|
|
|
|
callLst[:] = []
|
|
testme.spam
|
|
self.assertCallStack([('__getattr__', (testme, "spam"))])
|
|
|
|
callLst[:] = []
|
|
testme.eggs = "spam, spam, spam and ham"
|
|
self.assertCallStack([('__setattr__', (testme, "eggs",
|
|
"spam, spam, spam and ham"))])
|
|
|
|
callLst[:] = []
|
|
del testme.cardinal
|
|
self.assertCallStack([('__delattr__', (testme, "cardinal"))])
|
|
|
|
def testHasAttrString(self):
|
|
import sys
|
|
from test.support import import_helper
|
|
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
|
|
|
|
class A:
|
|
def __init__(self):
|
|
self.attr = 1
|
|
|
|
a = A()
|
|
self.assertEqual(_testlimitedcapi.object_hasattrstring(a, b"attr"), 1)
|
|
self.assertEqual(_testlimitedcapi.object_hasattrstring(a, b"noattr"), 0)
|
|
self.assertIsNone(sys.exception())
|
|
|
|
def testDel(self):
|
|
x = []
|
|
|
|
class DelTest:
|
|
def __del__(self):
|
|
x.append("crab people, crab people")
|
|
testme = DelTest()
|
|
del testme
|
|
import gc
|
|
gc.collect()
|
|
self.assertEqual(["crab people, crab people"], x)
|
|
|
|
def testBadTypeReturned(self):
|
|
# return values of some method are type-checked
|
|
class BadTypeClass:
|
|
def __int__(self):
|
|
return None
|
|
__float__ = __int__
|
|
__complex__ = __int__
|
|
__str__ = __int__
|
|
__repr__ = __int__
|
|
__bytes__ = __int__
|
|
__bool__ = __int__
|
|
__index__ = __int__
|
|
def index(x):
|
|
return [][x]
|
|
|
|
for f in [float, complex, str, repr, bytes, bin, oct, hex, bool, index]:
|
|
self.assertRaises(TypeError, f, BadTypeClass())
|
|
|
|
def testHashStuff(self):
|
|
# Test correct errors from hash() on objects with comparisons but
|
|
# no __hash__
|
|
|
|
class C0:
|
|
pass
|
|
|
|
hash(C0()) # This should work; the next two should raise TypeError
|
|
|
|
class C2:
|
|
def __eq__(self, other): return 1
|
|
|
|
self.assertRaises(TypeError, hash, C2())
|
|
|
|
|
|
def testSFBug532646(self):
|
|
# Test for SF bug 532646
|
|
|
|
class A:
|
|
pass
|
|
A.__call__ = A()
|
|
a = A()
|
|
|
|
try:
|
|
a() # This should not segfault
|
|
except RecursionError:
|
|
pass
|
|
else:
|
|
self.fail("Failed to raise RecursionError")
|
|
|
|
def testForExceptionsRaisedInInstanceGetattr2(self):
|
|
# Tests for exceptions raised in instance_getattr2().
|
|
|
|
def booh(self):
|
|
raise AttributeError("booh")
|
|
|
|
class A:
|
|
a = property(booh)
|
|
try:
|
|
A().a # Raised AttributeError: A instance has no attribute 'a'
|
|
except AttributeError as x:
|
|
if str(x) != "booh":
|
|
self.fail("attribute error for A().a got masked: %s" % x)
|
|
|
|
class E:
|
|
__eq__ = property(booh)
|
|
E() == E() # In debug mode, caused a C-level assert() to fail
|
|
|
|
class I:
|
|
__init__ = property(booh)
|
|
try:
|
|
# In debug mode, printed XXX undetected error and
|
|
# raises AttributeError
|
|
I()
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
self.fail("attribute error for I.__init__ got masked")
|
|
|
|
def assertNotOrderable(self, a, b):
|
|
with self.assertRaises(TypeError):
|
|
a < b
|
|
with self.assertRaises(TypeError):
|
|
a > b
|
|
with self.assertRaises(TypeError):
|
|
a <= b
|
|
with self.assertRaises(TypeError):
|
|
a >= b
|
|
|
|
def testHashComparisonOfMethods(self):
|
|
# Test comparison and hash of methods
|
|
class A:
|
|
def __init__(self, x):
|
|
self.x = x
|
|
def f(self):
|
|
pass
|
|
def g(self):
|
|
pass
|
|
def __eq__(self, other):
|
|
return True
|
|
def __hash__(self):
|
|
raise TypeError
|
|
class B(A):
|
|
pass
|
|
|
|
a1 = A(1)
|
|
a2 = A(1)
|
|
self.assertTrue(a1.f == a1.f)
|
|
self.assertFalse(a1.f != a1.f)
|
|
self.assertFalse(a1.f == a2.f)
|
|
self.assertTrue(a1.f != a2.f)
|
|
self.assertFalse(a1.f == a1.g)
|
|
self.assertTrue(a1.f != a1.g)
|
|
self.assertNotOrderable(a1.f, a1.f)
|
|
self.assertEqual(hash(a1.f), hash(a1.f))
|
|
|
|
self.assertFalse(A.f == a1.f)
|
|
self.assertTrue(A.f != a1.f)
|
|
self.assertFalse(A.f == A.g)
|
|
self.assertTrue(A.f != A.g)
|
|
self.assertTrue(B.f == A.f)
|
|
self.assertFalse(B.f != A.f)
|
|
self.assertNotOrderable(A.f, A.f)
|
|
self.assertEqual(hash(B.f), hash(A.f))
|
|
|
|
# the following triggers a SystemError in 2.4
|
|
a = A(hash(A.f)^(-1))
|
|
hash(a.f)
|
|
|
|
def testSetattrWrapperNameIntern(self):
|
|
# Issue #25794: __setattr__ should intern the attribute name
|
|
class A:
|
|
pass
|
|
|
|
def add(self, other):
|
|
return 'summa'
|
|
|
|
name = str(b'__add__', 'ascii') # shouldn't be optimized
|
|
self.assertIsNot(name, '__add__') # not interned
|
|
type.__setattr__(A, name, add)
|
|
self.assertEqual(A() + 1, 'summa')
|
|
|
|
name2 = str(b'__add__', 'ascii')
|
|
self.assertIsNot(name2, '__add__')
|
|
self.assertIsNot(name2, name)
|
|
type.__delattr__(A, name2)
|
|
with self.assertRaises(TypeError):
|
|
A() + 1
|
|
|
|
def testSetattrNonStringName(self):
|
|
class A:
|
|
pass
|
|
|
|
with self.assertRaises(TypeError):
|
|
type.__setattr__(A, b'x', None)
|
|
|
|
def testTypeAttributeAccessErrorMessages(self):
|
|
class A:
|
|
pass
|
|
|
|
error_msg = "type object 'A' has no attribute 'x'"
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
A.x
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del A.x
|
|
|
|
def testObjectAttributeAccessErrorMessages(self):
|
|
class A:
|
|
pass
|
|
class B:
|
|
y = 0
|
|
__slots__ = ('z',)
|
|
class C:
|
|
__slots__ = ("y",)
|
|
|
|
def __setattr__(self, name, value) -> None:
|
|
if name == "z":
|
|
super().__setattr__("y", 1)
|
|
else:
|
|
super().__setattr__(name, value)
|
|
|
|
error_msg = "'A' object has no attribute 'x'"
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
A().x
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del A().x
|
|
|
|
error_msg = "'B' object has no attribute 'x'"
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
B().x
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del B().x
|
|
with self.assertRaisesRegex(
|
|
AttributeError,
|
|
"'B' object has no attribute 'x' and no __dict__ for setting new attributes"
|
|
):
|
|
B().x = 0
|
|
with self.assertRaisesRegex(
|
|
AttributeError,
|
|
"'C' object has no attribute 'x'"
|
|
):
|
|
C().x = 0
|
|
|
|
error_msg = "'B' object attribute 'y' is read-only"
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del B().y
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
B().y = 0
|
|
|
|
error_msg = 'z'
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
B().z
|
|
with self.assertRaisesRegex(AttributeError, error_msg):
|
|
del B().z
|
|
|
|
def testConstructorErrorMessages(self):
|
|
# bpo-31506: Improves the error message logic for object_new & object_init
|
|
|
|
# Class without any method overrides
|
|
class C:
|
|
pass
|
|
|
|
error_msg = r'C.__init__\(\) takes exactly one argument \(the instance to initialize\)'
|
|
|
|
with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
|
|
C(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
|
|
C.__new__(C, 42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
C().__init__(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
|
|
object.__new__(C, 42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
object.__init__(C(), 42)
|
|
|
|
# Class with both `__init__` & `__new__` method overridden
|
|
class D:
|
|
def __new__(cls, *args, **kwargs):
|
|
super().__new__(cls, *args, **kwargs)
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
error_msg = r'object.__new__\(\) takes exactly one argument \(the type to instantiate\)'
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
D(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
D.__new__(D, 42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
object.__new__(D, 42)
|
|
|
|
# Class that only overrides __init__
|
|
class E:
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
error_msg = r'object.__init__\(\) takes exactly one argument \(the instance to initialize\)'
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
E().__init__(42)
|
|
|
|
with self.assertRaisesRegex(TypeError, error_msg):
|
|
object.__init__(E(), 42)
|
|
|
|
def testClassWithExtCall(self):
|
|
class Meta(int):
|
|
def __init__(*args, **kwargs):
|
|
pass
|
|
|
|
def __new__(cls, name, bases, attrs, **kwargs):
|
|
return bases, kwargs
|
|
|
|
d = {'metaclass': Meta}
|
|
|
|
class A(**d): pass
|
|
self.assertEqual(A, ((), {}))
|
|
class A(0, 1, 2, 3, 4, 5, 6, 7, **d): pass
|
|
self.assertEqual(A, (tuple(range(8)), {}))
|
|
class A(0, *range(1, 8), **d, foo='bar'): pass
|
|
self.assertEqual(A, (tuple(range(8)), {'foo': 'bar'}))
|
|
|
|
def testClassCallRecursionLimit(self):
|
|
class C:
|
|
def __init__(self):
|
|
self.c = C()
|
|
|
|
with self.assertRaises(RecursionError):
|
|
C()
|
|
|
|
def add_one_level():
|
|
#Each call to C() consumes 2 levels, so offset by 1.
|
|
C()
|
|
|
|
with self.assertRaises(RecursionError):
|
|
add_one_level()
|
|
|
|
def testMetaclassCallOptimization(self):
|
|
calls = 0
|
|
|
|
class TypeMetaclass(type):
|
|
def __call__(cls, *args, **kwargs):
|
|
nonlocal calls
|
|
calls += 1
|
|
return type.__call__(cls, *args, **kwargs)
|
|
|
|
class Type(metaclass=TypeMetaclass):
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
|
|
for i in range(100):
|
|
Type(i)
|
|
self.assertEqual(calls, 100)
|
|
|
|
def test_specialization_class_call_doesnt_crash(self):
|
|
# gh-123185
|
|
|
|
class Foo:
|
|
def __init__(self, arg):
|
|
pass
|
|
|
|
for _ in range(8):
|
|
try:
|
|
Foo()
|
|
except:
|
|
pass
|
|
|
|
|
|
from _testinternalcapi import has_inline_values
|
|
|
|
Py_TPFLAGS_MANAGED_DICT = (1 << 2)
|
|
|
|
class Plain:
|
|
pass
|
|
|
|
|
|
class WithAttrs:
|
|
|
|
def __init__(self):
|
|
self.a = 1
|
|
self.b = 2
|
|
self.c = 3
|
|
self.d = 4
|
|
|
|
|
|
class TestInlineValues(unittest.TestCase):
|
|
|
|
def test_flags(self):
|
|
self.assertEqual(Plain.__flags__ & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT)
|
|
self.assertEqual(WithAttrs.__flags__ & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT)
|
|
|
|
def test_has_inline_values(self):
|
|
c = Plain()
|
|
self.assertTrue(has_inline_values(c))
|
|
del c.__dict__
|
|
self.assertFalse(has_inline_values(c))
|
|
|
|
def test_instances(self):
|
|
self.assertTrue(has_inline_values(Plain()))
|
|
self.assertTrue(has_inline_values(WithAttrs()))
|
|
|
|
def test_inspect_dict(self):
|
|
for cls in (Plain, WithAttrs):
|
|
c = cls()
|
|
c.__dict__
|
|
self.assertTrue(has_inline_values(c))
|
|
|
|
def test_update_dict(self):
|
|
d = { "e": 5, "f": 6 }
|
|
for cls in (Plain, WithAttrs):
|
|
c = cls()
|
|
c.__dict__.update(d)
|
|
self.assertTrue(has_inline_values(c))
|
|
|
|
@staticmethod
|
|
def set_100(obj):
|
|
for i in range(100):
|
|
setattr(obj, f"a{i}", i)
|
|
|
|
def check_100(self, obj):
|
|
for i in range(100):
|
|
self.assertEqual(getattr(obj, f"a{i}"), i)
|
|
|
|
def test_many_attributes(self):
|
|
class C: pass
|
|
c = C()
|
|
self.assertTrue(has_inline_values(c))
|
|
self.set_100(c)
|
|
self.assertFalse(has_inline_values(c))
|
|
self.check_100(c)
|
|
c = C()
|
|
self.assertTrue(has_inline_values(c))
|
|
|
|
def test_many_attributes_with_dict(self):
|
|
class C: pass
|
|
c = C()
|
|
d = c.__dict__
|
|
self.assertTrue(has_inline_values(c))
|
|
self.set_100(c)
|
|
self.assertFalse(has_inline_values(c))
|
|
self.check_100(c)
|
|
|
|
def test_bug_117750(self):
|
|
"Aborted on 3.13a6"
|
|
class C:
|
|
def __init__(self):
|
|
self.__dict__.clear()
|
|
|
|
obj = C()
|
|
self.assertEqual(obj.__dict__, {})
|
|
obj.foo = None # Aborted here
|
|
self.assertEqual(obj.__dict__, {"foo":None})
|
|
|
|
def test_store_attr_deleted_dict(self):
|
|
class Foo:
|
|
pass
|
|
|
|
f = Foo()
|
|
del f.__dict__
|
|
f.a = 3
|
|
self.assertEqual(f.a, 3)
|
|
|
|
def test_rematerialize_object_dict(self):
|
|
# gh-121860: rematerializing an object's managed dictionary after it
|
|
# had been deleted caused a crash.
|
|
class Foo: pass
|
|
f = Foo()
|
|
f.__dict__["attr"] = 1
|
|
del f.__dict__
|
|
|
|
# Using a str subclass is a way to trigger the re-materialization
|
|
class StrSubclass(str): pass
|
|
self.assertFalse(hasattr(f, StrSubclass("attr")))
|
|
|
|
# Changing the __class__ also triggers the re-materialization
|
|
class Bar: pass
|
|
f.__class__ = Bar
|
|
self.assertIsInstance(f, Bar)
|
|
self.assertEqual(f.__dict__, {})
|
|
|
|
def test_store_attr_type_cache(self):
|
|
"""Verifies that the type cache doesn't provide a value which is
|
|
inconsistent from the dict."""
|
|
class X:
|
|
def __del__(inner_self):
|
|
v = C.a
|
|
self.assertEqual(v, C.__dict__['a'])
|
|
|
|
class C:
|
|
a = X()
|
|
|
|
# prime the cache
|
|
C.a
|
|
C.a
|
|
|
|
# destructor shouldn't be able to see inconsistent state
|
|
C.a = X()
|
|
C.a = X()
|
|
|
|
@cpython_only
|
|
def test_detach_materialized_dict_no_memory(self):
|
|
# Skip test if _testcapi is not available:
|
|
import_helper.import_module('_testcapi')
|
|
|
|
code = """if 1:
|
|
import test.support
|
|
import _testcapi
|
|
|
|
class A:
|
|
def __init__(self):
|
|
self.a = 1
|
|
self.b = 2
|
|
a = A()
|
|
d = a.__dict__
|
|
with test.support.catch_unraisable_exception() as ex:
|
|
_testcapi.set_nomemory(0, 1)
|
|
del a
|
|
assert ex.unraisable.exc_type is MemoryError
|
|
try:
|
|
d["a"]
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
assert False, "KeyError not raised"
|
|
"""
|
|
rc, out, err = script_helper.assert_python_ok("-c", code)
|
|
self.assertEqual(rc, 0)
|
|
self.assertFalse(out, msg=out.decode('utf-8'))
|
|
self.assertFalse(err, msg=err.decode('utf-8'))
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|