tests/basics: Add tests for PEP487 __set_name__.
Including the stochastic tests needed to guarantee sensitivity to the potential iterate-while-modifying hazard a naive implementation might have. Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
This commit is contained in:
committed by
Damien George
parent
3a72f95919
commit
82db5c81e0
@@ -1,22 +1,28 @@
|
|||||||
class Descriptor:
|
class Descriptor:
|
||||||
def __get__(self, obj, cls):
|
def __get__(self, obj, cls):
|
||||||
print('get')
|
print("get")
|
||||||
print(type(obj) is Main)
|
print(type(obj) is Main)
|
||||||
print(cls is Main)
|
print(cls is Main)
|
||||||
return 'result'
|
return "result"
|
||||||
|
|
||||||
def __set__(self, obj, val):
|
def __set__(self, obj, val):
|
||||||
print('set')
|
print("set")
|
||||||
print(type(obj) is Main)
|
print(type(obj) is Main)
|
||||||
print(val)
|
print(val)
|
||||||
|
|
||||||
def __delete__(self, obj):
|
def __delete__(self, obj):
|
||||||
print('delete')
|
print("delete")
|
||||||
print(type(obj) is Main)
|
print(type(obj) is Main)
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
print("set_name", name)
|
||||||
|
print(owner.__name__ == "Main")
|
||||||
|
|
||||||
|
|
||||||
class Main:
|
class Main:
|
||||||
Forward = Descriptor()
|
Forward = Descriptor()
|
||||||
|
|
||||||
|
|
||||||
m = Main()
|
m = Main()
|
||||||
try:
|
try:
|
||||||
m.__class__
|
m.__class__
|
||||||
@@ -26,15 +32,15 @@ except AttributeError:
|
|||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
r = m.Forward
|
r = m.Forward
|
||||||
if 'Descriptor' in repr(r.__class__):
|
if "Descriptor" in repr(r.__class__):
|
||||||
# Target doesn't support descriptors.
|
# Target doesn't support descriptors.
|
||||||
print('SKIP')
|
print("SKIP")
|
||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
# Test assignment and deletion.
|
# Test assignment and deletion.
|
||||||
|
|
||||||
print(r)
|
print(r)
|
||||||
m.Forward = 'a'
|
m.Forward = "a"
|
||||||
del m.Forward
|
del m.Forward
|
||||||
|
|
||||||
# Test that lookup of descriptors like __get__ are not passed into __getattr__.
|
# Test that lookup of descriptors like __get__ are not passed into __getattr__.
|
||||||
|
|||||||
182
tests/basics/class_setname_hazard.py
Normal file
182
tests/basics/class_setname_hazard.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
# Test that __set_name__ can access and mutate its owner argument.
|
||||||
|
|
||||||
|
|
||||||
|
def skip_if_no_descriptors():
|
||||||
|
class Descriptor:
|
||||||
|
def __get__(self, obj, cls):
|
||||||
|
return
|
||||||
|
|
||||||
|
class TestClass:
|
||||||
|
Forward = Descriptor()
|
||||||
|
|
||||||
|
a = TestClass()
|
||||||
|
try:
|
||||||
|
a.__class__
|
||||||
|
except AttributeError:
|
||||||
|
# Target doesn't support __class__.
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
b = a.Forward
|
||||||
|
if "Descriptor" in repr(b.__class__):
|
||||||
|
# Target doesn't support descriptors.
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
|
skip_if_no_descriptors()
|
||||||
|
|
||||||
|
|
||||||
|
# Test basic accesses and mutations.
|
||||||
|
|
||||||
|
|
||||||
|
class GetSibling:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
print(getattr(owner, name + "_sib"))
|
||||||
|
|
||||||
|
|
||||||
|
class GetSiblingTest:
|
||||||
|
desc = GetSibling()
|
||||||
|
desc_sib = 111
|
||||||
|
|
||||||
|
|
||||||
|
t110 = GetSiblingTest()
|
||||||
|
|
||||||
|
|
||||||
|
class SetSibling:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
setattr(owner, name + "_sib", 121)
|
||||||
|
|
||||||
|
|
||||||
|
class SetSiblingTest:
|
||||||
|
desc = SetSibling()
|
||||||
|
|
||||||
|
|
||||||
|
t120 = SetSiblingTest()
|
||||||
|
|
||||||
|
print(t120.desc_sib)
|
||||||
|
|
||||||
|
|
||||||
|
class DelSibling:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
delattr(owner, name + "_sib")
|
||||||
|
|
||||||
|
|
||||||
|
class DelSiblingTest:
|
||||||
|
desc = DelSibling()
|
||||||
|
desc_sib = 131
|
||||||
|
|
||||||
|
|
||||||
|
t130 = DelSiblingTest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(t130.desc_sib)
|
||||||
|
except AttributeError:
|
||||||
|
print("AttributeError")
|
||||||
|
|
||||||
|
|
||||||
|
class GetSelf:
|
||||||
|
x = 211
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
print(getattr(owner, name).x)
|
||||||
|
|
||||||
|
|
||||||
|
class GetSelfTest:
|
||||||
|
desc = GetSelf()
|
||||||
|
|
||||||
|
|
||||||
|
t210 = GetSelfTest()
|
||||||
|
|
||||||
|
|
||||||
|
class SetSelf:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
setattr(owner, name, 221)
|
||||||
|
|
||||||
|
|
||||||
|
class SetSelfTest:
|
||||||
|
desc = SetSelf()
|
||||||
|
|
||||||
|
|
||||||
|
t220 = SetSelfTest()
|
||||||
|
|
||||||
|
print(t220.desc)
|
||||||
|
|
||||||
|
|
||||||
|
class DelSelf:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
delattr(owner, name)
|
||||||
|
|
||||||
|
|
||||||
|
class DelSelfTest:
|
||||||
|
desc = DelSelf()
|
||||||
|
|
||||||
|
|
||||||
|
t230 = DelSelfTest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(t230.desc)
|
||||||
|
except AttributeError:
|
||||||
|
print("AttributeError")
|
||||||
|
|
||||||
|
|
||||||
|
# Test exception behavior.
|
||||||
|
|
||||||
|
|
||||||
|
class Raise:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
class RaiseTest:
|
||||||
|
desc = Raise()
|
||||||
|
except Exception as e: # CPython raises RuntimeError, MicroPython propagates the original exception
|
||||||
|
print("Exception")
|
||||||
|
|
||||||
|
|
||||||
|
# Ensure removed/overwritten class members still get __set_name__ called.
|
||||||
|
|
||||||
|
|
||||||
|
class SetSpecific:
|
||||||
|
def __init__(self, sib_name, sib_replace):
|
||||||
|
self.sib_name = sib_name
|
||||||
|
self.sib_replace = sib_replace
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
setattr(owner, self.sib_name, self.sib_replace)
|
||||||
|
|
||||||
|
|
||||||
|
class SetReplaceTest:
|
||||||
|
a = SetSpecific("b", 312) # one of these is changed first
|
||||||
|
b = SetSpecific("a", 311)
|
||||||
|
|
||||||
|
|
||||||
|
t310 = SetReplaceTest()
|
||||||
|
print(t310.a)
|
||||||
|
print(t310.b)
|
||||||
|
|
||||||
|
|
||||||
|
class DelSpecific:
|
||||||
|
def __init__(self, sib_name):
|
||||||
|
self.sib_name = sib_name
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
delattr(owner, self.sib_name)
|
||||||
|
|
||||||
|
|
||||||
|
class DelReplaceTest:
|
||||||
|
a = DelSpecific("b") # one of these is removed first
|
||||||
|
b = DelSpecific("a")
|
||||||
|
|
||||||
|
|
||||||
|
t320 = DelReplaceTest()
|
||||||
|
try:
|
||||||
|
print(t320.a)
|
||||||
|
except AttributeError:
|
||||||
|
print("AttributeError")
|
||||||
|
try:
|
||||||
|
print(t320.b)
|
||||||
|
except AttributeError:
|
||||||
|
print("AttributeError")
|
||||||
111
tests/basics/class_setname_hazard_rand.py
Normal file
111
tests/basics/class_setname_hazard_rand.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Test to make sure there's no sequence hazard even when a __set_name__ implementation
|
||||||
|
# mutates and reorders the namespace of its owner class.
|
||||||
|
# VERY hard bug to prove out except via a stochastic test.
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from random import choice
|
||||||
|
import re
|
||||||
|
except ImportError:
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
|
def skip_if_no_descriptors():
|
||||||
|
class Descriptor:
|
||||||
|
def __get__(self, obj, cls):
|
||||||
|
return
|
||||||
|
|
||||||
|
class TestClass:
|
||||||
|
Forward = Descriptor()
|
||||||
|
|
||||||
|
a = TestClass()
|
||||||
|
try:
|
||||||
|
a.__class__
|
||||||
|
except AttributeError:
|
||||||
|
# Target doesn't support __class__.
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
b = a.Forward
|
||||||
|
if "Descriptor" in repr(b.__class__):
|
||||||
|
# Target doesn't support descriptors.
|
||||||
|
print("SKIP")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
|
skip_if_no_descriptors()
|
||||||
|
|
||||||
|
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
# Would be r"[A-Z]{5}", but not all ports support the {n} quantifier.
|
||||||
|
junk_re = re.compile(r"[A-Z][A-Z][A-Z][A-Z][A-Z]")
|
||||||
|
|
||||||
|
|
||||||
|
def junk_fill(obj, n=10): # Add randomly-generated attributes to an object.
|
||||||
|
for i in range(n):
|
||||||
|
name = "".join(choice(letters) for j in range(5))
|
||||||
|
setattr(obj, name, object())
|
||||||
|
|
||||||
|
|
||||||
|
def junk_clear(obj): # Remove attributes added by junk_fill.
|
||||||
|
to_del = [name for name in dir(obj) if junk_re.match(name)]
|
||||||
|
for name in to_del:
|
||||||
|
delattr(obj, name)
|
||||||
|
|
||||||
|
|
||||||
|
def junk_sequencer():
|
||||||
|
global runs
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
owner, name = yield
|
||||||
|
runs[name] = runs.get(name, 0) + 1
|
||||||
|
junk_fill(owner)
|
||||||
|
finally:
|
||||||
|
junk_clear(owner)
|
||||||
|
|
||||||
|
|
||||||
|
class JunkMaker:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
global seq
|
||||||
|
seq.send((owner, name))
|
||||||
|
|
||||||
|
|
||||||
|
runs = {}
|
||||||
|
seq = junk_sequencer()
|
||||||
|
next(seq)
|
||||||
|
|
||||||
|
|
||||||
|
class Main:
|
||||||
|
a = JunkMaker()
|
||||||
|
b = JunkMaker()
|
||||||
|
c = JunkMaker()
|
||||||
|
d = JunkMaker()
|
||||||
|
e = JunkMaker()
|
||||||
|
f = JunkMaker()
|
||||||
|
g = JunkMaker()
|
||||||
|
h = JunkMaker()
|
||||||
|
i = JunkMaker()
|
||||||
|
j = JunkMaker()
|
||||||
|
k = JunkMaker()
|
||||||
|
l = JunkMaker()
|
||||||
|
m = JunkMaker()
|
||||||
|
n = JunkMaker()
|
||||||
|
o = JunkMaker()
|
||||||
|
p = JunkMaker()
|
||||||
|
q = JunkMaker()
|
||||||
|
r = JunkMaker()
|
||||||
|
s = JunkMaker()
|
||||||
|
t = JunkMaker()
|
||||||
|
u = JunkMaker()
|
||||||
|
v = JunkMaker()
|
||||||
|
w = JunkMaker()
|
||||||
|
x = JunkMaker()
|
||||||
|
y = JunkMaker()
|
||||||
|
z = JunkMaker()
|
||||||
|
|
||||||
|
|
||||||
|
seq.close()
|
||||||
|
|
||||||
|
for k in letters.lower():
|
||||||
|
print(k, runs.get(k, 0))
|
||||||
Reference in New Issue
Block a user