| import copy |
| import unittest |
| from test.test_support import run_unittest, TestFailed, check_warnings |
| |
| |
| # Fake a number that implements numeric methods through __coerce__ |
| class CoerceNumber: |
| def __init__(self, arg): |
| self.arg = arg |
| |
| def __repr__(self): |
| return '<CoerceNumber %s>' % repr(self.arg) |
| |
| def __coerce__(self, other): |
| if isinstance(other, CoerceNumber): |
| return self.arg, other.arg |
| else: |
| return (self.arg, other) |
| |
| # New-style class version of CoerceNumber |
| class CoerceTo(object): |
| def __init__(self, arg): |
| self.arg = arg |
| def __coerce__(self, other): |
| if isinstance(other, CoerceTo): |
| return self.arg, other.arg |
| else: |
| return self.arg, other |
| |
| |
| # Fake a number that implements numeric ops through methods. |
| class MethodNumber: |
| def __init__(self,arg): |
| self.arg = arg |
| |
| def __repr__(self): |
| return '<MethodNumber %s>' % repr(self.arg) |
| |
| def __add__(self,other): |
| return self.arg + other |
| |
| def __radd__(self,other): |
| return other + self.arg |
| |
| def __sub__(self,other): |
| return self.arg - other |
| |
| def __rsub__(self,other): |
| return other - self.arg |
| |
| def __mul__(self,other): |
| return self.arg * other |
| |
| def __rmul__(self,other): |
| return other * self.arg |
| |
| def __div__(self,other): |
| return self.arg / other |
| |
| def __rdiv__(self,other): |
| return other / self.arg |
| |
| def __truediv__(self,other): |
| return self.arg / other |
| |
| def __rtruediv__(self,other): |
| return other / self.arg |
| |
| def __floordiv__(self,other): |
| return self.arg // other |
| |
| def __rfloordiv__(self,other): |
| return other // self.arg |
| |
| def __pow__(self,other): |
| return self.arg ** other |
| |
| def __rpow__(self,other): |
| return other ** self.arg |
| |
| def __mod__(self,other): |
| return self.arg % other |
| |
| def __rmod__(self,other): |
| return other % self.arg |
| |
| def __cmp__(self, other): |
| return cmp(self.arg, other) |
| |
| |
| candidates = [2, 2L, 4.0, 2+0j, [1], (2,), None, |
| MethodNumber(2), CoerceNumber(2)] |
| |
| infix_binops = [ '+', '-', '*', '**', '%', '//', '/' ] |
| |
| TE = TypeError |
| # b = both normal and augmented give same result list |
| # s = single result lists for normal and augmented |
| # e = equals other results |
| # result lists: ['+', '-', '*', '**', '%', '//', ('classic /', 'new /')] |
| # ^^^^^^^^^^^^^^^^^^^^^^ |
| # 2-tuple if results differ |
| # else only one value |
| infix_results = { |
| # 2 |
| (0,0): ('b', [4, 0, 4, 4, 0, 1, (1, 1.0)]), |
| (0,1): ('e', (0,0)), |
| (0,2): ('b', [6.0, -2.0, 8.0, 16.0, 2.0, 0.0, 0.5]), |
| (0,3): ('b', [4+0j, 0+0j, 4+0j, 4+0j, 0+0j, 1+0j, 1+0j]), |
| (0,4): ('b', [TE, TE, [1, 1], TE, TE, TE, TE]), |
| (0,5): ('b', [TE, TE, (2, 2), TE, TE, TE, TE]), |
| (0,6): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (0,7): ('e', (0,0)), |
| (0,8): ('e', (0,0)), |
| |
| # 2L |
| (1,0): ('e', (0,0)), |
| (1,1): ('e', (0,1)), |
| (1,2): ('e', (0,2)), |
| (1,3): ('e', (0,3)), |
| (1,4): ('e', (0,4)), |
| (1,5): ('e', (0,5)), |
| (1,6): ('e', (0,6)), |
| (1,7): ('e', (0,7)), |
| (1,8): ('e', (0,8)), |
| |
| # 4.0 |
| (2,0): ('b', [6.0, 2.0, 8.0, 16.0, 0.0, 2.0, 2.0]), |
| (2,1): ('e', (2,0)), |
| (2,2): ('b', [8.0, 0.0, 16.0, 256.0, 0.0, 1.0, 1.0]), |
| (2,3): ('b', [6+0j, 2+0j, 8+0j, 16+0j, 0+0j, 2+0j, 2+0j]), |
| (2,4): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (2,5): ('e', (2,4)), |
| (2,6): ('e', (2,4)), |
| (2,7): ('e', (2,0)), |
| (2,8): ('e', (2,0)), |
| |
| # (2+0j) |
| (3,0): ('b', [4+0j, 0+0j, 4+0j, 4+0j, 0+0j, 1+0j, 1+0j]), |
| (3,1): ('e', (3,0)), |
| (3,2): ('b', [6+0j, -2+0j, 8+0j, 16+0j, 2+0j, 0+0j, 0.5+0j]), |
| (3,3): ('b', [4+0j, 0+0j, 4+0j, 4+0j, 0+0j, 1+0j, 1+0j]), |
| (3,4): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (3,5): ('e', (3,4)), |
| (3,6): ('e', (3,4)), |
| (3,7): ('e', (3,0)), |
| (3,8): ('e', (3,0)), |
| |
| # [1] |
| (4,0): ('b', [TE, TE, [1, 1], TE, TE, TE, TE]), |
| (4,1): ('e', (4,0)), |
| (4,2): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (4,3): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (4,4): ('b', [[1, 1], TE, TE, TE, TE, TE, TE]), |
| (4,5): ('s', [TE, TE, TE, TE, TE, TE, TE], [[1, 2], TE, TE, TE, TE, TE, TE]), |
| (4,6): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (4,7): ('e', (4,0)), |
| (4,8): ('e', (4,0)), |
| |
| # (2,) |
| (5,0): ('b', [TE, TE, (2, 2), TE, TE, TE, TE]), |
| (5,1): ('e', (5,0)), |
| (5,2): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (5,3): ('e', (5,2)), |
| (5,4): ('e', (5,2)), |
| (5,5): ('b', [(2, 2), TE, TE, TE, TE, TE, TE]), |
| (5,6): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (5,7): ('e', (5,0)), |
| (5,8): ('e', (5,0)), |
| |
| # None |
| (6,0): ('b', [TE, TE, TE, TE, TE, TE, TE]), |
| (6,1): ('e', (6,0)), |
| (6,2): ('e', (6,0)), |
| (6,3): ('e', (6,0)), |
| (6,4): ('e', (6,0)), |
| (6,5): ('e', (6,0)), |
| (6,6): ('e', (6,0)), |
| (6,7): ('e', (6,0)), |
| (6,8): ('e', (6,0)), |
| |
| # MethodNumber(2) |
| (7,0): ('e', (0,0)), |
| (7,1): ('e', (0,1)), |
| (7,2): ('e', (0,2)), |
| (7,3): ('e', (0,3)), |
| (7,4): ('e', (0,4)), |
| (7,5): ('e', (0,5)), |
| (7,6): ('e', (0,6)), |
| (7,7): ('e', (0,7)), |
| (7,8): ('e', (0,8)), |
| |
| # CoerceNumber(2) |
| (8,0): ('e', (0,0)), |
| (8,1): ('e', (0,1)), |
| (8,2): ('e', (0,2)), |
| (8,3): ('e', (0,3)), |
| (8,4): ('e', (0,4)), |
| (8,5): ('e', (0,5)), |
| (8,6): ('e', (0,6)), |
| (8,7): ('e', (0,7)), |
| (8,8): ('e', (0,8)), |
| } |
| |
| def process_infix_results(): |
| for key in sorted(infix_results): |
| val = infix_results[key] |
| if val[0] == 'e': |
| infix_results[key] = infix_results[val[1]] |
| else: |
| if val[0] == 's': |
| res = (val[1], val[2]) |
| elif val[0] == 'b': |
| res = (val[1], val[1]) |
| for i in range(1): |
| if isinstance(res[i][6], tuple): |
| if 1/2 == 0: |
| # testing with classic (floor) division |
| res[i][6] = res[i][6][0] |
| else: |
| # testing with -Qnew |
| res[i][6] = res[i][6][1] |
| infix_results[key] = res |
| |
| |
| with check_warnings(("classic (int|long) division", DeprecationWarning), |
| quiet=True): |
| process_infix_results() |
| # now infix_results has two lists of results for every pairing. |
| |
| prefix_binops = [ 'divmod' ] |
| prefix_results = [ |
| [(1,0), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1,0)], |
| [(1L,0L), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1L,0L)], |
| [(2.0,0.0), (2.0,0.0), (1.0,0.0), ((2+0j),0j), TE, TE, TE, TE, (2.0,0.0)], |
| [((1+0j),0j), ((1+0j),0j), (0j,(2+0j)), ((1+0j),0j), TE, TE, TE, TE, ((1+0j),0j)], |
| [TE, TE, TE, TE, TE, TE, TE, TE, TE], |
| [TE, TE, TE, TE, TE, TE, TE, TE, TE], |
| [TE, TE, TE, TE, TE, TE, TE, TE, TE], |
| [TE, TE, TE, TE, TE, TE, TE, TE, TE], |
| [(1,0), (1L,0L), (0.0,2.0), ((1+0j),0j), TE, TE, TE, TE, (1,0)] |
| ] |
| |
| def format_float(value): |
| if abs(value) < 0.01: |
| return '0.0' |
| else: |
| return '%.1f' % value |
| |
| # avoid testing platform fp quirks |
| def format_result(value): |
| if isinstance(value, complex): |
| return '(%s + %sj)' % (format_float(value.real), |
| format_float(value.imag)) |
| elif isinstance(value, float): |
| return format_float(value) |
| return str(value) |
| |
| class CoercionTest(unittest.TestCase): |
| def test_infix_binops(self): |
| for ia, a in enumerate(candidates): |
| for ib, b in enumerate(candidates): |
| results = infix_results[(ia, ib)] |
| for op, res, ires in zip(infix_binops, results[0], results[1]): |
| if res is TE: |
| self.assertRaises(TypeError, eval, |
| 'a %s b' % op, {'a': a, 'b': b}) |
| else: |
| self.assertEqual(format_result(res), |
| format_result(eval('a %s b' % op)), |
| '%s %s %s == %s failed' % (a, op, b, res)) |
| try: |
| z = copy.copy(a) |
| except copy.Error: |
| z = a # assume it has no inplace ops |
| if ires is TE: |
| try: |
| exec 'z %s= b' % op |
| except TypeError: |
| pass |
| else: |
| self.fail("TypeError not raised") |
| else: |
| exec('z %s= b' % op) |
| self.assertEqual(ires, z) |
| |
| def test_prefix_binops(self): |
| for ia, a in enumerate(candidates): |
| for ib, b in enumerate(candidates): |
| for op in prefix_binops: |
| res = prefix_results[ia][ib] |
| if res is TE: |
| self.assertRaises(TypeError, eval, |
| '%s(a, b)' % op, {'a': a, 'b': b}) |
| else: |
| self.assertEqual(format_result(res), |
| format_result(eval('%s(a, b)' % op)), |
| '%s(%s, %s) == %s failed' % (op, a, b, res)) |
| |
| def test_cmptypes(self): |
| # Built-in tp_compare slots expect their arguments to have the |
| # same type, but a user-defined __coerce__ doesn't have to obey. |
| # SF #980352 |
| evil_coercer = CoerceTo(42) |
| # Make sure these don't crash any more |
| self.assertNotEqual(cmp(u'fish', evil_coercer), 0) |
| self.assertNotEqual(cmp(slice(1), evil_coercer), 0) |
| # ...but that this still works |
| class WackyComparer(object): |
| def __cmp__(slf, other): |
| self.assertTrue(other == 42, 'expected evil_coercer, got %r' % other) |
| return 0 |
| __hash__ = None # Invalid cmp makes this unhashable |
| self.assertEqual(cmp(WackyComparer(), evil_coercer), 0) |
| # ...and classic classes too, since that code path is a little different |
| class ClassicWackyComparer: |
| def __cmp__(slf, other): |
| self.assertTrue(other == 42, 'expected evil_coercer, got %r' % other) |
| return 0 |
| self.assertEqual(cmp(ClassicWackyComparer(), evil_coercer), 0) |
| |
| def test_infinite_rec_classic_classes(self): |
| # if __coerce__() returns its arguments reversed it causes an infinite |
| # recursion for classic classes. |
| class Tester: |
| def __coerce__(self, other): |
| return other, self |
| |
| exc = TestFailed("__coerce__() returning its arguments reverse " |
| "should raise RuntimeError") |
| try: |
| Tester() + 1 |
| except (RuntimeError, TypeError): |
| return |
| except: |
| raise exc |
| else: |
| raise exc |
| |
| def test_main(): |
| with check_warnings(("complex divmod.., // and % are deprecated", |
| DeprecationWarning), |
| ("classic (int|long) division", DeprecationWarning), |
| quiet=True): |
| run_unittest(CoercionTest) |
| |
| if __name__ == "__main__": |
| test_main() |