1# python sucks! vim: set fileencoding=utf-8 :
2# Copyright © 2009–2010 Nokia Corporation
3# Copyright © 2009–2010 Collabora Ltd.
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18# 02110-1301 USA
19
20"""
21Infrastructure code for testing Mission Control
22"""
23
24import base64
25import os
26import sys
27
28import constants as cs
29import servicetest
30import twisted
31from twisted.internet import reactor
32
33import dbus
34import dbus.service
35
36from fakeaccountsservice import FakeAccountsService
37from fakeconnectivity import FakeConnectivity
38
39def install_colourer():
40    def red(s):
41        return '\x1b[31m%s\x1b[0m' % s
42
43    def green(s):
44        return '\x1b[32m%s\x1b[0m' % s
45
46    patterns = {
47        'handled': green,
48        'not handled': red,
49        }
50
51    class Colourer:
52        def __init__(self, fh, patterns):
53            self.fh = fh
54            self.patterns = patterns
55
56        def write(self, s):
57            f = self.patterns.get(s, lambda x: x)
58            self.fh.write(f(s))
59
60    sys.stdout = Colourer(sys.stdout, patterns)
61    return sys.stdout
62
63class MC(dbus.proxies.ProxyObject):
64    def __init__(self, queue, bus, wait_for_names=True, initially_online=True):
65        """
66        Arguments:
67
68          queue: an event queue
69          bus: a D-Bus connection
70          wait_for_names: if True, the constructor will wait for MC to have
71                          been service-activated before returning. if False,
72                          the caller may later call wait_for_names().
73          initially_online: whether the fake implementations of Network Manager
74                            and ConnMan should claim to be online or offline.
75        """
76        dbus.proxies.ProxyObject.__init__(self,
77            conn=bus,
78            bus_name=cs.MC,
79            object_path=cs.MC_PATH,
80            follow_name_owner_changes=True)
81
82        self.connectivity = FakeConnectivity(queue, bus, initially_online)
83        self.q = queue
84        self.bus = bus
85
86        if wait_for_names:
87            self.wait_for_names()
88
89    def wait_for_names(self, *also_expect):
90        """
91        Waits for MC to have claimed all its bus names, along with the
92        (optional) EventPatterns passed as arguments.
93        """
94
95        patterns = [
96            servicetest.EventPattern('dbus-signal', signal='NameOwnerChanged',
97                predicate=lambda e, name=name: e.args[0] == name and e.args[2] != '')
98            for name in [cs.AM, cs.CD, cs.MC]
99            if not self.bus.name_has_owner(name)]
100
101        patterns.extend(also_expect)
102
103        events = self.q.expect_many(*patterns)
104
105        return events[3:]
106
107def exec_test_deferred (fun, params, protocol=None, timeout=None,
108        preload_mc=True, initially_online=True, use_fake_accounts_service=True,
109        pass_kwargs=False):
110    colourer = None
111
112    if sys.stdout.isatty():
113        colourer = install_colourer()
114
115    queue = servicetest.IteratingEventQueue(timeout)
116    queue.verbose = (
117        os.environ.get('CHECK_TWISTED_VERBOSE', '') != ''
118        or '-v' in sys.argv)
119
120    bus = dbus.SessionBus()
121    queue.attach_to_bus(bus)
122    error = None
123
124    if preload_mc:
125        try:
126            mc = MC(queue, bus, initially_online=initially_online)
127        except Exception as e:
128            import traceback
129            traceback.print_exc()
130            os._exit(1)
131    else:
132        mc = None
133
134    if use_fake_accounts_service:
135        fake_accounts_service = FakeAccountsService(queue, bus)
136
137        if preload_mc:
138            queue.expect('dbus-signal',
139                    path=cs.TEST_DBUS_ACCOUNT_PLUGIN_PATH,
140                    interface=cs.TEST_DBUS_ACCOUNT_PLUGIN_IFACE,
141                    signal='Active')
142    else:
143        fake_accounts_service = None
144
145    if pass_kwargs:
146        kwargs=dict(fake_accounts_service=fake_accounts_service)
147    else:
148        kwargs=dict()
149
150    try:
151        fun(queue, bus, mc, **kwargs)
152    except Exception as e:
153        import traceback
154        traceback.print_exc()
155        error = e
156        queue.verbose = False
157
158    # Clean up any accounts which are left over from the test.
159    try:
160        am = AccountManager(bus)
161        am_props = am.Properties.GetAll(cs.AM)
162
163        for a in (am_props.get('ValidAccounts', []) +
164                am_props.get('InvalidAccounts', [])):
165            account = Account(bus, a)
166
167            try:
168                account.Properties.Set(cs.ACCOUNT, 'RequestedPresence',
169                        (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline',
170                            ''))
171            except dbus.DBusException as e:
172                print("Can't set %s offline: %s" % (a, e), file=sys.stderr)
173
174            try:
175                account.Properties.Set(cs.ACCOUNT, 'Enabled', False)
176            except dbus.DBusException as e:
177                print("Can't disable %s: %s" % (a, e), file=sys.stderr)
178
179            try:
180                account.Remove()
181            except dbus.DBusException as e:
182                print("Can't remove %s: %s" % (a, e), file=sys.stderr)
183
184            servicetest.sync_dbus(bus, queue, am)
185
186    except dbus.DBusException as e:
187        print("Couldn't clean up left-over accounts: %s" % e, file=sys.stderr)
188
189    except Exception as e:
190        import traceback
191        traceback.print_exc()
192        error = e
193
194    queue.cleanup()
195
196    if error is None:
197      reactor.callLater(0, reactor.stop)
198    else:
199      # please ignore the POSIX behind the curtain
200      os._exit(1)
201
202    if colourer:
203      sys.stdout = colourer.fh
204
205def exec_test(fun, params=None, protocol=None, timeout=None,
206              preload_mc=True, initially_online=True,
207              use_fake_accounts_service=True, pass_kwargs=False):
208  reactor.callWhenRunning (exec_test_deferred, fun, params, protocol, timeout,
209          preload_mc, initially_online, use_fake_accounts_service, pass_kwargs)
210  reactor.run()
211
212class SimulatedConnection(object):
213
214    def ensure_handle(self, type, identifier):
215        if (type, identifier) in self._handles:
216            return self._handles[(type, identifier)]
217
218        self._last_handle += 1
219        self._handles[(type, identifier)] = self._last_handle
220        self._identifiers[(type, self._last_handle)] = identifier
221        return self._last_handle
222
223    def __init__(self, q, bus, cmname, protocol, account_part, self_ident,
224            self_alias=None,
225            implement_get_interfaces=True, has_requests=True,
226            has_presence=False, has_aliasing=False, has_avatars=False,
227            avatars_persist=True, extra_interfaces=[], has_hidden=False,
228            implement_get_aliases=True, initial_avatar=None,
229            server_delays_avatar=False):
230        self.q = q
231        self.bus = bus
232
233        if self_alias is None:
234            self_alias = self_ident
235
236        self.bus_name = '.'.join([cs.tp_name_prefix, 'Connection',
237                cmname, protocol.replace('-', '_'), account_part])
238        self._bus_name_ref = dbus.service.BusName(self.bus_name, self.bus)
239        self.object_path = '/' + self.bus_name.replace('.', '/')
240
241        self._last_handle = 41
242        self._handles = {}
243        self._identifiers = {}
244        self.status = cs.CONN_STATUS_DISCONNECTED
245        self.reason = cs.CONN_STATUS_CONNECTING
246        self.self_ident = self_ident
247        self.self_alias = self_alias
248        self.self_handle = self.ensure_handle(cs.HT_CONTACT, self_ident)
249        self.channels = []
250        self.has_requests = has_requests
251        self.has_presence = has_presence
252        self.has_aliasing = has_aliasing
253        self.has_avatars = has_avatars
254        self.avatars_persist = avatars_persist
255        self.extra_interfaces = extra_interfaces[:]
256        self.avatar_delayed = server_delays_avatar
257
258        self.interfaces = []
259        self.interfaces.append(cs.CONN_IFACE_CONTACTS)
260
261        if self.has_requests:
262            self.interfaces.append(cs.CONN_IFACE_REQUESTS)
263        if self.has_aliasing:
264            self.interfaces.append(cs.CONN_IFACE_ALIASING)
265        if self.has_avatars:
266            self.interfaces.append(cs.CONN_IFACE_AVATARS)
267        if self.has_presence:
268            self.interfaces.append(cs.CONN_IFACE_SIMPLE_PRESENCE)
269        if self.extra_interfaces:
270            self.interfaces.extend(self.extra_interfaces)
271
272        if initial_avatar is not None:
273            self.avatar = initial_avatar
274        elif self.avatars_persist:
275            self.avatar = dbus.Struct((dbus.ByteArray(b'my old avatar'),
276                    'text/plain'), signature='ays')
277        else:
278            self.avatar = None
279
280        q.add_dbus_method_impl(self.Connect,
281                path=self.object_path, interface=cs.CONN, method='Connect')
282        q.add_dbus_method_impl(self.Disconnect,
283                path=self.object_path, interface=cs.CONN, method='Disconnect')
284        q.add_dbus_method_impl(self.GetSelfHandle,
285                path=self.object_path,
286                interface=cs.CONN, method='GetSelfHandle')
287        q.add_dbus_method_impl(self.GetStatus,
288                path=self.object_path, interface=cs.CONN, method='GetStatus')
289
290        q.add_dbus_method_impl(self.GetAll_Connection,
291                path=self.object_path,
292                interface=cs.PROPERTIES_IFACE, method='GetAll',
293                args=[cs.CONN])
294
295        if implement_get_interfaces:
296            q.add_dbus_method_impl(self.GetInterfaces,
297                    path=self.object_path, interface=cs.CONN,
298                    method='GetInterfaces')
299
300        q.add_dbus_method_impl(self.RequestHandles,
301                path=self.object_path, interface=cs.CONN,
302                method='RequestHandles')
303        q.add_dbus_method_impl(self.InspectHandles,
304                path=self.object_path, interface=cs.CONN,
305                method='InspectHandles')
306        q.add_dbus_method_impl(self.HoldHandles,
307                path=self.object_path, interface=cs.CONN,
308                method='HoldHandles')
309        q.add_dbus_method_impl(self.GetAll_Requests,
310                path=self.object_path,
311                interface=cs.PROPERTIES_IFACE, method='GetAll',
312                args=[cs.CONN_IFACE_REQUESTS])
313
314        q.add_dbus_method_impl(self.GetContactAttributes,
315                path=self.object_path,
316                interface=cs.CONN_IFACE_CONTACTS, method='GetContactAttributes')
317        q.add_dbus_method_impl(self.GetContactByID,
318                path=self.object_path,
319                interface=cs.CONN_IFACE_CONTACTS, method='GetContactByID')
320        q.add_dbus_method_impl(self.Get_ContactAttributeInterfaces,
321                path=self.object_path,
322                interface=cs.PROPERTIES_IFACE, method='Get',
323                args=[cs.CONN_IFACE_CONTACTS, 'ContactAttributeInterfaces'])
324        q.add_dbus_method_impl(self.GetAll_Contacts,
325                path=self.object_path,
326                interface=cs.PROPERTIES_IFACE, method='GetAll',
327                args=[cs.CONN_IFACE_CONTACTS])
328
329        if not has_requests:
330            q.add_dbus_method_impl(self.ListChannels,
331                    path=self.object_path, interface=cs.CONN,
332                    method='ListChannels')
333
334        if has_presence:
335            q.add_dbus_method_impl(self.SetPresence, path=self.object_path,
336                    interface=cs.CONN_IFACE_SIMPLE_PRESENCE,
337                    method='SetPresence')
338            q.add_dbus_method_impl(self.GetPresences, path=self.object_path,
339                    interface=cs.CONN_IFACE_SIMPLE_PRESENCE,
340                    method='GetPresences')
341            q.add_dbus_method_impl(self.Get_SimplePresenceStatuses,
342                    path=self.object_path, interface=cs.PROPERTIES_IFACE,
343                    method='Get',
344                    args=[cs.CONN_IFACE_SIMPLE_PRESENCE, 'Statuses'])
345            q.add_dbus_method_impl(self.GetAll_SimplePresence,
346                    path=self.object_path, interface=cs.PROPERTIES_IFACE,
347                    method='GetAll',
348                    args=[cs.CONN_IFACE_SIMPLE_PRESENCE])
349
350        if has_aliasing:
351            q.add_dbus_method_impl(self.GetAliasFlags,
352                    path=self.object_path, interface=cs.CONN_IFACE_ALIASING,
353                    method='GetAliasFlags',
354                    args=[])
355
356            if implement_get_aliases:
357                q.add_dbus_method_impl(self.GetAliases,
358                        path=self.object_path,
359                        interface=cs.CONN_IFACE_ALIASING, method='GetAliases')
360
361        if has_avatars:
362            q.add_dbus_method_impl(self.GetAvatarRequirements,
363                    path=self.object_path, interface=cs.CONN_IFACE_AVATARS,
364                    method='GetAvatarRequirements', args=[])
365            q.add_dbus_method_impl(self.GetAll_Avatars,
366                    path=self.object_path, interface=cs.PROPERTIES_IFACE,
367                    method='GetAll', args=[cs.CONN_IFACE_AVATARS])
368            q.add_dbus_method_impl(self.GetKnownAvatarTokens,
369                    path=self.object_path, interface=cs.CONN_IFACE_AVATARS,
370                    method='GetKnownAvatarTokens')
371            q.add_dbus_method_impl(self.SetAvatar,
372                    path=self.object_path, interface=cs.CONN_IFACE_AVATARS,
373                    method='SetAvatar')
374
375        self.statuses = dbus.Dictionary({
376            'available': (cs.PRESENCE_TYPE_AVAILABLE, True, True),
377            'away': (cs.PRESENCE_TYPE_AWAY, True, True),
378            'lunch': (cs.PRESENCE_TYPE_XA, True, True),
379            'busy': (cs.PRESENCE_TYPE_BUSY, True, True),
380            'phone': (cs.PRESENCE_TYPE_BUSY, True, True),
381            'offline': (cs.PRESENCE_TYPE_OFFLINE, False, False),
382            'error': (cs.PRESENCE_TYPE_ERROR, False, False),
383            'unknown': (cs.PRESENCE_TYPE_UNKNOWN, False, False),
384            }, signature='s(ubb)')
385
386        if has_hidden:
387            self.statuses['hidden'] = (cs.PRESENCE_TYPE_HIDDEN, True, True)
388
389        # "dbus.UInt32" to work around
390        # https://bugs.freedesktop.org/show_bug.cgi?id=69967
391        self.presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE),
392            'offline', ''), signature='uss')
393
394    def change_self_ident(self, ident):
395        self.self_ident = ident
396        self.self_handle = self.ensure_handle(cs.HT_CONTACT, ident)
397        self.q.dbus_emit(self.object_path, cs.CONN, 'SelfHandleChanged',
398                self.self_handle, signature='u')
399
400    def change_self_alias(self, alias):
401        self.self_alias = alias
402        self.q.dbus_emit(self.object_path, cs.CONN_IFACE_ALIASING,
403                'AliasesChanged', [(self.self_handle, self.self_alias)],
404                signature='a(us)')
405
406    def release_name(self):
407        del self._bus_name_ref
408
409    def GetAll_Connection(self, e):
410        self.q.dbus_return(e.message, {
411            'Interfaces': dbus.Array(self.interfaces, signature='s'),
412            'SelfHandle': dbus.UInt32(self.self_handle),
413            'Status': dbus.UInt32(self.status),
414            'HasImmortalHandles': dbus.Boolean(True),
415            }, signature='a{sv}')
416
417    def forget_avatar(self):
418        self.avatar = (dbus.ByteArray(b''), '')
419        self.avatar_delayed = False
420
421    # not actually very relevant for MC so hard-code 0 for now
422    def GetAliasFlags(self, e):
423        self.q.dbus_return(e.message, 0, signature='u')
424
425    def GetAliases(self, e):
426        ret = dbus.Dictionary(signature='us')
427        if self.self_handle in e.args[0]:
428            ret[self.self_handle] = self.self_alias
429
430        self.q.dbus_return(e.message, ret, signature='a{us}')
431
432    # mostly for the UI's benefit; for now hard-code the requirements from XMPP
433    def GetAvatarRequirements(self, e):
434        self.q.dbus_return(e.message, ['image/jpeg'], 0, 0, 96, 96, 8192,
435                signature='asqqqqu')
436
437    def GetAll_Avatars(self, e):
438        self.q.dbus_return(e.message, {
439            'SupportedAvatarMIMETypes': ['image/jpeg'],
440            'MinimumAvatarWidth': 0,
441            'RecommendedAvatarWidth': 64,
442            'MaximumAvatarWidth': 96,
443            'MinimumAvatarHeight': 0,
444            'RecommendedAvatarHeight': 64,
445            'MaximumAvatarHeight': 96,
446            'MaximumAvatarBytes': 8192,
447            }, signature='a{sv}')
448
449    def GetKnownAvatarTokens(self, e):
450        ret = dbus.Dictionary(signature='us')
451
452        # the user has an avatar already, if they persist; nobody else does
453        if self.self_handle in e.args[0]:
454            if self.avatar is None:
455                # GetKnownAvatarTokens has the special case that "where
456                # the avatar does not persist between connections, a CM
457                # should omit the self handle from the returned map until
458                # an avatar is explicitly set or cleared". We'd have been
459                # better off with a more explicit design, but it's too
460                # late now...
461                assert not self.avatars_persist
462            else:
463                # "a CM must always have the tokens for the self handle
464                # if one is set (even if it is set to no avatar)"
465                # so behave as though we'd done a network round-trip to
466                # check what our token was, and found our configured
467                # token
468                if self.avatar_delayed:
469                    self.q.dbus_emit(self.object_path, cs.CONN_IFACE_AVATARS,
470                            'AvatarUpdated', self.self_handle,
471                            str(self.avatar[0]), signature='us')
472
473                # we just stringify the avatar as the token
474                # (also, empty avatar => no avatar => empty token)
475                ret[self.self_handle] = str(self.avatar[0])
476
477        self.q.dbus_return(e.message, ret, signature='a{us}')
478
479    def SetAvatar(self, e):
480        self.avatar = dbus.Struct(e.args, signature='ays')
481        self.avatar_delayed = False
482
483        # we just stringify the avatar as the token
484        self.q.dbus_return(e.message, str(self.avatar[0]), signature='s')
485        self.q.dbus_emit(self.object_path, cs.CONN_IFACE_AVATARS,
486                'AvatarRetrieved', self.self_handle, str(self.avatar[0]),
487                self.avatar[0], self.avatar[1], signature='usays')
488
489    def GetPresences(self, e):
490        ret = dbus.Dictionary(signature='u(uss)')
491        contacts = e.args[0]
492        for contact in contacts:
493            if contact == self.self_handle:
494                ret[contact] = self.presence
495            else:
496                # stub - MC doesn't care
497                ret[contact] = dbus.Struct(
498                        (cs.PRESENCE_TYPE_UNKNOWN, 'unknown', ''),
499                        signature='uss')
500        self.q.dbus_return(e.message, ret, signature='a{u(uss)}')
501
502    def SetPresence(self, e):
503        if e.args[0] in self.statuses:
504            # "dbus.UInt32" to work around
505            # https://bugs.freedesktop.org/show_bug.cgi?id=69967
506            presence = dbus.Struct((dbus.UInt32(self.statuses[e.args[0]][0]),
507                    e.args[0], e.args[1]), signature='uss')
508
509            old_presence = self.presence
510
511            if presence != old_presence:
512                self.presence = presence
513
514                self.q.dbus_emit(self.object_path,
515                        cs.CONN_IFACE_SIMPLE_PRESENCE, 'PresencesChanged',
516                        { self.self_handle : presence },
517                        signature='a{u(uss)}')
518
519            self.q.dbus_return(e.message, signature='')
520        else:
521            self.q.dbus_raise(cs.INVALID_ARGUMENT, 'Unknown status')
522
523    def Get_SimplePresenceStatuses(self, e):
524        self.q.dbus_return(e.message, self.statuses, signature='v')
525
526    def GetAll_SimplePresence(self, e):
527        self.q.dbus_return(e.message,
528                {'Statuses': self.statuses}, signature='a{sv}')
529
530    def GetInterfaces(self, e):
531        self.q.dbus_return(e.message, self.interfaces, signature='as')
532
533    def Connect(self, e):
534        self.StatusChanged(cs.CONN_STATUS_CONNECTING,
535                cs.CONN_STATUS_REASON_REQUESTED)
536        self.q.dbus_return(e.message, signature='')
537
538    def Disconnect(self, e):
539        self.StatusChanged(cs.CONN_STATUS_DISCONNECTED,
540                cs.CONN_STATUS_REASON_REQUESTED)
541        self.q.dbus_return(e.message, signature='')
542        for c in self.channels:
543            c.close()
544
545    def inspect_handles(self, handles, htype=cs.HT_CONTACT):
546        ret = []
547
548        for h in handles:
549            if (htype, h) in self._identifiers:
550                ret.append(self._identifiers[(htype, h)])
551            else:
552                raise Exception(h)
553
554        return ret
555
556    def InspectHandles(self, e):
557        htype, hs = e.args
558
559        try:
560            ret = self.inspect_handles(hs, htype)
561            self.q.dbus_return(e.message, ret, signature='as')
562        except e:
563            self.q.dbus_raise(e.message, INVALID_HANDLE, str(e.args[0]))
564
565    def RequestHandles(self, e):
566        htype, idents = e.args
567        self.q.dbus_return(e.message,
568                [self.ensure_handle(htype, i) for i in idents],
569                signature='au')
570
571    def GetStatus(self, e):
572        self.q.dbus_return(e.message, self.status, signature='u')
573
574    def ConnectionError(self, error, details):
575        self.q.dbus_emit(self.object_path, cs.CONN, 'ConnectionError',
576                error, details, signature='sa{sv}')
577
578    def StatusChanged(self, status, reason):
579        self.status = status
580        self.reason = reason
581        self.q.dbus_emit(self.object_path, cs.CONN, 'StatusChanged',
582                status, reason, signature='uu')
583        if self.status == cs.CONN_STATUS_CONNECTED and self.has_presence:
584            if self.presence[0] == cs.PRESENCE_TYPE_OFFLINE:
585                # "dbus.UInt32" to work around
586                # https://bugs.freedesktop.org/show_bug.cgi?id=69967
587                self.presence = dbus.Struct((
588                    dbus.UInt32(cs.PRESENCE_TYPE_AVAILABLE),
589                    'available', ''), signature='uss')
590
591            self.q.dbus_emit(self.object_path,
592                    cs.CONN_IFACE_SIMPLE_PRESENCE, 'PresencesChanged',
593                    { self.self_handle : self.presence },
594                    signature='a{u(uss)}')
595
596    def ListChannels(self, e):
597        arr = dbus.Array(signature='(osuu)')
598
599        for c in self.channels:
600            arr.append(dbus.Struct(
601                (c.object_path,
602                 c.immutable[cs.CHANNEL + '.ChannelType'],
603                 c.immutable.get(cs.CHANNEL + '.TargetHandleType', 0),
604                 c.immutable.get(cs.CHANNEL + '.TargetHandle', 0)
605                ), signature='osuu'))
606
607        self.q.dbus_return(e.message, arr, signature='a(osuu)')
608
609    def get_channel_details(self):
610        return dbus.Array([(c.object_path, c.immutable)
611            for c in self.channels], signature='(oa{sv})')
612
613    def GetAll_Requests(self, e):
614        if self.has_requests:
615            self.q.dbus_return(e.message, {
616                'Channels': self.get_channel_details(),
617            }, signature='a{sv}')
618        else:
619            self.q.dbus_raise(e.message, cs.NOT_IMPLEMENTED, 'no Requests')
620
621    def GetSelfHandle(self, e):
622        self.q.dbus_return(e.message, self.self_handle, signature='u')
623
624    def HoldHandles(self, e):
625        # do nothing
626        self.q.dbus_return(e.message, signature='')
627
628    def NewChannels(self, channels):
629        for channel in channels:
630            assert not channel.announced
631            channel.announced = True
632            self.channels.append(channel)
633
634            self.q.dbus_emit(self.object_path, cs.CONN,
635                    'NewChannel',
636                    channel.object_path,
637                    channel.immutable[cs.CHANNEL + '.ChannelType'],
638                    channel.immutable.get(cs.CHANNEL + '.TargetHandleType', 0),
639                    channel.immutable.get(cs.CHANNEL + '.TargetHandle', 0),
640                    channel.immutable.get(cs.CHANNEL + '.Requested', False),
641                    signature='osuub')
642
643        if self.has_requests:
644            self.q.dbus_emit(self.object_path, cs.CONN_IFACE_REQUESTS,
645                    'NewChannels',
646                    [(channel.object_path, channel.immutable)
647                        for channel in channels],
648                    signature='a(oa{sv})')
649
650    def get_contact_attributes(self, h, ifaces):
651        id = self.inspect_handles([h])[0]
652        ifaces = set(ifaces).intersection(
653                self.get_contact_attribute_interfaces())
654
655        ret = dbus.Dictionary({}, signature='sv')
656        ret[cs.ATTR_CONTACT_ID] = id
657
658        if cs.CONN_IFACE_ALIASING in ifaces:
659            if h == self.self_handle:
660                ret[cs.ATTR_ALIAS] = self.self_alias
661            else:
662                ret[cs.ATTR_ALIAS] = id
663
664        if cs.CONN_IFACE_AVATARS in ifaces:
665            if h == self.self_handle:
666                if self.avatar is not None and not self.avatar_delayed:
667                    # We just stringify the avatar as the token
668                    # (also, empty avatar => no avatar => empty token).
669                    # This doesn't have the same special case that
670                    # GetKnownAvatarTokens does - if we don't know the
671                    # token yet, we don't wait.
672                    ret[cs.ATTR_AVATAR_TOKEN] = str(self.avatar[0])
673
674        if cs.CONN_IFACE_SIMPLE_PRESENCE in ifaces:
675            if h == self.self_handle:
676                ret[cs.ATTR_PRESENCE] = self.presence
677            else:
678                # stub - MC doesn't care
679                # "dbus.UInt32" to work around
680                # https://bugs.freedesktop.org/show_bug.cgi?id=69967
681                ret[cs.ATTR_PRESENCE] = (dbus.UInt32(cs.PRESENCE_UNKNOWN),
682                        'unknown', '')
683
684        return ret
685
686    def get_contact_attribute_interfaces(self):
687        return set(self.interfaces).intersection(set([
688            cs.CONN_IFACE_ALIASING,
689            cs.CONN_IFACE_AVATARS,
690            cs.CONN_IFACE_SIMPLE_PRESENCE,
691            ]))
692
693    def GetContactAttributes(self, e):
694        ret = dbus.Dictionary({}, signature='ua{sv}')
695
696        try:
697            for h in e.args[0]:
698                ret[dbus.UInt32(h)] = self.get_contact_attributes(h, e.args[1])
699
700            self.q.dbus_return(e.message, ret, signature='a{ua{sv}}')
701        except e:
702            self.q.dbus_raise(e.message, INVALID_HANDLE, str(e.args[0]))
703
704    def GetContactByID(self, e):
705        h = self.ensure_handle(e.args[0])
706        self.q.dbus_return(e.message, h,
707                self.get_contact_attributes(h, e.args[1]), signature='ua{sv}')
708
709    def GetAll_Contacts(self, e):
710        self.q.dbus_return(e.message, {
711            'ContactAttributeInterfaces':
712                self.get_contact_attribute_interfaces(),
713            }, signature='a{sv}')
714
715    def Get_ContactAttributeInterfaces(self, e):
716        self.q.dbus_return(e.message,
717            dbus.Array(self.get_contact_attribute_interfaces(), signature='s'),
718            signature='v')
719
720class SimulatedChannel(object):
721    def __init__(self, conn, immutable, mutable={},
722            destroyable=False, group=False):
723        self.conn = conn
724        self.q = conn.q
725        self.bus = conn.bus
726        self.object_path = conn.object_path + ('/_%x' % id(self))
727        self.immutable = immutable
728        self.properties = dbus.Dictionary({}, signature='sv')
729        self.properties.update(immutable)
730        self.properties.update(mutable)
731
732        self.q.add_dbus_method_impl(self.GetAll,
733                path=self.object_path,
734                interface=cs.PROPERTIES_IFACE, method='GetAll')
735        self.q.add_dbus_method_impl(self.Get,
736                path=self.object_path,
737                interface=cs.PROPERTIES_IFACE, method='Get')
738        self.q.add_dbus_method_impl(self.Close,
739                path=self.object_path,
740                interface=cs.CHANNEL, method='Close')
741        self.q.add_dbus_method_impl(self.GetInterfaces,
742                path=self.object_path,
743                interface=cs.CHANNEL, method='GetInterfaces')
744
745        if destroyable:
746            self.q.add_dbus_method_impl(self.Close,
747                path=self.object_path,
748                interface=cs.CHANNEL_IFACE_DESTROYABLE,
749                method='Destroy')
750
751        if group:
752            self.q.add_dbus_method_impl(self.GetGroupFlags,
753                path=self.object_path,
754                interface=cs.CHANNEL_IFACE_GROUP,
755                method='GetGroupFlags')
756            self.q.add_dbus_method_impl(self.GetSelfHandle,
757                path=self.object_path,
758                interface=cs.CHANNEL_IFACE_GROUP,
759                method='GetSelfHandle')
760            self.q.add_dbus_method_impl(self.GetAllMembers,
761                path=self.object_path,
762                interface=cs.CHANNEL_IFACE_GROUP,
763                method='GetAllMembers')
764            self.q.add_dbus_method_impl(self.GetLocalPendingMembersWithInfo,
765                path=self.object_path,
766                interface=cs.CHANNEL_IFACE_GROUP,
767                method='GetLocalPendingMembersWithInfo')
768            self.properties[cs.CHANNEL_IFACE_GROUP + '.SelfHandle'] \
769                    = self.conn.self_handle
770
771        self.announced = False
772        self.closed = False
773
774    def GetGroupFlags(self, e):
775        self.q.dbus_return(e.message, 0, signature='u')
776
777    def GetSelfHandle(self, e):
778        self.q.dbus_return(e.message,
779                self.properties[cs.CHANNEL_IFACE_GROUP + '.SelfHandle'],
780                signature='u')
781
782    def GetAllMembers(self, e):
783        # stub
784        self.q.dbus_return(e.message,
785                [self.properties[cs.CHANNEL_IFACE_GROUP + '.SelfHandle']],
786                [], [],
787                signature='auauau')
788
789    def GetLocalPendingMembersWithInfo(self, e):
790        # stub
791        self.q.dbus_return(e.message, [], signature='a(uuus)')
792
793    def announce(self):
794        self.conn.NewChannels([self])
795
796    def Close(self, e):
797        if not self.closed:
798            self.close()
799        self.q.dbus_return(e.message, signature='')
800
801    def close(self):
802        assert self.announced
803        assert not self.closed
804        self.closed = True
805        self.conn.channels.remove(self)
806        self.q.dbus_emit(self.object_path, cs.CHANNEL, 'Closed', signature='')
807        self.q.dbus_emit(self.conn.object_path, cs.CONN_IFACE_REQUESTS,
808                'ChannelClosed', self.object_path, signature='o')
809
810    def GetInterfaces(self, e):
811        self.q.dbus_return(e.message,
812                self.properties[cs.CHANNEL + '.Interfaces'], signature='as')
813
814    def GetAll(self, e):
815        iface = e.args[0] + '.'
816
817        ret = dbus.Dictionary({}, signature='sv')
818        for k in self.properties:
819            if k.startswith(iface):
820                tail = k[len(iface):]
821                if '.' not in tail:
822                    ret[tail] = self.properties[k]
823        assert ret  # die on attempts to get unimplemented interfaces
824        self.q.dbus_return(e.message, ret, signature='a{sv}')
825
826    def Get(self, e):
827        prop = e.args[0] + '.' + e.args[1]
828        self.q.dbus_return(e.message, self.properties[prop],
829                signature='v')
830
831def aasv(x):
832    return dbus.Array([dbus.Dictionary(d, signature='sv') for d in x],
833            signature='a{sv}')
834
835class SimulatedClient(object):
836    def __init__(self, q, bus, clientname,
837            observe=[], approve=[], handle=[],
838            cap_tokens=[], bypass_approval=False, wants_recovery=False,
839            request_notification=True, implement_get_interfaces=True,
840            is_handler=None, bypass_observers=False, delay_approvers=False):
841        self.q = q
842        self.bus = bus
843        self.bus_name = '.'.join([cs.tp_name_prefix, 'Client', clientname])
844        self._bus_name_ref = dbus.service.BusName(self.bus_name, self.bus)
845        self.object_path = '/' + self.bus_name.replace('.', '/')
846        self.observe = aasv(observe)
847        self.approve = aasv(approve)
848        self.handle = aasv(handle)
849        self.bypass_approval = bool(bypass_approval)
850        self.bypass_observers = bool(bypass_observers)
851        self.delay_approvers = bool(delay_approvers)
852        self.wants_recovery = bool(wants_recovery)
853        self.request_notification = bool(request_notification)
854        self.handled_channels = dbus.Array([], signature='o')
855        self.cap_tokens = dbus.Array(cap_tokens, signature='s')
856        self.is_handler = is_handler
857
858        if self.is_handler is None:
859            self.is_handler = bool(handle)
860
861        if implement_get_interfaces:
862            q.add_dbus_method_impl(self.Get_Interfaces,
863                    path=self.object_path, interface=cs.PROPERTIES_IFACE,
864                    method='Get', args=[cs.CLIENT, 'Interfaces'])
865            q.add_dbus_method_impl(self.GetAll_Client,
866                    path=self.object_path,
867                    interface=cs.PROPERTIES_IFACE, method='GetAll',
868                    args=[cs.CLIENT])
869
870        q.add_dbus_method_impl(self.Get_ObserverChannelFilter,
871                path=self.object_path, interface=cs.PROPERTIES_IFACE,
872                method='Get', args=[cs.OBSERVER, 'ObserverChannelFilter'])
873        q.add_dbus_method_impl(self.GetAll_Observer,
874                path=self.object_path,
875                interface=cs.PROPERTIES_IFACE, method='GetAll',
876                args=[cs.OBSERVER])
877
878        q.add_dbus_method_impl(self.Get_ApproverChannelFilter,
879                path=self.object_path, interface=cs.PROPERTIES_IFACE,
880                method='Get', args=[cs.APPROVER, 'ApproverChannelFilter'])
881        q.add_dbus_method_impl(self.GetAll_Approver,
882                path=self.object_path,
883                interface=cs.PROPERTIES_IFACE, method='GetAll',
884                args=[cs.APPROVER])
885
886        q.add_dbus_method_impl(self.Get_HandlerChannelFilter,
887                path=self.object_path, interface=cs.PROPERTIES_IFACE,
888                method='Get', args=[cs.HANDLER, 'HandlerChannelFilter'])
889        q.add_dbus_method_impl(self.Get_Capabilities,
890                path=self.object_path, interface=cs.PROPERTIES_IFACE,
891                method='Get', args=[cs.HANDLER, 'Capabilities'])
892        q.add_dbus_method_impl(self.Get_HandledChannels,
893                path=self.object_path, interface=cs.PROPERTIES_IFACE,
894                method='Get', args=[cs.HANDLER, 'HandledChannels'])
895        q.add_dbus_method_impl(self.Get_BypassApproval,
896                path=self.object_path, interface=cs.PROPERTIES_IFACE,
897                method='Get', args=[cs.HANDLER, 'BypassApproval'])
898        q.add_dbus_method_impl(self.Get_Recover,
899                path=self.object_path, interface=cs.PROPERTIES_IFACE,
900                method='Get', args=[cs.OBSERVER, 'Recover'])
901        q.add_dbus_method_impl(self.GetAll_Handler,
902                path=self.object_path,
903                interface=cs.PROPERTIES_IFACE, method='GetAll',
904                args=[cs.HANDLER])
905
906    def release_name(self):
907        del self._bus_name_ref
908
909    def reacquire_name(self):
910        self._bus_name_ref = dbus.service.BusName(self.bus_name, self.bus)
911
912    def get_interfaces(self):
913        ret = dbus.Array([], signature='s', variant_level=1)
914
915        if self.observe:
916            ret.append(cs.OBSERVER)
917
918        if self.approve:
919            ret.append(cs.APPROVER)
920
921        if self.is_handler:
922            ret.append(cs.HANDLER)
923
924        if self.request_notification:
925            ret.append(cs.CLIENT_IFACE_REQUESTS)
926
927        return ret
928
929    def Get_Interfaces(self, e):
930        self.q.dbus_return(e.message, self.get_interfaces(), signature='v',
931                bus=self.bus)
932
933    def GetAll_Client(self, e):
934        self.q.dbus_return(e.message, {'Interfaces': self.get_interfaces()},
935                signature='a{sv}', bus=self.bus)
936
937    def GetAll_Observer(self, e):
938        assert self.observe
939        self.q.dbus_return(e.message, {
940            'ObserverChannelFilter': self.observe,
941            'Recover': dbus.Boolean(self.wants_recovery),
942            'DelayApprovers': dbus.Boolean(self.delay_approvers),
943            },
944                signature='a{sv}', bus=self.bus)
945
946    def Get_ObserverChannelFilter(self, e):
947        assert self.observe
948        self.q.dbus_return(e.message, self.observe, signature='v',
949                bus=self.bus)
950
951    def GetAll_Approver(self, e):
952        assert self.approve
953        self.q.dbus_return(e.message, {'ApproverChannelFilter': self.approve},
954                signature='a{sv}', bus=self.bus)
955
956    def Get_ApproverChannelFilter(self, e):
957        assert self.approve
958        self.q.dbus_return(e.message, self.approve, signature='v',
959                bus=self.bus)
960
961    def GetAll_Handler(self, e):
962        assert self.is_handler
963        self.q.dbus_return(e.message, {
964            'HandlerChannelFilter': self.handle,
965            'BypassApproval': self.bypass_approval,
966            'BypassObservers': self.bypass_observers,
967            'HandledChannels': self.handled_channels,
968            'Capabilities': self.cap_tokens,
969            },
970                signature='a{sv}', bus=self.bus)
971
972    def Get_Capabilities(self, e):
973        self.q.dbus_return(e.message, self.cap_tokens, signature='v',
974                bus=self.bus)
975
976    def Get_HandledChannels(self, e):
977        self.q.dbus_return(e.message, self.handled_channels, signature='v',
978                bus=self.bus)
979
980    def Get_HandlerChannelFilter(self, e):
981        assert self.handle
982        self.q.dbus_return(e.message, self.handle, signature='v',
983                bus=self.bus)
984
985    def Get_BypassApproval(self, e):
986        assert self.handle
987        self.q.dbus_return(e.message, self.bypass_approval, signature='v',
988                bus=self.bus)
989
990    def Get_BypassApproval(self, e):
991        assert self.handle
992        self.q.dbus_return(e.message, self.bypass_observers, signature='v',
993                bus=self.bus)
994
995    def Get_Recover(self, e):
996        assert self.handle
997        self.q.dbus_return(e.message, self.recover, signature='v',
998                bus=self.bus)
999
1000def take_fakecm_name(bus):
1001    return dbus.service.BusName(cs.CM + '.fakecm', bus=bus)
1002
1003def create_fakecm_account(q, bus, mc, params, properties={},
1004                          cm_bus=None):
1005    """Create a fake connection manager and an account that uses it.
1006
1007    Optional keyword arguments:
1008    properties -- a dictionary from qualified property names to values to pass
1009                  to CreateAccount. If provided, this function will check that
1010                  the newly-created account has these properties.
1011    cm_bus     -- if not None, a BusConnection via which to claim the CM's
1012                  name. If None, 'bus' will be used.
1013
1014    Returns: (a BusName for the fake CM, an Account proxy)"""
1015
1016    if cm_bus is None:
1017        cm_bus = bus
1018
1019    cm_name_ref = take_fakecm_name(cm_bus)
1020
1021    account_manager = AccountManager(bus)
1022
1023    servicetest.call_async(q, account_manager, 'CreateAccount',
1024        'fakecm', 'fakeprotocol', 'fakeaccount', params, properties)
1025
1026    # Check whether the account being created is to be hidden; if so, then
1027    # expect a different signal. It annoys me that this has to be in here, but,
1028    # eh.
1029    if properties.get(cs.ACCOUNT_IFACE_HIDDEN + '.Hidden', False):
1030        validity_changed_pattern = servicetest.EventPattern('dbus-signal',
1031            path=cs.AM_PATH, signal='HiddenAccountValidityChanged',
1032            interface=cs.AM_IFACE_HIDDEN)
1033    else:
1034        validity_changed_pattern = servicetest.EventPattern('dbus-signal',
1035            path=cs.AM_PATH, signal='AccountValidityChanged', interface=cs.AM)
1036
1037    # The spec has no order guarantee here.
1038    # FIXME: MC ought to also introspect the CM and find out that the params
1039    # are in fact sufficient
1040    a_signal, am_signal, ret = q.expect_many(
1041            servicetest.EventPattern('dbus-signal',
1042                signal='AccountPropertyChanged', interface=cs.ACCOUNT,
1043                predicate=(lambda e: 'Valid' in e.args[0])),
1044            validity_changed_pattern,
1045            servicetest.EventPattern('dbus-return', method='CreateAccount'),
1046            )
1047    account_path = ret.value[0]
1048    assert am_signal.args == [account_path, True], am_signal.args
1049    assert a_signal.args[0]['Valid'] == True, a_signal.args
1050
1051    assert account_path is not None
1052
1053    account = Account(bus, account_path)
1054
1055    for key, value in properties.items():
1056        interface, prop = key.rsplit('.', 1)
1057        servicetest.assertEqual(value, account.Properties.Get(interface, prop))
1058
1059    return (cm_name_ref, account)
1060
1061def get_fakecm_account(bus, mc, account_path):
1062    account = Account(bus, account_path)
1063
1064    # Introspect Account for debugging purpose
1065    account_introspected = account.Introspect(
1066            dbus_interface=cs.INTROSPECTABLE_IFACE)
1067    #print account_introspected
1068
1069    return account
1070
1071def enable_fakecm_account(q, bus, mc, account, expected_params, **kwargs):
1072    # I'm too lazy to manually pass all the other kwargs to
1073    # expect_fakecm_connection
1074    try:
1075        requested_presence = kwargs['requested_presence']
1076        del kwargs['requested_presence']
1077    except KeyError:
1078        requested_presence = (2, 'available', '')
1079
1080    # Enable the account
1081    account.Properties.Set(cs.ACCOUNT, 'Enabled', True)
1082
1083    if requested_presence is not None:
1084        requested_presence = dbus.Struct(
1085                (dbus.UInt32(requested_presence[0]),) +
1086                tuple(requested_presence[1:]),
1087                signature='uss')
1088        account.Properties.Set(cs.ACCOUNT,
1089                'RequestedPresence', requested_presence)
1090
1091    return expect_fakecm_connection(q, bus, mc, account, expected_params, **kwargs)
1092
1093def expect_fakecm_connection(q, bus, mc, account, expected_params,
1094        has_requests=True, has_presence=False, has_aliasing=False,
1095        has_avatars=False, avatars_persist=True,
1096        extra_interfaces=[],
1097        expect_before_connect=(), expect_after_connect=(),
1098        has_hidden=False,
1099        self_ident='myself'):
1100    # make (safely) mutable copies
1101    expect_before_connect = list(expect_before_connect)
1102    expect_after_connect = list(expect_after_connect)
1103
1104    e = q.expect('dbus-method-call', method='RequestConnection',
1105            args=['fakeprotocol', expected_params],
1106            destination=cs.tp_name_prefix + '.ConnectionManager.fakecm',
1107            path=cs.tp_path_prefix + '/ConnectionManager/fakecm',
1108            interface=cs.tp_name_prefix + '.ConnectionManager',
1109            handled=False)
1110
1111    conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol',
1112                               account.object_path.split('/')[-1],
1113            self_ident, has_requests=has_requests, has_presence=has_presence,
1114            has_aliasing=has_aliasing, has_avatars=has_avatars,
1115            avatars_persist=avatars_persist, extra_interfaces=extra_interfaces,
1116            has_hidden=has_hidden)
1117
1118    q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
1119
1120    if has_requests:
1121        expect_before_connect.append(
1122                servicetest.EventPattern('dbus-method-call',
1123                    interface=cs.PROPERTIES_IFACE, method='GetAll',
1124                    args=[cs.CONN_IFACE_REQUESTS],
1125                    path=conn.object_path, handled=True))
1126
1127    if expect_before_connect:
1128        events = list(q.expect_many(*expect_before_connect))
1129        if has_requests:
1130            del events[-1]
1131    else:
1132        events = []
1133
1134    q.expect('dbus-method-call', method='Connect',
1135            path=conn.object_path, handled=True)
1136    conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
1137
1138    expect_after_connect = list(expect_after_connect)
1139
1140    if not has_requests:
1141        expect_after_connect.append(
1142                servicetest.EventPattern('dbus-method-call',
1143                    interface=cs.CONN, method='ListChannels', args=[],
1144                    path=conn.object_path, handled=True))
1145
1146    events = events + list(q.expect_many(*expect_after_connect))
1147
1148    if not has_requests:
1149        del events[-1]
1150
1151    if events:
1152        return (conn,) + tuple(events)
1153
1154    return conn
1155
1156def expect_client_setup(q, clients, got_interfaces_already=False):
1157    patterns = []
1158
1159    def is_client_setup(e):
1160        if e.method == 'Get' and e.args == [cs.CLIENT, 'Interfaces']:
1161            return True
1162        if e.method == 'GetAll' and e.args == [cs.CLIENT]:
1163            return True
1164        return False
1165
1166    def is_approver_setup(e):
1167        if e.method == 'Get' and \
1168                e.args == [cs.APPROVER, 'ApproverChannelFilter']:
1169            return True
1170        if e.method == 'GetAll' and e.args == [cs.APPROVER]:
1171            return True
1172        return False
1173
1174    def is_observer_setup(e):
1175        if e.method == 'Get' and \
1176                e.args == [cs.OBSERVER, 'ObserverChannelFilter']:
1177            return True
1178        if e.method == 'GetAll' and e.args == [cs.OBSERVER]:
1179            return True
1180        return False
1181
1182    def is_handler_setup(e):
1183        if e.method == 'Get' and \
1184                e.args == [cs.HANDLER, 'HandlerChannelFilter']:
1185            return True
1186        if e.method == 'GetAll' and e.args == [cs.HANDLER]:
1187            return True
1188        return False
1189
1190    for client in clients:
1191        if not got_interfaces_already:
1192            patterns.append(servicetest.EventPattern('dbus-method-call',
1193                interface=cs.PROPERTIES_IFACE,
1194                path=client.object_path, handled=True,
1195                predicate=is_client_setup))
1196
1197        if client.observe:
1198            patterns.append(servicetest.EventPattern('dbus-method-call',
1199                interface=cs.PROPERTIES_IFACE,
1200                path=client.object_path, handled=True,
1201                predicate=is_observer_setup))
1202
1203        if client.approve:
1204            patterns.append(servicetest.EventPattern('dbus-method-call',
1205                interface=cs.PROPERTIES_IFACE,
1206                path=client.object_path, handled=True,
1207                predicate=is_approver_setup))
1208
1209        if client.handle:
1210            patterns.append(servicetest.EventPattern('dbus-method-call',
1211                interface=cs.PROPERTIES_IFACE,
1212                path=client.object_path, predicate=is_handler_setup))
1213
1214    q.expect_many(*patterns)
1215
1216def get_account_manager(bus):
1217    """
1218    A backwards-compatibility synonym for constructing a new AccountManager
1219    object. Please don't use this in new tests.
1220    """
1221    return AccountManager(bus)
1222
1223class AccountManager(servicetest.ProxyWrapper):
1224    def __init__(self, bus):
1225        bare_am = bus.get_object(cs.AM, cs.AM_PATH,
1226            follow_name_owner_changes=True)
1227
1228        servicetest.ProxyWrapper.__init__(self, bare_am, cs.AM, {})
1229
1230class Account(servicetest.ProxyWrapper):
1231    def __init__(self, bus, account_path):
1232        servicetest.ProxyWrapper.__init__(self,
1233            bus.get_object(cs.AM, account_path),
1234            cs.ACCOUNT, {})
1235
1236class ChannelDispatcher(servicetest.ProxyWrapper):
1237    def __init__(self, bus):
1238        bare_cd = bus.get_object(cs.CD, cs.CD_PATH,
1239            follow_name_owner_changes=True)
1240
1241        servicetest.ProxyWrapper.__init__(self, bare_cd, cs.CD, {})
1242
1243class ChannelDispatchOperation(servicetest.ProxyWrapper):
1244    def __init__(self, bus, path):
1245        bare_cdo = bus.get_object(cs.CD, path)
1246        servicetest.ProxyWrapper.__init__(self, bare_cdo, cs.CDO, {})
1247
1248class ChannelRequest(servicetest.ProxyWrapper):
1249    def __init__(self, bus, path):
1250        bare_cr = bus.get_object(cs.CD, path)
1251        servicetest.ProxyWrapper.__init__(self, bare_cr, cs.CR, {})
1252
1253def connect_to_mc(q, bus, mc):
1254    account_manager = AccountManager(bus)
1255
1256    # Introspect AccountManager for debugging purpose
1257    account_manager_introspected = account_manager.Introspect(
1258            dbus_interface=cs.INTROSPECTABLE_IFACE)
1259    #print account_manager_introspected
1260
1261    # Check AccountManager has D-Bus property interface
1262    properties = account_manager.Properties.GetAll(cs.AM)
1263    assert properties is not None
1264    interfaces = properties.get('Interfaces')
1265
1266    return account_manager, properties, interfaces
1267
1268def tell_mc_to_die(q, bus):
1269    """Instructs the running Mission Control to die via a magic method call in
1270    the version built for tests."""
1271
1272    secret_debug_api = dbus.Interface(bus.get_object(cs.AM, "/"),
1273        'org.freedesktop.Telepathy.MissionControl5.RegressionTests')
1274    secret_debug_api.Abort()
1275
1276    # Make sure MC exits
1277    q.expect('dbus-signal', signal='NameOwnerChanged',
1278        predicate=(lambda e:
1279            e.args[0] == 'org.freedesktop.Telepathy.AccountManager' and
1280            e.args[2] == ''))
1281
1282def resuscitate_mc(q, bus, mc):
1283    """Having killed MC with tell_mc_to_die(), this function revives it."""
1284    # We kick the daemon asynchronously because nm-glib makes blocking calls
1285    # back to us during initialization...
1286    bus.call_async(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH,
1287        dbus.BUS_DAEMON_IFACE, 'StartServiceByName', 'su', (cs.MC, 0),
1288        reply_handler=None, error_handler=None)
1289
1290    # Wait until it's up
1291    mc.wait_for_names()
1292
1293    return connect_to_mc(q, bus, mc)
1294
1295def keyfile_read(fname):
1296    groups = { None: {} }
1297    group = None
1298    for line in open(fname):
1299        line = line[:-1].decode('utf-8').strip()
1300        if not line or line.startswith('#'):
1301            continue
1302
1303        if line.startswith('[') and line.endswith(']'):
1304            group = line[1:-1]
1305            groups[group] = {}
1306            continue
1307
1308        if '=' in line:
1309            k, v = line.split('=', 1)
1310        else:
1311            k = line
1312            v = None
1313
1314        groups[group][k] = v
1315    return groups
1316
1317def read_account_keyfile():
1318    """Reads the keyfile used by the 'diverted' storage plugin used by most of
1319    the tests."""
1320    key_file_name = os.path.join(os.getenv('XDG_CACHE_HOME'),
1321        'mcp-test-diverted-account-plugin.conf')
1322    return keyfile_read(key_file_name)
1323