Skip to content

Commit

Permalink
Fix implementerOnly when duplicating something inherited from the par…
Browse files Browse the repository at this point in the history
…ent.

Seen in plone.app.testing as a problem in Products.GenericSetup.

Added tests for that.
  • Loading branch information
jamadden committed Apr 7, 2020
1 parent 4cb3e63 commit 97c0fbc
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 79 deletions.
5 changes: 5 additions & 0 deletions src/zope/interface/declarations.py
Expand Up @@ -431,8 +431,13 @@ def classImplementsOnly(cls, *interfaces):
in *interfaces*. This can be used to alter the interface resolution order.
"""
spec = implementedBy(cls)
# Clear out everything inherited. It's important to
# also clear the bases right now so that we don't improperly discard
# interfaces that are already implemented by *old* bases that we're
# about to get rid of.
spec.declared = ()
spec.inherit = None
spec.__bases__ = ()
_classImplements_ordered(spec, interfaces, ())


Expand Down
163 changes: 84 additions & 79 deletions src/zope/interface/tests/test_declarations.py
Expand Up @@ -784,27 +784,58 @@ def _getTargetClass(self):
return implementedBy


class Test_classImplementsOnly(unittest.TestCase):
class _ImplementsTestMixin(object):
FUT_SETS_PROVIDED_BY = True

def _callFUT(self, *args, **kw):
from zope.interface.declarations import classImplementsOnly
return classImplementsOnly(*args, **kw)
def _callFUT(self, cls, iface):
# Declare that *cls* implements *iface*; return *cls*
raise NotImplementedError

def test_no_existing(self):
def _check_implementer(self, Foo,
orig_spec=None,
spec_name=__name__ + '.Foo',
inherit="not given"):
from zope.interface.declarations import ClassProvides
from zope.interface.interface import InterfaceClass
IFoo = InterfaceClass('IFoo')

returned = self._callFUT(Foo, IFoo)

self.assertIs(returned, Foo)
spec = Foo.__implemented__
if orig_spec is not None:
self.assertIs(spec, orig_spec)

self.assertEqual(spec.__name__,
spec_name)
inherit = Foo if inherit == "not given" else inherit
self.assertIs(spec.inherit, inherit)
self.assertIs(Foo.__implemented__, spec)
if self.FUT_SETS_PROVIDED_BY:
self.assertIsInstance(Foo.__providedBy__, ClassProvides)
self.assertIsInstance(Foo.__provides__, ClassProvides)
self.assertEqual(Foo.__provides__, Foo.__providedBy__)

return Foo, IFoo

def test_oldstyle_class(self):
# This only matters on Python 2
class Foo:
pass
self._check_implementer(Foo)

def test_newstyle_class(self):
class Foo(object):
pass
ifoo = InterfaceClass('IFoo')
self._callFUT(Foo, ifoo)
spec = Foo.__implemented__ # pylint:disable=no-member
self.assertEqual(spec.__name__,
'zope.interface.tests.test_declarations.Foo')
self.assertIsNone(spec.inherit)
self.assertIs(Foo.__implemented__, spec) # pylint:disable=no-member
self.assertIsInstance(Foo.__providedBy__, ClassProvides) # pylint:disable=no-member
self.assertIsInstance(Foo.__provides__, ClassProvides) # pylint:disable=no-member
self.assertEqual(Foo.__provides__, Foo.__providedBy__) # pylint:disable=no-member
self._check_implementer(Foo)

class Test_classImplementsOnly(_ImplementsTestMixin, unittest.TestCase):
FUT_SETS_PROVIDED_BY = False

def _callFUT(self, cls, iface):
from zope.interface.declarations import classImplementsOnly
classImplementsOnly(cls, iface)
return cls

def test_w_existing_Implements(self):
from zope.interface.declarations import Implements
Expand All @@ -822,50 +853,52 @@ class Foo(object):
self.assertEqual(impl.inherit, None)
self.assertEqual(impl.declared, (IBar,))

def test_oldstyle_class(self):
from zope.interface.declarations import Implements
from zope.interface.interface import InterfaceClass
IBar = InterfaceClass('IBar')
old_spec = Implements(IBar)

class Test_classImplements(unittest.TestCase):

def _callFUT(self, cls, iface):
from zope.interface.declarations import classImplements
result = classImplements(cls, iface) # pylint:disable=assignment-from-no-return
self.assertIsNone(result)
return cls
class Foo:
__implemented__ = old_spec
self._check_implementer(Foo, old_spec, '?', inherit=None)

def __check_implementer(self, Foo):
from zope.interface.declarations import ClassProvides
def test_newstyle_class(self):
from zope.interface.declarations import Implements
from zope.interface.interface import InterfaceClass
IFoo = InterfaceClass('IFoo')

returned = self._callFUT(Foo, IFoo)
IBar = InterfaceClass('IBar')
old_spec = Implements(IBar)

self.assertIs(returned, Foo)
spec = Foo.__implemented__
class Foo(object):
__implemented__ = old_spec
self._check_implementer(Foo, old_spec, '?', inherit=None)

self.assertEqual(spec.__name__,
'zope.interface.tests.test_declarations.Foo')
self.assertIs(spec.inherit, Foo)
self.assertIs(Foo.__implemented__, spec)
self.assertIsInstance(Foo.__providedBy__, ClassProvides)
self.assertIsInstance(Foo.__provides__, ClassProvides)
self.assertEqual(Foo.__provides__, Foo.__providedBy__)

return Foo, IFoo
def test_redundant_with_super_still_implements(self):
Base, IBase = self._check_implementer(
type('Foo', (object,), {}),
inherit=None,
)

def test_oldstyle_class(self):
# This only matters on Python 2
class Foo:
class Child(Base):
pass
self.__check_implementer(Foo)

def test_newstyle_class(self):
class Foo(object):
pass
self.__check_implementer(Foo)
self._callFUT(Child, IBase)
self.assertTrue(IBase.implementedBy(Child))


class Test_classImplements(_ImplementsTestMixin, unittest.TestCase):

def _callFUT(self, cls, iface):
from zope.interface.declarations import classImplements
result = classImplements(cls, iface) # pylint:disable=assignment-from-no-return
self.assertIsNone(result)
return cls

def __check_implementer_redundant(self, Base):
# If we @implementer exactly what was already present, we write
# no declared attributes on the parent (we still set everything, though)
Base, IBase = self.__check_implementer(Base)
Base, IBase = self._check_implementer(Base)

class Child(Base):
pass
Expand Down Expand Up @@ -1026,7 +1059,7 @@ class Foo(object):



class Test_implementer_only(unittest.TestCase):
class Test_implementer_only(Test_classImplementsOnly):

def _getTargetClass(self):
from zope.interface.declarations import implementer_only
Expand All @@ -1035,6 +1068,10 @@ def _getTargetClass(self):
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)

def _callFUT(self, cls, iface):
decorator = self._makeOne(iface)
return decorator(cls)

def test_function(self):
from zope.interface.interface import InterfaceClass
IFoo = InterfaceClass('IFoo')
Expand All @@ -1052,38 +1089,6 @@ def _method(self):
raise NotImplementedError()
self.assertRaises(ValueError, decorator, Bar._method)

def test_oldstyle_class(self):
# TODO Py3 story
from zope.interface.declarations import Implements
from zope.interface.interface import InterfaceClass
IFoo = InterfaceClass('IFoo')
IBar = InterfaceClass('IBar')
old_spec = Implements(IBar)
class Foo:
__implemented__ = old_spec
decorator = self._makeOne(IFoo)
returned = decorator(Foo)
self.assertTrue(returned is Foo)
spec = Foo.__implemented__
self.assertEqual(spec.__name__, '?')
self.assertTrue(spec.inherit is None)
self.assertTrue(Foo.__implemented__ is spec)

def test_newstyle_class(self):
from zope.interface.declarations import Implements
from zope.interface.interface import InterfaceClass
IFoo = InterfaceClass('IFoo')
IBar = InterfaceClass('IBar')
old_spec = Implements(IBar)
class Foo(object):
__implemented__ = old_spec
decorator = self._makeOne(IFoo)
returned = decorator(Foo)
self.assertTrue(returned is Foo)
spec = Foo.__implemented__
self.assertEqual(spec.__name__, '?')
self.assertTrue(spec.inherit is None)
self.assertTrue(Foo.__implemented__ is spec)


# Test '_implements' by way of 'implements{,Only}', its only callers.
Expand Down

0 comments on commit 97c0fbc

Please sign in to comment.