|
1 import copy |
|
2 import warnings |
|
3 import unittest |
|
4 from test.test_support import run_unittest, TestFailed |
|
5 |
|
6 # Fake a number that implements numeric methods through __coerce__ |
|
7 class CoerceNumber: |
|
8 def __init__(self, arg): |
|
9 self.arg = arg |
|
10 |
|
11 def __repr__(self): |
|
12 return '<CoerceNumber %s>' % repr(self.arg) |
|
13 |
|
14 def __coerce__(self, other): |
|
15 if isinstance(other, CoerceNumber): |
|
16 return self.arg, other.arg |
|
17 else: |
|
18 return (self.arg, other) |
|
19 |
|
20 # New-style class version of CoerceNumber |
|
21 class CoerceTo(object): |
|
22 def __init__(self, arg): |
|
23 self.arg = arg |
|
24 def __coerce__(self, other): |
|
25 if isinstance(other, CoerceTo): |
|
26 return self.arg, other.arg |
|
27 else: |
|
28 return self.arg, other |
|
29 |
|
30 |
|
31 # Fake a number that implements numeric ops through methods. |
|
32 class MethodNumber: |
|
33 def __init__(self,arg): |
|
34 self.arg = arg |
|
35 |
|
36 def __repr__(self): |
|
37 return '<MethodNumber %s>' % repr(self.arg) |
|
38 |
|
39 def __add__(self,other): |
|
40 return self.arg + other |
|
41 |
|
42 def __radd__(self,other): |
|
43 return other + self.arg |
|
44 |
|
45 def __sub__(self,other): |
|
46 return self.arg - other |
|
47 |
|
48 def __rsub__(self,other): |
|
49 return other - self.arg |
|
50 |
|
51 def __mul__(self,other): |
|
52 return self.arg * other |
|
53 |
|
54 def __rmul__(self,other): |
|
55 return other * self.arg |
|
56 |
|
57 def __div__(self,other): |
|
58 return self.arg / other |
|
59 |
|
60 def __rdiv__(self,other): |
|
61 return other / self.arg |
|
62 |
|
63 def __truediv__(self,other): |
|
64 return self.arg / other |
|
65 |
|
66 def __rtruediv__(self,other): |
|
67 return other / self.arg |
|
68 |
|
69 def __floordiv__(self,other): |
|
70 return self.arg // other |
|
71 |
|
72 def __rfloordiv__(self,other): |
|
73 return other // self.arg |
|
74 |
|
75 def __pow__(self,other): |
|
76 return self.arg ** other |
|
77 |
|
78 def __rpow__(self,other): |
|
79 return other ** self.arg |
|
80 |
|
81 def __mod__(self,other): |
|
82 return self.arg % other |
|
83 |
|
84 def __rmod__(self,other): |
|
85 return other % self.arg |
|
86 |
|
87 def __cmp__(self, other): |
|
88 return cmp(self.arg, other) |
|
89 |
|
90 |
|
91 candidates = [2, 2L, 4.0, 2+0j, [1], (2,), None, |
|
92 MethodNumber(2), CoerceNumber(2)] |
|
93 |
|
94 infix_binops = [ '+', '-', '*', '**', '%', '//', '/' ] |
|
95 |
|
96 TE = TypeError |
|
97 # b = both normal and augmented give same result list |
|
98 # s = single result lists for normal and augmented |
|
99 # e = equals other results |
|
100 # result lists: ['+', '-', '*', '**', '%', '//', ('classic /', 'new /')] |
|
101 # ^^^^^^^^^^^^^^^^^^^^^^ |
|
102 # 2-tuple if results differ |
|
103 # else only one value |
|
104 infix_results = { |
|
105 # 2 |
|
106 (0,0): ('b', [4, 0, 4, 4, 0, 1, (1, 1.0)]), |
|
107 (0,1): ('e', (0,0)), |
|
108 (0,2): ('b', [6.0, -2.0, 8.0, 16.0, 2.0, 0.0, 0.5]), |
|
109 (0,3): ('b', [4+0j, 0+0j, 4+0j, 4+0j, 0+0j, 1+0j, 1+0j]), |
|
110 (0,4): ('b', [TE, TE, [1, 1], TE, TE, TE, TE]), |
|
111 (0,5): ('b', [TE, TE, (2, 2), TE, TE, TE, TE]), |
|
112 (0,6): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
113 (0,7): ('e', (0,0)), |
|
114 (0,8): ('e', (0,0)), |
|
115 |
|
116 # 2L |
|
117 (1,0): ('e', (0,0)), |
|
118 (1,1): ('e', (0,1)), |
|
119 (1,2): ('e', (0,2)), |
|
120 (1,3): ('e', (0,3)), |
|
121 (1,4): ('e', (0,4)), |
|
122 (1,5): ('e', (0,5)), |
|
123 (1,6): ('e', (0,6)), |
|
124 (1,7): ('e', (0,7)), |
|
125 (1,8): ('e', (0,8)), |
|
126 |
|
127 # 4.0 |
|
128 (2,0): ('b', [6.0, 2.0, 8.0, 16.0, 0.0, 2.0, 2.0]), |
|
129 (2,1): ('e', (2,0)), |
|
130 (2,2): ('b', [8.0, 0.0, 16.0, 256.0, 0.0, 1.0, 1.0]), |
|
131 (2,3): ('b', [6+0j, 2+0j, 8+0j, 16+0j, 0+0j, 2+0j, 2+0j]), |
|
132 (2,4): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
133 (2,5): ('e', (2,4)), |
|
134 (2,6): ('e', (2,4)), |
|
135 (2,7): ('e', (2,0)), |
|
136 (2,8): ('e', (2,0)), |
|
137 |
|
138 # (2+0j) |
|
139 (3,0): ('b', [4+0j, 0+0j, 4+0j, 4+0j, 0+0j, 1+0j, 1+0j]), |
|
140 (3,1): ('e', (3,0)), |
|
141 (3,2): ('b', [6+0j, -2+0j, 8+0j, 16+0j, 2+0j, 0+0j, 0.5+0j]), |
|
142 (3,3): ('b', [4+0j, 0+0j, 4+0j, 4+0j, 0+0j, 1+0j, 1+0j]), |
|
143 (3,4): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
144 (3,5): ('e', (3,4)), |
|
145 (3,6): ('e', (3,4)), |
|
146 (3,7): ('e', (3,0)), |
|
147 (3,8): ('e', (3,0)), |
|
148 |
|
149 # [1] |
|
150 (4,0): ('b', [TE, TE, [1, 1], TE, TE, TE, TE]), |
|
151 (4,1): ('e', (4,0)), |
|
152 (4,2): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
153 (4,3): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
154 (4,4): ('b', [[1, 1], TE, TE, TE, TE, TE, TE]), |
|
155 (4,5): ('s', [TE, TE, TE, TE, TE, TE, TE], [[1, 2], TE, TE, TE, TE, TE, TE]), |
|
156 (4,6): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
157 (4,7): ('e', (4,0)), |
|
158 (4,8): ('e', (4,0)), |
|
159 |
|
160 # (2,) |
|
161 (5,0): ('b', [TE, TE, (2, 2), TE, TE, TE, TE]), |
|
162 (5,1): ('e', (5,0)), |
|
163 (5,2): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
164 (5,3): ('e', (5,2)), |
|
165 (5,4): ('e', (5,2)), |
|
166 (5,5): ('b', [(2, 2), TE, TE, TE, TE, TE, TE]), |
|
167 (5,6): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
168 (5,7): ('e', (5,0)), |
|
169 (5,8): ('e', (5,0)), |
|
170 |
|
171 # None |
|
172 (6,0): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
|
173 (6,1): ('e', (6,0)), |
|
174 (6,2): ('e', (6,0)), |
|
175 (6,3): ('e', (6,0)), |
|
176 (6,4): ('e', (6,0)), |
|
177 (6,5): ('e', (6,0)), |
|
178 (6,6): ('e', (6,0)), |
|
179 (6,7): ('e', (6,0)), |
|
180 (6,8): ('e', (6,0)), |
|
181 |
|
182 # MethodNumber(2) |
|
183 (7,0): ('e', (0,0)), |
|
184 (7,1): ('e', (0,1)), |
|
185 (7,2): ('e', (0,2)), |
|
186 (7,3): ('e', (0,3)), |
|
187 (7,4): ('e', (0,4)), |
|
188 (7,5): ('e', (0,5)), |
|
189 (7,6): ('e', (0,6)), |
|
190 (7,7): ('e', (0,7)), |
|
191 (7,8): ('e', (0,8)), |
|
192 |
|
193 # CoerceNumber(2) |
|
194 (8,0): ('e', (0,0)), |
|
195 (8,1): ('e', (0,1)), |
|
196 (8,2): ('e', (0,2)), |
|
197 (8,3): ('e', (0,3)), |
|
198 (8,4): ('e', (0,4)), |
|
199 (8,5): ('e', (0,5)), |
|
200 (8,6): ('e', (0,6)), |
|
201 (8,7): ('e', (0,7)), |
|
202 (8,8): ('e', (0,8)), |
|
203 } |
|
204 |
|
205 def process_infix_results(): |
|
206 for key in sorted(infix_results): |
|
207 val = infix_results[key] |
|
208 if val[0] == 'e': |
|
209 infix_results[key] = infix_results[val[1]] |
|
210 else: |
|
211 if val[0] == 's': |
|
212 res = (val[1], val[2]) |
|
213 elif val[0] == 'b': |
|
214 res = (val[1], val[1]) |
|
215 for i in range(1): |
|
216 if isinstance(res[i][6], tuple): |
|
217 if 1/2 == 0: |
|
218 # testing with classic (floor) division |
|
219 res[i][6] = res[i][6][0] |
|
220 else: |
|
221 # testing with -Qnew |
|
222 res[i][6] = res[i][6][1] |
|
223 infix_results[key] = res |
|
224 |
|
225 |
|
226 |
|
227 process_infix_results() |
|
228 # now infix_results has two lists of results for every pairing. |
|
229 |
|
230 prefix_binops = [ 'divmod' ] |
|
231 prefix_results = [ |
|
232 [(1,0), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1,0)], |
|
233 [(1L,0L), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1L,0L)], |
|
234 [(2.0,0.0), (2.0,0.0), (1.0,0.0), ((2+0j),0j), TE, TE, TE, TE, (2.0,0.0)], |
|
235 [((1+0j),0j), ((1+0j),0j), (0j,(2+0j)), ((1+0j),0j), TE, TE, TE, TE, ((1+0j),0j)], |
|
236 [TE, TE, TE, TE, TE, TE, TE, TE, TE], |
|
237 [TE, TE, TE, TE, TE, TE, TE, TE, TE], |
|
238 [TE, TE, TE, TE, TE, TE, TE, TE, TE], |
|
239 [TE, TE, TE, TE, TE, TE, TE, TE, TE], |
|
240 [(1,0), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1,0)] |
|
241 ] |
|
242 |
|
243 def format_float(value): |
|
244 if abs(value) < 0.01: |
|
245 return '0.0' |
|
246 else: |
|
247 return '%.1f' % value |
|
248 |
|
249 # avoid testing platform fp quirks |
|
250 def format_result(value): |
|
251 if isinstance(value, complex): |
|
252 return '(%s + %sj)' % (format_float(value.real), |
|
253 format_float(value.imag)) |
|
254 elif isinstance(value, float): |
|
255 return format_float(value) |
|
256 return str(value) |
|
257 |
|
258 class CoercionTest(unittest.TestCase): |
|
259 def test_infix_binops(self): |
|
260 for ia, a in enumerate(candidates): |
|
261 for ib, b in enumerate(candidates): |
|
262 results = infix_results[(ia, ib)] |
|
263 for op, res, ires in zip(infix_binops, results[0], results[1]): |
|
264 if res is TE: |
|
265 self.assertRaises(TypeError, eval, |
|
266 'a %s b' % op, {'a': a, 'b': b}) |
|
267 else: |
|
268 self.assertEquals(format_result(res), |
|
269 format_result(eval('a %s b' % op)), |
|
270 '%s %s %s == %s failed' % (a, op, b, res)) |
|
271 try: |
|
272 z = copy.copy(a) |
|
273 except copy.Error: |
|
274 z = a # assume it has no inplace ops |
|
275 if ires is TE: |
|
276 try: |
|
277 exec 'z %s= b' % op |
|
278 except TypeError: |
|
279 pass |
|
280 else: |
|
281 self.fail("TypeError not raised") |
|
282 else: |
|
283 exec('z %s= b' % op) |
|
284 self.assertEquals(ires, z) |
|
285 |
|
286 def test_prefix_binops(self): |
|
287 for ia, a in enumerate(candidates): |
|
288 for ib, b in enumerate(candidates): |
|
289 for op in prefix_binops: |
|
290 res = prefix_results[ia][ib] |
|
291 if res is TE: |
|
292 self.assertRaises(TypeError, eval, |
|
293 '%s(a, b)' % op, {'a': a, 'b': b}) |
|
294 else: |
|
295 self.assertEquals(format_result(res), |
|
296 format_result(eval('%s(a, b)' % op)), |
|
297 '%s(%s, %s) == %s failed' % (op, a, b, res)) |
|
298 |
|
299 def test_cmptypes(self): |
|
300 # Built-in tp_compare slots expect their arguments to have the |
|
301 # same type, but a user-defined __coerce__ doesn't have to obey. |
|
302 # SF #980352 |
|
303 evil_coercer = CoerceTo(42) |
|
304 # Make sure these don't crash any more |
|
305 self.assertNotEquals(cmp(u'fish', evil_coercer), 0) |
|
306 self.assertNotEquals(cmp(slice(1), evil_coercer), 0) |
|
307 # ...but that this still works |
|
308 class WackyComparer(object): |
|
309 def __cmp__(slf, other): |
|
310 self.assert_(other == 42, 'expected evil_coercer, got %r' % other) |
|
311 return 0 |
|
312 __hash__ = None # Invalid cmp makes this unhashable |
|
313 self.assertEquals(cmp(WackyComparer(), evil_coercer), 0) |
|
314 # ...and classic classes too, since that code path is a little different |
|
315 class ClassicWackyComparer: |
|
316 def __cmp__(slf, other): |
|
317 self.assert_(other == 42, 'expected evil_coercer, got %r' % other) |
|
318 return 0 |
|
319 self.assertEquals(cmp(ClassicWackyComparer(), evil_coercer), 0) |
|
320 |
|
321 def test_infinite_rec_classic_classes(self): |
|
322 # if __coerce__() returns its arguments reversed it causes an infinite |
|
323 # recursion for classic classes. |
|
324 class Tester: |
|
325 def __coerce__(self, other): |
|
326 return other, self |
|
327 |
|
328 exc = TestFailed("__coerce__() returning its arguments reverse " |
|
329 "should raise RuntimeError") |
|
330 try: |
|
331 Tester() + 1 |
|
332 except (RuntimeError, TypeError): |
|
333 return |
|
334 except: |
|
335 raise exc |
|
336 else: |
|
337 raise exc |
|
338 |
|
339 def test_main(): |
|
340 warnings.filterwarnings("ignore", |
|
341 r'complex divmod\(\), // and % are deprecated', |
|
342 DeprecationWarning, |
|
343 r'test.test_coercion$') |
|
344 run_unittest(CoercionTest) |
|
345 |
|
346 if __name__ == "__main__": |
|
347 test_main() |