1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
|
import pytest
from pypy.objspace.std.test import test_typeobject
@pytest.mark.skipif('config.option.runappdirect')
class AppTestMethodCaching(test_typeobject.AppTestTypeObject):
spaceconfig = {"objspace.std.withmethodcachecounter": True}
def setup_class(cls):
# This is for the following tests, which are a bit fragile and
# historically have been failing once in a while. With this hack,
# they are run up to 5 times in a row, saving the frame of the
# failed attempt. This means occasional collisions should work
# differently during the retry.
cls.w_retry = cls.space.appexec([], """():
def retry(run):
keepalive = []
for i in range(4):
try:
return run()
except AssertionError:
import sys
keepalive.append(sys.exc_info())
return run()
return retry
""")
def test_mix_classes(self):
@self.retry
def run():
import __pypy__
class A(object):
def f(self):
return 42
class B(object):
def f(self):
return 43
class C(object):
def f(self):
return 44
l = [A(), B(), C()] * 10
__pypy__.reset_method_cache_counter()
for i, a in enumerate(l):
assert a.f() == 42 + i % 3
cache_counter = __pypy__.method_cache_counter("f")
assert cache_counter[0] >= 15
assert cache_counter[1] >= 3 # should be (27, 3)
assert sum(cache_counter) == 30
def test_change_methods(self):
# this test fails because of the following line in typeobject.py:427
# if cached_name is name:
# in py3k, identifiers are stored in W_UnicodeObject and unwrapped by
# calling space.str_w, which .encode('ascii') the string, thus
# creating new strings all the time. The problem should be solved when
# we implement proper unicode identifiers in py3k
@self.retry
def run():
import __pypy__
class A(object):
def f(self):
return 42
l = [A()] * 10
__pypy__.reset_method_cache_counter()
for i, a in enumerate(l):
assert a.f() == 42 + i
A.f = eval("lambda self: %s" % (42 + i + 1, ))
cache_counter = __pypy__.method_cache_counter("f")
#
# a bit of explanation about what's going on. (1) is the line "a.f()"
# and (2) is "A.f = ...".
#
# at line (1) we do the lookup on type(a).f
#
# at line (2) we do a setattr on A. However, descr_setattr does also a
# lookup of type(A).f i.e. type.f, to check if by chance 'f' is a data
# descriptor.
#
# At the first iteration:
# (1) is a miss because it's the first lookup of A.f. The result is cached
#
# (2) is a miss because it is the first lookup of type.f. The
# (non-existant) result is cached. The version of A changes, and 'f'
# is changed to be a cell object, so that subsequest assignments won't
# change the version of A
#
# At the second iteration:
# (1) is a miss because the version of A changed just before
# (2) is a hit, because type.f is cached. The version of A no longer changes
#
# At the third and subsequent iterations:
# (1) is a hit, because the version of A did not change
# (2) is a hit, see above
assert cache_counter == (17, 3)
def test_subclasses(self):
@self.retry
def run():
import __pypy__
class A(object):
def f(self):
return 42
class B(object):
def f(self):
return 43
class C(A):
pass
l = [A(), B(), C()] * 10
__pypy__.reset_method_cache_counter()
for i, a in enumerate(l):
assert a.f() == 42 + (i % 3 == 1)
cache_counter = __pypy__.method_cache_counter("f")
assert cache_counter[0] >= 15
assert cache_counter[1] >= 3 # should be (27, 3)
assert sum(cache_counter) == 30
def test_many_names(self):
@self.retry
def run():
import __pypy__
laste = None
for j in range(20):
class A(object):
def f(self):
return 42
class B(object):
def f(self):
return 43
class C(A):
pass
l = [A(), B(), C()] * 10
__pypy__.reset_method_cache_counter()
for i, a in enumerate(l):
assert a.f() == 42 + (i % 3 == 1)
cache_counter = __pypy__.method_cache_counter("f")
assert cache_counter[0] >= 15
assert cache_counter[1] >= 3 # should be (27, 3)
assert sum(cache_counter) == 30
a = A()
names = [name for name in A.__dict__.keys()
if not name.startswith('_')]
names.sort()
names_repeated = names * 10
result = []
__pypy__.reset_method_cache_counter()
for name in names_repeated:
result.append(getattr(a, name))
append_counter = __pypy__.method_cache_counter("append")
names_counters = [__pypy__.method_cache_counter(name)
for name in names]
try:
assert append_counter[0] >= 10 * len(names) - 1
for name, count in zip(names, names_counters):
assert count == (9, 1), str((name, count))
break
except AssertionError as e:
laste = e
else:
raise laste
def test_mutating_bases(self):
class C(object):
pass
class C2(object):
foo = 5
class D(C):
pass
class E(D):
pass
d = D()
e = E()
D.__bases__ = (C2,)
assert e.foo == 5
class F(object):
foo = 3
D.__bases__ = (C, F)
assert e.foo == 3
def test_custom_metaclass(self):
@self.retry
def run():
import __pypy__
for j in range(20):
class MetaA(type):
def __getattribute__(self, x):
return 1
def f(self):
return 42
A = type.__new__(MetaA, "A", (), {"f": f})
l = [type.__getattribute__(A, "__new__")(A)] * 10
__pypy__.reset_method_cache_counter()
for i, a in enumerate(l):
# use getattr to circumvent the mapdict cache
assert getattr(a, "f")() == 42
cache_counter = __pypy__.method_cache_counter("f")
assert sum(cache_counter) == 10
if cache_counter == (9, 1):
break
#else the moon is misaligned, try again
else:
raise AssertionError("cache_counter = %r" % (cache_counter,))
def test_mutate_class(self):
@self.retry
def run():
import __pypy__
class A(object):
x = 1
y = 2
__pypy__.reset_method_cache_counter()
a = A()
for i in range(100):
assert a.y == 2
assert a.x == i + 1
A.x += 1
cache_counter = __pypy__.method_cache_counter("x")
# XXX this is the bad case for the mapdict cache: looking up
# non-method attributes from the class
assert cache_counter[0] >= 450
assert cache_counter[1] >= 1
assert sum(cache_counter) == 500
__pypy__.reset_method_cache_counter()
a = A()
for i in range(100):
assert a.y == 2
setattr(a, "a%s" % i, i)
cache_counter = __pypy__.method_cache_counter("x")
assert cache_counter[0] == 0 # 0 hits, because all the attributes are new
|