~nskaggs/autopilot/add-click-rule

« back to all changes in this revision

Viewing changes to autopilot/introspection/_object_registry.py

Fix proxy creation for the root of the introspection tree. Fixes: https://bugs.launchpad.net/bugs/1306330, https://bugs.launchpad.net/bugs/1348399.

Approved by Christopher Lee, PS Jenkins bot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
authors to write their own classes to be used instead of the generic one that
27
27
autopilot creates.
28
28
 
 
29
This module contains two global dictionaries, both are keys by unique
 
30
connection id's (UUID objects). The values are described below.
 
31
 
 
32
* ``_object_registry`` contains dictionaries of class names (as strings)
 
33
  to class objects. Custom proxy classes defined by test authors will end up
 
34
  with their class object in this dictionary. This is used when we want to
 
35
  create a proxy instance - if nothing matches in this dictionary then we
 
36
  create a generic proxy instance instead.
 
37
 
 
38
* ``_proxy_extensions`` contains a tuple of extension classes to mix in to
 
39
   *every proxy class*. This is used to extend the proxy API on a
 
40
   per-connection basis. For example, Qt-based apps allow us to monitor
 
41
   signals and slots in the application, but Gtk apps do not.
 
42
 
29
43
"""
30
44
 
31
45
from uuid import uuid4
35
49
from contextlib import contextmanager
36
50
 
37
51
_object_registry = {}
 
52
_proxy_extensions = {}
 
53
 
 
54
 
 
55
def register_extension_classes_for_proxy_base(proxy_base, extensions):
 
56
    global _proxy_extensions
 
57
    _proxy_extensions[proxy_base._id] = (proxy_base,) + extensions
 
58
 
 
59
 
 
60
def _get_proxy_bases_for_id(id):
 
61
    global _proxy_extensions
 
62
    return _proxy_extensions.get(id, ())
38
63
 
39
64
 
40
65
class IntrospectableObjectMetaclass(type):
50
75
        custom proxy classes for more than one process at the same time and
51
76
        avoid clashes in the dictionary.
52
77
        """
 
78
        cls_id = None
53
79
 
54
 
        # ignore the classes at the top of the inheritance heirarchy (i.e.- the
55
 
        # ones that we control.)
56
 
        if classname not in (
57
 
            'ApplicationProxyObject',
58
 
            'CustomEmulatorBase',
59
 
            'DBusIntrospectionObject',
60
 
            'DBusIntrospectionObjectBase',
61
 
        ):
62
 
            # also ignore classes that derive from a class that already has the
63
 
            # _id attribute set.
64
 
            have_id = any([hasattr(b, '_id') for b in bases])
65
 
            if not have_id:
 
80
        for base in bases:
 
81
            if hasattr(base, '_id'):
 
82
                cls_id = base._id
 
83
                break
 
84
        else:
 
85
            # Ignore classes that are in the autopilot class heirarchy:
 
86
            if classname not in (
 
87
                'ApplicationProxyObject',
 
88
                'CustomEmulatorBase',
 
89
                'DBusIntrospectionObject',
 
90
                'DBusIntrospectionObjectBase',
 
91
            ):
66
92
                # Add the '_id' attribute as a class attr:
67
 
                classdict['_id'] = uuid4()
 
93
                cls_id = classdict['_id'] = uuid4()
68
94
 
 
95
        # use the bases passed to us, but extend it with whatever is stored in
 
96
        # the proxy_extensions dictionary.
 
97
        extensions = _get_proxy_bases_for_id(cls_id)
 
98
        for extension in extensions:
 
99
            if extension not in bases:
 
100
                bases += (extension,)
69
101
        # make the object. Nothing special here.
70
102
        class_object = type.__new__(cls, classname, bases, classdict)
71
103
 
72
 
        # If the newly made object has an id, add it to the object registry.
73
 
        if getattr(class_object, '_id', None) is not None:
74
 
            if class_object._id in _object_registry:
75
 
                _object_registry[class_object._id][classname] = class_object
76
 
            else:
77
 
                _object_registry[class_object._id] = {classname: class_object}
 
104
        if not classdict.get('__generated', False):
 
105
            # If the newly made object has an id, add it to the object
 
106
            # registry.
 
107
            if getattr(class_object, '_id', None) is not None:
 
108
                if class_object._id in _object_registry:
 
109
                    _object_registry[class_object._id][classname] = \
 
110
                        class_object
 
111
                else:
 
112
                    _object_registry[class_object._id] = \
 
113
                        {classname: class_object}
78
114
        # in all cases, return the class unchanged.
79
115
        return class_object
80
116
 
86
122
)
87
123
 
88
124
 
89
 
def _get_proxy_object_class(object_id, default_class, path, state):
 
125
def _get_proxy_object_class(object_id, path, state):
90
126
    """Return a custom proxy class, from the object registry or the default.
91
127
 
92
128
    This function first inspects the object registry using the object_id passed
105
141
    :raises ValueError: if more than one class in the dict matches
106
142
 
107
143
    """
108
 
    class_type = None
109
 
    if object_id is not None:
110
 
        class_type = _try_custom_proxy_classes(object_id, path, state)
 
144
    class_type = _try_custom_proxy_classes(object_id, path, state)
111
145
 
112
146
    return class_type or _get_default_proxy_class(
113
 
        default_class,
 
147
        object_id,
114
148
        get_classname_from_path(path)
115
149
    )
116
150
 
132
166
    if len(possible_classes) > 1:
133
167
        raise ValueError(
134
168
            'More than one custom proxy class matches this object: '
135
 
            'Matching classes are: %s. State is %s.  Path is %s.'
136
 
            ','.join([repr(c) for c in possible_classes]),
137
 
            repr(state),
138
 
            path)
 
169
            'Matching classes are: %s. State is %s.  Path is %s.' % (
 
170
                ','.join([repr(c) for c in possible_classes]),
 
171
                repr(state),
 
172
                path,
 
173
            )
 
174
        )
139
175
    if len(possible_classes) == 1:
140
176
        return possible_classes[0]
141
177
    return None
142
178
 
143
179
 
144
 
def _get_default_proxy_class(default_class, name):
 
180
def _get_default_proxy_class(id, name):
145
181
    """Return a custom proxy object class of the default or a base class.
146
182
 
147
183
    We want the object to inherit from the class that is set as the emulator
156
192
    get_debug_logger().warning(
157
193
        "Generating introspection instance for type '%s' based on generic "
158
194
        "class.", name)
159
 
    for base in default_class.__bases__:
160
 
        if hasattr(base, '_id'):
161
 
            base_class = base
162
 
            break
163
 
    else:
164
 
        base_class = default_class
165
195
    if isinstance(name, bytes):
166
196
        name = name.decode('utf-8')
167
 
    return type(name, (base_class,), {})
 
197
    return type(name, _get_proxy_bases_for_id(id), dict(__generated=True))
168
198
 
169
199
 
170
200
@contextmanager