879 lines
25 KiB
Python
879 lines
25 KiB
Python
![]() |
# Copyright (c) Twisted Matrix Laboratories.
|
||
|
# See LICENSE for details.
|
||
|
|
||
|
|
||
|
"""
|
||
|
Test cases for Twisted component architecture.
|
||
|
"""
|
||
|
|
||
|
|
||
|
from functools import wraps
|
||
|
|
||
|
from zope.interface import Attribute, Interface, implementer
|
||
|
from zope.interface.adapter import AdapterRegistry
|
||
|
|
||
|
from twisted.python import components
|
||
|
from twisted.python.compat import cmp, comparable
|
||
|
from twisted.python.components import _addHook, _removeHook, proxyForInterface
|
||
|
from twisted.trial import unittest
|
||
|
|
||
|
|
||
|
class Compo(components.Componentized):
|
||
|
num = 0
|
||
|
|
||
|
def inc(self):
|
||
|
self.num = self.num + 1
|
||
|
return self.num
|
||
|
|
||
|
|
||
|
class IAdept(Interface):
|
||
|
def adaptorFunc():
|
||
|
raise NotImplementedError()
|
||
|
|
||
|
|
||
|
class IElapsed(Interface):
|
||
|
def elapsedFunc():
|
||
|
"""
|
||
|
1!
|
||
|
"""
|
||
|
|
||
|
|
||
|
@implementer(IAdept)
|
||
|
class Adept(components.Adapter):
|
||
|
def __init__(self, orig):
|
||
|
self.original = orig
|
||
|
self.num = 0
|
||
|
|
||
|
def adaptorFunc(self):
|
||
|
self.num = self.num + 1
|
||
|
return self.num, self.original.inc()
|
||
|
|
||
|
|
||
|
@implementer(IElapsed)
|
||
|
class Elapsed(components.Adapter):
|
||
|
def elapsedFunc(self):
|
||
|
return 1
|
||
|
|
||
|
|
||
|
class AComp(components.Componentized):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class BComp(AComp):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class CComp(BComp):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class ITest(Interface):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class ITest2(Interface):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class ITest3(Interface):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class ITest4(Interface):
|
||
|
pass
|
||
|
|
||
|
|
||
|
@implementer(ITest, ITest3, ITest4)
|
||
|
class Test(components.Adapter):
|
||
|
def __init__(self, orig):
|
||
|
pass
|
||
|
|
||
|
|
||
|
@implementer(ITest2)
|
||
|
class Test2:
|
||
|
temporaryAdapter = 1
|
||
|
|
||
|
def __init__(self, orig):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class RegistryUsingMixin:
|
||
|
"""
|
||
|
Mixin for test cases which modify the global registry somehow.
|
||
|
"""
|
||
|
|
||
|
def setUp(self):
|
||
|
"""
|
||
|
Configure L{twisted.python.components.registerAdapter} to mutate an
|
||
|
alternate registry to improve test isolation.
|
||
|
"""
|
||
|
# Create a brand new, empty registry and put it onto the components
|
||
|
# module where registerAdapter will use it. Also ensure that it goes
|
||
|
# away at the end of the test.
|
||
|
scratchRegistry = AdapterRegistry()
|
||
|
self.patch(components, "globalRegistry", scratchRegistry)
|
||
|
# Hook the new registry up to the adapter lookup system and ensure that
|
||
|
# association is also discarded after the test.
|
||
|
hook = _addHook(scratchRegistry)
|
||
|
self.addCleanup(_removeHook, hook)
|
||
|
|
||
|
|
||
|
class ComponentizedTests(unittest.SynchronousTestCase, RegistryUsingMixin):
|
||
|
"""
|
||
|
Simple test case for caching in Componentized.
|
||
|
"""
|
||
|
|
||
|
def setUp(self):
|
||
|
RegistryUsingMixin.setUp(self)
|
||
|
|
||
|
components.registerAdapter(Test, AComp, ITest)
|
||
|
components.registerAdapter(Test, AComp, ITest3)
|
||
|
components.registerAdapter(Test2, AComp, ITest2)
|
||
|
|
||
|
def testComponentized(self):
|
||
|
components.registerAdapter(Adept, Compo, IAdept)
|
||
|
components.registerAdapter(Elapsed, Compo, IElapsed)
|
||
|
|
||
|
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)
|
||
|
co3 = c.getComponent(ITest3)
|
||
|
co4 = c.getComponent(ITest4)
|
||
|
self.assertIsNone(co4)
|
||
|
self.assertIs(co1, co3)
|
||
|
|
||
|
def test_getComponentDefaults(self):
|
||
|
"""
|
||
|
Test that a default value specified to Componentized.getComponent if
|
||
|
there is no component for the requested interface.
|
||
|
"""
|
||
|
componentized = components.Componentized()
|
||
|
default = object()
|
||
|
self.assertIs(componentized.getComponent(ITest, default), default)
|
||
|
self.assertIs(componentized.getComponent(ITest, default=default), default)
|
||
|
self.assertIs(componentized.getComponent(ITest), None)
|
||
|
|
||
|
def test_setAdapter(self):
|
||
|
"""
|
||
|
C{Componentized.setAdapter} sets a component for an interface by
|
||
|
wrapping the instance with the given adapter class.
|
||
|
"""
|
||
|
componentized = components.Componentized()
|
||
|
componentized.setAdapter(IAdept, Adept)
|
||
|
component = componentized.getComponent(IAdept)
|
||
|
self.assertEqual(component.original, componentized)
|
||
|
self.assertIsInstance(component, Adept)
|
||
|
|
||
|
def test_addAdapter(self):
|
||
|
"""
|
||
|
C{Componentized.setAdapter} adapts the instance by wrapping it with
|
||
|
given adapter class, then stores it using C{addComponent}.
|
||
|
"""
|
||
|
componentized = components.Componentized()
|
||
|
componentized.addAdapter(Adept, ignoreClass=True)
|
||
|
component = componentized.getComponent(IAdept)
|
||
|
self.assertEqual(component.original, componentized)
|
||
|
self.assertIsInstance(component, Adept)
|
||
|
|
||
|
def test_setComponent(self):
|
||
|
"""
|
||
|
C{Componentized.setComponent} stores the given component using the
|
||
|
given interface as the key.
|
||
|
"""
|
||
|
componentized = components.Componentized()
|
||
|
obj = object()
|
||
|
componentized.setComponent(ITest, obj)
|
||
|
self.assertIs(componentized.getComponent(ITest), obj)
|
||
|
|
||
|
def test_unsetComponent(self):
|
||
|
"""
|
||
|
C{Componentized.setComponent} removes the cached component for the
|
||
|
given interface.
|
||
|
"""
|
||
|
componentized = components.Componentized()
|
||
|
obj = object()
|
||
|
componentized.setComponent(ITest, obj)
|
||
|
componentized.unsetComponent(ITest)
|
||
|
self.assertIsNone(componentized.getComponent(ITest))
|
||
|
|
||
|
def test_reprableComponentized(self):
|
||
|
"""
|
||
|
C{ReprableComponentized} has a C{__repr__} that lists its cache.
|
||
|
"""
|
||
|
rc = components.ReprableComponentized()
|
||
|
rc.setComponent(ITest, "hello")
|
||
|
result = repr(rc)
|
||
|
self.assertIn("ITest", result)
|
||
|
self.assertIn("hello", result)
|
||
|
|
||
|
|
||
|
class AdapterTests(unittest.SynchronousTestCase):
|
||
|
"""Test adapters."""
|
||
|
|
||
|
def testAdapterGetComponent(self):
|
||
|
o = object()
|
||
|
a = Adept(o)
|
||
|
self.assertRaises(components.CannotAdapt, ITest, a)
|
||
|
self.assertIsNone(ITest(a, None))
|
||
|
|
||
|
|
||
|
class IMeta(Interface):
|
||
|
pass
|
||
|
|
||
|
|
||
|
@implementer(IMeta)
|
||
|
class MetaAdder(components.Adapter):
|
||
|
def add(self, num):
|
||
|
return self.original.num + num
|
||
|
|
||
|
|
||
|
@implementer(IMeta)
|
||
|
class BackwardsAdder(components.Adapter):
|
||
|
def add(self, num):
|
||
|
return self.original.num - num
|
||
|
|
||
|
|
||
|
class MetaNumber:
|
||
|
"""
|
||
|
Integer wrapper for Interface adaptation tests.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, num):
|
||
|
self.num = num
|
||
|
|
||
|
|
||
|
class ComponentNumber(components.Componentized):
|
||
|
def __init__(self):
|
||
|
self.num = 0
|
||
|
components.Componentized.__init__(self)
|
||
|
|
||
|
|
||
|
@implementer(IMeta)
|
||
|
class ComponentAdder(components.Adapter):
|
||
|
"""
|
||
|
Adder for componentized adapter tests.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, original):
|
||
|
components.Adapter.__init__(self, original)
|
||
|
self.num = self.original.num
|
||
|
|
||
|
def add(self, num):
|
||
|
self.num += num
|
||
|
return self.num
|
||
|
|
||
|
|
||
|
class IAttrX(Interface):
|
||
|
"""
|
||
|
Base interface for test of adapter with C{__cmp__}.
|
||
|
"""
|
||
|
|
||
|
def x():
|
||
|
"""
|
||
|
Return a value.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class IAttrXX(Interface):
|
||
|
"""
|
||
|
Adapted interface for test of adapter with C{__cmp__}.
|
||
|
"""
|
||
|
|
||
|
def xx():
|
||
|
"""
|
||
|
Return a tuple of values.
|
||
|
"""
|
||
|
|
||
|
|
||
|
@implementer(IAttrX)
|
||
|
class Xcellent:
|
||
|
"""
|
||
|
L{IAttrX} implementation for test of adapter with C{__cmp__}.
|
||
|
"""
|
||
|
|
||
|
def x(self):
|
||
|
"""
|
||
|
Return a value.
|
||
|
|
||
|
@return: a value
|
||
|
"""
|
||
|
return "x!"
|
||
|
|
||
|
|
||
|
@comparable
|
||
|
class DoubleXAdapter:
|
||
|
"""
|
||
|
Adapter with __cmp__.
|
||
|
"""
|
||
|
|
||
|
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)
|
||
|
|
||
|
|
||
|
class MetaInterfaceTests(RegistryUsingMixin, unittest.SynchronousTestCase):
|
||
|
def test_basic(self):
|
||
|
"""
|
||
|
Registered adapters can be used to adapt classes to an interface.
|
||
|
"""
|
||
|
components.registerAdapter(MetaAdder, MetaNumber, IMeta)
|
||
|
n = MetaNumber(1)
|
||
|
self.assertEqual(IMeta(n).add(1), 2)
|
||
|
|
||
|
def testComponentizedInteraction(self):
|
||
|
components.registerAdapter(ComponentAdder, ComponentNumber, IMeta)
|
||
|
c = ComponentNumber()
|
||
|
IMeta(c).add(1)
|
||
|
IMeta(c).add(1)
|
||
|
self.assertEqual(IMeta(c).add(1), 3)
|
||
|
|
||
|
def testAdapterWithCmp(self):
|
||
|
# Make sure that a __cmp__ on an adapter doesn't break anything
|
||
|
components.registerAdapter(DoubleXAdapter, IAttrX, IAttrXX)
|
||
|
xx = IAttrXX(Xcellent())
|
||
|
self.assertEqual(("x!", "x!"), xx.xx())
|
||
|
|
||
|
|
||
|
class RegistrationTests(RegistryUsingMixin, unittest.SynchronousTestCase):
|
||
|
"""
|
||
|
Tests for adapter registration.
|
||
|
"""
|
||
|
|
||
|
def _registerAdapterForClassOrInterface(self, original):
|
||
|
"""
|
||
|
Register an adapter with L{components.registerAdapter} for the given
|
||
|
class or interface and verify that the adapter can be looked up with
|
||
|
L{components.getAdapterFactory}.
|
||
|
"""
|
||
|
adapter = lambda o: None
|
||
|
components.registerAdapter(adapter, original, ITest)
|
||
|
self.assertIs(components.getAdapterFactory(original, ITest, None), adapter)
|
||
|
|
||
|
def test_registerAdapterForClass(self):
|
||
|
"""
|
||
|
Test that an adapter from a class can be registered and then looked
|
||
|
up.
|
||
|
"""
|
||
|
|
||
|
class TheOriginal:
|
||
|
pass
|
||
|
|
||
|
return self._registerAdapterForClassOrInterface(TheOriginal)
|
||
|
|
||
|
def test_registerAdapterForInterface(self):
|
||
|
"""
|
||
|
Test that an adapter from an interface can be registered and then
|
||
|
looked up.
|
||
|
"""
|
||
|
return self._registerAdapterForClassOrInterface(ITest2)
|
||
|
|
||
|
def _duplicateAdapterForClassOrInterface(self, original):
|
||
|
"""
|
||
|
Verify that L{components.registerAdapter} raises L{ValueError} if the
|
||
|
from-type/interface and to-interface pair is not unique.
|
||
|
"""
|
||
|
firstAdapter = lambda o: False
|
||
|
secondAdapter = lambda o: True
|
||
|
components.registerAdapter(firstAdapter, original, ITest)
|
||
|
self.assertRaises(
|
||
|
ValueError, components.registerAdapter, secondAdapter, original, ITest
|
||
|
)
|
||
|
# Make sure that the original adapter is still around as well
|
||
|
self.assertIs(components.getAdapterFactory(original, ITest, None), firstAdapter)
|
||
|
|
||
|
def test_duplicateAdapterForClass(self):
|
||
|
"""
|
||
|
Test that attempting to register a second adapter from a class
|
||
|
raises the appropriate exception.
|
||
|
"""
|
||
|
|
||
|
class TheOriginal:
|
||
|
pass
|
||
|
|
||
|
return self._duplicateAdapterForClassOrInterface(TheOriginal)
|
||
|
|
||
|
def test_duplicateAdapterForInterface(self):
|
||
|
"""
|
||
|
Test that attempting to register a second adapter from an interface
|
||
|
raises the appropriate exception.
|
||
|
"""
|
||
|
return self._duplicateAdapterForClassOrInterface(ITest2)
|
||
|
|
||
|
def _duplicateAdapterForClassOrInterfaceAllowed(self, original):
|
||
|
"""
|
||
|
Verify that when C{components.ALLOW_DUPLICATES} is set to C{True}, new
|
||
|
adapter registrations for a particular from-type/interface and
|
||
|
to-interface pair replace older registrations.
|
||
|
"""
|
||
|
firstAdapter = lambda o: False
|
||
|
secondAdapter = lambda o: True
|
||
|
|
||
|
class TheInterface(Interface):
|
||
|
pass
|
||
|
|
||
|
components.registerAdapter(firstAdapter, original, TheInterface)
|
||
|
components.ALLOW_DUPLICATES = True
|
||
|
try:
|
||
|
components.registerAdapter(secondAdapter, original, TheInterface)
|
||
|
self.assertIs(
|
||
|
components.getAdapterFactory(original, TheInterface, None),
|
||
|
secondAdapter,
|
||
|
)
|
||
|
finally:
|
||
|
components.ALLOW_DUPLICATES = False
|
||
|
|
||
|
# It should be rejected again at this point
|
||
|
self.assertRaises(
|
||
|
ValueError, components.registerAdapter, firstAdapter, original, TheInterface
|
||
|
)
|
||
|
|
||
|
self.assertIs(
|
||
|
components.getAdapterFactory(original, TheInterface, None), secondAdapter
|
||
|
)
|
||
|
|
||
|
def test_duplicateAdapterForClassAllowed(self):
|
||
|
"""
|
||
|
Test that when L{components.ALLOW_DUPLICATES} is set to a true
|
||
|
value, duplicate registrations from classes are allowed to override
|
||
|
the original registration.
|
||
|
"""
|
||
|
|
||
|
class TheOriginal:
|
||
|
pass
|
||
|
|
||
|
return self._duplicateAdapterForClassOrInterfaceAllowed(TheOriginal)
|
||
|
|
||
|
def test_duplicateAdapterForInterfaceAllowed(self):
|
||
|
"""
|
||
|
Test that when L{components.ALLOW_DUPLICATES} is set to a true
|
||
|
value, duplicate registrations from interfaces are allowed to
|
||
|
override the original registration.
|
||
|
"""
|
||
|
|
||
|
class TheOriginal(Interface):
|
||
|
pass
|
||
|
|
||
|
return self._duplicateAdapterForClassOrInterfaceAllowed(TheOriginal)
|
||
|
|
||
|
def _multipleInterfacesForClassOrInterface(self, original):
|
||
|
"""
|
||
|
Verify that an adapter can be registered for multiple to-interfaces at a
|
||
|
time.
|
||
|
"""
|
||
|
adapter = lambda o: None
|
||
|
components.registerAdapter(adapter, original, ITest, ITest2)
|
||
|
self.assertIs(components.getAdapterFactory(original, ITest, None), adapter)
|
||
|
self.assertIs(components.getAdapterFactory(original, ITest2, None), adapter)
|
||
|
|
||
|
def test_multipleInterfacesForClass(self):
|
||
|
"""
|
||
|
Test the registration of an adapter from a class to several
|
||
|
interfaces at once.
|
||
|
"""
|
||
|
|
||
|
class TheOriginal:
|
||
|
pass
|
||
|
|
||
|
return self._multipleInterfacesForClassOrInterface(TheOriginal)
|
||
|
|
||
|
def test_multipleInterfacesForInterface(self):
|
||
|
"""
|
||
|
Test the registration of an adapter from an interface to several
|
||
|
interfaces at once.
|
||
|
"""
|
||
|
return self._multipleInterfacesForClassOrInterface(ITest3)
|
||
|
|
||
|
def _subclassAdapterRegistrationForClassOrInterface(self, original):
|
||
|
"""
|
||
|
Verify that a new adapter can be registered for a particular
|
||
|
to-interface from a subclass of a type or interface which already has an
|
||
|
adapter registered to that interface and that the subclass adapter takes
|
||
|
precedence over the base class adapter.
|
||
|
"""
|
||
|
firstAdapter = lambda o: True
|
||
|
secondAdapter = lambda o: False
|
||
|
|
||
|
class TheSubclass(original):
|
||
|
pass
|
||
|
|
||
|
components.registerAdapter(firstAdapter, original, ITest)
|
||
|
components.registerAdapter(secondAdapter, TheSubclass, ITest)
|
||
|
self.assertIs(components.getAdapterFactory(original, ITest, None), firstAdapter)
|
||
|
self.assertIs(
|
||
|
components.getAdapterFactory(TheSubclass, ITest, None), secondAdapter
|
||
|
)
|
||
|
|
||
|
def test_subclassAdapterRegistrationForClass(self):
|
||
|
"""
|
||
|
Test that an adapter to a particular interface can be registered
|
||
|
from both a class and its subclass.
|
||
|
"""
|
||
|
|
||
|
class TheOriginal:
|
||
|
pass
|
||
|
|
||
|
return self._subclassAdapterRegistrationForClassOrInterface(TheOriginal)
|
||
|
|
||
|
def test_subclassAdapterRegistrationForInterface(self):
|
||
|
"""
|
||
|
Test that an adapter to a particular interface can be registered
|
||
|
from both an interface and its subclass.
|
||
|
"""
|
||
|
return self._subclassAdapterRegistrationForClassOrInterface(ITest2)
|
||
|
|
||
|
|
||
|
class IProxiedInterface(Interface):
|
||
|
"""
|
||
|
An interface class for use by L{proxyForInterface}.
|
||
|
"""
|
||
|
|
||
|
ifaceAttribute = Attribute(
|
||
|
"""
|
||
|
An example declared attribute, which should be proxied."""
|
||
|
)
|
||
|
|
||
|
def yay(*a, **kw):
|
||
|
"""
|
||
|
A sample method which should be proxied.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class IProxiedSubInterface(IProxiedInterface):
|
||
|
"""
|
||
|
An interface that derives from another for use with L{proxyForInterface}.
|
||
|
"""
|
||
|
|
||
|
def boo():
|
||
|
"""
|
||
|
A different sample method which should be proxied.
|
||
|
"""
|
||
|
|
||
|
|
||
|
@implementer(IProxiedInterface)
|
||
|
class Yayable: # type: ignore[misc]
|
||
|
# class does not implement Attribute ifaceAttribute
|
||
|
# so we need to turn off mypy warning
|
||
|
"""
|
||
|
A provider of L{IProxiedInterface} which increments a counter for
|
||
|
every call to C{yay}.
|
||
|
|
||
|
@ivar yays: The number of times C{yay} has been called.
|
||
|
"""
|
||
|
|
||
|
def __init__(self):
|
||
|
self.yays = 0
|
||
|
self.yayArgs = []
|
||
|
|
||
|
def yay(self, *a, **kw):
|
||
|
"""
|
||
|
Increment C{self.yays}.
|
||
|
"""
|
||
|
self.yays += 1
|
||
|
self.yayArgs.append((a, kw))
|
||
|
return self.yays
|
||
|
|
||
|
|
||
|
@implementer(IProxiedSubInterface)
|
||
|
class Booable: # type: ignore[misc]
|
||
|
# class does not implement Attribute ifaceAttribute
|
||
|
# so we need to turn off mypy warning
|
||
|
"""
|
||
|
An implementation of IProxiedSubInterface
|
||
|
"""
|
||
|
|
||
|
yayed = False
|
||
|
booed = False
|
||
|
|
||
|
def yay(self, *a, **kw):
|
||
|
"""
|
||
|
Mark the fact that 'yay' has been called.
|
||
|
"""
|
||
|
self.yayed = True
|
||
|
|
||
|
def boo(self):
|
||
|
"""
|
||
|
Mark the fact that 'boo' has been called.1
|
||
|
"""
|
||
|
self.booed = True
|
||
|
|
||
|
|
||
|
class IMultipleMethods(Interface):
|
||
|
"""
|
||
|
An interface with multiple methods.
|
||
|
"""
|
||
|
|
||
|
def methodOne():
|
||
|
"""
|
||
|
The first method. Should return 1.
|
||
|
"""
|
||
|
|
||
|
def methodTwo():
|
||
|
"""
|
||
|
The second method. Should return 2.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class MultipleMethodImplementor:
|
||
|
"""
|
||
|
A precise implementation of L{IMultipleMethods}.
|
||
|
"""
|
||
|
|
||
|
def methodOne(self):
|
||
|
"""
|
||
|
@return: 1
|
||
|
"""
|
||
|
return 1
|
||
|
|
||
|
def methodTwo(self):
|
||
|
"""
|
||
|
@return: 2
|
||
|
"""
|
||
|
return 2
|
||
|
|
||
|
|
||
|
class ProxyForInterfaceTests(unittest.SynchronousTestCase):
|
||
|
"""
|
||
|
Tests for L{proxyForInterface}.
|
||
|
"""
|
||
|
|
||
|
def test_original(self):
|
||
|
"""
|
||
|
Proxy objects should have an C{original} attribute which refers to the
|
||
|
original object passed to the constructor.
|
||
|
"""
|
||
|
original = object()
|
||
|
proxy = proxyForInterface(IProxiedInterface)(original)
|
||
|
self.assertIs(proxy.original, original)
|
||
|
|
||
|
def test_proxyMethod(self):
|
||
|
"""
|
||
|
The class created from L{proxyForInterface} passes methods on an
|
||
|
interface to the object which is passed to its constructor.
|
||
|
"""
|
||
|
klass = proxyForInterface(IProxiedInterface)
|
||
|
yayable = Yayable()
|
||
|
proxy = klass(yayable)
|
||
|
proxy.yay()
|
||
|
self.assertEqual(proxy.yay(), 2)
|
||
|
self.assertEqual(yayable.yays, 2)
|
||
|
|
||
|
def test_decoratedProxyMethod(self):
|
||
|
"""
|
||
|
Methods of the class created from L{proxyForInterface} can be used with
|
||
|
the decorator-helper L{functools.wraps}.
|
||
|
"""
|
||
|
base = proxyForInterface(IProxiedInterface)
|
||
|
|
||
|
class klass(base):
|
||
|
@wraps(base.yay)
|
||
|
def yay(self):
|
||
|
self.original.yays += 1
|
||
|
return base.yay(self)
|
||
|
|
||
|
original = Yayable()
|
||
|
yayable = klass(original)
|
||
|
yayable.yay()
|
||
|
self.assertEqual(2, original.yays)
|
||
|
|
||
|
def test_proxyAttribute(self):
|
||
|
"""
|
||
|
Proxy objects should proxy declared attributes, but not other
|
||
|
attributes.
|
||
|
"""
|
||
|
yayable = Yayable()
|
||
|
yayable.ifaceAttribute = object()
|
||
|
proxy = proxyForInterface(IProxiedInterface)(yayable)
|
||
|
self.assertIs(proxy.ifaceAttribute, yayable.ifaceAttribute)
|
||
|
self.assertRaises(AttributeError, lambda: proxy.yays)
|
||
|
|
||
|
def test_proxySetAttribute(self):
|
||
|
"""
|
||
|
The attributes that proxy objects proxy should be assignable and affect
|
||
|
the original object.
|
||
|
"""
|
||
|
yayable = Yayable()
|
||
|
proxy = proxyForInterface(IProxiedInterface)(yayable)
|
||
|
thingy = object()
|
||
|
proxy.ifaceAttribute = thingy
|
||
|
self.assertIs(yayable.ifaceAttribute, thingy)
|
||
|
|
||
|
def test_proxyDeleteAttribute(self):
|
||
|
"""
|
||
|
The attributes that proxy objects proxy should be deletable and affect
|
||
|
the original object.
|
||
|
"""
|
||
|
yayable = Yayable()
|
||
|
yayable.ifaceAttribute = None
|
||
|
proxy = proxyForInterface(IProxiedInterface)(yayable)
|
||
|
del proxy.ifaceAttribute
|
||
|
self.assertFalse(hasattr(yayable, "ifaceAttribute"))
|
||
|
|
||
|
def test_multipleMethods(self):
|
||
|
"""
|
||
|
[Regression test] The proxy should send its method calls to the correct
|
||
|
method, not the incorrect one.
|
||
|
"""
|
||
|
multi = MultipleMethodImplementor()
|
||
|
proxy = proxyForInterface(IMultipleMethods)(multi)
|
||
|
self.assertEqual(proxy.methodOne(), 1)
|
||
|
self.assertEqual(proxy.methodTwo(), 2)
|
||
|
|
||
|
def test_subclassing(self):
|
||
|
"""
|
||
|
It is possible to subclass the result of L{proxyForInterface}.
|
||
|
"""
|
||
|
|
||
|
class SpecializedProxy(proxyForInterface(IProxiedInterface)):
|
||
|
"""
|
||
|
A specialized proxy which can decrement the number of yays.
|
||
|
"""
|
||
|
|
||
|
def boo(self):
|
||
|
"""
|
||
|
Decrement the number of yays.
|
||
|
"""
|
||
|
self.original.yays -= 1
|
||
|
|
||
|
yayable = Yayable()
|
||
|
special = SpecializedProxy(yayable)
|
||
|
self.assertEqual(yayable.yays, 0)
|
||
|
special.boo()
|
||
|
self.assertEqual(yayable.yays, -1)
|
||
|
|
||
|
def test_proxyName(self):
|
||
|
"""
|
||
|
The name of a proxy class indicates which interface it proxies.
|
||
|
"""
|
||
|
proxy = proxyForInterface(IProxiedInterface)
|
||
|
self.assertEqual(
|
||
|
proxy.__name__,
|
||
|
"(Proxy for " "twisted.python.test.test_components.IProxiedInterface)",
|
||
|
)
|
||
|
|
||
|
def test_implements(self):
|
||
|
"""
|
||
|
The resulting proxy implements the interface that it proxies.
|
||
|
"""
|
||
|
proxy = proxyForInterface(IProxiedInterface)
|
||
|
self.assertTrue(IProxiedInterface.implementedBy(proxy))
|
||
|
|
||
|
def test_proxyDescriptorGet(self):
|
||
|
"""
|
||
|
_ProxyDescriptor's __get__ method should return the appropriate
|
||
|
attribute of its argument's 'original' attribute if it is invoked with
|
||
|
an object. If it is invoked with None, it should return a false
|
||
|
class-method emulator instead.
|
||
|
|
||
|
For some reason, Python's documentation recommends to define
|
||
|
descriptors' __get__ methods with the 'type' parameter as optional,
|
||
|
despite the fact that Python itself never actually calls the descriptor
|
||
|
that way. This is probably do to support 'foo.__get__(bar)' as an
|
||
|
idiom. Let's make sure that the behavior is correct. Since we don't
|
||
|
actually use the 'type' argument at all, this test calls it the
|
||
|
idiomatic way to ensure that signature works; test_proxyInheritance
|
||
|
verifies the how-Python-actually-calls-it signature.
|
||
|
"""
|
||
|
|
||
|
class Sample:
|
||
|
called = False
|
||
|
|
||
|
def hello(self):
|
||
|
self.called = True
|
||
|
|
||
|
fakeProxy = Sample()
|
||
|
testObject = Sample()
|
||
|
fakeProxy.original = testObject
|
||
|
pd = components._ProxyDescriptor("hello", "original")
|
||
|
self.assertEqual(pd.__get__(fakeProxy), testObject.hello)
|
||
|
fakeClassMethod = pd.__get__(None)
|
||
|
fakeClassMethod(fakeProxy)
|
||
|
self.assertTrue(testObject.called)
|
||
|
|
||
|
def test_proxyInheritance(self):
|
||
|
"""
|
||
|
Subclasses of the class returned from L{proxyForInterface} should be
|
||
|
able to upcall methods by reference to their superclass, as any normal
|
||
|
Python class can.
|
||
|
"""
|
||
|
|
||
|
class YayableWrapper(proxyForInterface(IProxiedInterface)):
|
||
|
"""
|
||
|
This class does not override any functionality.
|
||
|
"""
|
||
|
|
||
|
class EnhancedWrapper(YayableWrapper):
|
||
|
"""
|
||
|
This class overrides the 'yay' method.
|
||
|
"""
|
||
|
|
||
|
wrappedYays = 1
|
||
|
|
||
|
def yay(self, *a, **k):
|
||
|
self.wrappedYays += 1
|
||
|
return YayableWrapper.yay(self, *a, **k) + 7
|
||
|
|
||
|
yayable = Yayable()
|
||
|
wrapper = EnhancedWrapper(yayable)
|
||
|
self.assertEqual(wrapper.yay(3, 4, x=5, y=6), 8)
|
||
|
self.assertEqual(yayable.yayArgs, [((3, 4), dict(x=5, y=6))])
|
||
|
|
||
|
def test_interfaceInheritance(self):
|
||
|
"""
|
||
|
Proxies of subinterfaces generated with proxyForInterface should allow
|
||
|
access to attributes of both the child and the base interfaces.
|
||
|
"""
|
||
|
proxyClass = proxyForInterface(IProxiedSubInterface)
|
||
|
booable = Booable()
|
||
|
proxy = proxyClass(booable)
|
||
|
proxy.yay()
|
||
|
proxy.boo()
|
||
|
self.assertTrue(booable.yayed)
|
||
|
self.assertTrue(booable.booed)
|
||
|
|
||
|
def test_attributeCustomization(self):
|
||
|
"""
|
||
|
The original attribute name can be customized via the
|
||
|
C{originalAttribute} argument of L{proxyForInterface}: the attribute
|
||
|
should change, but the methods of the original object should still be
|
||
|
callable, and the attributes still accessible.
|
||
|
"""
|
||
|
yayable = Yayable()
|
||
|
yayable.ifaceAttribute = object()
|
||
|
proxy = proxyForInterface(IProxiedInterface, originalAttribute="foo")(yayable)
|
||
|
self.assertIs(proxy.foo, yayable)
|
||
|
|
||
|
# Check the behavior
|
||
|
self.assertEqual(proxy.yay(), 1)
|
||
|
self.assertIs(proxy.ifaceAttribute, yayable.ifaceAttribute)
|
||
|
thingy = object()
|
||
|
proxy.ifaceAttribute = thingy
|
||
|
self.assertIs(yayable.ifaceAttribute, thingy)
|
||
|
del proxy.ifaceAttribute
|
||
|
self.assertFalse(hasattr(yayable, "ifaceAttribute"))
|