Skip to content

Commit

Permalink
Fix IRO consistency tests on Python 2.
Browse files Browse the repository at this point in the history
basestring was messing things up. See this build:

https://travis-ci.org/zopefoundation/zope.interface/builds/660386194

For some reason, though, I didn't reproduce this at all originally.
When recreating my virtualenvironments (tox -r) I started getting it
in PyPy, but I still can't reproduce it in CPython 2.7.17. Travis is
2.7.15, but there don't seem to be any relevant changes.
  • Loading branch information
jamadden committed Mar 10, 2020
1 parent acaa96f commit 2349c39
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 18 deletions.
9 changes: 7 additions & 2 deletions src/zope/interface/common/__init__.py
Expand Up @@ -121,18 +121,20 @@ def __init__(self, name, bases, attrs):
# go ahead and give us a name to ease debugging.
self.__name__ = name
extra_classes = attrs.pop('extra_classes', ())
ignored_classes = attrs.pop('ignored_classes', ())

if 'abc' not in attrs:
# Something like ``IList(ISequence)``: We're extending
# abc interfaces but not an ABC interface ourself.
InterfaceClass.__init__(self, name, bases, attrs)
ABCInterfaceClass.__register_classes(self, extra_classes)
ABCInterfaceClass.__register_classes(self, extra_classes, ignored_classes)
self.__class__ = InterfaceClass
return

based_on = attrs.pop('abc')
self.__abc = based_on
self.__extra_classes = tuple(extra_classes)
self.__ignored_classes = tuple(ignored_classes)

assert name[1:] == based_on.__name__, (name, based_on)
methods = {
Expand Down Expand Up @@ -215,11 +217,14 @@ def __method_from_function(self, function, name):
method.positional = method.positional[1:]
return method

def __register_classes(self, conformers=None):
def __register_classes(self, conformers=None, ignored_classes=None):
# Make the concrete classes already present in our ABC's registry
# declare that they implement this interface.
conformers = conformers if conformers is not None else self.getRegisteredConformers()
ignored = ignored_classes if ignored_classes is not None else self.__ignored_classes
for cls in conformers:
if cls in ignored:
continue
classImplements(cls, self)

def getABC(self):
Expand Down
4 changes: 4 additions & 0 deletions src/zope/interface/common/collections.py
Expand Up @@ -177,6 +177,10 @@ class ISequence(IReversible,
ICollection):
abc = abc.Sequence
extra_classes = (UserString,)
# On Python 2, basestring is registered as an ISequence, and
# its subclass str is an IByteString. If we also register str as
# an ISequence, that tends to lead to inconsistent resolution order.
ignored_classes = (basestring,) if str is bytes else () # pylint:disable=undefined-variable

@optional
def __reversed__():
Expand Down
12 changes: 8 additions & 4 deletions src/zope/interface/common/tests/__init__.py
Expand Up @@ -38,7 +38,8 @@ def iter_abc_interfaces(predicate=lambda iface: True):
if not predicate(iface):
continue

registered = list(iface.getRegisteredConformers())
registered = set(iface.getRegisteredConformers())
registered -= set(iface._ABCInterfaceClass__ignored_classes)
if registered:
yield iface, registered

Expand All @@ -53,17 +54,20 @@ def add_verify_tests(cls, iface_classes_iter):
cls.maxDiff = None
for iface, registered_classes in iface_classes_iter:
for stdlib_class in registered_classes:

def test(self, stdlib_class=stdlib_class, iface=iface):
if stdlib_class in self.UNVERIFIABLE or stdlib_class.__name__ in self.UNVERIFIABLE:
self.skipTest("Unable to verify %s" % stdlib_class)

self.assertTrue(self.verify(iface, stdlib_class))

suffix = stdlib_class.__name__ + '_' + iface.__name__
suffix = "%s_%s_%s" % (
stdlib_class.__name__,
iface.__module__.replace('.', '_'),
iface.__name__
)
name = 'test_auto_' + suffix
test.__name__ = name
assert not hasattr(cls, name)
assert not hasattr(cls, name), (name, list(cls.__dict__))
setattr(cls, name, test)

def test_ro(self, stdlib_class=stdlib_class, iface=iface):
Expand Down
44 changes: 32 additions & 12 deletions src/zope/interface/ro.py
Expand Up @@ -56,8 +56,8 @@ def _legacy_mergeOrderings(orderings):

return result

def _legacy_flatten(ob):
result = [ob]
def _legacy_flatten(begin):
result = [begin]
i = 0
for ob in iter(result):
i += 1
Expand All @@ -83,7 +83,8 @@ def _legacy_ro(ob):
# Allow changing during runtime for tests.
STRICT_RO = False
TRACK_BAD_IRO = None
BAD_IROS = None

BAD_IROS = None # WeakKeyDictionary{C: formatted exception}

class InconsistentResolutionOrderWarning(PendingDeprecationWarning):
"""
Expand All @@ -95,6 +96,28 @@ class InconsistentResolutionOrderError(TypeError):
The error raised when an invalid IRO is requested in strict mode.
"""

def __init__(self, C, base_tree_remaining, memo):
self.C = C
self.base_ros = {
base: ro(base, memo) for base in C.__bases__
}
# Unfortunately, this doesn't necessarily directly match
# up to any transformation on C.__bases__, because
# if any were fully used up, they were removed already.
self.base_tree_remaining = base_tree_remaining

TypeError.__init__(self)

def __str__(self):
import pprint
return "%s: For object %r.\nBase ROs:\n%s\nConflict Location:\n%s" % (
self.__class__.__name__,
self.C,
pprint.pformat(self.base_ros),
pprint.pformat(self.base_tree_remaining),
)



def _can_choose_base(base, base_tree_remaining):
# From C3:
Expand Down Expand Up @@ -135,8 +158,8 @@ def _warn_iro():


def _find_next_base(C, base_tree_remaining, memo, ignore_first=True, strict=False):
global TRACK_BAD_IRO
global BAD_IROS
global TRACK_BAD_IRO # pylint:disable=global-statement
global BAD_IROS # pylint:disable=global-statement

base = None
warn_iro = _warn_iro
Expand Down Expand Up @@ -185,13 +208,10 @@ def _find_next_base(C, base_tree_remaining, memo, ignore_first=True, strict=Fals
if strict or TRACK_BAD_IRO:
catch = () if strict else InconsistentResolutionOrderError
try:
raise InconsistentResolutionOrderError(
"Invalid IRO", C,
[[C]] + [ro(base, memo) for base in C.__bases__] + [list(C.__bases__)]
)
except catch as ex:
BAD_IROS[C] = ex
del ex
raise InconsistentResolutionOrderError(C, base_tree_remaining, memo)
except catch:
import traceback
BAD_IROS[C] = traceback.format_exc()

if ignore_first:
warn_iro()
Expand Down

0 comments on commit 2349c39

Please sign in to comment.