"Test the functionality of Python classes implementing operators."

import unittest

from test import test_support

testmeths = [

# Binary operations
    "add",
    "radd",
    "sub",
    "rsub",
    "mul",
    "rmul",
    "div",
    "rdiv",
    "mod",
    "rmod",
    "divmod",
    "rdivmod",
    "pow",
    "rpow",
    "rshift",
    "rrshift",
    "lshift",
    "rlshift",
    "and",
    "rand",
    "or",
    "ror",
    "xor",
    "rxor",

# List/dict operations
    "contains",
    "getitem",
    "getslice",
    "setitem",
    "setslice",
    "delitem",
    "delslice",

# Unary operations
    "neg",
    "pos",
    "abs",

# generic operations
    "init",
    ]

# These need to return something other than None
#    "coerce",
#    "hash",
#    "str",
#    "repr",
#    "int",
#    "long",
#    "float",
#    "oct",
#    "hex",

# These are separate because they can influence the test of other methods.
#    "getattr",
#    "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):
        return (self,) + args

    @trackCall
    def __hash__(self, *args):
        return hash(id(self))

    @trackCall
    def __str__(self, *args):
        return "AllTests"

    @trackCall
    def __repr__(self, *args):
        return "AllTests"

    @trackCall
    def __int__(self, *args):
        return 1

    @trackCall
    def __float__(self, *args):
        return 1.0

    @trackCall
    def __long__(self, *args):
        return 1L

    @trackCall
    def __oct__(self, *args):
        return '01'

    @trackCall
    def __hex__(self, *args):
        return '0x1'

    @trackCall
    def __cmp__(self, *args):
        return 0

# Synthesize all the other AllTests methods from the names in testmeths.

method_template = """\
@trackCall
def __%(method)s__(self, *args):
    pass
"""

for method in testmeths:
    exec method_template % locals() in AllTests.__dict__

del method, method_template

class ClassTests(unittest.TestCase):
    def setUp(self):
        callLst[:] = []

    def assertCallStack(self, expected_calls):
        actualCallList = callLst[:]  # need to copy because the comparison below will add
                                     # additional calls to callLst
        if expected_calls != actualCallList:
            self.fail("Expected call list:\n  %s\ndoes not match actual call list\n  %s" %
                      (expected_calls, actualCallList))

    def testInit(self):
        foo = AllTests()
        self.assertCallStack([("__init__", (foo,))])

    def testBinaryOps(self):
        testme = AllTests()
        # Binary operations

        callLst[:] = []
        testme + 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__add__", (testme, 1))])

        callLst[:] = []
        1 + testme
        self.assertCallStack([("__coerce__", (testme, 1)), ("__radd__", (testme, 1))])

        callLst[:] = []
        testme - 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__sub__", (testme, 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))])


            callLst[:] = []
            1 / testme
            self.assertCallStack([("__coerce__", (testme, 1)), ("__rdiv__", (testme, 1))])

        callLst[:] = []
        testme % 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__mod__", (testme, 1))])

        callLst[:] = []
        1 % testme
        self.assertCallStack([("__coerce__", (testme, 1)), ("__rmod__", (testme, 1))])


        callLst[:] = []
        divmod(testme,1)
        self.assertCallStack([("__coerce__", (testme, 1)), ("__divmod__", (testme, 1))])

        callLst[:] = []
        divmod(1, testme)
        self.assertCallStack([("__coerce__", (testme, 1)), ("__rdivmod__", (testme, 1))])

        callLst[:] = []
        testme ** 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__pow__", (testme, 1))])

        callLst[:] = []
        1 ** testme
        self.assertCallStack([("__coerce__", (testme, 1)), ("__rpow__", (testme, 1))])

        callLst[:] = []
        testme >> 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__rshift__", (testme, 1))])

        callLst[:] = []
        1 >> testme
        self.assertCallStack([("__coerce__", (testme, 1)), ("__rrshift__", (testme, 1))])

        callLst[:] = []
        testme << 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__lshift__", (testme, 1))])

        callLst[:] = []
        1 << testme
        self.assertCallStack([("__coerce__", (testme, 1)), ("__rlshift__", (testme, 1))])

        callLst[:] = []
        testme & 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__and__", (testme, 1))])

        callLst[:] = []
        1 & testme
        self.assertCallStack([("__coerce__", (testme, 1)), ("__rand__", (testme, 1))])

        callLst[:] = []
        testme | 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__or__", (testme, 1))])

        callLst[:] = []
        1 | testme
        self.assertCallStack([("__coerce__", (testme, 1)), ("__ror__", (testme, 1))])

        callLst[:] = []
        testme ^ 1
        self.assertCallStack([("__coerce__", (testme, 1)), ("__xor__", (testme, 1))])

        callLst[:] = []
        1 ^ testme
        self.assertCallStack([("__coerce__", (testme, 1)), ("__rxor__", (testme, 1))])

    def testListAndDictOps(self):
        testme = AllTests()

        # List/dict operations

        class Empty: pass

        try:
            1 in Empty()
            self.fail('failed, should have raised TypeError')
        except TypeError:
            pass

        callLst[:] = []
        1 in testme
        self.assertCallStack([('__contains__', (testme, 1))])

        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))])

        callLst[:] = []
        testme[:42]
        self.assertCallStack([('__getslice__', (testme, 0, 42))])

        callLst[:] = []
        testme[:42] = "The Answer"
        self.assertCallStack([('__setslice__', (testme, 0, 42, "The Answer"))])

        callLst[:] = []
        del testme[:42]
        self.assertCallStack([('__delslice__', (testme, 0, 42))])

        callLst[:] = []
        testme[2:1024:10]
        self.assertCallStack([('__getitem__', (testme, slice(2, 1024, 10)))])

        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)))])

        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)))])

        # Now remove the slice hooks to see if converting normal slices to
        #  slice object works.

        getslice = AllTests.__getslice__
        del AllTests.__getslice__
        setslice = AllTests.__setslice__
        del AllTests.__setslice__
        delslice = AllTests.__delslice__
        del AllTests.__delslice__

        # 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)))])

        # Restore the slice methods, or the tests will fail with regrtest -R.
        AllTests.__getslice__ = getslice
        AllTests.__setslice__ = setslice
        AllTests.__delslice__ = delslice


    @test_support.cpython_only
    def testDelItem(self):
        class A:
            ok = False
            def __delitem__(self, key):
                self.ok = True
        a = A()
        # Subtle: we need to call PySequence_SetItem, not PyMapping_SetItem.
        from _testcapi import sequence_delitem
        sequence_delitem(a, 2)
        self.assertTrue(a.ok)


    def testUnaryOps(self):
        testme = AllTests()

        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,))])


    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))])

        callLst[:] = []
        testme > 1
        self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (testme, 1))])

        callLst[:] = []
        eval('testme <> 1')  # XXX kill this in py3k
        self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (testme, 1))])

        callLst[:] = []
        testme != 1
        self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (testme, 1))])

        callLst[:] = []
        1 == testme
        self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))])

        callLst[:] = []
        1 < testme
        self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))])

        callLst[:] = []
        1 > testme
        self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))])

        callLst[:] = []
        eval('1 <> testme')
        self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))])

        callLst[:] = []
        1 != testme
        self.assertCallStack([("__coerce__", (testme, 1)), ('__cmp__', (1, testme))])


    def testGetSetAndDel(self):
        # Interfering tests
        class ExtraTests(AllTests):
            @trackCall
            def __getattr__(self, *args):
                return "SomeVal"

            @trackCall
            def __setattr__(self, *args):
                pass

            @trackCall
            def __delattr__(self, *args):
                pass

        testme = ExtraTests()

        callLst[:] = []
        testme.spam
        self.assertCallStack([('__getattr__', (testme, "spam"))])

        callLst[:] = []
        testme.eggs = "spam, spam, spam and ham"
        self.assertCallStack([('__setattr__', (testme, "eggs",
                                               "spam, spam, spam and ham"))])

        callLst[:] = []
        del testme.cardinal
        self.assertCallStack([('__delattr__', (testme, "cardinal"))])

    def testDel(self):
        x = []

        class DelTest:
            def __del__(self):
                x.append("crab people, crab people")
        testme = DelTest()
        del testme
        import gc
        gc.collect()
        self.assertEqual(["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

            @trackCall
            def __long__(self):
                return 64

        mixIntAndLong = IntLongMixClass()

        callLst[:] = []
        as_int = int(mixIntAndLong)
        self.assertEqual(type(as_int), long)
        self.assertEqual(as_int, 42L)
        self.assertCallStack([('__int__', (mixIntAndLong,))])

        callLst[:] = []
        as_long = long(mixIntAndLong)
        self.assertEqual(type(as_long), long)
        self.assertEqual(as_long, 64)
        self.assertCallStack([('__long__', (mixIntAndLong,))])

    def testHashStuff(self):
        # Test correct errors from hash() on objects with comparisons but
        #  no __hash__

        class C0:
            pass

        hash(C0()) # This should work; the next two should raise TypeError

        class C1:
            def __cmp__(self, other): return 0

        self.assertRaises(TypeError, hash, C1())

        class C2:
            def __eq__(self, other): return 1

        self.assertRaises(TypeError, hash, C2())


    def testSFBug532646(self):
        # Test for SF bug 532646

        class A:
            pass
        A.__call__ = A()
        a = A()

        try:
            a() # This should not segfault
        except RuntimeError:
            pass
        else:
            self.fail("Failed to raise RuntimeError")

    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.assertEqual(a1.f, a1.f)
        self.assertNotEqual(a1.f, a2.f)
        self.assertNotEqual(a1.f, a1.g)
        self.assertEqual(a1.f, A(1).f)
        self.assertEqual(hash(a1.f), hash(a1.f))
        self.assertEqual(hash(a1.f), hash(A(1).f))

        self.assertNotEqual(A.f, a1.f)
        self.assertNotEqual(A.f, A.g)
        self.assertEqual(B.f, A.f)
        self.assertEqual(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 testAttrSlots(self):
        class C:
            pass
        for c in C, C():
            self.assertRaises(TypeError, type(c).__getattribute__, c, [])
            self.assertRaises(TypeError, type(c).__setattr__, c, [], [])

def test_main():
    with test_support.check_py3k_warnings(
            (".+__(get|set|del)slice__ has been removed", DeprecationWarning),
            ("classic int division", DeprecationWarning),
            ("<> not supported", DeprecationWarning)):
        test_support.run_unittest(ClassTests)

if __name__=='__main__':
    test_main()
