aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/zope.interface/py3/zope/interface/common/tests/__init__.py
blob: f70a53099907d699b6054ed7f77c454c836aad68 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
##############################################################################
# Copyright (c) 2020 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
##############################################################################

import unittest

from zope.interface.common import ABCInterface
from zope.interface.common import ABCInterfaceClass
from zope.interface.verify import verifyClass
from zope.interface.verify import verifyObject


def iter_abc_interfaces(predicate=lambda iface: True):
    # Iterate ``(iface, classes)``, where ``iface`` is a descendent of
    # the ABCInterfaceClass passing the *predicate* and ``classes`` is
    # an iterable of classes registered to conform to that interface.
    #
    # Note that some builtin classes are registered for two distinct parts of
    # the ABC/interface tree. For example, bytearray is both ByteString and
    # MutableSequence.
    seen = set()
    stack = list(
        ABCInterface.dependents
    )  # subclasses, but also implementedBy objects

    while stack:
        iface = stack.pop(0)
        if iface in seen or not isinstance(iface, ABCInterfaceClass):
            continue
        seen.add(iface)
        stack.extend(list(iface.dependents))
        if not predicate(iface):
            continue

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


def add_abc_interface_tests(cls, module):
    def predicate(iface):
        return iface.__module__ == module
    add_verify_tests(cls, iter_abc_interfaces(predicate))


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 = "{}_{}_{}_{}".format(
                stdlib_class.__module__.replace('.', '_'),
                stdlib_class.__name__,
                iface.__module__.replace('.', '_'),
                iface.__name__
            )
            name = 'test_auto_' + suffix
            test.__name__ = name
            assert not hasattr(cls, name), (name, list(cls.__dict__))
            setattr(cls, name, test)

            def test_ro(self, stdlib_class=stdlib_class, iface=iface):
                from zope.interface import Interface
                from zope.interface import implementedBy
                from zope.interface import ro
                self.assertEqual(
                    tuple(ro.ro(iface, strict=True)),
                    iface.__sro__)
                implements = implementedBy(stdlib_class)
                sro = implements.__sro__
                self.assertIs(sro[-1], Interface)

                if stdlib_class not in self.UNVERIFIABLE_RO:
                    # Check that we got the strict C3 resolution order, unless
                    # we know we cannot. Note that 'Interface' is virtual base
                    # that doesn't necessarily appear at the same place in the
                    # calculated SRO as in the final SRO.
                    strict = stdlib_class not in self.NON_STRICT_RO
                    isro = ro.ro(implements, strict=strict)
                    isro.remove(Interface)
                    isro.append(Interface)

                    self.assertEqual(tuple(isro), sro)

            name = 'test_auto_ro_' + suffix
            test_ro.__name__ = name
            assert not hasattr(cls, name)
            setattr(cls, name, test_ro)


class VerifyClassMixin(unittest.TestCase):
    verifier = staticmethod(verifyClass)
    UNVERIFIABLE = ()
    NON_STRICT_RO = ()
    UNVERIFIABLE_RO = ()

    def _adjust_object_before_verify(self, iface, x):
        return x

    def verify(self, iface, klass, **kwargs):
        return self.verifier(iface,
                             self._adjust_object_before_verify(iface, klass),
                             **kwargs)


class VerifyObjectMixin(VerifyClassMixin):
    verifier = staticmethod(verifyObject)
    CONSTRUCTORS = {
    }

    def _adjust_object_before_verify(self, iface, x):
        constructor = self.CONSTRUCTORS.get(x)
        if not constructor:
            constructor = self.CONSTRUCTORS.get(iface)
        if not constructor:
            constructor = self.CONSTRUCTORS.get(x.__name__)
        if not constructor:
            constructor = x
        if constructor is unittest.SkipTest:
            self.skipTest("Cannot create " + str(x))

        try:
            result = constructor()
        except Exception as e:  # pragma: no cover
            raise TypeError(
                f'Failed to create instance of {constructor}') from e
        if hasattr(result, 'close'):
            self.addCleanup(result.close)
        return result