Index: test_class.py =================================================================== --- test_class.py (revision 54043) +++ test_class.py (working copy) @@ -1,5 +1,9 @@ "Test the functionality of Python classes implementing operators." +import unittest +import sys + +from test import test_support from test.test_support import TestFailed testmeths = [ @@ -64,55 +68,64 @@ # "setattr", # "delattr", +callLst = [] + +def trackCall(f): + def track(*args, **kwargs): + global callLst + callLst.append((f.__name__, args)) + return f(*args, **kwargs) + return track + class AllTests: + trackCall = trackCall + + @trackCall def __coerce__(self, *args): - print "__coerce__:", args return (self,) + args + @trackCall def __hash__(self, *args): - print "__hash__:", args return hash(id(self)) + @trackCall def __str__(self, *args): - print "__str__:", args return "AllTests" + @trackCall def __repr__(self, *args): - print "__repr__:", args return "AllTests" + @trackCall def __int__(self, *args): - print "__int__:", args return 1 + @trackCall def __float__(self, *args): - print "__float__:", args return 1.0 + @trackCall def __long__(self, *args): - print "__long__:", args return 1L + @trackCall def __oct__(self, *args): - print "__oct__:", args return '01' + @trackCall def __hex__(self, *args): - print "__hex__:", args return '0x1' + @trackCall def __cmp__(self, *args): - print "__cmp__:", args return 0 - def __del__(self, *args): - print "__del__:", args +# Synthesize all the other AllTests methods from the names in testmeths. -# Synthesize AllTests methods from the names in testmeths. - method_template = """\ +@trackCall def __%(method)s__(self, *args): - print "__%(method)s__:", args + pass """ for method in testmeths: @@ -120,293 +133,370 @@ del method, method_template -# this also tests __init__ of course. -testme = AllTests() +# return values of some method are type-checked +class BadTypeClass: + def __int__(self): + return None + __float__ = __int__ + __long__ = __int__ + __str__ = __int__ + __repr__ = __int__ + __oct__ = __int__ + __hex__ = __int__ -# Binary operations +class ClassTests(unittest.TestCase): + def setUp(self): + callLst[:] = [] + + def assertLastCallWas(self, fname, args): + self.assertEquals((fname, args), callLst[-1]) + + def testInit(self): + foo = AllTests() + self.assertLastCallWas("__init__", (foo,)) -testme + 1 -1 + testme + def testBinaryOps(self): + testme = AllTests() + # Binary operations -testme - 1 -1 - testme + testme + 1 + self.assertLastCallWas("__add__", (testme, 1)) + 1 + testme + self.assertLastCallWas("__radd__", (testme, 1)) + + testme - 1 + self.assertLastCallWas("__sub__", (testme, 1)) + 1 - testme + self.assertLastCallWas("__rsub__", (testme, 1)) + + testme * 1 + self.assertLastCallWas("__mul__", (testme, 1)) + 1 * testme + self.assertLastCallWas("__rmul__", (testme, 1)) + + if 1/2 == 0: + testme / 1 + self.assertLastCallWas("__div__", (testme, 1)) + 1 / testme + self.assertLastCallWas("__rdiv__", (testme, 1)) -testme * 1 -1 * testme + testme % 1 + self.assertLastCallWas("__mod__", (testme, 1)) + 1 % testme + self.assertLastCallWas("__rmod__", (testme, 1)) -if 1/2 == 0: - testme / 1 - 1 / testme -else: - # True division is in effect, so "/" doesn't map to __div__ etc; but - # the canned expected-output file requires that __div__ etc get called. - testme.__coerce__(1) - testme.__div__(1) - testme.__coerce__(1) - testme.__rdiv__(1) + divmod(testme,1) + self.assertLastCallWas("__divmod__", (testme, 1)) + divmod(1, testme) + self.assertLastCallWas("__rdivmod__", (testme, 1)) -testme % 1 -1 % testme + testme ** 1 + self.assertLastCallWas("__pow__", (testme, 1)) + 1 ** testme + self.assertLastCallWas("__rpow__", (testme, 1)) -divmod(testme,1) -divmod(1, testme) + testme >> 1 + self.assertLastCallWas("__rshift__", (testme, 1)) + 1 >> testme + self.assertLastCallWas("__rrshift__", (testme, 1)) -testme ** 1 -1 ** testme + testme << 1 + self.assertLastCallWas("__lshift__", (testme, 1)) + 1 << testme + self.assertLastCallWas("__rlshift__", (testme, 1)) -testme >> 1 -1 >> testme + testme & 1 + self.assertLastCallWas("__and__", (testme, 1)) + 1 & testme + self.assertLastCallWas("__rand__", (testme, 1)) -testme << 1 -1 << testme + testme | 1 + self.assertLastCallWas("__or__", (testme, 1)) + 1 | testme + self.assertLastCallWas("__ror__", (testme, 1)) -testme & 1 -1 & testme + testme ^ 1 + self.assertLastCallWas("__xor__", (testme, 1)) + 1 ^ testme + self.assertLastCallWas("__rxor__", (testme, 1)) -testme | 1 -1 | testme + def testListAndDictOps(self): + testme = AllTests() + + # List/dict operations + + class Empty: pass -testme ^ 1 -1 ^ testme + try: + 1 in Empty() + self.fail('failed, should have raised TypeError') + except TypeError: + pass + 1 in testme + self.assertLastCallWas('__contains__', (testme, 1)) -# List/dict operations + testme[1] + self.assertLastCallWas('__getitem__', (testme, 1)) + testme[1] = 1 + self.assertLastCallWas('__setitem__', (testme, 1, 1)) + del testme[1] + self.assertLastCallWas('__delitem__', (testme, 1)) -class Empty: pass + testme[:42] + self.assertLastCallWas('__getslice__', (testme, 0, 42)) + testme[:42] = "The Answer" + self.assertLastCallWas('__setslice__', (testme, 0, 42, "The Answer")) + del testme[:42] + self.assertLastCallWas('__delslice__', (testme, 0, 42)) -try: - 1 in Empty() - print 'failed, should have raised TypeError' -except TypeError: - pass + testme[2:1024:10] + self.assertLastCallWas('__getitem__', (testme, slice(2, 1024, 10))) + testme[2:1024:10] = "A lot" + self.assertLastCallWas('__setitem__', (testme, slice(2, 1024, 10), "A lot")) + del testme[2:1024:10] + self.assertLastCallWas('__delitem__', (testme, slice(2, 1024, 10))) -1 in testme + testme[:42, ..., :24:, 24, 100] + self.assertLastCallWas('__getitem__', (testme, (slice(None, 42, None), Ellipsis, slice(None, 24, None), 24, 100))) + testme[:42, ..., :24:, 24, 100] = "Strange" + self.assertLastCallWas('__setitem__', (testme, (slice(None, 42, None), Ellipsis, slice(None, 24, None), 24, 100), "Strange")) + del testme[:42, ..., :24:, 24, 100] + self.assertLastCallWas('__delitem__', (testme, (slice(None, 42, None), Ellipsis, slice(None, 24, None), 24, 100))) -testme[1] -testme[1] = 1 -del testme[1] + # Now remove the slice hooks to see if converting normal slices to slice + # object works. -testme[:42] -testme[:42] = "The Answer" -del testme[:42] + del AllTests.__getslice__ + del AllTests.__setslice__ + del AllTests.__delslice__ -testme[2:1024:10] -testme[2:1024:10] = "A lot" -del testme[2:1024:10] + if sys.platform[:4] != 'java': + # XXX when using new-style classes the slice testme[:42] produces slice(None, 42, None) + # instead of slice(0, 42, None). py3k will have to change this test. + testme[:42] + self.assertLastCallWas('__getitem__', (testme, slice(0, 42, None))) + testme[:42] = "The Answer" + self.assertLastCallWas('__setitem__', (testme, slice(0, 42, None), "The Answer")) + del testme[:42] + self.assertLastCallWas('__delitem__', (testme, slice(0, 42, None))) + else: + # This works under Jython, but the actual slice values are + # different. + # XXX Jython devs need to fill in these tests + pass -testme[:42, ..., :24:, 24, 100] -testme[:42, ..., :24:, 24, 100] = "Strange" -del testme[:42, ..., :24:, 24, 100] + def testUnaryOps(self): + # Unary operations + testme = AllTests() + -testme + self.assertLastCallWas('__neg__', testme) + +testme + self.assertLastCallWas('__pos__', testme) + abs(testme) + self.assertLastCallWas('__abs__', testme) + int(testme) + self.assertLastCallWas('__int__', testme) + long(testme) + self.assertLastCallWas('__long__', testme) + float(testme) + self.assertLastCallWas('__float__', testme) + oct(testme) + self.assertLastCallWas('__oct__', testme) + hex(testme) + self.assertLastCallWas('__hex__', testme) -# Now remove the slice hooks to see if converting normal slices to slice -# object works. -del AllTests.__getslice__ -del AllTests.__setslice__ -del AllTests.__delslice__ + def testMisc(self): + testme = AllTests() -import sys -if sys.platform[:4] != 'java': - testme[:42] - testme[:42] = "The Answer" - del testme[:42] -else: - # This works under Jython, but the actual slice values are - # different. - print "__getitem__: (slice(0, 42, None),)" - print "__setitem__: (slice(0, 42, None), 'The Answer')" - print "__delitem__: (slice(0, 42, None),)" + # And the rest... + + hash(testme) + self.assertLastCallWas('__hash__', testme) + repr(testme) + self.assertLastCallWas('__repr__', testme) + str(testme) + self.assertLastCallWas('__str__', testme) + + testme == 1 + self.assertLastCallWas('__cmp__', testme) + testme < 1 + self.assertLastCallWas('__cmp__', testme) + testme > 1 + self.assertLastCallWas('__cmp__', testme) + testme <> 1 # XXX kill this in py3k + self.assertLastCallWas('__cmp__', testme) + testme != 1 + self.assertLastCallWas('__cmp__', testme) + 1 == testme + self.assertLastCallWas('__cmp__', testme) + 1 < testme + self.assertLastCallWas('__cmp__', testme) + 1 > testme + self.assertLastCallWas('__cmp__', testme) + 1 <> testme + self.assertLastCallWas('__cmp__', testme) + 1 != testme + self.assertLastCallWas('__cmp__', testme) + + if sys.platform[:4] == 'java': + import java + java.lang.System.gc() -# Unary operations --testme -+testme -abs(testme) -int(testme) -long(testme) -float(testme) -oct(testme) -hex(testme) + def testGetSetAndDel(self): + # Interfering tests + class ExtraTests(AllTests): + @trackCall + def __getattr__(self, *args): + return "SomeVal" + + @trackCall + def __setattr__(self, *args): + pass -# And the rest... + @trackCall + def __delattr__(self, *args): + pass -hash(testme) -repr(testme) -str(testme) + testme = ExtraTests() + testme.spam + self.assertLastCallWas('__getattr__', (testme, "spam")) + testme.eggs = "spam, spam, spam and ham" + self.assertLastCallWas('__setattr__', (testme, "eggs", "spam, spam, spam and ham")) + del testme.cardinal + self.assertLastCallWas('__delattr__', (testme, "cardinal")) -testme == 1 -testme < 1 -testme > 1 -testme <> 1 -testme != 1 -1 == testme -1 < testme -1 > testme -1 <> testme -1 != testme + def testDel(self): + global x + x = None + class DelTest: + def __del__(self): + global x + x = "crab people, crab people" + testme = DelTest() + del testme + import gc + gc.collect() + self.assert_(x) + + def testBadTypeReturned(self): + self.assertRaises(TypeError, lambda: int(BadTypeClass())) + self.assertRaises(TypeError, lambda: float(BadTypeClass())) + self.assertRaises(TypeError, lambda: long(BadTypeClass())) + self.assertRaises(TypeError, lambda: str(BadTypeClass())) + self.assertRaises(TypeError, lambda: repr(BadTypeClass())) + self.assertRaises(TypeError, lambda: oct(BadTypeClass())) + self.assertRaises(TypeError, lambda: hex(BadTypeClass())) + + def testMixIntsAndLongs(self): + # mixing up ints and longs is okay + class IntLongMixClass: + def __int__(self): + return 0L -# This test has to be last (duh.) + def __long__(self): + return 0 -del testme -if sys.platform[:4] == 'java': - import java - java.lang.System.gc() + int(IntLongMixClass()) + long(IntLongMixClass()) -# Interfering tests + def testHashStuff(self): + # Test correct errors from hash() on objects with comparisons but no __hash__ -class ExtraTests: - def __getattr__(self, *args): - print "__getattr__:", args - return "SomeVal" + class C0: + pass - def __setattr__(self, *args): - print "__setattr__:", args + hash(C0()) # This should work; the next two should raise TypeError - def __delattr__(self, *args): - print "__delattr__:", args + class C1: + def __cmp__(self, other): return 0 -testme = ExtraTests() -testme.spam -testme.eggs = "spam, spam, spam and ham" -del testme.cardinal + self.assertRaises(TypeError, hash, C1()) + + class C2: + def __eq__(self, other): return 1 + self.assertRaises(TypeError, hash, C2()) -# return values of some method are type-checked -class BadTypeClass: - def __int__(self): - return None - __float__ = __int__ - __long__ = __int__ - __str__ = __int__ - __repr__ = __int__ - __oct__ = __int__ - __hex__ = __int__ -def check_exc(stmt, exception): - """Raise TestFailed if executing 'stmt' does not raise 'exception' - """ - try: - exec stmt - except exception: - pass - else: - raise TestFailed, "%s should raise %s" % (stmt, exception) + def testSFBug532646(self): + # Test for SF bug 532646 -check_exc("int(BadTypeClass())", TypeError) -check_exc("float(BadTypeClass())", TypeError) -check_exc("long(BadTypeClass())", TypeError) -check_exc("str(BadTypeClass())", TypeError) -check_exc("repr(BadTypeClass())", TypeError) -check_exc("oct(BadTypeClass())", TypeError) -check_exc("hex(BadTypeClass())", TypeError) + class A: + pass + A.__call__ = A() + a = A() + + try: + a() # This should not segfault + self.fail("how could this not have overflowed the stack?") + except RuntimeError: + pass -# mixing up ints and longs is okay -class IntLongMixClass: - def __int__(self): - return 0L + def testForExceptionsRaisedInInstanceGetattr2(self): + # Tests for exceptions raised in instance_getattr2(). - def __long__(self): - return 0 + def booh(self): + raise AttributeError, "booh" -try: - int(IntLongMixClass()) -except TypeError: - raise TestFailed, "TypeError should not be raised" + class A: + a = property(booh) + try: + A().a # Raised AttributeError: A instance has no attribute 'a' + except AttributeError, x: + if str(x) != "booh": + print "attribute error for A().a got masked:", str(x) -try: - long(IntLongMixClass()) -except TypeError: - raise TestFailed, "TypeError should not be raised" + class E: + __eq__ = property(booh) + E() == E() # In debug mode, caused a C-level assert() to fail + class I: + __init__ = property(booh) + try: + I() # In debug mode, printed XXX undetected error and raises AttributeError + except AttributeError, x: + pass + else: + print "attribute error for I.__init__ got masked" -# Test correct errors from hash() on objects with comparisons but no __hash__ + def testHashComparisonOfMethods(self): + # Test comparison and hash of methods + class A: + def __init__(self, x): + self.x = x + def f(self): + pass + def g(self): + pass + def __eq__(self, other): + return self.x == other.x + def __hash__(self): + return self.x + class B(A): + pass -class C0: - pass + a1 = A(1) + a2 = A(2) + assert a1.f == a1.f + assert a1.f != a2.f + assert a1.f != a1.g + assert a1.f == A(1).f + assert hash(a1.f) == hash(a1.f) + assert hash(a1.f) == hash(A(1).f) -hash(C0()) # This should work; the next two should raise TypeError + assert A.f != a1.f + assert A.f != A.g + assert B.f == A.f + assert hash(B.f) == hash(A.f) -class C1: - def __cmp__(self, other): return 0 + # the following triggers a SystemError in 2.4 + a = A(hash(A.f.im_func)^(-1)) + hash(a.f) -check_exc("hash(C1())", TypeError) +if __name__=='__main__': + test_support.run_unittest(ClassTests) -class C2: - def __eq__(self, other): return 1 - -check_exc("hash(C2())", TypeError) - -# Test for SF bug 532646 - -class A: - pass -A.__call__ = A() -a = A() -try: - a() # This should not segfault -except RuntimeError: - pass -else: - raise TestFailed, "how could this not have overflowed the stack?" - - -# Tests for exceptions raised in instance_getattr2(). - -def booh(self): - raise AttributeError, "booh" - -class A: - a = property(booh) -try: - A().a # Raised AttributeError: A instance has no attribute 'a' -except AttributeError, x: - if str(x) != "booh": - print "attribute error for A().a got masked:", str(x) - -class E: - __eq__ = property(booh) -E() == E() # In debug mode, caused a C-level assert() to fail - -class I: - __init__ = property(booh) -try: - I() # In debug mode, printed XXX undetected error and raises AttributeError -except AttributeError, x: - pass -else: - print "attribute error for I.__init__ got masked" - - -# Test comparison and hash of methods -class A: - def __init__(self, x): - self.x = x - def f(self): - pass - def g(self): - pass - def __eq__(self, other): - return self.x == other.x - def __hash__(self): - return self.x -class B(A): - pass - -a1 = A(1) -a2 = A(2) -assert a1.f == a1.f -assert a1.f != a2.f -assert a1.f != a1.g -assert a1.f == A(1).f -assert hash(a1.f) == hash(a1.f) -assert hash(a1.f) == hash(A(1).f) - -assert A.f != a1.f -assert A.f != A.g -assert B.f == A.f -assert hash(B.f) == hash(A.f) - -# the following triggers a SystemError in 2.4 -a = A(hash(A.f.im_func)^(-1)) -hash(a.f)