# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
# See LICENSE for details.


"""Test cases for Twisted component architecture."""

from twisted.trial import unittest, util
from twisted.python import components
import warnings

compWarn = {'category':components.ComponentsDeprecationWarning}
suppress = [util.suppress(**compWarn)]

# Also filter warnings generated by top-level code
warnings.filterwarnings('ignore', **compWarn)

class IAdder(components.Interface):
    """A sample interface that adds stuff."""

    def add(self, a, b):
        """Returns the sub of a and b."""
        raise NotImplementedError

class ISub(IAdder):
    """Sub-interface."""

class IMultiply(components.Interface):
    """Interface that multiplies stuff."""

    def multiply(self, a, b):
        """Multiply two items."""
        raise NotImplementedError


class IntAdder:
    """Class that implements IAdder interface."""

    __implements__ = IAdder

    def add(self, a, b):
        return a + b

class Sub:
    """Class that implements ISub."""

    __implements__ = ISub

    def add(self, a, b):
        return 3


class IntMultiplyWithAdder:
    """Multiply, using Adder object."""

    __implements__ = IMultiply

    def __init__(self, adder):
        self.adder = adder

    def multiply(self, a, b):
        result = 0
        for i in range(a):
            result = self.adder.add(result, b)
        return result

components.registerAdapter(IntMultiplyWithAdder, IntAdder, IMultiply)

class MultiplyAndAdd:
    """Multiply and add."""

    __implements__ = (IAdder, IMultiply)

    def add(self, a, b):
        return a + b

    def multiply(self, a, b):
        return a * b

class IFoo(ISub):
    pass

class FooAdapterForMAA:

    __implements__ = IFoo

    def __init__(self, instance):
        self.instance = instance

    def add(self, a, b):
        return self.instance.add(a, b)

components.registerAdapter(FooAdapterForMAA, MultiplyAndAdd, IFoo)


class InterfacesTestCase(unittest.TestCase):
    """Test interfaces."""

    tuples = ([1, [1]],
              [(2, 3), [2, 3]],
              [(2, (3, (4,)), (1, 5)), [2, 3, 4, 1, 5]],
              [(), []],
              )

    def testModules(self):
        self.assertEquals(components.Interface.__module__, "twisted.python.components")
        self.assertEquals(IAdder.__module__, "twisted.test.test_components")
        self.assertEquals(IFoo.__module__, "twisted.test.test_components")
    
    def testTupleTrees(self):
        for tree, result in self.tuples:
            self.assertEquals(components.tupleTreeToList(tree), result)

    def testClasses(self):
        components.fixClassImplements(Sub)
        components.fixClassImplements(MultiplyAndAdd)
        self.assert_( IMultiply.implementedBy(MultiplyAndAdd) )
        self.assert_( IAdder.implementedBy(MultiplyAndAdd) )
        self.assert_( IAdder.implementedBy(Sub) )
        self.assert_( ISub.implementedBy(Sub) )

    def testInstances(self):
        o = MultiplyAndAdd()
        self.assert_( components.implements(o, IMultiply) )
        self.assert_( components.implements(o, IMultiply) )

        o = Sub()
        self.assert_( components.implements(o, IAdder) )
        self.assert_( components.implements(o, ISub) )

    def testOther(self):
        self.assert_( not components.implements(3, ISub) )
        self.assert_( not components.implements("foo", ISub) )

    def testGetInterfaces(self):
        l = components.getInterfaces(Sub)
        l.sort()
        l2 = [IAdder, ISub]
        l2.sort()
        self.assertEquals(l, l2)

        l = components.getInterfaces(MultiplyAndAdd)
        l.sort()
        l2 = [IAdder, IMultiply]
        l2.sort()
        self.assertEquals(l, l2)

    def testSuperInterfaces(self):
        l = components.superInterfaces(ISub)
        l.sort()
        l2 = [ISub, IAdder]
        l2.sort()
        self.assertEquals(l, l2)


class Compo(components.Componentized):
    num = 0
    def inc(self):
        self.num = self.num + 1
        return self.num

class IAdept(components.Interface):
    def adaptorFunc(self):
        raise NotImplementedError()

class IElapsed(components.Interface):
    def elapsedFunc(self):
        """
        1!
        """

class Adept(components.Adapter):
    __implements__ = IAdept,
    def __init__(self, orig):
        self.original = orig
        self.num = 0
    def adaptorFunc(self):
        self.num = self.num + 1
        return self.num, self.original.inc()

class Elapsed(components.Adapter):
    __implements__ = IElapsed
    def elapsedFunc(self):
        return 1

components.registerAdapter(Adept, Compo, IAdept)
components.registerAdapter(Elapsed, Compo, IElapsed)

class AComp(components.Componentized):
    pass
class BComp(AComp):
    pass
class CComp(BComp):
    pass

class ITest(components.Interface):
    pass
class ITest2(components.Interface):
    pass
class ITest3(components.Interface):
    pass
class ITest4(components.Interface):
    pass
class Test(components.Adapter):
    __implements__ = ITest, ITest3, ITest4
    def __init__(self, orig):
        pass
class Test2:
    __implements__ = ITest2,
    temporaryAdapter = 1
    def __init__(self, orig):
        pass

components.registerAdapter(Test, AComp, ITest)
components.registerAdapter(Test, AComp, ITest3)
components.registerAdapter(Test2, AComp, ITest2)




class ComponentizedTestCase(unittest.TestCase):
    """Simple test case for caching in Componentized.
    """
    def testComponentized(self):
        c = Compo()
        assert c.getComponent(IAdept).adaptorFunc() == (1, 1)
        assert c.getComponent(IAdept).adaptorFunc() == (2, 2)
        assert IElapsed(IAdept(c)).elapsedFunc() == 1

    def testInheritanceAdaptation(self):
        c = CComp()
        co1 = c.getComponent(ITest)
        co2 = c.getComponent(ITest)
        co3 = c.getComponent(ITest2)
        co4 = c.getComponent(ITest2)
        assert co1 is co2
        assert co3 is not co4
        c.removeComponent(co1)
        co5 = c.getComponent(ITest)
        co6 = c.getComponent(ITest)
        assert co5 is co6
        assert co1 is not co5

    def testMultiAdapter(self):
        c = CComp()
        co1 = c.getComponent(ITest)
        co2 = c.getComponent(ITest2)
        co3 = c.getComponent(ITest3)
        co4 = c.getComponent(ITest4)
        assert co4 == None
        assert co1 is co3

class AdapterTestCase(unittest.TestCase):
    """Test adapters."""

    def testNoAdapter(self):
        o = Sub()
        multiplier = components.getAdapter(o, IMultiply, None)
        self.assertEquals(multiplier, None)

    def testSelfIsAdapter(self):
        o = IntAdder()
        adder = components.getAdapter(o, IAdder, None)
        self.assert_( o is adder )

    def testGetAdapter(self):
        o = IntAdder()
        self.assertEquals(o.add(3, 4), 7)

        # get an object implementing IMultiply
        multiplier = components.getAdapter(o, IMultiply, None)

        # check that it complies with the IMultiply interface
        self.assertEquals(multiplier.multiply(3, 4), 12)

    def testGetAdapterClass(self):
        mklass = components.getAdapterClass(IntAdder, IMultiply, None)
        self.assertEquals(mklass, IntMultiplyWithAdder)

    def testGetSubAdapter(self):
        o = MultiplyAndAdd()
        self.assert_( not components.implements(o, IFoo) )
        foo = components.getAdapter(o, IFoo, None)
        self.assert_( isinstance(foo, FooAdapterForMAA) )

    def testParentInterface(self):
        o = Sub()
        adder = components.getAdapter(o, IAdder, None)
        self.assertIdentical(o, adder)

    def testBadRegister(self):
        # should fail because we already registered an IMultiply adapter for IntAdder
        self.assertRaises(ValueError, components.registerAdapter, IntMultiplyWithAdder, IntAdder, IMultiply)
    
    def testAllowDuplicates(self):
        components.ALLOW_DUPLICATES = 1
        try: 
            components.registerAdapter(IntMultiplyWithAdder, IntAdder,
                                       IMultiply)
        except ValueError:
            self.fail("Should have allowed re-registration")
            
        # should fail because we already registered an IMultiply adapter
        # for IntAdder
        components.ALLOW_DUPLICATES = 0
        self.assertRaises(ValueError, components.registerAdapter,
                          IntMultiplyWithAdder, IntAdder, IMultiply)
    
    def testAdapterGetComponent(self):
        o = object()
        a = Adept(o)
        self.assertRaises(components.CannotAdapt, IAdder, a)
        self.assertEquals(IAdder(a, default=None), None)

    def testMultipleInterfaceRegistration(self):
        class IMIFoo(components.Interface):
            pass
        class IMIBar(components.Interface):
            pass
        class MIFooer(components.Adapter):
            __implements__ = IMIFoo, IMIBar
        class Blegh:
            pass
        components.registerAdapter(MIFooer, Blegh, IMIFoo, IMIBar)
        self.assert_(isinstance(IMIFoo(Blegh()), MIFooer))
        self.assert_(isinstance(IMIBar(Blegh()), MIFooer))

class IMeta(components.Interface):
    def __adapt__(o, dflt):
        if hasattr(o, 'add'):
            return o
        elif hasattr(o, 'num'):
            return MetaAdder(o)
        return dflt

class MetaAdder(components.Adapter):
    __implements__ = IMeta
    def add(self, num):
        return self.original.num + num

class BackwardsAdder(components.Adapter):
    __implements__ = IMeta
    def add(self, num):
        return self.original.num - num

class MetaNumber:
    def __init__(self, num):
        self.num = num

class FakeAdder:
    def add(self, num):
        return num + 5

class FakeNumber:
    num = 3

class ComponentNumber(components.Componentized):
    def __init__(self):
        self.num = 0
        components.Componentized.__init__(self)

class ComponentMeta(components.Adapter):
    __implements__ = IMeta
    def __init__(self, original):
        components.Adapter.__init__(self, original)
        self.num = self.original.num

class ComponentAdder(ComponentMeta):
    def add(self, num):
        self.num += num
        return self.num

class ComponentDoubler(ComponentMeta):
    def add(self, num):
        self.num += (num * 2)
        return self.original.num

components.registerAdapter(MetaAdder, MetaNumber, IMeta)
components.registerAdapter(ComponentAdder, ComponentNumber, IMeta)

class IAttrX(components.Interface):
    def x(self):
        pass

class IAttrXX(components.Interface):
    def xx(self):
        pass

class Xcellent:
    __implements__ = IAttrX
    def x(self):
        return 'x!'

class DoubleXAdapter:
    num = 42
    def __init__(self, original):
        self.original = original
    def xx(self):
        return (self.original.x(), self.original.x())
    def __cmp__(self, other):
        return cmp(self.num, other.num)

components.registerAdapter(DoubleXAdapter, IAttrX, IAttrXX)

class TestMetaInterface(unittest.TestCase):
    
    def testBasic(self):
        n = MetaNumber(1)
        self.assertEquals(IMeta(n).add(1), 2)

    def testInterfaceAdaptMethod(self):
        a = FakeAdder()
        self.assertIdentical(IMeta(a), a)
        n2 = FakeNumber()
        self.assertEquals(IMeta(n2).add(3), 6)

    def testComponentizedInteraction(self):
        c = ComponentNumber()
        IMeta(c).add(1)
        IMeta(c).add(1)
        self.assertEquals(IMeta(c).add(1), 3)

    def testRegistryPersistence(self):
        n = MetaNumber(1)
        i1 = IMeta(n, persist=True)
        i2 = IMeta(n, persist=True)
        i3 = IMeta(n, persist=False)
        i4 = IMeta(n)
        self.assertIdentical(i1, i2)
        self.assertNotEqual(i1, i3)
        self.assertIdentical(i1, i4)
        import weakref
        r = weakref.ref(i1)
        del i1, i2, i3, i4
        self.assertNotEqual(r(), IMeta(n))
        self.assertNotEqual(IMeta(n), IMeta(n))

    def testAdapterWithCmp(self):
        # Make sure that a __cmp__ on an adapter doesn't break anything
        xx = IAttrXX(Xcellent())
        self.assertEqual(('x!', 'x!'), xx.xx())


class IISource1(components.Interface): pass
class IISource2(components.Interface): pass
class IIDest1(components.Interface): pass

class Dest1Impl(components.Adapter):
    __implements__ = IIDest1

class Dest1Impl2(components.Adapter):
    __implements__ = IIDest1

class Source12:
    __implements__ = IISource1, IISource2

class Source21:
    __implements__ = IISource2, IISource1

IISource1.adaptWith(Dest1Impl,  IIDest1)
IISource2.adaptWith(Dest1Impl2, IIDest1)

class TestInterfaceInterface(unittest.TestCase):

    def testBasic(self):
        s12 = Source12()
        d = IIDest1(s12)
        self.failUnless(isinstance(d, Dest1Impl), str(s12))
        s21 = Source21()
        d = IIDest1(s21)
        self.failUnless(isinstance(d, Dest1Impl2), str(s21))


class TestZIBC(unittest.TestCase):
    def testAttributes(self):
        class IFoo(components.Interface):
            a = 3
        self.assertEquals(IFoo.a, 3)


from zope import interface as zinterface

class IZope(zinterface.Interface):
    def amethod(a, b):
        pass

class Zopeable:
    pass

class Zoper:
    zinterface.implements(IZope, IAdder)
components.backwardsCompatImplements(Zoper) # add __implements__

components.registerAdapter(lambda o: id(o), Zopeable, IZope)

class SubZoper(Zoper):
    zinterface.implements(ISub)


class OldStyle:
    __implements__ = IAdder
class NewSubOfOldStyle(OldStyle):
    zinterface.implements(IZope)
components.backwardsCompatImplements(NewSubOfOldStyle)

class TestZope(unittest.TestCase):

    def testAdapter(self):
        x = Zopeable()
        self.assertEquals(id(x), IZope(x))

    def testOldSubclass(self):
        # someone has third party class that subclasses Zoper,
        # and expects Zoper to have __implements__ since Zoper
        # was originally written for twisted 1.3, pre zope.interface
        class IFoo(components.Interface):
            pass
        class ThirdParty(Zoper):
            __implements__ = Zoper.__implements__, IFoo
        self.assert_(components.implements(ThirdParty(), IAdder))
        self.assert_(components.implements(ThirdParty(), IFoo))
        self.assert_(components.implements(ThirdParty(), IZope))

    def testNewSubclass(self):
        # new-style implementing class subclasses backwardsCompatImplements class
        o = SubZoper()
        self.assert_(components.implements(o, IZope))
        self.assert_(components.implements(o, ISub))

    def testNewSubclassOfOld(self):
        o = NewSubOfOldStyle()
        self.assert_(components.implements(o, IZope))
        self.assert_(components.implements(o, IAdder))
        
    def testSignatureString(self):
        # Make sure it cuts off the self from old t.p.c signatures.
        self.assertEquals(IAdder['add'].getSignatureString(), "(a, b)")
        self.assertEquals(IZope['amethod'].getSignatureString(), "(a, b)")
        
warnings.filterwarnings('default', **compWarn)
