1 | import copy
|
---|
2 | import unittest
|
---|
3 | from test.test_support import run_unittest, TestFailed, check_warnings
|
---|
4 |
|
---|
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 | with check_warnings(("classic (int|long) division", DeprecationWarning),
|
---|
227 | quiet=True):
|
---|
228 | process_infix_results()
|
---|
229 | # now infix_results has two lists of results for every pairing.
|
---|
230 |
|
---|
231 | prefix_binops = [ 'divmod' ]
|
---|
232 | prefix_results = [
|
---|
233 | [(1,0), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1,0)],
|
---|
234 | [(1L,0L), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1L,0L)],
|
---|
235 | [(2.0,0.0), (2.0,0.0), (1.0,0.0), ((2+0j),0j), TE, TE, TE, TE, (2.0,0.0)],
|
---|
236 | [((1+0j),0j), ((1+0j),0j), (0j,(2+0j)), ((1+0j),0j), TE, TE, TE, TE, ((1+0j),0j)],
|
---|
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 | [TE, TE, TE, TE, TE, TE, TE, TE, TE],
|
---|
241 | [(1,0), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1,0)]
|
---|
242 | ]
|
---|
243 |
|
---|
244 | def format_float(value):
|
---|
245 | if abs(value) < 0.01:
|
---|
246 | return '0.0'
|
---|
247 | else:
|
---|
248 | return '%.1f' % value
|
---|
249 |
|
---|
250 | # avoid testing platform fp quirks
|
---|
251 | def format_result(value):
|
---|
252 | if isinstance(value, complex):
|
---|
253 | return '(%s + %sj)' % (format_float(value.real),
|
---|
254 | format_float(value.imag))
|
---|
255 | elif isinstance(value, float):
|
---|
256 | return format_float(value)
|
---|
257 | return str(value)
|
---|
258 |
|
---|
259 | class CoercionTest(unittest.TestCase):
|
---|
260 | def test_infix_binops(self):
|
---|
261 | for ia, a in enumerate(candidates):
|
---|
262 | for ib, b in enumerate(candidates):
|
---|
263 | results = infix_results[(ia, ib)]
|
---|
264 | for op, res, ires in zip(infix_binops, results[0], results[1]):
|
---|
265 | if res is TE:
|
---|
266 | self.assertRaises(TypeError, eval,
|
---|
267 | 'a %s b' % op, {'a': a, 'b': b})
|
---|
268 | else:
|
---|
269 | self.assertEqual(format_result(res),
|
---|
270 | format_result(eval('a %s b' % op)),
|
---|
271 | '%s %s %s == %s failed' % (a, op, b, res))
|
---|
272 | try:
|
---|
273 | z = copy.copy(a)
|
---|
274 | except copy.Error:
|
---|
275 | z = a # assume it has no inplace ops
|
---|
276 | if ires is TE:
|
---|
277 | try:
|
---|
278 | exec 'z %s= b' % op
|
---|
279 | except TypeError:
|
---|
280 | pass
|
---|
281 | else:
|
---|
282 | self.fail("TypeError not raised")
|
---|
283 | else:
|
---|
284 | exec('z %s= b' % op)
|
---|
285 | self.assertEqual(ires, z)
|
---|
286 |
|
---|
287 | def test_prefix_binops(self):
|
---|
288 | for ia, a in enumerate(candidates):
|
---|
289 | for ib, b in enumerate(candidates):
|
---|
290 | for op in prefix_binops:
|
---|
291 | res = prefix_results[ia][ib]
|
---|
292 | if res is TE:
|
---|
293 | self.assertRaises(TypeError, eval,
|
---|
294 | '%s(a, b)' % op, {'a': a, 'b': b})
|
---|
295 | else:
|
---|
296 | self.assertEqual(format_result(res),
|
---|
297 | format_result(eval('%s(a, b)' % op)),
|
---|
298 | '%s(%s, %s) == %s failed' % (op, a, b, res))
|
---|
299 |
|
---|
300 | def test_cmptypes(self):
|
---|
301 | # Built-in tp_compare slots expect their arguments to have the
|
---|
302 | # same type, but a user-defined __coerce__ doesn't have to obey.
|
---|
303 | # SF #980352
|
---|
304 | evil_coercer = CoerceTo(42)
|
---|
305 | # Make sure these don't crash any more
|
---|
306 | self.assertNotEqual(cmp(u'fish', evil_coercer), 0)
|
---|
307 | self.assertNotEqual(cmp(slice(1), evil_coercer), 0)
|
---|
308 | # ...but that this still works
|
---|
309 | class WackyComparer(object):
|
---|
310 | def __cmp__(slf, other):
|
---|
311 | self.assertTrue(other == 42, 'expected evil_coercer, got %r' % other)
|
---|
312 | return 0
|
---|
313 | __hash__ = None # Invalid cmp makes this unhashable
|
---|
314 | self.assertEqual(cmp(WackyComparer(), evil_coercer), 0)
|
---|
315 | # ...and classic classes too, since that code path is a little different
|
---|
316 | class ClassicWackyComparer:
|
---|
317 | def __cmp__(slf, other):
|
---|
318 | self.assertTrue(other == 42, 'expected evil_coercer, got %r' % other)
|
---|
319 | return 0
|
---|
320 | self.assertEqual(cmp(ClassicWackyComparer(), evil_coercer), 0)
|
---|
321 |
|
---|
322 | def test_infinite_rec_classic_classes(self):
|
---|
323 | # if __coerce__() returns its arguments reversed it causes an infinite
|
---|
324 | # recursion for classic classes.
|
---|
325 | class Tester:
|
---|
326 | def __coerce__(self, other):
|
---|
327 | return other, self
|
---|
328 |
|
---|
329 | exc = TestFailed("__coerce__() returning its arguments reverse "
|
---|
330 | "should raise RuntimeError")
|
---|
331 | try:
|
---|
332 | Tester() + 1
|
---|
333 | except (RuntimeError, TypeError):
|
---|
334 | return
|
---|
335 | except:
|
---|
336 | raise exc
|
---|
337 | else:
|
---|
338 | raise exc
|
---|
339 |
|
---|
340 | def test_main():
|
---|
341 | with check_warnings(("complex divmod.., // and % are deprecated",
|
---|
342 | DeprecationWarning),
|
---|
343 | ("classic (int|long) division", DeprecationWarning),
|
---|
344 | quiet=True):
|
---|
345 | run_unittest(CoercionTest)
|
---|
346 |
|
---|
347 | if __name__ == "__main__":
|
---|
348 | test_main()
|
---|