Skip to content

Commit 34098c2

Browse files
committed
fixed lookup of some special methods
1 parent 1640549 commit 34098c2

File tree

5 files changed

+489
-113
lines changed

5 files changed

+489
-113
lines changed
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
import sys
41+
import math
42+
import unittest
43+
44+
class SpecialMethodTest(unittest.TestCase):
45+
46+
def test_special(self):
47+
def fun_float(arg):
48+
return 4.2
49+
def fun_int(arg):
50+
return 42
51+
def fun_int2(arg1, arg2):
52+
return 42
53+
def fun_complex(arg):
54+
return (4+2j)
55+
def fun_str(arg):
56+
return '42'
57+
def fun_str2(arg1, arg2):
58+
return '42'
59+
def fun_bool(arg):
60+
return True
61+
def fun_bytes(arg):
62+
return b'42'
63+
def fun_empty_seq(arg):
64+
return []
65+
def fun_seq(arg):
66+
return [42]
67+
specials = [
68+
("__bytes__", bytes, 1, 1.1, "b'abc'", fun_bytes, True, []),
69+
("__dir__", dir, 1, 1.1, 'abc', fun_empty_seq, True, []),
70+
("__round__", round, 1, 1.1, 'abc', fun_int2, True, [1]),
71+
("__complex__", complex, 1, 1.1, '1+1j', fun_complex, True, []),
72+
("__float__", float, 1, 1.1, '1.1', fun_float, True, []),
73+
("__int__", int, 1, 1.1, '1', fun_int, True, []),
74+
("__bool__", bool, 1, 1.1, 'True', fun_bool, True, []),
75+
("__str__", str, 1, 1.1, 'abc', fun_str, True, []),
76+
("__add__", None, 1, 1.1, '1', fun_int2, False, [1]),
77+
("__sub__", None, 1, 1.1, '1', fun_int2, False, [1]),
78+
("__mul__", None, 1, 1.1, '1', fun_int2, False, [1]),
79+
("__truediv__", None, 1, 1.1, '1', fun_int2, False, [1]),
80+
("__floordiv__", None, 1, 1.1, '1', fun_int2, False, [1]),
81+
("__abs__", abs, -1, -1.1, '-1', fun_int, False, []),
82+
("__hash__", hash, 1, 1.1, '1', fun_int, False, []),
83+
("__divmod__", divmod, 1, 1.1, '1', fun_int2, False, [1]),
84+
("__len__", len, 1, 1.1, '1', fun_int, False, []),
85+
("__pow__", pow, 1, 1.1, '1', fun_int2, False, [1]),
86+
("__repr__", repr, 1, 1.1, 'abc', fun_str, False, []),
87+
# TODO: fix __format__, __floor__, __trunc__, __ceil__, __sizeof__
88+
# recursion when special method set to constructor builtin
89+
# >>> setattr(C, "__format__", format)
90+
# >>> format(C())
91+
# ("__sizeof__", sys.getsizeof, 1, 1.1, 'abc', fun_int, False, []),
92+
("__format__", format, 1, 1.1, 'abc', fun_str2, False, ['']),
93+
("__floor__", math.floor, 1, 1.1, 'abc', fun_int, False, []),
94+
("__trunc__", math.trunc, 1, 1.1, 'abc', fun_int, False, []),
95+
("__ceil__", math.ceil, 1, 1.1, 'abc', fun_int, False, []),
96+
]
97+
for name, runner, int_repr, float_repr, str_repr, meth_impl, check_with_runner_set, additional_args in specials:
98+
def check_method_as_attr(X, repr):
99+
setattr(X, name, meth_impl)
100+
x = X(repr)
101+
if(name == "__add__"):
102+
rx = x + 1
103+
mx = meth_impl(x, *additional_args)
104+
elif(name == "__sub__"):
105+
rx = x - 1
106+
mx = meth_impl(x, *additional_args)
107+
elif(name == "__mul__"):
108+
rx = x * 1
109+
mx = meth_impl(x, *additional_args)
110+
elif(name == "__truediv__"):
111+
rx = x / 1
112+
mx = meth_impl(x, *additional_args)
113+
elif(name == "__floordiv__"):
114+
rx = x // 1
115+
mx = meth_impl(x, *additional_args)
116+
else:
117+
rx = runner(x, *additional_args)
118+
if name == "__complex__" and type(repr) == str:
119+
# str subtypes behave different
120+
mx = complex(repr)
121+
else:
122+
mx = meth_impl(x, *additional_args)
123+
124+
if mx != rx:
125+
raise AssertionError("expected runner '" + str(runner) + "' and method '" + str(meth_impl) + "' result to be the same for '" + name + "' : runner = " + str(rx) + ", method = " + str(mx))
126+
127+
def check_runner_as_attr(X, repr):
128+
setattr(X, name, runner)
129+
x = X(repr)
130+
# just check that no error is raised
131+
runner(x, *additional_args)
132+
133+
class XI(int):
134+
pass
135+
136+
class XF(float):
137+
pass
138+
139+
class XS(str):
140+
pass
141+
142+
check_method_as_attr(XI, int_repr)
143+
check_method_as_attr(XF, float_repr)
144+
check_method_as_attr(XS, str_repr)
145+
146+
if check_with_runner_set:
147+
check_runner_as_attr(XI, int_repr)
148+
check_runner_as_attr(XF, float_repr)
149+
check_runner_as_attr(XS, str_repr)
150+
151+
# modified version of test_descr.py#test_special_method_lookup() - added more special methods to the test
152+
def test_special2(self):
153+
def run_context(manager):
154+
with manager:
155+
pass
156+
def iden(self):
157+
return self
158+
def hello(self):
159+
return b"hello"
160+
def fun_empty_seq(self):
161+
return []
162+
def zero(self):
163+
return 0
164+
def fint2(a1, a2):
165+
return 42
166+
def ffloat(self):
167+
return 4.2
168+
def fstr(self):
169+
return '42'
170+
def complex_num(self):
171+
return 1j
172+
def stop(self):
173+
raise StopIteration
174+
def return_true(self, thing=None):
175+
return True
176+
def do_isinstance(obj):
177+
return isinstance(int, obj)
178+
def do_issubclass(obj):
179+
return issubclass(int, obj)
180+
def do_dict_missing(checker):
181+
class DictSub(checker.__class__, dict):
182+
pass
183+
self.assertEqual(DictSub()["hi"], 4)
184+
def some_number(self_, key):
185+
self.assertEqual(key, "hi")
186+
return 4
187+
def swallow(*args): pass
188+
def format_impl(self, spec):
189+
return "hello"
190+
specials = [
191+
("__bytes__", bytes, hello, set(), {}),
192+
("__reversed__", reversed, fun_empty_seq, set(), {}),
193+
("__length_hint__", list, zero, set(), {"__iter__" : iden, "__next__" : stop}),
194+
("__sizeof__", sys.getsizeof, zero, set(), {}),
195+
("__instancecheck__", do_isinstance, return_true, set(), {}),
196+
("__missing__", do_dict_missing, some_number, set(("__class__",)), {}),
197+
("__subclasscheck__", do_issubclass, return_true, set(("__bases__",)), {}),
198+
("__enter__", run_context, iden, set(), {"__exit__" : swallow}),
199+
("__exit__", run_context, swallow, set(), {"__enter__" : iden}),
200+
("__complex__", complex, complex_num, set(), {}),
201+
("__format__", format, format_impl, set(), {}),
202+
("__floor__", math.floor, zero, set(), {}),
203+
("__trunc__", math.trunc, zero, set(), {}),
204+
("__trunc__", int, zero, set(), {}),
205+
("__ceil__", math.ceil, zero, set(), {}),
206+
("__dir__", dir, fun_empty_seq, set(), {}),
207+
("__round__", round, zero, set(), {}),
208+
# ----------------------------------------------------
209+
("__float__", float, ffloat, set(), {}),
210+
("__int__", int, zero, set(), {}),
211+
("__bool__", bool, return_true, set(), {}),
212+
("__str__", str, fstr, set(), {}),
213+
("__abs__", abs, zero, set(), {}),
214+
("__hash__", hash, zero, set(), {}),
215+
("__divmod__", divmod, fint2, set(), {}),
216+
("__len__", len, zero, set(), {}),
217+
("__pow__", pow, fint2, set(), {}),
218+
("__repr__", repr, fstr, set(), {}),
219+
("__add__", None, fint2, set(), {}),
220+
("__sub__", None, fint2, set(), {}),
221+
("__mul__", None, fint2, set(), {}),
222+
("__truediv__", None, fint2, set(), {}),
223+
("__floordiv__", None, fint2, set(), {}),
224+
]
225+
class SpecialDescr(object):
226+
def __init__(self, impl):
227+
self.impl = impl
228+
def __get__(self, obj, owner):
229+
record.append(1)
230+
return self.impl.__get__(obj, owner)
231+
class MyException(Exception):
232+
pass
233+
class ErrDescr(object):
234+
def __get__(self, obj, owner):
235+
raise MyException
236+
for name, runner, meth_impl, ok, env in specials:
237+
class X:
238+
pass
239+
for attr, obj in env.items():
240+
setattr(X, attr, obj)
241+
setattr(X, name, meth_impl)
242+
if name == "__divmod__" or name == "__pow__":
243+
runner(X(), 1)
244+
elif name == "__add__":
245+
X() + 1
246+
elif name == "__mul__":
247+
X() * 1
248+
elif name == "__truediv__":
249+
X() / 1
250+
elif name == "__floordiv__":
251+
X() // 1
252+
elif name == "__sub__":
253+
X() - 1
254+
else:
255+
runner(X())
256+
record = []
257+
class X:
258+
pass
259+
for attr, obj in env.items():
260+
setattr(X, attr, obj)
261+
setattr(X, name, SpecialDescr(meth_impl))
262+
if name == "__divmod__" or name == "__pow__":
263+
runner(X(), 1)
264+
elif name == "__add__":
265+
X() + 1
266+
elif name == "__mul__":
267+
X() * 1
268+
elif name == "__truediv__":
269+
X() / 1
270+
elif name == "__floordiv__":
271+
X() // 1
272+
elif name == "__sub__":
273+
X() - 1
274+
else:
275+
runner(X())
276+
self.assertEqual(record, [1], name)
277+
class X:
278+
pass
279+
for attr, obj in env.items():
280+
setattr(X, attr, obj)
281+
setattr(X, name, ErrDescr())
282+
if name == "__divmod__" or name == "__pow__":
283+
self.assertRaises(MyException, runner, X(), 1)
284+
elif name == "__add__":
285+
def fe(): X() + 1
286+
self.assertRaises(MyException, fe)
287+
elif name == "__mul__":
288+
def fe(): X() * 1
289+
self.assertRaises(MyException, fe)
290+
elif name == "__truediv__":
291+
def fe(): X() / 1
292+
self.assertRaises(MyException, fe)
293+
elif name == "__floordiv__":
294+
def fe(): X() // 1
295+
self.assertRaises(MyException, fe)
296+
elif name == "__sub__":
297+
def fe(): X() - 1
298+
self.assertRaises(MyException, fe)
299+
elif name == "__repr__":
300+
runner(X())
301+
elif name == "__hash__":
302+
# XXX just skip, does not work in cpython either
303+
pass
304+
else:
305+
self.assertRaises(MyException, runner, X())
306+
307+
308+
def test_reversed(self):
309+
class C(list):
310+
pass
311+
312+
def f(o):
313+
return [42]
314+
315+
setattr(C, "__reversed__", f)
316+
assert list(reversed(C([1,2,3]))) == [42]
317+
318+
setattr(C, "__reversed__", reversed)
319+
try:
320+
reversed(C([1,2,3]))
321+
except TypeError:
322+
pass

0 commit comments

Comments
 (0)