From d09406214ece363935635ea7d8aa42f0c58e95fa Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Tue, 1 Oct 2013 15:16:56 +0100 Subject: [PATCH 10/11] avatar-refresh test: subsume avatar-persist, and test more situations We have some sort of combinatorial explosion going on here, and it seems best to test it in a somewhat systematic way: * is the protocol one where avatars persist on the server (Gabble) or not (Salut)? * if it's like Gabble, does it download our own avatar token before signalling CONNECTED (as I suspect Haze does), or on-demand after GetKnownAvatarTokens (as Gabble appears to)? * if it's like Gabble, is the server storing an avatar for us? * in either case, do we have an avatar stored locally, and has it previously been uploaded or not? In addition, the avatar-refresh and avatar-persist tests exercised migration from ~/.missioncontrol and a low-priority XDG_DATA_DIRS entry (respectively) to ~/.local/share. I didn't do that in a loop, because it isn't applicable in all cases and would lead to even more combinations - testing each case once should be enough. --- tests/twisted/Makefile.am | 1 - tests/twisted/account-manager/avatar-persist.py | 181 ----------- tests/twisted/account-manager/avatar-refresh.py | 411 +++++++++++++++++++----- tests/twisted/mctest.py | 41 ++- 4 files changed, 371 insertions(+), 263 deletions(-) delete mode 100644 tests/twisted/account-manager/avatar-persist.py diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am index 7fd0660..e47c070 100644 --- a/tests/twisted/Makefile.am +++ b/tests/twisted/Makefile.am @@ -86,7 +86,6 @@ TWISTED_SLOW_TESTS = \ # Tests that need their own MC instance. TWISTED_SEPARATE_TESTS = \ account-manager/auto-connect.py \ - account-manager/avatar-persist.py \ account-manager/avatar-refresh.py \ account-manager/device-idle.py \ account-manager/make-valid.py \ diff --git a/tests/twisted/account-manager/avatar-persist.py b/tests/twisted/account-manager/avatar-persist.py deleted file mode 100644 index f7560e7..0000000 --- a/tests/twisted/account-manager/avatar-persist.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright (C) 2009 Nokia Corporation -# Copyright (C) 2009 Collabora Ltd. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA - -import dbus -"""Feature test for signing in and setting an avatar, on CMs like Gabble where -the avatar is stored by the server. -""" - -import os - -import dbus -import dbus.service - -from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \ - call_async, assertEquals -from mctest import exec_test, SimulatedConnection, create_fakecm_account, MC -import constants as cs - -cm_name_ref = dbus.service.BusName( - cs.tp_name_prefix + '.ConnectionManager.fakecm', bus=dbus.SessionBus()) - -account_id = 'fakecm/fakeprotocol/jc_2edenton_40unatco_2eint' - -def preseed(q, bus, fake_accounts_service): - - accounts_dir = os.environ['MC_ACCOUNT_DIR'] - - try: - os.mkdir(accounts_dir, 0700) - except OSError: - pass - - fake_accounts_service.update_attributes(account_id, changed={ - 'manager': 'fakecm', - 'protocol': 'fakeprotocol', - 'DisplayName': 'Work account', - 'NormalizedName': 'jc.denton@unatco.int', - 'Enabled': True, - 'ConnectAutomatically': True, - 'AutomaticPresence': (dbus.UInt32(2), 'available', - 'My vision is augmented'), - 'Nickname': 'JC', - 'AvatarMime': 'image/jpeg', - 'avatar_token': 'Deus Ex', - }) - fake_accounts_service.update_parameters(account_id, untyped={ - 'account': 'jc.denton@unatco.int', - 'password': 'ionstorm', - }) - - datadirs = os.environ['XDG_DATA_DIRS'].split(':') - - os.makedirs(datadirs[0] + '/telepathy/mission-control') - avatar_filename = (datadirs[0] + '/telepathy/mission-control/' + - account_id.replace('/', '-') + '.avatar') - avatar_bin = open(avatar_filename, 'w') - avatar_bin.write('Deus Ex') - avatar_bin.close() - - account_connections_file = open(accounts_dir + '/.mc_connections', 'w') - account_connections_file.write("") - account_connections_file.close() - -def test(q, bus, unused, **kwargs): - fake_accounts_service = kwargs['fake_accounts_service'] - preseed(q, bus, fake_accounts_service) - - expected_params = { - 'account': 'jc.denton@unatco.int', - 'password': 'ionstorm', - } - - mc = MC(q, bus) - - e = q.expect('dbus-method-call', method='RequestConnection', - args=['fakeprotocol', expected_params], - destination=cs.tp_name_prefix + '.ConnectionManager.fakecm', - path=cs.tp_path_prefix + '/ConnectionManager/fakecm', - interface=cs.tp_name_prefix + '.ConnectionManager', - handled=False) - - conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_', - 'myself', has_avatars=True, avatars_persist=True) - conn.avatar = dbus.Struct((dbus.ByteArray('MJ12'), 'image/png'), - signature='ays') - - q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so') - - account_path = (cs.tp_path_prefix + '/Account/' + account_id) - - q.expect('dbus-method-call', method='Connect', - path=conn.object_path, handled=True, interface=cs.CONN) - - conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CSR_NONE_SPECIFIED) - - # We haven't changed the avatar since we last signed in, so we don't set - # it - on the contrary, we pick up the remote avatar (which has changed - # since we were last here) to store it in the Account. - request_avatars_call, e = q.expect_many( - EventPattern('dbus-method-call', - interface=cs.CONN_IFACE_AVATARS, method='RequestAvatars', - args=[[conn.self_handle]], - handled=False), - EventPattern('dbus-signal', signal='AccountPropertyChanged', - path=account_path, interface=cs.ACCOUNT, - predicate=(lambda e: - e.args[0].get('ConnectionStatus') == - cs.CONN_STATUS_CONNECTED), - ), - ) - - q.dbus_return(request_avatars_call.message, signature='') - - q.dbus_emit(conn.object_path, cs.CONN_IFACE_AVATARS, 'AvatarRetrieved', - conn.self_handle, str(conn.avatar[0]), - dbus.ByteArray(conn.avatar[0]), conn.avatar[1], signature='usays') - - q.expect('dbus-signal', path=account_path, - interface=cs.ACCOUNT_IFACE_AVATAR, signal='AvatarChanged'), - - account = bus.get_object(cs.AM, account_path) - account_props = dbus.Interface(account, cs.PROPERTIES_IFACE) - assert account_props.Get(cs.ACCOUNT_IFACE_AVATAR, 'Avatar', - byte_arrays=True) == conn.avatar - - # The avatar wasn't deleted from $XDG_DATA_DIRS, but it was overridden. - assert not os.path.exists(os.environ['MC_ACCOUNT_DIR'] + '/' + - account_id + '/avatar.bin') - assert not os.path.exists(os.environ['MC_ACCOUNT_DIR'] + '/fakecm') - - avatar_filename = account_id - avatar_filename = avatar_filename.replace('/', '-') + '.avatar' - avatar_filename = (os.environ['XDG_DATA_HOME'] + - '/telepathy/mission-control/' + avatar_filename) - assertEquals('MJ12', ''.join(open(avatar_filename, 'r').readlines())) - - datadirs = os.environ['XDG_DATA_DIRS'].split(':') - low_prio_filename = account_id - low_prio_filename = low_prio_filename.replace('/', '-') + '.avatar' - low_prio_filename = (datadirs[0] + - '/telepathy/mission-control/' + low_prio_filename) - assertEquals('Deus Ex', ''.join(open(low_prio_filename, 'r').readlines())) - - # If we set the avatar to be empty, that's written out as a file, - # so it'll override the one in XDG_DATA_DIRS - call_async(q, account_props, 'Set', cs.ACCOUNT_IFACE_AVATAR, 'Avatar', - (dbus.ByteArray(''), '')) - - q.expect_many( - EventPattern('dbus-method-call', - interface=cs.CONN_IFACE_AVATARS, method='ClearAvatar', - args=[]), - EventPattern('dbus-signal', path=account.object_path, - interface=cs.ACCOUNT_IFACE_AVATAR, signal='AvatarChanged'), - EventPattern('dbus-return', method='Set') - ) - - assert account_props.Get(cs.ACCOUNT_IFACE_AVATAR, 'Avatar', - byte_arrays=True) == ('', '') - - assertEquals('', ''.join(open(avatar_filename, 'r').readlines())) - assertEquals('Deus Ex', ''.join(open(low_prio_filename, 'r').readlines())) - -if __name__ == '__main__': - exec_test(test, {}, preload_mc=False, use_fake_accounts_service=True, - pass_kwargs=True) diff --git a/tests/twisted/account-manager/avatar-refresh.py b/tests/twisted/account-manager/avatar-refresh.py index 0c46636..03a02b9 100644 --- a/tests/twisted/account-manager/avatar-refresh.py +++ b/tests/twisted/account-manager/avatar-refresh.py @@ -26,18 +26,322 @@ import os import dbus import dbus.service -from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \ - call_async, assertEquals +from servicetest import (EventPattern, tp_name_prefix, tp_path_prefix, + call_async, assertEquals, sync_dbus) from mctest import exec_test, SimulatedConnection, create_fakecm_account, MC import constants as cs cm_name_ref = dbus.service.BusName( cs.tp_name_prefix + '.ConnectionManager.fakecm', bus=dbus.SessionBus()) -account_id = 'fakecm/fakeprotocol/jc_2edenton_40unatco_2eint' +class Account(object): + def __init__(self, fake_accounts_service, accounts_dir, + avatars_persist, server_delays, local_avatar, remote_avatar): -def preseed(q, bus, fake_accounts_service): + self.avatars_persist = avatars_persist + self.server_delays = server_delays + self.local_avatar = local_avatar + self.remote_avatar = remote_avatar + + if avatars_persist: + s_persist = 'persist' + else: + s_persist = 'transient' + + if server_delays: + s_delay = 'delay' + else: + s_delay = 'immediate' + + self.id = ('fakecm/fakeprotocol/%s_%s_L%s_R%s' % + (s_persist, s_delay, local_avatar, remote_avatar)) + + fake_accounts_service.update_attributes(self.id, changed={ + 'manager': 'fakecm', + 'protocol': 'fakeprotocol', + 'DisplayName': 'Test account', + 'NormalizedName': 'jc.denton@unatco.int', + 'Enabled': False, + 'ConnectAutomatically': True, + 'AutomaticPresence': (dbus.UInt32(2), 'available', + 'My vision is augmented'), + 'Nickname': 'JC', + }) + fake_accounts_service.update_parameters(self.id, untyped={ + 'account': self.id, + 'password': self.id, + }) + + self.avatar_location = None + + if local_avatar is not None: + if local_avatar: + mime = 'image/jpeg' + + if remote_avatar and avatars_persist: + # exercise override of an avatar in $XDG_DATA_DIRS + self.avatar_location = 'datadir' + avatar_filename = self.id + datadir = os.environ['XDG_DATA_DIRS'].split(':')[0] + datadir += '/telepathy/mission-control' + avatar_filename = ( + avatar_filename.replace('/', '-') + '.avatar') + if not os.path.isdir(datadir): + os.makedirs(datadir) + avatar_filename = datadir + '/' + avatar_filename + avatar_bin = open(avatar_filename, 'w') + avatar_bin.write(local_avatar) + avatar_bin.close() + elif not avatars_persist: + self.avatar_location = 'old' + # exercise migration from ~/.mission-control in a + # situation where MC should "win" + os.makedirs(accounts_dir + '/' + self.id) + avatar_bin = open( + accounts_dir + '/' + self.id + '/avatar.bin', 'w') + avatar_bin.write(local_avatar) + avatar_bin.close() + else: + # store it in the normal location + self.avatar_location = 'home' + avatar_filename = self.id + datadir = os.environ['XDG_DATA_HOME'] + datadir += '/telepathy/mission-control' + avatar_filename = ( + avatar_filename.replace('/', '-') + '.avatar') + if not os.path.isdir(datadir): + os.makedirs(datadir) + avatar_filename = datadir + '/' + avatar_filename + avatar_bin = open(avatar_filename, 'w') + avatar_bin.write(local_avatar) + avatar_bin.close() + else: + mime = '' + + if local_avatar == 'old': + # the fake CM just uses the avatar string as its own token + fake_accounts_service.update_attributes(self.id, changed={ + 'AvatarMime': mime, + 'avatar_token': local_avatar, + }) + else: + # either the local avatar is "no avatar", or it was set + # while offline so we don't know its token + fake_accounts_service.update_attributes(self.id, changed={ + 'AvatarMime': mime, + 'avatar_token': '', + }) + + # Ideal behaviour: + if local_avatar == 'new': + # If the local avatar has been set since last login, MC + # should upload it + self.winner = 'MC' + elif local_avatar and not avatars_persist: + # If we have an avatar and it doesn't persist, MC should + # upload it + self.winner = 'MC' + elif avatars_persist and remote_avatar: + # If the server stores our avatar, MC should download it + self.winner = 'service' + else: + # Nobody has an avatar - nothing should happen + self.winner = None + + # Hack around bugs... ideally, the tests would pass without these. + if server_delays and local_avatar == 'old' and remote_avatar: + # What we *should* do is wait for GetKnownAvatarTokens + # (because GetContactAttributes isn't guaranteed to fetch + # our own up-to-date avatar token from the server), then + # download the remote avatar. We currently don't. + self.winner = 'MC' + elif server_delays and remote_avatar and not local_avatar: + # What we *should* do is wait for GetKnownAvatarTokens + # (because GetContactAttributes isn't guaranteed to fetch + # our own up-to-date avatar token from the server), then + # download the remote avatar. At the moment we never actually + # download it at all. + self.winner = None + elif avatars_persist and local_avatar == 'old' and not remote_avatar: + # What we *should* do is work out that the avatar on the + # server has been deleted since we last signed in, + # and delete our local avatar to match. (telepathy-spec + # does provide a way to distinguish between this and + # "the protocol doesn't store avatars", but it's + # really subtle; it's hardly surprising if this is wrong.) + self.winner = 'MC' + + def test(self, q, bus, mc): + expected_params = { + 'account': self.id, + 'password': self.id, + } + + account_path = (cs.ACCOUNT_PATH_PREFIX + self.id) + account_proxy = bus.get_object(cs.AM, account_path) + + account_proxy.Set(cs.ACCOUNT, 'Enabled', True, + dbus_interface=cs.PROPERTIES_IFACE) + + e = q.expect('dbus-method-call', method='RequestConnection', + args=['fakeprotocol', expected_params], + destination=cs.tp_name_prefix + '.ConnectionManager.fakecm', + path=cs.tp_path_prefix + '/ConnectionManager/fakecm', + interface=cs.tp_name_prefix + '.ConnectionManager', + handled=False) + + if self.remote_avatar is None: + initial_avatar = None + elif self.remote_avatar: + initial_avatar = dbus.Struct((dbus.ByteArray(self.remote_avatar), + 'text/plain'), signature='ays') + else: + initial_avatar = dbus.Struct((dbus.ByteArray(''), ''), + signature='ays') + + conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', + self.id.replace('fakecm/fakeprotocol/', ''), + 'myself', has_avatars=True, + avatars_persist=self.avatars_persist, + server_delays_avatar=self.server_delays, + initial_avatar=initial_avatar, + ) + + q.dbus_return(e.message, conn.bus_name, conn.object_path, + signature='so') + + if self.winner != 'MC': + q.forbid_events([ + EventPattern('dbus-method-call', method='SetAvatar'), + ]) + + if self.winner != 'service': + q.forbid_events([ + EventPattern('dbus-signal', signal='AvatarChanged', + path=account_path), + ]) + + q.expect('dbus-method-call', method='Connect', + path=conn.object_path, handled=True, interface=cs.CONN) + + conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CSR_NONE_SPECIFIED) + if self.winner == 'MC': + # MC should upload the avatar. + _, e = q.expect_many( + EventPattern('dbus-method-call', + interface=cs.CONN_IFACE_AVATARS, method='SetAvatar', + args=[self.local_avatar, 'image/jpeg'], + handled=True), + EventPattern('dbus-signal', signal='AccountPropertyChanged', + path=account_path, interface=cs.ACCOUNT, + predicate=(lambda e: + e.args[0].get('ConnectionStatus') == + cs.CONN_STATUS_CONNECTED), + ), + ) + elif self.winner == 'service': + # We haven't changed the avatar since we last signed in, so we + # don't set it - on the contrary, we pick up the remote avatar + # (which has changed since we were last here) to store it in the + # Account, unless the token says there is no avatar. + if conn.avatar[0]: + request_avatars_call, e = q.expect_many( + EventPattern('dbus-method-call', + interface=cs.CONN_IFACE_AVATARS, + method='RequestAvatars', + args=[[conn.self_handle]], + handled=False), + EventPattern('dbus-signal', + signal='AccountPropertyChanged', + path=account_path, interface=cs.ACCOUNT, + predicate=(lambda e: + e.args[0].get('ConnectionStatus') == + cs.CONN_STATUS_CONNECTED), + ), + ) + + q.dbus_return(request_avatars_call.message, signature='') + + q.dbus_emit(conn.object_path, cs.CONN_IFACE_AVATARS, + 'AvatarRetrieved', + conn.self_handle, str(conn.avatar[0]), + dbus.ByteArray(conn.avatar[0]), conn.avatar[1], + signature='usays') + + q.expect('dbus-signal', path=account_path, + interface=cs.ACCOUNT_IFACE_AVATAR, signal='AvatarChanged'), + + account_props = dbus.Interface(account_proxy, cs.PROPERTIES_IFACE) + assert account_props.Get(cs.ACCOUNT_IFACE_AVATAR, 'Avatar', + byte_arrays=True) == conn.avatar + + sync_dbus(bus, q, mc) + q.unforbid_all() + + if self.local_avatar: + self.test_migration(bus, q, conn, account_proxy) + + def test_migration(self, bus, q, conn, account_proxy): + if self.avatar_location == 'old': + # The avatar got migrated to the new location. + assert not os.path.exists(os.environ['MC_ACCOUNT_DIR'] + '/' + + self.id + '/avatar.bin') + assert not os.path.exists(os.environ['MC_ACCOUNT_DIR'] + '/fakecm') + avatar_filename = self.id + avatar_filename = avatar_filename.replace('/', '-') + '.avatar' + avatar_filename = (os.environ['XDG_DATA_HOME'] + + '/telepathy/mission-control/' + avatar_filename) + assertEquals(conn.avatar[0], ''.join(open(avatar_filename, + 'r').readlines())) + elif self.avatar_location == 'datadir' and self.winner == 'service': + # The avatar wasn't deleted from $XDG_DATA_DIRS, but it was + # overridden. + assert not os.path.exists(os.environ['MC_ACCOUNT_DIR'] + '/' + + self.id + '/avatar.bin') + assert not os.path.exists(os.environ['MC_ACCOUNT_DIR'] + '/fakecm') + + avatar_filename = self.id + avatar_filename = avatar_filename.replace('/', '-') + '.avatar' + avatar_filename = (os.environ['XDG_DATA_HOME'] + + '/telepathy/mission-control/' + avatar_filename) + assertEquals(self.remote_avatar, ''.join(open(avatar_filename, + 'r').readlines())) + + datadirs = os.environ['XDG_DATA_DIRS'].split(':') + low_prio_filename = self.id + low_prio_filename = low_prio_filename.replace('/', '-') + '.avatar' + low_prio_filename = (datadirs[0] + + '/telepathy/mission-control/' + low_prio_filename) + assertEquals(self.local_avatar, ''.join(open(low_prio_filename, + 'r').readlines())) + + account_props = dbus.Interface(account_proxy, cs.PROPERTIES_IFACE) + + # If we set the avatar to be empty, that's written out as a file, + # so it'll override the one in XDG_DATA_DIRS + call_async(q, account_props, 'Set', cs.ACCOUNT_IFACE_AVATAR, + 'Avatar', (dbus.ByteArray(''), '')) + + q.expect_many( + EventPattern('dbus-method-call', + interface=cs.CONN_IFACE_AVATARS, method='ClearAvatar', + args=[]), + EventPattern('dbus-signal', path=account_proxy.object_path, + interface=cs.ACCOUNT_IFACE_AVATAR, + signal='AvatarChanged'), + EventPattern('dbus-return', method='Set') + ) + + assert account_props.Get(cs.ACCOUNT_IFACE_AVATAR, 'Avatar', + byte_arrays=True) == ('', '') + + assertEquals('', ''.join(open(avatar_filename, 'r').readlines())) + assertEquals(self.local_avatar, ''.join(open(low_prio_filename, + 'r').readlines())) + +def preseed(q, bus, fake_accounts_service): + accounts = [] accounts_dir = os.environ['MC_ACCOUNT_DIR'] try: @@ -45,85 +349,44 @@ def preseed(q, bus, fake_accounts_service): except OSError: pass - fake_accounts_service.update_attributes(account_id, changed={ - 'manager': 'fakecm', - 'protocol': 'fakeprotocol', - 'DisplayName': 'Work account', - 'NormalizedName': 'jc.denton@unatco.int', - 'Enabled': True, - 'ConnectAutomatically': True, - 'AutomaticPresence': (dbus.UInt32(2), 'available', - 'My vision is augmented'), - 'Nickname': 'JC', - 'AvatarMime': 'image/jpeg', - 'avatar_token': 'Deus Ex', - }) - fake_accounts_service.update_parameters(account_id, untyped={ - 'account': 'jc.denton@unatco.int', - 'password': 'ionstorm', - }) - - os.makedirs(accounts_dir + '/' + account_id) - avatar_bin = open(accounts_dir + '/' + account_id + '/avatar.bin', 'w') - avatar_bin.write('Deus Ex') - avatar_bin.close() - account_connections_file = open(accounts_dir + '/.mc_connections', 'w') account_connections_file.write("") account_connections_file.close() + i = 0 + for local_avatar in ('new', 'old', '', None): + # This is what the spec says Salut should do: omit the remote + # avatar from the result of ContactAttributeInterfaces + # and GetKnownAvatarTokens. + accounts.append(Account(fake_accounts_service, accounts_dir, + avatars_persist=False, server_delays=False, + local_avatar=local_avatar, remote_avatar=None)) + + for have_remote_avatar in (True, False): + for server_delays in (True, False): + if have_remote_avatar: + # the avatars have to be unique, otherwise the + # avatar cache will break our RequestAvatars expectation + remote_avatar = 'remote%d' % i + i += 1 + else: + remote_avatar = '' + + accounts.append(Account(fake_accounts_service, accounts_dir, + avatars_persist=True, server_delays=server_delays, + local_avatar=local_avatar, + remote_avatar=remote_avatar)) + + return accounts + def test(q, bus, unused, **kwargs): fake_accounts_service = kwargs['fake_accounts_service'] - preseed(q, bus, fake_accounts_service) - - expected_params = { - 'account': 'jc.denton@unatco.int', - 'password': 'ionstorm', - } + accounts = preseed(q, bus, fake_accounts_service) mc = MC(q, bus) - e = q.expect('dbus-method-call', method='RequestConnection', - args=['fakeprotocol', expected_params], - destination=cs.tp_name_prefix + '.ConnectionManager.fakecm', - path=cs.tp_path_prefix + '/ConnectionManager/fakecm', - interface=cs.tp_name_prefix + '.ConnectionManager', - handled=False) - - conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_', - 'myself', has_avatars=True, avatars_persist=False) - - q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so') - - account_path = (cs.tp_path_prefix + '/Account/' + account_id) - - q.expect('dbus-method-call', method='Connect', - path=conn.object_path, handled=True, interface=cs.CONN) - - conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CSR_NONE_SPECIFIED) - - _, e = q.expect_many( - EventPattern('dbus-method-call', - interface=cs.CONN_IFACE_AVATARS, method='SetAvatar', - args=['Deus Ex', 'image/jpeg'], - handled=True), - EventPattern('dbus-signal', signal='AccountPropertyChanged', - path=account_path, interface=cs.ACCOUNT, - predicate=(lambda e: - e.args[0].get('ConnectionStatus') == - cs.CONN_STATUS_CONNECTED), - ), - ) - - # The avatar got migrated, too. - assert not os.path.exists(os.environ['MC_ACCOUNT_DIR'] + '/' + - account_id + '/avatar.bin') - assert not os.path.exists(os.environ['MC_ACCOUNT_DIR'] + '/fakecm') - avatar_filename = account_id - avatar_filename = avatar_filename.replace('/', '-') + '.avatar' - avatar_filename = (os.environ['XDG_DATA_HOME'] + - '/telepathy/mission-control/' + avatar_filename) - assertEquals('Deus Ex', ''.join(open(avatar_filename, 'r').readlines())) + for account in accounts: + account.test(q, bus, mc) if __name__ == '__main__': exec_test(test, {}, preload_mc=False, use_fake_accounts_service=True, diff --git a/tests/twisted/mctest.py b/tests/twisted/mctest.py index 27ae204..a814082 100644 --- a/tests/twisted/mctest.py +++ b/tests/twisted/mctest.py @@ -225,7 +225,8 @@ class SimulatedConnection(object): implement_get_interfaces=True, has_requests=True, has_presence=False, has_aliasing=False, has_avatars=False, avatars_persist=True, extra_interfaces=[], has_hidden=False, - implement_get_aliases=True): + implement_get_aliases=True, initial_avatar=None, + server_delays_avatar=False): self.q = q self.bus = bus @@ -252,6 +253,7 @@ class SimulatedConnection(object): self.has_avatars = has_avatars self.avatars_persist = avatars_persist self.extra_interfaces = extra_interfaces[:] + self.avatar_delayed = server_delays_avatar self.interfaces = [] self.interfaces.append(cs.CONN_IFACE_CONTACTS) @@ -267,9 +269,11 @@ class SimulatedConnection(object): if self.extra_interfaces: self.interfaces.extend(self.extra_interfaces) - if self.avatars_persist: + if initial_avatar is not None: + self.avatar = initial_avatar + elif self.avatars_persist: self.avatar = dbus.Struct((dbus.ByteArray('my old avatar'), - 'text/plain'), signature='ays') + 'text/plain'), signature='ays') else: self.avatar = None @@ -415,6 +419,7 @@ class SimulatedConnection(object): def forget_avatar(self): self.avatar = (dbus.ByteArray(''), '') + self.avatar_delayed = False # not actually very relevant for MC so hard-code 0 for now def GetAliasFlags(self, e): @@ -449,7 +454,25 @@ class SimulatedConnection(object): # the user has an avatar already, if they persist; nobody else does if self.self_handle in e.args[0]: - if self.avatar is not None: + if self.avatar is None: + # GetKnownAvatarTokens has the special case that "where + # the avatar does not persist between connections, a CM + # should omit the self handle from the returned map until + # an avatar is explicitly set or cleared". We'd have been + # better off with a more explicit design, but it's too + # late now... + assert not self.avatars_persist + else: + # "a CM must always have the tokens for the self handle + # if one is set (even if it is set to no avatar)" + # so behave as though we'd done a network round-trip to + # check what our token was, and found our configured + # token + if self.avatar_delayed: + self.q.dbus_emit(self.object_path, cs.CONN_IFACE_AVATARS, + 'AvatarUpdated', self.self_handle, + str(self.avatar[0]), signature='us') + # we just stringify the avatar as the token # (also, empty avatar => no avatar => empty token) ret[self.self_handle] = str(self.avatar[0]) @@ -458,6 +481,7 @@ class SimulatedConnection(object): def SetAvatar(self, e): self.avatar = dbus.Struct(e.args, signature='ays') + self.avatar_delayed = False # we just stringify the avatar as the token self.q.dbus_return(e.message, str(self.avatar[0]), signature='s') @@ -646,9 +670,12 @@ class SimulatedConnection(object): if cs.CONN_IFACE_AVATARS in ifaces: if h == self.self_handle: - if self.avatar is not None: - # we just stringify the avatar as the token - # (also, empty avatar => no avatar => empty token) + if self.avatar is not None and not self.avatar_delayed: + # We just stringify the avatar as the token + # (also, empty avatar => no avatar => empty token). + # This doesn't have the same special case that + # GetKnownAvatarTokens does - if we don't know the + # token yet, we don't wait. ret[cs.ATTR_AVATAR_TOKEN] = str(self.avatar[0]) if cs.CONN_IFACE_SIMPLE_PRESENCE in ifaces: -- 1.8.4.rc3