Index: test_class.py =================================================================== --- test_class.py (revision 54933) +++ 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,62 @@ # "setattr", # "delattr", +callLst = [] +def trackCall(f): + def track(*args, **kwargs): + 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 +131,486 @@ del method, method_template -# this also tests __init__ of course. -testme = AllTests() +class ClassTests(unittest.TestCase): + def setUp(self): + callLst[:] = [] + + def assertCallStack(self, calls): + # We don't use assertEquals here because it creates an infinite + # loop if it prints an error message + if (calls != callLst): + self.fail("Expected call list:\n %s\ndoes not match actual call list\n %s" %( + calls, callLst[:])) -# Binary operations + def testInit(self): + foo = AllTests() + self.assertCallStack([("__init__", (foo,))]) -testme + 1 -1 + testme + def testBinaryOps(self): + testme = AllTests() + # Binary operations -testme - 1 -1 - testme + callLst[:] = [] + testme + 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__add__", (testme, 1))]) + + callLst[:] = [] + 1 + testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__radd__", (testme, 1))]) -testme * 1 -1 * testme + callLst[:] = [] + testme - 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__sub__", (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) + callLst[:] = [] + 1 - testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rsub__", (testme, 1))]) + + callLst[:] = [] + testme * 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__mul__", (testme, 1))]) + + callLst[:] = [] + 1 * testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rmul__", (testme, 1))]) + + if 1/2 == 0: + callLst[:] = [] + testme / 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__div__", (testme, 1))]) -testme % 1 -1 % testme -divmod(testme,1) -divmod(1, testme) + callLst[:] = [] + 1 / testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rdiv__", (testme, 1))]) -testme ** 1 -1 ** testme + callLst[:] = [] + testme % 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__mod__", (testme, 1))]) + + callLst[:] = [] + 1 % testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rmod__", (testme, 1))]) -testme >> 1 -1 >> testme -testme << 1 -1 << testme + callLst[:] = [] + divmod(testme,1) + self.assertCallStack([("__coerce__", (testme, 1)), ("__divmod__", (testme, 1))]) -testme & 1 -1 & testme + callLst[:] = [] + divmod(1, testme) + self.assertCallStack([("__coerce__", (testme, 1)), ("__rdivmod__", (testme, 1))]) -testme | 1 -1 | testme + callLst[:] = [] + testme ** 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__pow__", (testme, 1))]) + + callLst[:] = [] + 1 ** testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rpow__", (testme, 1))]) -testme ^ 1 -1 ^ testme + callLst[:] = [] + testme >> 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__rshift__", (testme, 1))]) + callLst[:] = [] + 1 >> testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rrshift__", (testme, 1))]) -# List/dict operations + callLst[:] = [] + testme << 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__lshift__", (testme, 1))]) -class Empty: pass + callLst[:] = [] + 1 << testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rlshift__", (testme, 1))]) -try: - 1 in Empty() - print 'failed, should have raised TypeError' -except TypeError: - pass + callLst[:] = [] + testme & 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__and__", (testme, 1))]) -1 in testme + callLst[:] = [] + 1 & testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rand__", (testme, 1))]) -testme[1] -testme[1] = 1 -del testme[1] + callLst[:] = [] + testme | 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__or__", (testme, 1))]) -testme[:42] -testme[:42] = "The Answer" -del testme[:42] + callLst[:] = [] + 1 | testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__ror__", (testme, 1))]) -testme[2:1024:10] -testme[2:1024:10] = "A lot" -del testme[2:1024:10] + callLst[:] = [] + testme ^ 1 + self.assertCallStack([("__coerce__", (testme, 1)), ("__xor__", (testme, 1))]) -testme[:42, ..., :24:, 24, 100] -testme[:42, ..., :24:, 24, 100] = "Strange" -del testme[:42, ..., :24:, 24, 100] + callLst[:] = [] + 1 ^ testme + self.assertCallStack([("__coerce__", (testme, 1)), ("__rxor__", (testme, 1))]) + def testListAndDictOps(self): + testme = AllTests() + + # List/dict operations + + class Empty: pass -# Now remove the slice hooks to see if converting normal slices to slice -# object works. + try: + 1 in Empty() + self.fail('failed, should have raised TypeError') + except TypeError: + pass -del AllTests.__getslice__ -del AllTests.__setslice__ -del AllTests.__delslice__ + callLst[:] = [] + 1 in testme + self.assertCallStack([('__contains__', (testme, 1))]) -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),)" + callLst[:] = [] + testme[1] + self.assertCallStack([('__getitem__', (testme, 1))]) + + callLst[:] = [] + testme[1] = 1 + self.assertCallStack([('__setitem__', (testme, 1, 1))]) + + callLst[:] = [] + del testme[1] + self.assertCallStack([('__delitem__', (testme, 1))]) -# Unary operations + callLst[:] = [] + testme[:42] + self.assertCallStack([('__getslice__', (testme, 0, 42))]) --testme -+testme -abs(testme) -int(testme) -long(testme) -float(testme) -oct(testme) -hex(testme) + callLst[:] = [] + testme[:42] = "The Answer" + self.assertCallStack([('__setslice__', (testme, 0, 42, "The Answer"))]) -# And the rest... + callLst[:] = [] + del testme[:42] + self.assertCallStack([('__delslice__', (testme, 0, 42))]) -hash(testme) -repr(testme) -str(testme) + callLst[:] = [] + testme[2:1024:10] + self.assertCallStack([('__getitem__', (testme, slice(2, 1024, 10)))]) -testme == 1 -testme < 1 -testme > 1 -testme <> 1 -testme != 1 -1 == testme -1 < testme -1 > testme -1 <> testme -1 != testme + callLst[:] = [] + testme[2:1024:10] = "A lot" + self.assertCallStack([('__setitem__', (testme, slice(2, 1024, 10), + "A lot"))]) + callLst[:] = [] + del testme[2:1024:10] + self.assertCallStack([('__delitem__', (testme, slice(2, 1024, 10)))]) -# This test has to be last (duh.) + callLst[:] = [] + testme[:42, ..., :24:, 24, 100] + self.assertCallStack([('__getitem__', (testme, (slice(None, 42, None), + Ellipsis, + slice(None, 24, None), + 24, 100)))]) + callLst[:] = [] + testme[:42, ..., :24:, 24, 100] = "Strange" + self.assertCallStack([('__setitem__', (testme, (slice(None, 42, None), + Ellipsis, + slice(None, 24, None), + 24, 100), "Strange"))]) + callLst[:] = [] + del testme[:42, ..., :24:, 24, 100] + self.assertCallStack([('__delitem__', (testme, (slice(None, 42, None), + Ellipsis, + slice(None, 24, None), + 24, 100)))]) -del testme -if sys.platform[:4] == 'java': - import java - java.lang.System.gc() + # Now remove the slice hooks to see if converting normal slices to + # slice object works. -# Interfering tests + del AllTests.__getslice__ + del AllTests.__setslice__ + del AllTests.__delslice__ -class ExtraTests: - def __getattr__(self, *args): - print "__getattr__:", args - return "SomeVal" + # 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. + callLst[:] = [] + testme[:42] + self.assertCallStack([('__getitem__', (testme, slice(0, 42, None)))]) + + callLst[:] = [] + testme[:42] = "The Answer" + self.assertCallStack([('__setitem__', (testme, slice(0, 42, None), + "The Answer"))]) + callLst[:] = [] + del testme[:42] + self.assertCallStack([('__delitem__', (testme, slice(0, 42, None)))]) - def __setattr__(self, *args): - print "__setattr__:", args - def __delattr__(self, *args): - print "__delattr__:", args + def testUnaryOps(self): + testme = AllTests() -testme = ExtraTests() -testme.spam -testme.eggs = "spam, spam, spam and ham" -del testme.cardinal + callLst[:] = [] + -testme + self.assertCallStack([('__neg__', (testme,))]) + callLst[:] = [] + +testme + self.assertCallStack([('__pos__', (testme,))]) + callLst[:] = [] + abs(testme) + self.assertCallStack([('__abs__', (testme,))]) + callLst[:] = [] + int(testme) + self.assertCallStack([('__int__', (testme,))]) + callLst[:] = [] + long(testme) + self.assertCallStack([('__long__', (testme,))]) + callLst[:] = [] + float(testme) + self.assertCallStack([('__float__', (testme,))]) + callLst[:] = [] + oct(testme) + self.assertCallStack([('__oct__', (testme,))]) + callLst[:] = [] + hex(testme) + self.assertCallStack([('__hex__', (testme,))]) -# 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 testMisc(self): + testme = AllTests() + + callLst[:] = [] + hash(testme) + self.assertCallStack([('__hash__', (testme,))]) + + callLst[:] = [] + repr(testme) + self.assertCallStack([('__repr__', (testme,))]) + + callLst[:] = [] + str(testme) + self.assertCallStack([('__str__', (testme,))]) + + callLst[:] = [] + testme == 1 + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (testme, 1))]) + + callLst[:] = [] + testme < 1 + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (testme, 1))]) -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) + callLst[:] = [] + testme > 1 + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (testme, 1))]) + + callLst[:] = [] + testme <> 1 # XXX kill this in py3k + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (testme, 1))]) -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) + callLst[:] = [] + testme != 1 + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (testme, 1))]) -# mixing up ints and longs is okay -class IntLongMixClass: - def __int__(self): - return 0L + callLst[:] = [] + 1 == testme + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))]) - def __long__(self): - return 0 + callLst[:] = [] + 1 < testme + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))]) -try: - int(IntLongMixClass()) -except TypeError: - raise TestFailed, "TypeError should not be raised" + callLst[:] = [] + 1 > testme + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))]) -try: - long(IntLongMixClass()) -except TypeError: - raise TestFailed, "TypeError should not be raised" + callLst[:] = [] + 1 <> testme + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))]) + callLst[:] = [] + 1 != testme + self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))]) -# Test correct errors from hash() on objects with comparisons but no __hash__ -class C0: - pass + def testGetSetAndDel(self): + # Interfering tests + class ExtraTests(AllTests): + @trackCall + def __getattr__(self, *args): + return "SomeVal" + + @trackCall + def __setattr__(self, *args): + pass -hash(C0()) # This should work; the next two should raise TypeError + @trackCall + def __delattr__(self, *args): + pass -class C1: - def __cmp__(self, other): return 0 + testme = ExtraTests() -check_exc("hash(C1())", TypeError) + callLst[:] = [] + testme.spam + self.assertCallStack([('__getattr__', (testme, "spam"))]) -class C2: - def __eq__(self, other): return 1 + callLst[:] = [] + testme.eggs = "spam, spam, spam and ham" + self.assertCallStack([('__setattr__', (testme, "eggs", + "spam, spam, spam and ham"))]) -check_exc("hash(C2())", TypeError) + callLst[:] = [] + del testme.cardinal + self.assertCallStack([('__delattr__', (testme, "cardinal"))]) -# Test for SF bug 532646 + def testDel(self): + x = [] + + class DelTest: + def __del__(self): + x.append("crab people, crab people") + testme = DelTest() + del testme + import gc + gc.collect() + self.assertEquals(["crab people, crab people"], x) + + def testBadTypeReturned(self): + # 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__ + + for f in [int, float, long, str, repr, oct, hex]: + self.assertRaises(TypeError, f, BadTypeClass()) + + def testMixIntsAndLongs(self): + # mixing up ints and longs is okay + class IntLongMixClass: + @trackCall + def __int__(self): + return 42L -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?" + @trackCall + def __long__(self): + return 64 + mixIntAndLong = IntLongMixClass() + + callLst[:] = [] + as_int = int(mixIntAndLong) + self.assertEquals(type(as_int), long) + self.assertEquals(as_int, 42L) + self.assertCallStack([('__int__', (mixIntAndLong,))]) + + callLst[:] = [] + as_long = long(mixIntAndLong) + self.assertEquals(type(as_long), int) + self.assertEquals(as_long, 64) + self.assertCallStack([('__long__', (mixIntAndLong,))]) -# Tests for exceptions raised in instance_getattr2(). + def testHashStuff(self): + # Test correct errors from hash() on objects with comparisons but + # no __hash__ -def booh(self): - raise AttributeError, "booh" + class C0: + pass -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) + hash(C0()) # This should work; the next two should raise TypeError -class E: - __eq__ = property(booh) -E() == E() # In debug mode, caused a C-level assert() to fail + class C1: + def __cmp__(self, other): return 0 -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" + self.assertRaises(TypeError, hash, C1()) + + class C2: + def __eq__(self, other): return 1 + self.assertRaises(TypeError, hash, C2()) -# 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) + def testSFBug532646(self): + # Test for SF bug 532646 -assert A.f != a1.f -assert A.f != A.g -assert B.f == A.f -assert hash(B.f) == hash(A.f) + class A: + pass + A.__call__ = A() + a = A() + + try: + a() # This should not segfault + except RuntimeError: + pass + else: + self.fail("Failed to raise RuntimeError") -# the following triggers a SystemError in 2.4 -a = A(hash(A.f.im_func)^(-1)) -hash(a.f) + def testForExceptionsRaisedInInstanceGetattr2(self): + # 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": + self.fail("attribute error for A().a got masked: %s" % x) + + class E: + __eq__ = property(booh) + E() == E() # In debug mode, caused a C-level assert() to fail + + class I: + __init__ = property(booh) + try: + # In debug mode, printed XXX undetected error and + # raises AttributeError + I() + except AttributeError, x: + pass + else: + self.fail("attribute error for I.__init__ got masked") + + 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 + + a1 = A(1) + a2 = A(2) + self.assertEquals(a1.f, a1.f) + self.assertNotEquals(a1.f, a2.f) + self.assertNotEquals(a1.f, a1.g) + self.assertEquals(a1.f, A(1).f) + self.assertEquals(hash(a1.f), hash(a1.f)) + self.assertEquals(hash(a1.f), hash(A(1).f)) + + self.assertNotEquals(A.f, a1.f) + self.assertNotEquals(A.f, A.g) + self.assertEquals(B.f, A.f) + self.assertEquals(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) + +def test_main(): + test_support.run_unittest(ClassTests) + +if __name__=='__main__': + test_main()