summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2010-12-22 15:54:58 +0000
committerJonny Lamb <jonny.lamb@collabora.co.uk>2010-12-22 15:54:58 +0000
commit5b34520e25c014377a27f22d35b958ded994a2dd (patch)
treeb3c3c0eb6e0d39d85ce823db1a35d3c317571c7e /tests
parent1d9f17613ac2ce1dc3928ab0848d7b9b987b6330 (diff)
tests: move from test/ to tests/
So so annoying. Signed-off-by: Jonny Lamb <jonny.lamb@collabora.co.uk>
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile.am52
-rw-r--r--tests/account-store-default.c285
-rw-r--r--tests/account-store-default.h41
-rw-r--r--tests/account-store-libaccounts.c627
-rw-r--r--tests/account-store-libaccounts.h38
-rw-r--r--tests/account-store.c274
-rw-r--r--tests/keyring-command.c172
-rw-r--r--tests/twisted/Makefile.am295
-rw-r--r--tests/twisted/README34
-rw-r--r--tests/twisted/account-manager/account-basics.py248
-rw-r--r--tests/twisted/account-manager/auto-away.py208
-rw-r--r--tests/twisted/account-manager/auto-connect.py175
-rw-r--r--tests/twisted/account-manager/avatar-persist.py143
-rw-r--r--tests/twisted/account-manager/avatar-refresh.py124
-rw-r--r--tests/twisted/account-manager/avatar.py112
-rw-r--r--tests/twisted/account-manager/bad-cm.py79
-rw-r--r--tests/twisted/account-manager/create-auto-connect.py68
-rw-r--r--tests/twisted/account-manager/create-twice.py64
-rw-r--r--tests/twisted/account-manager/create-with-properties.py174
-rw-r--r--tests/twisted/account-manager/device-idle.py129
-rw-r--r--tests/twisted/account-manager/enable-auto-connect.py69
-rw-r--r--tests/twisted/account-manager/enable.py62
-rw-r--r--tests/twisted/account-manager/make-valid.py237
-rw-r--r--tests/twisted/account-manager/nickname.py93
-rw-r--r--tests/twisted/account-manager/param-types.py86
-rwxr-xr-xtests/twisted/account-manager/presence.py136
-rw-r--r--tests/twisted/account-manager/reconnect.py202
-rw-r--r--tests/twisted/account-manager/recover-from-disconnect.py197
-rw-r--r--tests/twisted/account-manager/request-online.py170
-rw-r--r--tests/twisted/account-manager/server-drops-us.py128
-rw-r--r--tests/twisted/account-manager/service.py112
-rw-r--r--tests/twisted/account-manager/update-parameters.py194
-rw-r--r--tests/twisted/account-requests/cancel.py127
-rw-r--r--tests/twisted/account-requests/create-text.py176
-rw-r--r--tests/twisted/account-requests/delete-account-during-request.py136
-rw-r--r--tests/twisted/account-storage/default-keyring-storage.py228
-rw-r--r--tests/twisted/account-storage/diverted-storage.py139
-rw-r--r--tests/twisted/account-storage/libaccounts-sso-storage.py89
-rwxr-xr-xtests/twisted/account/addressing.py85
-rw-r--r--tests/twisted/accounts/README3
-rw-r--r--tests/twisted/capabilities/contact-caps.py161
-rw-r--r--tests/twisted/capabilities/draft-1.py121
-rw-r--r--tests/twisted/capabilities/legacy-caps.py85
-rw-r--r--tests/twisted/chandlers/README4
-rw-r--r--tests/twisted/constants.py188
-rw-r--r--tests/twisted/crash-recovery/crash-recovery.py135
-rw-r--r--tests/twisted/dispatcher/already-has-channel.py223
-rw-r--r--tests/twisted/dispatcher/already-has-obsolete.py221
-rw-r--r--tests/twisted/dispatcher/approver-fails.py158
-rw-r--r--tests/twisted/dispatcher/bypass-approval.py293
-rw-r--r--tests/twisted/dispatcher/bypass-observers.py291
-rw-r--r--tests/twisted/dispatcher/cancel.py163
-rw-r--r--tests/twisted/dispatcher/capture-bundle.py284
-rw-r--r--tests/twisted/dispatcher/connect-for-request.py155
-rw-r--r--tests/twisted/dispatcher/create-at-startup.py232
-rw-r--r--tests/twisted/dispatcher/create-delayed-by-mini-plugin.py119
-rw-r--r--tests/twisted/dispatcher/create-delayed-by-plugin.py166
-rw-r--r--tests/twisted/dispatcher/create-handler-fails.py189
-rw-r--r--tests/twisted/dispatcher/create-hints.py201
-rw-r--r--tests/twisted/dispatcher/create-no-preferred-handler.py195
-rw-r--r--tests/twisted/dispatcher/create-rejected-by-mini-plugin.py102
-rw-r--r--tests/twisted/dispatcher/create-text.py223
-rw-r--r--tests/twisted/dispatcher/created-behind-our-back.py106
-rw-r--r--tests/twisted/dispatcher/dispatch-activatable.py133
-rw-r--r--tests/twisted/dispatcher/dispatch-before-connected.py104
-rw-r--r--tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py261
-rw-r--r--tests/twisted/dispatcher/dispatch-delayed-by-plugin.py272
-rw-r--r--tests/twisted/dispatcher/dispatch-obsolete.py191
-rw-r--r--tests/twisted/dispatcher/dispatch-rejected-by-mini-plugin.py217
-rw-r--r--tests/twisted/dispatcher/dispatch-rejected-by-plugin.py239
-rw-r--r--tests/twisted/dispatcher/dispatch-text.py542
-rw-r--r--tests/twisted/dispatcher/ensure-and-redispatch.py338
-rw-r--r--tests/twisted/dispatcher/ensure-is-approval.py258
-rw-r--r--tests/twisted/dispatcher/ensure-rapidly.py243
-rw-r--r--tests/twisted/dispatcher/exploding-bundles.py323
-rw-r--r--tests/twisted/dispatcher/fdo-21034.py78
-rw-r--r--tests/twisted/dispatcher/handle-channels-fails.py223
-rw-r--r--tests/twisted/dispatcher/lose-text.py178
-rw-r--r--tests/twisted/dispatcher/recover-from-disconnect.py274
-rw-r--r--tests/twisted/dispatcher/request-disabled-account.py97
-rw-r--r--tests/twisted/dispatcher/respawn-activatable-observers.py235
-rw-r--r--tests/twisted/dispatcher/respawn-observers.py220
-rw-r--r--tests/twisted/dispatcher/undispatchable.py75
-rw-r--r--tests/twisted/dispatcher/vanishing-client.py111
-rw-r--r--tests/twisted/fakeclient.py99
-rw-r--r--tests/twisted/fakecm.py292
-rw-r--r--tests/twisted/mc-debug-server.c268
-rw-r--r--tests/twisted/mcp-account-diversion.c286
-rw-r--r--tests/twisted/mcp-dbus-caller-permission.c264
-rw-r--r--tests/twisted/mcp-plugin.c442
-rw-r--r--tests/twisted/mctest.py1054
-rw-r--r--tests/twisted/servicetest.py655
-rw-r--r--tests/twisted/telepathy/clients/AbiWord.client16
-rw-r--r--tests/twisted/telepathy/clients/Logger.client10
-rw-r--r--tests/twisted/telepathy/clients/README4
-rw-r--r--tests/twisted/telepathy/managers/README4
-rw-r--r--tests/twisted/telepathy/managers/fakecm.manager15
-rw-r--r--tests/twisted/telepathy/managers/onewitheverything.manager48
-rw-r--r--tests/twisted/test-plugin.c227
-rw-r--r--tests/twisted/tools/Client.AbiWord.service.in3
-rw-r--r--tests/twisted/tools/Client.Logger.service.in3
-rw-r--r--tests/twisted/tools/Makefile.am40
-rw-r--r--tests/twisted/tools/MissionControl5.service.in3
-rw-r--r--tests/twisted/tools/exec-with-log.sh.in65
-rw-r--r--tests/twisted/tools/fake-startup.sh5
-rw-r--r--tests/twisted/tools/tmp-session-bus.conf.in30
-rw-r--r--tests/twisted/tools/valgrind.supp809
-rw-r--r--tests/twisted/tools/with-session-bus.sh84
-rw-r--r--tests/value-is-same.c209
109 files changed, 19573 insertions, 0 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 00000000..7f6c3996
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,52 @@
+INCLUDES = \
+ $(DBUS_CFLAGS) \
+ $(TELEPATHY_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/src \
+ -I$(top_builddir)/src \
+ -DMC_DISABLE_DEPRECATED \
+ -DLIBDIR="@libdir@" -DLIBVERSION="0"
+
+AM_CFLAGS = $(ERROR_CFLAGS)
+
+if ENABLE_GNOME_KEYRING
+INCLUDES += $(GNOME_KEYRING_CFLAGS)
+endif
+
+SUBDIRS = . twisted
+
+TEST_EXECUTABLES = test-value-is-same
+NON_TEST_EXECUTABLES = account-store
+
+if ENABLE_GNOME_KEYRING
+NON_TEST_EXECUTABLES += keyring-command
+endif
+
+noinst_PROGRAMS = $(TEST_EXECUTABLES) $(NON_TEST_EXECUTABLES)
+
+TESTS = $(TEST_EXECUTABLES)
+
+# This test needs linking against the convenience library, so it can use
+# MC internals.
+test_value_is_same_SOURCES = value-is-same.c
+test_value_is_same_LDADD = $(top_builddir)/src/libmcd-convenience.la
+
+account_store_LDADD = $(GLIB_LIBS)
+account_store_SOURCES = \
+ account-store.c \
+ account-store-default.c \
+ account-store-default.h
+
+if ENABLE_GNOME_KEYRING
+account_store_LDADD += $(GNOME_KEYRING_LIBS)
+
+keyring_command_LDADD = $(GLIB_LIBS) $(GNOME_KEYRING_LIBS)
+keyring_command_SOURCES = keyring-command.c
+endif
+
+if ENABLE_LIBACCOUNTS_SSO
+account_store_SOURCES += account-store-libaccounts.c account-store-libaccounts.h
+account_store_LDADD += $(LIBACCOUNTS_SSO_LIBS)
+INCLUDES += $(LIBACCOUNTS_SSO_CFLAGS)
+endif
diff --git a/tests/account-store-default.c b/tests/account-store-default.c
new file mode 100644
index 00000000..464aa986
--- /dev/null
+++ b/tests/account-store-default.c
@@ -0,0 +1,285 @@
+/*
+ * MC account storage backend inspector, default backend
+ *
+ * Copyright © 2010 Nokia Corporation
+ * Copyright © 2010 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
+ */
+
+#include "config.h"
+#include <glib.h>
+#include <string.h>
+
+#include "account-store-default.h"
+
+#if ENABLE_GNOME_KEYRING
+#include <gnome-keyring.h>
+
+GnomeKeyringPasswordSchema keyring_schema =
+ { GNOME_KEYRING_ITEM_GENERIC_SECRET,
+ { { "account", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+ { "param", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+ { NULL, 0 } } };
+
+static gboolean
+_keyring_remove_account (const gchar *acct)
+{
+ GList *i;
+ GList *items;
+ GnomeKeyringAttributeList *match = gnome_keyring_attribute_list_new ();
+ GnomeKeyringResult ok;
+
+ gnome_keyring_attribute_list_append_string (match, "account", acct);
+
+ ok = gnome_keyring_find_items_sync (GNOME_KEYRING_ITEM_GENERIC_SECRET,
+ match, &items);
+
+ if (ok != GNOME_KEYRING_RESULT_OK)
+ goto finished;
+
+ for (i = items; i != NULL; i = g_list_next (i))
+ {
+ GnomeKeyringFound *found = i->data;
+ ok = gnome_keyring_item_delete_sync (found->keyring, found->item_id);
+ if (ok != GNOME_KEYRING_RESULT_OK)
+ break;
+ }
+
+ finished:
+ gnome_keyring_attribute_list_free (match);
+
+ return ok = GNOME_KEYRING_RESULT_OK;
+}
+
+static gchar *
+_get_secret_from_keyring (const gchar *account, const gchar *key)
+{
+ GnomeKeyringResult ok = GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON;
+ GnomeKeyringAttributeList *match = gnome_keyring_attribute_list_new ();
+ GList *items = NULL;
+ GList *i;
+ gchar *secret;
+
+ gnome_keyring_attribute_list_append_string (match, "account", account);
+
+ ok = gnome_keyring_find_items_sync (GNOME_KEYRING_ITEM_GENERIC_SECRET,
+ match, &items);
+
+ if (ok != GNOME_KEYRING_RESULT_OK)
+ goto finished;
+
+ for (i = items; i != NULL; i = g_list_next (i))
+ {
+ gsize j;
+ GnomeKeyringFound *entry = i->data;
+ GnomeKeyringAttributeList *data = entry->attributes;
+
+ for (j = 0; j < data->len; j++)
+ {
+ GnomeKeyringAttribute *attr =
+ &(gnome_keyring_attribute_list_index (data, j));
+ const gchar *name = attr->name;
+ const gchar *value = NULL;
+ const gchar *param = NULL;
+
+ switch (attr->type)
+ {
+ case GNOME_KEYRING_ATTRIBUTE_TYPE_STRING:
+ if (g_strcmp0 ("param", name) == 0)
+ {
+ param = attr->value.string;
+ value = entry->secret;
+ }
+ break;
+
+ default:
+ g_warning ("Unsupported value type for %s.%s", account, name);
+ }
+
+ if (param != NULL && value != NULL && g_str_equal (param, key))
+ secret = g_strdup (value);
+ }
+ }
+
+ gnome_keyring_found_list_free (items);
+
+ finished:
+ gnome_keyring_attribute_list_free (match);
+
+ return secret;
+}
+
+#else
+
+static gchar *
+_get_secret_from_keyring (const gchar *account,
+ const gchar *key)
+{
+ return NULL;
+}
+
+static gboolean
+_keyring_remove_account (const gchar *acct)
+{
+ return TRUE;
+}
+
+#endif
+
+static const gchar *default_config (void)
+{
+ const gchar *base;
+ static const gchar *path = NULL;
+
+ if (path != NULL)
+ return path;
+
+ base = g_getenv ("MC_ACCOUNT_DIR");
+
+ if (!base)
+ base = ACCOUNTS_DIR;
+
+ if (!base)
+ return NULL;
+
+ if (base[0] == '~')
+ path = g_build_filename (g_get_home_dir(), base + 1, "accounts.cfg", NULL);
+ else
+ path = g_build_filename (base, "accounts.cfg", NULL);
+
+ return path;
+}
+
+static GKeyFile * default_keyfile (void)
+{
+ GError *error = NULL;
+ static GKeyFile *keyfile = NULL;
+ const gchar *path = NULL;
+
+ if (keyfile != NULL)
+ return keyfile;
+
+ path = default_config ();
+
+ keyfile = g_key_file_new ();
+
+ if (!g_key_file_load_from_file (keyfile, path, 0, &error))
+ {
+ if (error != NULL)
+ g_warning ("keyfile '%s' error: %s", path, error->message);
+ else
+ g_warning ("keyfile '%s' error: unknown error", path);
+
+ g_key_file_free (keyfile);
+ g_error_free (error);
+ keyfile = NULL;
+ }
+
+ return keyfile;
+}
+
+static gboolean commit_changes (void)
+{
+ gsize n = 0;
+ gchar *data = NULL;
+ gboolean done = FALSE;
+ GKeyFile *keyfile = default_keyfile ();
+ const gchar *config = default_config ();
+
+ data = g_key_file_to_data (keyfile, &n, NULL);
+ done = g_file_set_contents (config, data, n, NULL);
+
+ g_free (data);
+
+ return done;
+}
+
+gchar *
+default_get (const gchar *account,
+ const gchar *key)
+{
+ const gchar *pkey = key;
+ gchar *value = NULL;
+
+ if (g_str_has_prefix (key, "param-"))
+ pkey = key + strlen("param-");
+
+ value = _get_secret_from_keyring (account, pkey);
+
+ if (value == NULL)
+ value = g_key_file_get_string (default_keyfile (), account, key, NULL);
+
+ return value;
+}
+
+gboolean
+default_set (const gchar *account,
+ const gchar *key,
+ const gchar *value)
+{
+ GKeyFile *keyfile = NULL;
+
+#if ENABLE_GNOME_KEYRING
+ if (g_str_equal (key, "param-password"))
+ {
+ GnomeKeyringResult result = GNOME_KEYRING_RESULT_CANCELLED;
+ gchar *name =
+ g_strdup_printf ("account: %s; param: %s", account, "password");
+
+ result = gnome_keyring_store_password_sync (&keyring_schema, NULL,
+ name, value,
+ "account", account,
+ "param", "password",
+ NULL);
+
+ g_free (name);
+
+ return result == GNOME_KEYRING_RESULT_OK;
+ }
+#endif
+
+ keyfile = default_keyfile ();
+
+ if (keyfile == NULL)
+ return FALSE;
+
+ g_key_file_set_string (keyfile, account, key, value);
+
+ return commit_changes ();
+}
+
+gboolean
+default_delete (const gchar *account)
+{
+ GKeyFile *keyfile = default_keyfile ();
+
+ g_key_file_remove_group (keyfile, account, NULL);
+ _keyring_remove_account (account);
+
+ return commit_changes ();
+}
+
+gboolean
+default_exists (const gchar *account)
+{
+ return g_key_file_has_group (default_keyfile (), account);
+}
+
+GStrv
+default_list (void)
+{
+ return g_key_file_get_groups (default_keyfile (), NULL);
+}
diff --git a/tests/account-store-default.h b/tests/account-store-default.h
new file mode 100644
index 00000000..739f95cc
--- /dev/null
+++ b/tests/account-store-default.h
@@ -0,0 +1,41 @@
+/*
+ * MC account storage backend inspector, default backend
+ *
+ * Copyright © 2010 Nokia Corporation
+ * Copyright © 2010 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
+ */
+
+#ifndef _ACCOUNT_STORE_DEFAULT_H_
+#define _ACCOUNT_STORE_DEFAULT_H_
+
+#include <glib.h>
+#include <glib-object.h>
+
+gchar * default_get (const gchar *account,
+ const gchar *key);
+
+gboolean default_set (const gchar *account,
+ const gchar *key,
+ const gchar *value);
+
+gboolean default_delete (const gchar *account);
+
+gboolean default_exists (const gchar *account);
+
+GStrv default_list (void);
+
+#endif
diff --git a/tests/account-store-libaccounts.c b/tests/account-store-libaccounts.c
new file mode 100644
index 00000000..11c24006
--- /dev/null
+++ b/tests/account-store-libaccounts.c
@@ -0,0 +1,627 @@
+/*
+ * MC account storage backend inspector, libaccounts backend
+ *
+ * Copyright © 2010 Nokia Corporation
+ * Copyright © 2010 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
+ */
+
+#include <string.h>
+#include <libaccounts-glib/ag-manager.h>
+#include <libaccounts-glib/ag-account.h>
+#include <glib.h>
+#include "account-store-libaccounts.h"
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "account-store-libaccounts"
+
+/* MC <-> AG global/local setting meta data */
+#define MCPP "param-"
+#define AGPP "parameters/"
+#define LIBACCT_ID_KEY "libacct-uid"
+
+#define MC_ENABLED_KEY "Enabled"
+#define AG_ENABLED_KEY "enabled"
+
+#define AG_LABEL_KEY "name"
+#define MC_LABEL_KEY "DisplayName"
+
+#define AG_ACCOUNT_KEY "username"
+#define MC_ACCOUNT_KEY "account"
+#define PASSWORD_KEY "password"
+#define AG_ACCOUNT_ALT_KEY AGPP "account"
+
+#define MC_CMANAGER_KEY "manager"
+#define MC_PROTOCOL_KEY "protocol"
+#define MC_IDENTITY_KEY "tmc-uid"
+
+#define SERVICES_KEY "sso-services"
+
+#define MC_SERVICE_KEY "Service"
+
+typedef struct {
+ gchar *mc_name;
+ gchar *ag_name;
+ gboolean global; /* global ag setting or service specific? */
+ gboolean readable; /* does the _standard_ read method copy this into MC? */
+ gboolean writable; /* does the _standard_ write method copy this into AG? */
+ gboolean freeable; /* should clear_setting_data deallocate the names? */
+} Setting;
+
+#define GLOBAL TRUE
+#define SERVICE FALSE
+#define READABLE TRUE
+#define UNREADABLE FALSE
+#define WRITABLE TRUE
+#define UNWRITABLE FALSE
+
+typedef enum {
+ SETTING_MC,
+ SETTING_AG,
+} SettingType;
+
+Setting setting_map[] = {
+ { MC_ENABLED_KEY , AG_ENABLED_KEY , GLOBAL , UNREADABLE, UNWRITABLE },
+ { MCPP MC_ACCOUNT_KEY, AG_ACCOUNT_KEY , GLOBAL , READABLE , UNWRITABLE },
+ { MCPP PASSWORD_KEY , PASSWORD_KEY , GLOBAL , READABLE , WRITABLE },
+ { MC_LABEL_KEY , AG_LABEL_KEY , GLOBAL , READABLE , WRITABLE },
+ { LIBACCT_ID_KEY , LIBACCT_ID_KEY , GLOBAL , UNREADABLE, UNWRITABLE },
+ { MC_IDENTITY_KEY , MC_IDENTITY_KEY, SERVICE, READABLE , WRITABLE },
+ { MC_CMANAGER_KEY , MC_CMANAGER_KEY, SERVICE, READABLE , UNWRITABLE },
+ { MC_PROTOCOL_KEY , MC_PROTOCOL_KEY, SERVICE, READABLE , UNWRITABLE },
+ { MC_SERVICE_KEY , MC_SERVICE_KEY , SERVICE, UNREADABLE, UNWRITABLE },
+ { SERVICES_KEY , SERVICES_KEY , GLOBAL , UNREADABLE, UNWRITABLE },
+ { NULL , NULL , SERVICE, UNREADABLE, UNWRITABLE }
+};
+
+static void
+clear_setting_data (Setting *setting)
+{
+ if (setting == NULL)
+ return;
+
+ if (!setting->freeable)
+ return;
+
+ g_free (setting->mc_name);
+ g_free (setting->ag_name);
+ setting->mc_name = NULL;
+ setting->ag_name = NULL;
+}
+
+static Setting *
+setting_data (const gchar *name, SettingType type)
+{
+ guint i = 0;
+ static Setting parameter = { NULL, NULL, SERVICE, READABLE, WRITABLE, TRUE };
+ const gchar *prefix;
+
+ for (; setting_map[i].mc_name != NULL; i++)
+ {
+ const gchar *setting_name = NULL;
+
+ if (type == SETTING_MC)
+ setting_name = setting_map[i].mc_name;
+ else
+ setting_name = setting_map[i].ag_name;
+
+ if (g_strcmp0 (name, setting_name) == 0)
+ return &setting_map[i];
+ }
+
+ prefix = (type == SETTING_MC) ? MCPP : AGPP;
+
+ if (!g_str_has_prefix (name, prefix))
+ { /* a non-parameter setting */
+ parameter.mc_name = g_strdup (name);
+ parameter.ag_name = g_strdup (name);
+ }
+ else
+ { /* a setting that is a parameter on both sides (AG & MC) */
+ const guint plength = strlen (prefix);
+
+ parameter.mc_name = g_strdup_printf ("%s%s", MCPP, name + plength);
+ parameter.ag_name = g_strdup_printf ("%s%s", AGPP, name + plength);
+ }
+
+ return &parameter;
+}
+
+
+/* logging helpers: */
+static void
+_g_log_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer unused_data)
+{
+ /* the libaccounts code is currently very chatty when debugging: *
+ * we are only interested in or own debugging output for now. */
+ if ((gchar *)log_domain != (gchar *)G_LOG_DOMAIN)
+ return;
+
+ g_log_default_handler (log_domain, log_level, message, unused_data);
+}
+
+static void
+toggle_mute (void)
+{
+ static GLogFunc old = NULL;
+
+ if (old == NULL)
+ {
+ old = g_log_set_default_handler (_g_log_handler, NULL);
+ }
+ else
+ {
+ g_log_set_default_handler (old, NULL);
+ old = NULL;
+ }
+}
+
+static gchar *
+_gvalue_to_string (const GValue *val)
+{
+ switch (G_VALUE_TYPE (val))
+ {
+ case G_TYPE_STRING:
+ return g_value_dup_string (val);
+ case G_TYPE_BOOLEAN:
+ return g_strdup (g_value_get_boolean (val) ? "true" : "false");
+ case G_TYPE_CHAR:
+ return g_strdup_printf ("%c", g_value_get_uchar (val));
+ case G_TYPE_UCHAR:
+ return g_strdup_printf ("%c", g_value_get_char (val));
+ case G_TYPE_INT:
+ return g_strdup_printf ("%i", g_value_get_int (val));
+ case G_TYPE_UINT:
+ return g_strdup_printf ("%u", g_value_get_uint (val));
+ case G_TYPE_LONG:
+ return g_strdup_printf ("%ld", g_value_get_long (val));
+ case G_TYPE_ULONG:
+ return g_strdup_printf ("%lu", g_value_get_ulong (val));
+ case G_TYPE_INT64:
+ return g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (val));
+ case G_TYPE_UINT64:
+ return g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (val));
+ case G_TYPE_ENUM:
+ return g_strdup_printf ("%d" , g_value_get_enum (val));
+ case G_TYPE_FLAGS:
+ return g_strdup_printf ("%u", g_value_get_flags (val));
+ case G_TYPE_FLOAT:
+ return g_strdup_printf ("%f", g_value_get_float (val));
+ case G_TYPE_DOUBLE:
+ return g_strdup_printf ("%g", g_value_get_double (val));
+ default:
+ g_warning ("Unsupported type %s", G_VALUE_TYPE_NAME (val));
+ return NULL;
+ }
+}
+
+static AgManager *
+get_ag_manager (void)
+{
+ static AgManager *agm = NULL;
+
+ toggle_mute ();
+
+ if (agm != NULL)
+ return agm;
+
+ agm = ag_manager_new ();
+
+ toggle_mute ();
+
+ return agm;
+}
+
+static AgAccount *
+get_ag_account (const gchar *mc_account)
+{
+ AgAccount *ag_account = NULL;
+ AgManager *ag_manager = get_ag_manager ();
+ GList *ag_ids = NULL;
+ GList *ag_id;
+
+ toggle_mute ();
+
+ ag_ids = ag_manager_list_by_service_type (ag_manager, "IM");
+ g_debug ("%d accounts in SSO", g_list_length (ag_ids));
+
+ for (ag_id = ag_ids; ag_id != NULL; ag_id = g_list_next (ag_id))
+ {
+ AgAccountId id = GPOINTER_TO_UINT (ag_id->data);
+ AgAccount *account = ag_manager_get_account (ag_manager, id);
+
+ if (account != NULL)
+ {
+ GValue value = { 0 };
+ AgSettingSource source = AG_SETTING_SOURCE_NONE;
+
+ g_value_init (&value, G_TYPE_STRING);
+ ag_account_select_service (account, NULL);
+
+ source = ag_account_get_value (account, MC_IDENTITY_KEY, &value);
+
+ if (source != AG_SETTING_SOURCE_NONE)
+ {
+ if (g_str_equal (g_value_get_string (&value), mc_account))
+ {
+ ag_account = g_object_ref (account);
+ ag_id = NULL;
+ }
+
+ g_value_unset (&value);
+ }
+
+ g_object_unref (account);
+ }
+ }
+
+ ag_manager_list_free (ag_ids);
+
+ toggle_mute ();
+
+ return ag_account;
+}
+
+static gboolean
+_ag_account_select_default_im_service (AgAccount *account)
+{
+ gboolean have_im_service = FALSE;
+ GList *first = ag_account_list_services_by_type (account, "IM");
+
+ if (first != NULL && first->data != NULL)
+ {
+ have_im_service = TRUE;
+ ag_account_select_service (account, first->data);
+ }
+
+ ag_service_list_free (first);
+
+ return have_im_service;
+}
+
+/* enabled is actually a tri-state<->boolean mapping */
+static gboolean _sso_account_enabled (AgAccount *account, AgService *service)
+{
+ gboolean local = FALSE;
+ gboolean global = FALSE;
+ AgService *original = ag_account_get_selected_service (account);
+
+ if (service == NULL)
+ {
+ _ag_account_select_default_im_service (account);
+ local = ag_account_get_enabled (account);
+ }
+ else
+ {
+ if (original != service)
+ ag_account_select_service (account, service);
+
+ local = ag_account_get_enabled (account);
+ }
+
+ ag_account_select_service (account, NULL);
+ global = ag_account_get_enabled (account);
+
+ ag_account_select_service (account, original);
+
+ g_debug ("_sso_account_enabled: global:%d && local:%d", global, local);
+
+ return local && global;
+}
+
+static void _sso_account_enable (AgAccount *account,
+ AgService *service,
+ gboolean on)
+{
+ AgService *original = ag_account_get_selected_service (account);
+
+ /* turn the local enabled flag on/off as required */
+ if (service != NULL)
+ ag_account_select_service (account, service);
+ else
+ _ag_account_select_default_im_service (account);
+
+ ag_account_set_enabled (account, on);
+
+ /* if we are turning the account on, the global flag must also be set *
+ * NOTE: this isn't needed when turning the account off */
+ if (on)
+ {
+ ag_account_select_service (account, NULL);
+ ag_account_set_enabled (account, on);
+ }
+
+ ag_account_select_service (account, original);
+}
+
+/* saving settings other than the enabled tri-state */
+static void
+save_setting (AgAccount *account,
+ const Setting *setting,
+ const gchar *val)
+{
+ AgService *service = ag_account_get_selected_service (account);
+
+ if (!setting->writable)
+ return;
+
+ if (setting->global)
+ ag_account_select_service (account, NULL);
+ else if (service == NULL)
+ _ag_account_select_default_im_service (account);
+
+ if (val != NULL)
+ {
+ GValue value = { 0 };
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, val);
+ ag_account_set_value (account, setting->ag_name, &value);
+ g_value_unset (&value);
+ }
+ else
+ {
+ ag_account_set_value (account, setting->ag_name, NULL);
+ }
+
+ /* leave the selected service as we found it: */
+ ag_account_select_service (account, service);
+}
+
+gchar *
+libaccounts_get (const gchar *mc_account, const gchar *key)
+{
+ gchar *rval = NULL;
+ AgAccount *ag_account = get_ag_account (mc_account);
+ Setting *setting = setting_data (key, SETTING_MC);
+
+ toggle_mute ();
+
+ if (ag_account != NULL)
+ {
+
+ if (setting == NULL)
+ {
+ g_debug ("setting %s is unknown/unmapped, aborting update", key);
+ rval = g_strdup ("");
+ goto done;
+ }
+
+ g_debug ("MC key %s -> AG key %s", key, setting->ag_name);
+
+ if (g_str_equal (setting->ag_name, AG_ENABLED_KEY))
+ {
+ gboolean on = _sso_account_enabled (ag_account, NULL);
+
+ rval = g_strdup (on ? "true" : "false");
+ goto done;
+ }
+ else
+ {
+ GValue value = { 0 };
+ AgSettingSource source = AG_SETTING_SOURCE_NONE;
+
+ g_value_init (&value, G_TYPE_STRING);
+
+ /* the 'account' parameter is a special case for historical reasons */
+ if (g_str_equal (key, MCPP MC_ACCOUNT_KEY))
+ {
+ _ag_account_select_default_im_service (ag_account);
+ source =
+ ag_account_get_value (ag_account, AG_ACCOUNT_ALT_KEY, &value);
+
+ if (source != AG_SETTING_SOURCE_NONE)
+ goto found;
+ }
+
+ if (setting->global)
+ ag_account_select_service (ag_account, NULL);
+ else
+ _ag_account_select_default_im_service (ag_account);
+
+ source = ag_account_get_value (ag_account, setting->ag_name, &value);
+
+ found:
+ if (source != AG_SETTING_SOURCE_NONE)
+ {
+ rval = _gvalue_to_string (&value);
+ g_value_unset (&value);
+ }
+ }
+ }
+
+ done:
+ toggle_mute ();
+
+ if (ag_account)
+ g_object_unref (ag_account);
+
+ clear_setting_data (setting);
+
+ return rval;
+}
+
+gboolean
+libaccounts_set (const gchar *mc_account,
+ const gchar *key,
+ const gchar *value)
+{
+ gboolean done = FALSE;
+ AgAccount *ag_account = get_ag_account (mc_account);
+ Setting *setting = setting_data (key, SETTING_MC);
+
+ toggle_mute ();
+
+ if (ag_account != NULL)
+ {
+ if (setting == NULL)
+ {
+ g_debug ("setting %s is unknown/unmapped, aborting update", key);
+ goto done;
+ }
+
+ if (g_str_equal (setting->ag_name, MC_ENABLED_KEY))
+ {
+ gboolean on = g_str_equal (value, "true");
+
+ _sso_account_enable (ag_account, NULL, on);
+ done = TRUE;
+ goto done;
+ }
+ else
+ {
+ save_setting (ag_account, setting, value);
+ done = TRUE;
+ }
+
+ if (done)
+ ag_account_store (ag_account, NULL, NULL);
+
+ }
+
+ done:
+ toggle_mute ();
+
+ if (ag_account)
+ g_object_unref (ag_account);
+
+ clear_setting_data (setting);
+
+ return done;
+}
+
+gboolean
+libaccounts_delete (const gchar *mc_account)
+{
+ gboolean done = FALSE;
+ AgAccount *ag_account = get_ag_account (mc_account);
+
+ toggle_mute ();
+
+ if(ag_account != NULL)
+ {
+ ag_account_delete (ag_account);
+ ag_account_store (ag_account, NULL, NULL);
+ g_object_unref (ag_account);
+ done = TRUE;
+ }
+
+ toggle_mute ();
+
+ return done;
+}
+
+gboolean
+libaccounts_exists (const gchar *mc_account)
+{
+ gboolean exists = FALSE;
+ AgAccount *ag_account = get_ag_account (mc_account);
+
+ toggle_mute ();
+
+ if (ag_account != NULL)
+ {
+ exists = TRUE;
+ g_object_unref (ag_account);
+ }
+
+ toggle_mute ();
+
+ return exists;
+}
+
+GStrv
+libaccounts_list (void)
+{
+ AgManager *ag_manager = get_ag_manager ();
+ GList *ag_ids = ag_manager_list_by_service_type (ag_manager, "IM");
+ guint len = g_list_length (ag_ids);
+ GStrv rval = NULL;
+ GList *id;
+ guint i = 0;
+ Setting *setting = setting_data (MC_IDENTITY_KEY, SETTING_AG);
+
+ if (len == 0)
+ goto done;
+
+ rval = g_new (gchar *, len + 1);
+ rval[len] = NULL;
+
+ for (id = ag_ids; id && i < len; id = g_list_next (id))
+ {
+ GValue value = { 0 };
+ AgAccountId uid = GPOINTER_TO_UINT (id->data);
+ AgAccount *ag_account = ag_manager_get_account (ag_manager, uid);
+ AgSettingSource source = AG_SETTING_SOURCE_NONE;
+
+ if (ag_account)
+ {
+ if (setting->global)
+ ag_account_select_service (ag_account, NULL);
+ else
+ _ag_account_select_default_im_service (ag_account);
+
+ source = ag_account_get_value (ag_account, setting->ag_name, &value);
+ }
+
+ if (source != AG_SETTING_SOURCE_NONE)
+ {
+ rval[i++] = _gvalue_to_string (&value);
+ g_value_unset (&value);
+ }
+ else
+ {
+ GValue cmanager = { 0 };
+ GValue protocol = { 0 };
+ GValue account = { 0 };
+ const gchar *acct = NULL;
+ const gchar *cman = NULL;
+ const gchar *proto = NULL;
+
+ g_value_init (&cmanager, G_TYPE_STRING);
+ g_value_init (&protocol, G_TYPE_STRING);
+ g_value_init (&account, G_TYPE_STRING);
+
+ _ag_account_select_default_im_service (ag_account);
+
+ ag_account_get_value (ag_account, MC_CMANAGER_KEY, &cmanager);
+ cman = g_value_get_string (&cmanager);
+
+ ag_account_get_value (ag_account, MC_PROTOCOL_KEY, &protocol);
+ proto = g_value_get_string (&protocol);
+
+ ag_account_select_service (ag_account, NULL);
+ ag_account_get_value (ag_account, AG_ACCOUNT_KEY, &account);
+ acct = g_value_get_string (&account);
+
+ rval[i++] = g_strdup_printf ("unnamed account #%u (%s/%s/%s)",
+ uid, cman, proto, acct);
+
+ g_value_unset (&cmanager);
+ g_value_unset (&protocol);
+ g_value_unset (&account);
+ }
+ }
+
+ done:
+ g_list_free (ag_ids);
+ clear_setting_data (setting);
+
+ return rval;
+}
diff --git a/tests/account-store-libaccounts.h b/tests/account-store-libaccounts.h
new file mode 100644
index 00000000..cb2352b1
--- /dev/null
+++ b/tests/account-store-libaccounts.h
@@ -0,0 +1,38 @@
+/*
+ * MC account storage backend inspector, libaccounts backend
+ *
+ * Copyright © 2010 Nokia Corporation
+ * Copyright © 2010 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
+ */
+
+#ifndef _ACCOUNT_STORE_LIBACCOUNTS_H_
+#define _ACCOUNT_STORE_LIBACCOUNTS_H_
+
+gchar * libaccounts_get (const gchar *mc_account,
+ const gchar *key);
+
+gboolean libaccounts_set (const gchar *mc_account,
+ const gchar *key,
+ const gchar *value);
+
+gboolean libaccounts_delete (const gchar *mc_account);
+
+gboolean libaccounts_exists (const gchar *mc_account);
+
+GStrv libaccounts_list (void);
+
+#endif
diff --git a/tests/account-store.c b/tests/account-store.c
new file mode 100644
index 00000000..5483527a
--- /dev/null
+++ b/tests/account-store.c
@@ -0,0 +1,274 @@
+/*
+ * MC account storage backend inspector
+ *
+ * Copyright © 2010 Nokia Corporation
+ * Copyright © 2010 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
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include "account-store-default.h"
+
+#define DOCSTRING_A \
+ "%s OP BACKEND ACCOUNT [KEY [VALUE]]\n\n" \
+ " OP := <get | set | del | has | list>\n" \
+ " BACKEND := <"
+
+#define DOCSTRING_B \
+ ">\n" \
+ " ACCOUNT := <MANAGER>/<PROTOCOL>/<ACCOUNT-UID>\n" \
+ " KEY := <manager | protocol | DisplayName | param-<PARAMETER>>\n" \
+ " VALUE := <STRING>\n\n"
+
+#if ENABLE_LIBACCOUNTS_SSO
+#include "account-store-libaccounts.h"
+#endif
+
+typedef struct {
+ const gchar *name;
+ gchar * (*get) (const gchar *account, const gchar *key);
+ gboolean (*set) (const gchar *account, const gchar *key, const gchar *value);
+ gboolean (*delete) (const gchar *account);
+ gboolean (*exists) (const gchar *account);
+ GStrv (*list) (void);
+} Backend;
+
+typedef enum {
+ OP_UNKNOWN,
+ OP_GET,
+ OP_SET,
+ OP_DELETE,
+ OP_EXISTS,
+ OP_LIST,
+} Operation;
+
+const Backend backends[] = {
+ { "default",
+ default_get,
+ default_set,
+ default_delete,
+ default_exists,
+ default_list, },
+
+#if ENABLE_LIBACCOUNTS_SSO
+ { "libaccounts",
+ libaccounts_get,
+ libaccounts_set,
+ libaccounts_delete,
+ libaccounts_exists,
+ libaccounts_list, },
+#endif
+
+ { NULL }
+};
+
+static void usage (const gchar *name, const gchar *fmt,
+ ...) G_GNUC_NORETURN;
+
+#if ENABLE_GNOME_KEYRING
+#include <gnome-keyring.h>
+
+static void
+setup_default_keyring (void)
+{
+ GnomeKeyringResult result;
+
+ g_debug ("Setting default keyring to: %s", g_getenv ("MC_KEYRING_NAME"));
+
+ if (g_getenv ("MC_KEYRING_NAME") != NULL)
+ {
+ const gchar *keyring_name = g_getenv ("MC_KEYRING_NAME");
+
+ g_debug ("MC Keyring name: %s", keyring_name);
+
+ if ((result = gnome_keyring_set_default_keyring_sync (keyring_name)) ==
+ GNOME_KEYRING_RESULT_OK)
+ {
+ g_debug ("Successfully set up temporary keyring %s for tests",
+ keyring_name);
+ }
+ else
+ {
+ g_warning ("Failed to set %s as the default keyring: %s",
+ keyring_name, gnome_keyring_result_to_message (result));
+ }
+ }
+}
+#endif
+
+int main (int argc, char **argv)
+{
+ int i;
+ const gchar *op_name = NULL;
+ const gchar *backend = NULL;
+ const gchar *account = NULL;
+ const gchar *setting = NULL;
+ const gchar *value = NULL;
+ const Backend *store = NULL;
+ Operation op = OP_UNKNOWN;
+ gchar *output = NULL;
+ gboolean success = FALSE;
+
+ g_type_init ();
+ g_set_application_name (argv[0]);
+
+#if ENABLE_GNOME_KEYRING
+ setup_default_keyring ();
+#endif
+
+ if (argc < 3)
+ usage (argv[0], "");
+
+ op_name = argv[1];
+ backend = argv[2];
+
+ for (i = 0; backends[i].name != NULL; i++)
+ {
+ if (g_str_equal (backends[i].name, backend))
+ {
+ store = &backends[i];
+ break;
+ }
+ }
+
+ if (store == NULL)
+ usage (argv[0], "No such backend %s", backend);
+
+ if (g_str_equal (op_name, "get"))
+ op = OP_GET;
+ else if (g_str_equal (op_name, "set"))
+ op = OP_SET;
+ else if (g_str_equal (op_name, "del"))
+ op = OP_DELETE;
+ else if (g_str_equal (op_name, "has"))
+ op = OP_EXISTS;
+ else if (g_str_equal (op_name, "list"))
+ op = OP_LIST;
+
+ switch (op)
+ {
+ case OP_SET:
+
+ if (argc >= 6)
+ value = argv[5];
+
+ case OP_GET:
+
+ if (argc < 5)
+ usage (argv[0], "op '%s' requires an account and key", op_name);
+
+ account = argv[3];
+ setting = argv[4];
+
+ if (account == NULL || *account == '\0')
+ usage (argv[0], "op '%s' requires an account", op_name);
+
+ if (setting == NULL || *setting == '\0')
+ usage (argv[0], "op '%s' requires a key", op_name);
+
+ break;
+
+ case OP_DELETE:
+ case OP_EXISTS:
+
+ if (argc < 4)
+ usage (argv[0], "op '%s' requires an account", op_name);
+
+ account = argv[3];
+ break;
+
+ case OP_LIST:
+ if (argc < 3)
+ usage (argv[0], "op '%s' requires an backend", op_name);
+ break;
+
+ case OP_UNKNOWN:
+ usage (argv[0], "Unknown operation: %s", op_name);
+ }
+
+ /* if we got this far, we have all the args we need: */
+ switch (op)
+ {
+ GStrv list;
+
+ case OP_GET:
+ output = store->get (account, setting);
+ success = output != NULL;
+ break;
+
+ case OP_SET:
+ success = store->set (account, setting, value);
+ output = g_strdup_printf ("%s.%s set to '%s' in %s",
+ account, setting, value, store->name);
+ break;
+
+ case OP_DELETE:
+ success = store->delete (account);
+ output = g_strdup_printf ("%s deleted from %s", account, store->name);
+ break;
+
+ case OP_EXISTS:
+ success = store->exists (account);
+ if (success)
+ output = g_strdup_printf ("Exists in %s", store->name);
+ break;
+
+ case OP_LIST:
+ list = store->list ();
+ output = g_strjoinv ("\n", list);
+ g_strfreev (list);
+ break;
+
+ default:
+ output = g_strdup ("Unknown operation");
+ }
+
+ if (output != NULL)
+ printf ("%s\n", output);
+
+ g_free (output);
+
+ return success ? 0 : 1;
+}
+
+static void
+usage (const gchar *name, const gchar *fmt, ...)
+{
+ guint i;
+ va_list ap;
+
+ fprintf (stderr, DOCSTRING_A, name);
+
+ fprintf (stderr, "%s", backends[0].name);
+
+ for (i = 1; backends[i].name != NULL; i++)
+ fprintf (stderr, " | %s", backends[i].name);
+
+ fprintf (stderr, DOCSTRING_B);
+
+ va_start (ap, fmt);
+ vfprintf (stderr, fmt, ap);
+ va_end (ap);
+
+ exit (1);
+}
diff --git a/tests/keyring-command.c b/tests/keyring-command.c
new file mode 100644
index 00000000..640f448a
--- /dev/null
+++ b/tests/keyring-command.c
@@ -0,0 +1,172 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 8 -*- */
+/*
+ * This file is part of mission-control
+ *
+ * Copyright (C) 2007-2010 Nokia Corporation
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * Contact: Naba Kumar <naba.kumar@nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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
+ *
+ */
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib-object.h>
+#include <gnome-keyring.h>
+
+
+static gboolean
+create_keyring (gchar *keyring_name)
+{
+ GnomeKeyringResult result;
+
+ result = gnome_keyring_create_sync (keyring_name, "");
+
+ if (result == GNOME_KEYRING_RESULT_OK)
+ return TRUE;
+
+ g_warning ("Failed to create keyring %s: %s", keyring_name,
+ gnome_keyring_result_to_message (result));
+
+ return FALSE;
+}
+
+static gchar *
+create_random_keyring (void)
+{
+ gchar *keyring_name = NULL;
+ GnomeKeyringResult result;
+
+ while (TRUE)
+ {
+ keyring_name = g_strdup_printf ("mc-test-%u", g_random_int ());
+
+ result = gnome_keyring_create_sync (keyring_name, "");
+
+ if (result == GNOME_KEYRING_RESULT_OK)
+ {
+ return keyring_name;
+ }
+ else if (result == GNOME_KEYRING_RESULT_KEYRING_ALREADY_EXISTS)
+ {
+ g_free (keyring_name);
+ keyring_name = NULL;
+ continue;
+ }
+ else
+ {
+ g_warning ("Failed to create keyring %s: %s", keyring_name,
+ gnome_keyring_result_to_message (result));
+ g_free (keyring_name);
+ return NULL;
+ }
+ }
+
+ g_assert_not_reached ();
+}
+
+static gboolean
+remove_keyring (gchar *keyring_name)
+{
+ GnomeKeyringResult result;
+
+ result = gnome_keyring_delete_sync (keyring_name);
+
+ if (result == GNOME_KEYRING_RESULT_OK)
+ {
+ return TRUE;
+ }
+ else
+ {
+ g_warning ("Failed to remove keyring %s: %s", keyring_name,
+ gnome_keyring_result_to_message (result));
+ return FALSE;
+ }
+}
+
+static void
+show_help (gchar *name)
+{
+ g_printf ("%s - utility for creating and removing gnome keyrings\n", name);
+ g_printf ("Usage: %s create [KEYRING]\n", name);
+ g_printf (" %s remove KEYRING\n", name);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_type_init ();
+
+ if (argc < 2)
+ {
+ show_help (argv[0]);
+ return 0;
+ }
+
+ if (!g_strcmp0 (argv[1], "create"))
+ {
+ if (argc < 3)
+ {
+ gchar *keyring_name = create_random_keyring ();
+
+ if (keyring_name)
+ {
+ g_printf("%s\n", keyring_name);
+ g_free (keyring_name);
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ else
+ {
+ if (create_keyring (argv[2]))
+ {
+ g_printf("%s\n", argv[2]);
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ }
+
+ if (!g_strcmp0 (argv[1], "remove"))
+ {
+ if (argc < 3)
+ {
+ show_help (argv[0]);
+ return -1;
+ }
+
+ if (remove_keyring (argv[2]))
+ {
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ show_help (argv[0]);
+ return -1;
+}
+
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
new file mode 100644
index 00000000..fd2d610b
--- /dev/null
+++ b/tests/twisted/Makefile.am
@@ -0,0 +1,295 @@
+TWISTED_TESTS =
+
+TWISTED_BASIC_TESTS = \
+ account/addressing.py \
+ account-manager/account-basics.py \
+ account-manager/bad-cm.py \
+ account-manager/avatar.py \
+ account-manager/create-auto-connect.py \
+ account-manager/create-twice.py \
+ account-manager/create-with-properties.py \
+ account-manager/enable.py \
+ account-manager/enable-auto-connect.py \
+ account-manager/nickname.py \
+ account-manager/service.py \
+ account-manager/param-types.py \
+ account-manager/presence.py \
+ account-manager/reconnect.py \
+ account-manager/recover-from-disconnect.py \
+ account-manager/request-online.py \
+ account-manager/update-parameters.py \
+ account-requests/cancel.py \
+ account-requests/create-text.py \
+ account-requests/delete-account-during-request.py \
+ capabilities/draft-1.py \
+ capabilities/contact-caps.py \
+ capabilities/legacy-caps.py \
+ dispatcher/approver-fails.py \
+ dispatcher/already-has-channel.py \
+ dispatcher/already-has-obsolete.py \
+ dispatcher/bypass-approval.py \
+ dispatcher/bypass-observers.py \
+ dispatcher/cancel.py \
+ dispatcher/capture-bundle.py \
+ dispatcher/connect-for-request.py \
+ dispatcher/create-handler-fails.py \
+ dispatcher/create-hints.py \
+ dispatcher/create-no-preferred-handler.py \
+ dispatcher/create-delayed-by-mini-plugin.py \
+ dispatcher/create-rejected-by-mini-plugin.py \
+ dispatcher/create-text.py \
+ dispatcher/created-behind-our-back.py \
+ dispatcher/dispatch-activatable.py \
+ dispatcher/dispatch-before-connected.py \
+ dispatcher/dispatch-delayed-by-mini-plugin.py \
+ dispatcher/dispatch-obsolete.py \
+ dispatcher/dispatch-rejected-by-mini-plugin.py \
+ dispatcher/dispatch-text.py \
+ dispatcher/ensure-and-redispatch.py \
+ dispatcher/ensure-is-approval.py \
+ dispatcher/ensure-rapidly.py \
+ dispatcher/exploding-bundles.py \
+ dispatcher/fdo-21034.py \
+ dispatcher/handle-channels-fails.py \
+ dispatcher/lose-text.py \
+ dispatcher/recover-from-disconnect.py \
+ dispatcher/request-disabled-account.py \
+ dispatcher/respawn-observers.py \
+ dispatcher/respawn-activatable-observers.py \
+ dispatcher/undispatchable.py \
+ dispatcher/vanishing-client.py
+
+if ENABLE_MCD_PLUGINS
+TWISTED_BASIC_TESTS += \
+ dispatcher/create-delayed-by-plugin.py \
+ dispatcher/dispatch-delayed-by-plugin.py \
+ dispatcher/dispatch-rejected-by-plugin.py
+endif
+
+# Tests that aren't to be run if we're running the real (installed) MC,
+# because they rely on special behaviour of mc-debug-server.
+#
+# For simplicity, these are also separate tests: at least
+# account-storage/*.py need their own instances.
+TWISTED_SPECIAL_BUILD_TESTS = \
+ account-manager/auto-away.py \
+ account-storage/default-keyring-storage.py \
+ account-storage/diverted-storage.py
+
+TWISTED_SLOW_TESTS = \
+ account-manager/server-drops-us.py
+
+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 \
+ crash-recovery/crash-recovery.py \
+ dispatcher/create-at-startup.py
+
+TWISTED_SEPARATE_TESTS += $(TWISTED_SPECIAL_BUILD_TESTS)
+
+if HAVE_MCE
+HAVE_MCE_PYBOOL = True
+else
+HAVE_MCE_PYBOOL = False
+endif
+
+config.py: Makefile
+ $(AM_V_GEN) { \
+ echo "HAVE_MCE = $(HAVE_MCE_PYBOOL)"; \
+ } > $@
+
+BUILT_SOURCES = config.py
+
+AM_CFLAGS = $(ERROR_CFLAGS)
+
+# A demo dispatcher plugin (new, minimal API)
+noinst_LTLIBRARIES = mcp-plugin.la mcp-account-diversion.la mcp-dbus-caller-permission.la
+mcp_plugin_la_SOURCES = mcp-plugin.c
+# these runes are necessary to make libtool build a dlopen()able shared
+# library even though it's not going to be installed - the default for noinst
+# libraries is static
+mcp_plugin_la_LDFLAGS = -module -shared -avoid-version -rpath @abs_builddir@
+
+mcp_account_diversion_la_SOURCES = mcp-account-diversion.c
+mcp_account_diversion_la_LDFLAGS = $(mcp_plugin_la_LDFLAGS)
+
+mcp_dbus_caller_permission_la_SOURCES = mcp-dbus-caller-permission.c
+mcp_dbus_caller_permission_la_LDFLAGS = $(mcp_plugin_la_LDFLAGS)
+
+if ENABLE_MCD_PLUGINS
+# A demo dispatcher plugin (old API, with access to MCD internals)
+noinst_LTLIBRARIES += test-plugin.la
+test_plugin_la_SOURCES = test-plugin.c
+test_plugin_la_LDFLAGS = -module -shared -avoid-version -rpath @abs_builddir@
+endif
+
+# A debug version of the normal MC executable, which exits cleanly on
+# disconnection from D-Bus (so gcov info gets written out)
+noinst_PROGRAMS = mc-debug-server
+mc_debug_server_SOURCES = mc-debug-server.c
+mc_debug_server_LDADD = \
+ $(top_builddir)/src/libmissioncontrol-server.la \
+ $(top_builddir)/src/libmcd-convenience.la
+
+INCLUDES = \
+ -I$(top_srcdir) -I$(top_builddir) \
+ -I$(top_srcdir)/src -I$(top_builddir)/src \
+ $(DBUS_CFLAGS) \
+ $(TELEPATHY_CFLAGS) \
+ -DMC_DISABLE_DEPRECATED \
+ -DLIBDIR="@libdir@" -DLIBVERSION="0"
+
+if ENABLE_GNOME_KEYRING
+INCLUDES += $(GNOME_KEYRING_CFLAGS)
+endif
+
+TESTS =
+
+TMPSUFFIX = foo
+
+BASIC_TESTS_ENVIRONMENT = \
+ PYTHONPATH=@abs_top_srcdir@/tests/twisted:@abs_top_builddir@/tests/twisted \
+ MC_DEBUG=all \
+ MC_FILTER_PLUGIN_DIR=@abs_top_builddir@/tests/twisted/.libs \
+ MC_CHANDLERS_DIR=@abs_top_srcdir@/tests/twisted/chandlers \
+ MC_ACCOUNT_DIR=@abs_top_builddir@/tests/twisted/tmp-$(TMPSUFFIX) \
+ XDG_CONFIG_HOME=@abs_top_builddir@/tests/twisted/tmp-$(TMPSUFFIX) \
+ XDG_CONFIG_DIRS=@abs_top_srcdir@/tests/twisted \
+ XDG_DATA_HOME=@abs_top_builddir@/tests/twisted/tmp-$(TMPSUFFIX) \
+ XDG_DATA_DIRS=@abs_top_srcdir@/tests/twisted \
+ XDG_CACHE_DIR=@abs_top_builddir@/tests/twisted/tmp-$(TMPSUFFIX) \
+ XDG_CACHE_HOME=@abs_top_builddir@/tests/twisted/tmp-$(TMPSUFFIX) \
+ MC_CLIENTS_DIR=@abs_top_srcdir@/tests/twisted/telepathy/clients \
+ MC_MANAGER_DIR=@abs_top_srcdir@/tests/twisted/telepathy/managers \
+ G_DEBUG=fatal_criticals
+
+if ENABLE_GNOME_KEYRING
+BASIC_TESTS_ENVIRONMENT += MC_TEST_GNOME_KEYRING=1
+endif
+
+if ENABLE_LIBACCOUNTS_SSO
+BASIC_TESTS_ENVIRONMENT += AG_DEBUG=all \
+ ACCOUNTS=@abs_top_builddir@/tests/twisted/tmp-$(TMPSUFFIX) \
+ AG_SERVICES=@abs_top_builddir@/tests/twisted/tmp-$(TMPSUFFIX)
+endif
+
+WITH_SESSION_BUS = \
+ sh $(srcdir)/tools/with-session-bus.sh \
+ --config-file=tools/tmp-session-bus.conf --
+
+COMBINED_TESTS_ENVIRONMENT = \
+ $(BASIC_TESTS_ENVIRONMENT) \
+ $(PYTHON)
+
+SEPARATE_TESTS_ENVIRONMENT = \
+ $(BASIC_TESTS_ENVIRONMENT) \
+ $(WITH_SESSION_BUS) \
+ $(PYTHON)
+
+if WANT_TWISTED_TESTS
+check-local: check-twisted
+
+if HAVE_SERVER
+
+installcheck-local: installcheck-twisted
+installcheck-twisted:
+ $(MAKE) check-twisted \
+ TWISTED_SPECIAL_BUILD_TESTS= \
+ MC_EXECUTABLE=@libexecdir@/mission-control-5
+
+endif # HAVE_SERVER
+
+endif # WANT_TWISTED_TESTS
+
+check-twisted:
+ $(MAKE) -C tools
+ rm -f tools/core
+ rm -f tools/missioncontrol-*.log
+ mkdir tmp-$$$$ && { \
+ $(MAKE) check-combined TMPSUFFIX=$$$$; \
+ e=$$?; \
+ rm -rf tmp-$$$$; \
+ exit $$e; }
+ for TESTS in $(TWISTED_SEPARATE_TESTS); do export TESTS; ( \
+ rm -rf tmp-$$$$ && mkdir tmp-$$$$ && { \
+ e=0; \
+ $(MAKE) check-separate \
+ TMPSUFFIX=$$$$ \
+ TESTS=$$TESTS \
+ || e=$$?; \
+ rm -rf tmp-$$$$; \
+ }; exit $$e ) || exit $$?; done
+ @if test -e tools/core; then\
+ echo "Core dump exists: tools/core";\
+ exit 1;\
+ fi
+
+check-torture:
+ mkdir tmp-$$$$ && { \
+ env $(BASIC_TESTS_ENVIRONMENT) $(WITH_SESSION_BUS) \
+ $(MAKE) _check-torture TMPSUFFIX=$$$$; \
+ e=$$?; \
+ rm -rf tmp-$$$$; \
+ exit $$e; }
+
+TORTURE_REPEATS = 100
+
+_check-torture:
+ for i in `seq 1 $(TORTURE_REPEATS)`; do \
+ $(MAKE) check-TESTS \
+ TESTS='$$(TWISTED_BASIC_TESTS) $$(TWISTED_SLOW_TESTS)'\
+ TESTS_ENVIRONMENT='$$(COMBINED_TESTS_ENVIRONMENT)'; \
+ e=$$?; \
+ test z$$e = z0 || break; \
+ done
+
+CHECK_TWISTED_SLOW =
+
+check-combined:
+ if test x$(CHECK_TWISTED_SLOW) = x; then \
+ extra_tests= ; \
+ else \
+ extra_tests=' $$(TWISTED_SLOW_TESTS)'; \
+ fi; \
+ env $(BASIC_TESTS_ENVIRONMENT) $(WITH_SESSION_BUS) \
+ $(MAKE) check-TESTS \
+ TESTS='$$(TWISTED_BASIC_TESTS)'"$${extra_tests}" \
+ TESTS_ENVIRONMENT='$$(COMBINED_TESTS_ENVIRONMENT)'
+
+check-separate:
+ echo "Running $(TESTS) in tmp-$(TMPSUFFIX)"
+ $(MAKE) check-TESTS \
+ TESTS_ENVIRONMENT='$$(SEPARATE_TESTS_ENVIRONMENT)'
+
+EXTRA_DIST = \
+ $(TWISTED_BASIC_TESTS) \
+ $(TWISTED_SEPARATE_TESTS) \
+ accounts/README \
+ chandlers/README \
+ telepathy/clients/README \
+ telepathy/clients/AbiWord.client \
+ telepathy/clients/Logger.client \
+ telepathy/managers/fakecm.manager \
+ telepathy/managers/onewitheverything.manager \
+ telepathy/managers/README \
+ constants.py \
+ fakeclient.py \
+ fakecm.py \
+ mctest.py \
+ servicetest.py
+
+CLEANFILES = \
+ accounts/accounts.cfg \
+ accounts/.mc_connections \
+ mc-[1-9]*.log \
+ *.pyc \
+ */*.pyc \
+ with-session-bus-*.dbus-monitor-logs \
+ config.py
+
+check_misc_sources = $(TESTS)
+
+SUBDIRS = tools
diff --git a/tests/twisted/README b/tests/twisted/README
new file mode 100644
index 00000000..56f664a8
--- /dev/null
+++ b/tests/twisted/README
@@ -0,0 +1,34 @@
+To run Twisted tests:
+
+ make -C tests/twisted check-twisted
+
+To run an individual Twisted test:
+
+ make -C tests/twisted check-twisted \
+ TWISTED_BASIC_TESTS= \
+ TWISTED_SEPARATE_TESTS=dispatcher/create-at-startup.py
+
+To run with debug information:
+
+ make -C tests/twisted check-twisted \
+ TWISTED_BASIC_TESTS= \
+ TWISTED_SEPARATE_TESTS=dispatcher/create-at-startup.py \
+ CHECK_TWISTED_VERBOSE=1
+
+To debug an individual test you can set one of the following env variable:
+
+ * MISSIONCONTROL_TEST_VALGRIND : to run Mission Control inside valgrind. The
+ report is added to tools/missioncontrol-testing.log.
+
+ * MISSIONCONTROL_TEST_REFDBG : to run Mission Control inside refdbg. The
+ report is written to tools/refdbg.log. You can change
+ MISSIONCONTROL_WRAPPER to use an alternative refdbg and change
+ REFDBG_OPTIONS to set your own parameters. Example:
+ export MISSIONCONTROL_TEST_REFDBG=1
+ export MISSIONCONTROL_WRAPPER="/path/to/refdbg"
+ export REFDBG_OPTIONS="btnum=16"
+
+ * MISSIONCONTROL_WRAPPER="nemiver" : to run Mission Control inside the
+ graphical debugger nemiver. You'll be able to set up breakpoints; then hit
+ the "continue" button to launch Mission Control.
+
diff --git a/tests/twisted/account-manager/account-basics.py b/tests/twisted/account-manager/account-basics.py
new file mode 100644
index 00000000..51328ce9
--- /dev/null
+++ b/tests/twisted/account-manager/account-basics.py
@@ -0,0 +1,248 @@
+# 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
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account, get_account_manager
+import constants as cs
+
+def test(q, bus, mc):
+ # Get the AccountManager interface
+ account_manager = get_account_manager(bus)
+ account_manager_iface = dbus.Interface(account_manager, cs.AM)
+
+ # Introspect AccountManager for debugging purpose
+ account_manager_introspected = account_manager.Introspect(
+ dbus_interface=cs.INTROSPECTABLE_IFACE)
+ #print account_manager_introspected
+
+ # Check AccountManager has D-Bus property interface
+ properties = account_manager.GetAll(cs.AM,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('ValidAccounts') == [], \
+ properties.get('ValidAccounts')
+ assert properties.get('InvalidAccounts') == [], \
+ properties.get('InvalidAccounts')
+ interfaces = properties.get('Interfaces')
+
+ # assert that current functionality exists
+ assert cs.AM_IFACE_NOKIA_QUERY in interfaces, interfaces
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_path = account.__dbus_object_path__
+
+ # Check the account is correctly created
+ properties = account_manager.GetAll(cs.AM,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('ValidAccounts') == [account_path], properties
+ account_path = properties['ValidAccounts'][0]
+ assert isinstance(account_path, dbus.ObjectPath), repr(account_path)
+ assert properties.get('InvalidAccounts') == [], properties
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+ # Introspect Account for debugging purpose
+ account_introspected = account.Introspect(
+ dbus_interface=cs.INTROSPECTABLE_IFACE)
+ #print account_introspected
+
+ # Check Account has D-Bus property interface
+ properties = account_props.GetAll(cs.ACCOUNT)
+ assert properties is not None
+
+ assert properties.get('DisplayName') == 'fakeaccount', \
+ properties.get('DisplayName')
+ assert properties.get('Icon') == '', properties.get('Icon')
+ assert properties.get('Valid') == True, properties.get('Valid')
+ assert properties.get('Enabled') == False, properties.get('Enabled')
+ #assert properties.get('Nickname') == 'fakenick', properties.get('Nickname')
+ assert properties.get('Parameters') == params, properties.get('Parameters')
+ assert properties.get('Connection') == '/', properties.get('Connection')
+ assert properties.get('NormalizedName') == '', \
+ properties.get('NormalizedName')
+
+ interfaces = properties.get('Interfaces')
+ assert cs.ACCOUNT_IFACE_AVATAR in interfaces, interfaces
+ assert cs.ACCOUNT_IFACE_NOKIA_COMPAT in interfaces, interfaces
+ assert cs.ACCOUNT_IFACE_NOKIA_CONDITIONS in interfaces, interfaces
+
+ # sanity check
+ for k in properties:
+ assert account_props.Get(cs.ACCOUNT, k) == properties[k], k
+
+ # Alter some miscellaneous r/w properties
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'DisplayName',
+ 'Work account')
+ q.expect_many(
+ EventPattern('dbus-signal',
+ path=account_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT,
+ args=[{'DisplayName': 'Work account'}]),
+ EventPattern('dbus-return', method='Set'),
+ )
+ assert account_props.Get(cs.ACCOUNT, 'DisplayName') == 'Work account'
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Icon', 'im-jabber')
+ q.expect_many(
+ EventPattern('dbus-signal',
+ path=account_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT,
+ args=[{'Icon': 'im-jabber'}]),
+ EventPattern('dbus-return', method='Set'),
+ )
+ assert account_props.Get(cs.ACCOUNT, 'Icon') == 'im-jabber'
+
+ assert account_props.Get(cs.ACCOUNT, 'HasBeenOnline') == False
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Nickname', 'Joe Bloggs')
+ q.expect_many(
+ EventPattern('dbus-signal',
+ path=account_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT,
+ args=[{'Nickname': 'Joe Bloggs'}]),
+ EventPattern('dbus-return', method='Set'),
+ )
+ assert account_props.Get(cs.ACCOUNT, 'Nickname') == 'Joe Bloggs'
+
+ call_async(q, dbus.Interface(account, cs.ACCOUNT_IFACE_NOKIA_COMPAT),
+ 'SetHasBeenOnline')
+ q.expect_many(
+ EventPattern('dbus-signal',
+ path=account_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT,
+ args=[{'HasBeenOnline': True}]),
+ EventPattern('dbus-return', method='SetHasBeenOnline'),
+ )
+ assert account_props.Get(cs.ACCOUNT, 'HasBeenOnline') == True
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT_IFACE_NOKIA_COMPAT,
+ 'SecondaryVCardFields',
+ ['x-badger', 'x-mushroom'])
+ # there's no change notification for the Compat properties
+ q.expect_many(
+ EventPattern('dbus-return', method='Set'),
+ )
+ assert account_props.Get(cs.ACCOUNT_IFACE_NOKIA_COMPAT,
+ 'SecondaryVCardFields') == ['x-badger', 'x-mushroom']
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT_IFACE_NOKIA_CONDITIONS,
+ 'Condition',
+ dbus.Dictionary({':foo': 'bar'}, signature='ss'))
+ # there's no change notification for the Condition
+ q.expect_many(
+ EventPattern('dbus-return', method='Set'),
+ )
+ assert account_props.Get(cs.ACCOUNT_IFACE_NOKIA_CONDITIONS,
+ 'Condition') == {':foo': 'bar'}
+
+ # Set some properties to invalidly typed values - this currently succeeds
+ # but is a no-op, although in future it should change to raising an
+ # exception
+
+ # this variable's D-Bus type must differ from the types of all known
+ # properties
+ badly_typed = dbus.Struct(('wrongly typed',), signature='s')
+
+ for p in ('DisplayName', 'Icon', 'Enabled', 'Nickname',
+ 'AutomaticPresence', 'ConnectAutomatically', 'RequestedPresence'):
+ try:
+ account_props.Set(cs.ACCOUNT, p, badly_typed)
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == cs.INVALID_ARGUMENT, \
+ (p, e.get_dbus_name())
+ else:
+ raise AssertionError('Setting %s with wrong type should fail' % p)
+
+ for p in ('Avatar',):
+ try:
+ account_props.Set(cs.ACCOUNT_IFACE_AVATAR, p, badly_typed)
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == cs.INVALID_ARGUMENT, \
+ (p, e.get_dbus_name())
+ else:
+ raise AssertionError('Setting %s with wrong type should fail' % p)
+
+ for p in ('Profile', 'SecondaryVCardFields'):
+ try:
+ account_props.Set(cs.ACCOUNT_IFACE_NOKIA_COMPAT, p, badly_typed)
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == cs.INVALID_ARGUMENT, \
+ (p, e.get_dbus_name())
+ else:
+ raise AssertionError('Setting %s with wrong type should fail' % p)
+
+ for p in ('Condition',):
+ try:
+ account_props.Set(cs.ACCOUNT_IFACE_NOKIA_CONDITIONS, p,
+ badly_typed)
+ except dbus.DBusException, e:
+ assert e.get_dbus_name() == cs.INVALID_ARGUMENT, \
+ (p, e.get_dbus_name())
+ else:
+ raise AssertionError('Setting %s with wrong type should fail' % p)
+
+ # Make sure MC hasn't crashed yet, and make sure some properties are what
+ # we expect them to be
+
+ properties = account_props.GetAll(cs.ACCOUNT)
+ assert properties['DisplayName'] == 'Work account'
+ assert properties['Icon'] == 'im-jabber'
+ properties = account_props.GetAll(cs.ACCOUNT_IFACE_AVATAR)
+ assert properties['Avatar'] == ([], '')
+ properties = account_props.GetAll(cs.ACCOUNT_IFACE_NOKIA_COMPAT)
+ assert properties['SecondaryVCardFields'] == ['x-badger', 'x-mushroom']
+
+ # Delete the account
+ assert account_iface.Remove() is None
+ account_event, account_manager_event = q.expect_many(
+ EventPattern('dbus-signal',
+ path=account_path,
+ signal='Removed',
+ interface=cs.ACCOUNT,
+ args=[]
+ ),
+ EventPattern('dbus-signal',
+ path=cs.AM_PATH,
+ signal='AccountRemoved',
+ interface=cs.AM,
+ args=[account_path]
+ ),
+ )
+
+ # Check the account is correctly deleted
+ properties = account_manager.GetAll(cs.AM,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('ValidAccounts') == [], properties
+ assert properties.get('InvalidAccounts') == [], properties
+
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/auto-away.py b/tests/twisted/account-manager/auto-away.py
new file mode 100644
index 00000000..6d2fc81b
--- /dev/null
+++ b/tests/twisted/account-manager/auto-away.py
@@ -0,0 +1,208 @@
+# 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
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix
+from mctest import exec_test, SimulatedConnection, create_fakecm_account,\
+ SimulatedChannel
+import constants as cs
+
+def test(q, bus, mc):
+ cm_name_ref = dbus.service.BusName(
+ tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ # Create an account
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ # The account is initially valid but disabled
+ assert not account.Get(cs.ACCOUNT, 'Enabled',
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert account.Get(cs.ACCOUNT, 'Valid',
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ # Enable the account
+ account.Set(cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect('dbus-signal',
+ path=account.object_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT)
+
+ assert account.Get(cs.ACCOUNT, 'Enabled',
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert account.Get(cs.ACCOUNT, 'Valid',
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ # Check the requested presence is offline
+ properties = account.GetAll(cs.ACCOUNT,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('RequestedPresence') == \
+ dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE),
+ 'offline', '')), \
+ properties.get('RequestedPresence')
+
+ # Go online
+ requested_presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_AVAILABLE),
+ dbus.String(u'available'), dbus.String(u'staring at the sea')))
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself', has_presence=True)
+ conn.statuses = dbus.Dictionary({
+ 'available': (cs.PRESENCE_TYPE_AVAILABLE, True, True),
+ 'away': (cs.PRESENCE_TYPE_AWAY, True, True),
+ 'busy': (cs.PRESENCE_TYPE_BUSY, True, True),
+ 'offline': (cs.PRESENCE_TYPE_OFFLINE, False, False),
+ }, signature='s(ubb)')
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+ conn.presence = dbus.Struct((cs.PRESENCE_TYPE_AVAILABLE, 'available', ''),
+ signature='uss')
+
+ # MC does some setup, including fetching the list of Channels
+
+ get_statuses = q.expect('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='Get',
+ args=[cs.CONN_IFACE_SIMPLE_PRESENCE, 'Statuses'],
+ path=conn.object_path, handled=True)
+
+ call, signal = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ args=['available', 'staring at the sea'],
+ handled=True),
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ predicate = lambda e: 'CurrentPresence' in e.args[0]),
+ )
+ assert signal.args[0]['CurrentPresence'] == (cs.PRESENCE_TYPE_AVAILABLE,
+ 'available', '')
+
+ e = q.expect('dbus-signal',
+ path=account.object_path,
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ predicate = lambda e: 'CurrentPresence' in e.args[0])
+ assert e.args[0]['CurrentPresence'] == requested_presence
+
+ # Check the requested presence is online
+ properties = account.GetAll(cs.ACCOUNT,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('RequestedPresence') == requested_presence, \
+ properties.get('RequestedPresence')
+
+ # This is normally a C API, only exposed to D-Bus here for testing
+ secret_debug_api = dbus.Interface(bus.get_object(cs.AM, "/"),
+ 'org.freedesktop.Telepathy.MissionControl5.RegressionTests')
+ MCD_SYSTEM_IDLE = 32
+
+ # Set the idle flag
+ secret_debug_api.ChangeSystemFlags(dbus.UInt32(MCD_SYSTEM_IDLE),
+ dbus.UInt32(0))
+
+ e = q.expect('dbus-method-call',
+ path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ args=['away', ''],
+ handled=True)
+
+ # Unset the idle flag
+ secret_debug_api.ChangeSystemFlags(dbus.UInt32(0),
+ dbus.UInt32(MCD_SYSTEM_IDLE))
+
+ # MC puts the account back online
+
+ e = q.expect('dbus-method-call',
+ path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ args=['available', 'staring at the sea'],
+ handled=True)
+
+ # Go to a non-Available status
+ requested_presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_BUSY),
+ dbus.String(u'busy'), dbus.String(u'in the great below')))
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ e = q.expect('dbus-method-call',
+ path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ args=['busy', 'in the great below'],
+ handled=True)
+
+ forbidden = [EventPattern('dbus-method-call',
+ path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence')]
+ q.forbid_events(forbidden)
+
+ # Set the idle flag
+ secret_debug_api.ChangeSystemFlags(dbus.UInt32(MCD_SYSTEM_IDLE),
+ dbus.UInt32(0))
+
+ # MC does not put the account away
+
+ # Unset the idle flag
+ secret_debug_api.ChangeSystemFlags(dbus.UInt32(0),
+ dbus.UInt32(MCD_SYSTEM_IDLE))
+
+ # MC does not put the account back online
+
+ q.unforbid_events(forbidden)
+
+ # Put the account offline
+ requested_presence = (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', '')
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ # In response, MC tells us to Disconnect, and we do
+ q.expect('dbus-method-call', method='Disconnect',
+ path=conn.object_path, handled=True)
+
+ properties = account.GetAll(cs.ACCOUNT, dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties['Connection'] == '/'
+ assert properties['ConnectionStatus'] == cs.CONN_STATUS_DISCONNECTED
+ assert properties['CurrentPresence'] == requested_presence
+ assert properties['RequestedPresence'] == requested_presence
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/auto-connect.py b/tests/twisted/account-manager/auto-connect.py
new file mode 100644
index 00000000..badcf7fc
--- /dev/null
+++ b/tests/twisted/account-manager/auto-connect.py
@@ -0,0 +1,175 @@
+# 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 automatically signing in and setting presence etc.
+"""
+
+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, \
+ make_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():
+
+ accounts_dir = os.environ['MC_ACCOUNT_DIR']
+
+ accounts_cfg = open(accounts_dir + '/accounts.cfg', 'w')
+
+ # As a regression test for part of fd.o #28557, the password starts and
+ # ends with a double backslash, which is represented in the file as a
+ # quadruple backslash.
+ accounts_cfg.write(r"""# Telepathy accounts
+[%s]
+manager=fakecm
+protocol=fakeprotocol
+DisplayName=Work account
+NormalizedName=jc.denton@unatco.int
+param-account=jc.denton@unatco.int
+param-password=\\\\ionstorm\\\\
+Enabled=1
+ConnectAutomatically=1
+AutomaticPresenceType=2
+AutomaticPresenceStatus=available
+AutomaticPresenceMessage=My vision is augmented
+Nickname=JC
+AvatarMime=image/jpeg
+""" % account_id)
+ accounts_cfg.close()
+
+ 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()
+
+def test(q, bus, unused):
+
+ expected_params = {
+ 'account': 'jc.denton@unatco.int',
+ 'password': r'\\ionstorm\\',
+ }
+
+ mc = make_mc(bus, q.append)
+
+ request_conn, prop_changed, _ = q.expect_many(
+ EventPattern('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),
+ EventPattern('dbus-signal', signal='AccountPropertyChanged',
+ predicate=(lambda e: 'ConnectionStatus' in e.args[0])),
+ EventPattern('dbus-signal', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.AM and e.args[2]),
+ )
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself', has_presence=True, has_aliasing=True, has_avatars=True)
+
+ assertEquals('/', prop_changed.args[0].get('Connection'))
+ assertEquals('', prop_changed.args[0].get('ConnectionError'))
+ assertEquals({}, prop_changed.args[0].get('ConnectionErrorDetails'))
+ assertEquals(cs.CONN_STATUS_CONNECTING,
+ prop_changed.args[0].get('ConnectionStatus'))
+ assertEquals(cs.CONN_STATUS_REASON_REQUESTED,
+ prop_changed.args[0].get('ConnectionStatusReason'))
+
+ q.dbus_return(request_conn.message, conn.bus_name, conn.object_path,
+ signature='so')
+
+ account_path = (cs.tp_path_prefix + '/Account/' + account_id)
+ account = bus.get_object(
+ cs.tp_name_prefix + '.AccountManager',
+ account_path)
+
+ prop_changed, _ = q.expect_many(
+ EventPattern('dbus-signal', signal='AccountPropertyChanged',
+ predicate=(lambda e: 'ConnectionStatus' in e.args[0])),
+ EventPattern('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True, interface=cs.CONN),
+ )
+
+ assertEquals(conn.object_path, prop_changed.args[0].get('Connection'))
+ assertEquals('', prop_changed.args[0].get('ConnectionError'))
+ assertEquals({}, prop_changed.args[0].get('ConnectionErrorDetails'))
+ assertEquals(cs.CONN_STATUS_CONNECTING,
+ prop_changed.args[0].get('ConnectionStatus'))
+ assertEquals(cs.CONN_STATUS_REASON_REQUESTED,
+ prop_changed.args[0].get('ConnectionStatusReason'))
+
+ props = account.GetAll(cs.ACCOUNT, dbus_interface=cs.PROPERTIES_IFACE)
+ assert props['Connection'] == conn.object_path
+ assert props['ConnectionStatus'] == cs.CONN_STATUS_CONNECTING
+ assert props['ConnectionStatusReason'] == cs.CONN_STATUS_REASON_REQUESTED
+
+ print "becoming connected"
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ set_aliases, set_presence, set_avatar, prop_changed = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_ALIASING, method='SetAliases',
+ args=[{ conn.self_handle: 'JC' }],
+ handled=False),
+ EventPattern('dbus-method-call', path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ handled=True),
+ 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),
+ ),
+ )
+
+ assertEquals(conn.object_path, prop_changed.args[0].get('Connection'))
+ assertEquals('', prop_changed.args[0].get('ConnectionError'))
+ assertEquals({}, prop_changed.args[0].get('ConnectionErrorDetails'))
+ assertEquals(cs.CONN_STATUS_CONNECTED,
+ prop_changed.args[0].get('ConnectionStatus'))
+ assertEquals(cs.CONN_STATUS_REASON_REQUESTED,
+ prop_changed.args[0].get('ConnectionStatusReason'))
+
+ assert account.Get(cs.ACCOUNT, 'CurrentPresence',
+ dbus_interface=cs.PROPERTIES_IFACE) == (cs.PRESENCE_TYPE_AVAILABLE,
+ 'available', 'My vision is augmented')
+
+ q.dbus_return(set_aliases.message, signature='')
+
+if __name__ == '__main__':
+ preseed()
+ exec_test(test, {}, preload_mc=False)
diff --git a/tests/twisted/account-manager/avatar-persist.py b/tests/twisted/account-manager/avatar-persist.py
new file mode 100644
index 00000000..e1334435
--- /dev/null
+++ b/tests/twisted/account-manager/avatar-persist.py
@@ -0,0 +1,143 @@
+# 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
+from mctest import exec_test, SimulatedConnection, create_fakecm_account, \
+ make_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():
+
+ accounts_dir = os.environ['MC_ACCOUNT_DIR']
+
+ accounts_cfg = open(accounts_dir + '/accounts.cfg', 'w')
+ accounts_cfg.write("""# Telepathy accounts
+[%s]
+manager=fakecm
+protocol=fakeprotocol
+DisplayName=Work account
+NormalizedName=jc.denton@unatco.int
+param-account=jc.denton@unatco.int
+param-password=ionstorm
+Enabled=1
+ConnectAutomatically=1
+AutomaticPresenceType=2
+AutomaticPresenceStatus=available
+AutomaticPresenceMessage=My vision is augmented
+Nickname=JC
+AvatarMime=image/jpeg
+avatar_token=Deus Ex
+""" % account_id)
+ accounts_cfg.close()
+
+ 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()
+
+def test(q, bus, unused):
+
+ expected_params = {
+ 'account': 'jc.denton@unatco.int',
+ 'password': 'ionstorm',
+ }
+
+ mc = make_mc(bus, q.append)
+
+ e, _ = q.expect_many(
+ EventPattern('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),
+ EventPattern('dbus-signal', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.AM and e.args[2]),
+ )
+
+ 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.CONN_STATUS_REASON_NONE)
+
+ # 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='GetKnownAvatarTokens',
+ args=[[conn.self_handle]],
+ handled=True),
+ 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
+
+if __name__ == '__main__':
+ preseed()
+ exec_test(test, {}, preload_mc=False)
diff --git a/tests/twisted/account-manager/avatar-refresh.py b/tests/twisted/account-manager/avatar-refresh.py
new file mode 100644
index 00000000..7cc2d2c8
--- /dev/null
+++ b/tests/twisted/account-manager/avatar-refresh.py
@@ -0,0 +1,124 @@
+# 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 Salut where
+the avatar must be reset every time you sign in.
+"""
+
+import os
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, create_fakecm_account, \
+ make_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():
+
+ accounts_dir = os.environ['MC_ACCOUNT_DIR']
+
+ accounts_cfg = open(accounts_dir + '/accounts.cfg', 'w')
+ accounts_cfg.write("""# Telepathy accounts
+[%s]
+manager=fakecm
+protocol=fakeprotocol
+DisplayName=Work account
+NormalizedName=jc.denton@unatco.int
+param-account=jc.denton@unatco.int
+param-password=ionstorm
+Enabled=1
+ConnectAutomatically=1
+AutomaticPresenceType=2
+AutomaticPresenceStatus=available
+AutomaticPresenceMessage=My vision is augmented
+Nickname=JC
+AvatarMime=image/jpeg
+avatar_token=Deus Ex
+""" % account_id)
+ accounts_cfg.close()
+
+ 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()
+
+def test(q, bus, unused):
+
+ expected_params = {
+ 'account': 'jc.denton@unatco.int',
+ 'password': 'ionstorm',
+ }
+
+ mc = make_mc(bus, q.append)
+
+ e, _ = q.expect_many(
+ EventPattern('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),
+ EventPattern('dbus-signal', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.AM and e.args[2]),
+ )
+
+ 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.CONN_STATUS_REASON_NONE)
+
+ _, _, e = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_AVATARS, method='GetKnownAvatarTokens',
+ args=[[conn.self_handle]],
+ handled=True),
+ 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),
+ ),
+ )
+
+if __name__ == '__main__':
+ preseed()
+ exec_test(test, {}, preload_mc=False)
diff --git a/tests/twisted/account-manager/avatar.py b/tests/twisted/account-manager/avatar.py
new file mode 100644
index 00000000..c616e146
--- /dev/null
+++ b/tests/twisted/account-manager/avatar.py
@@ -0,0 +1,112 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, assertEquals
+from mctest import exec_test, create_fakecm_account, enable_fakecm_account
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "me@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT_IFACE_AVATAR, 'Avatar',
+ dbus.Struct((dbus.ByteArray('AAAA'), 'image/jpeg')))
+ q.expect_many(
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ signal='AvatarChanged',
+ interface=cs.ACCOUNT_IFACE_AVATAR,
+ args=[]),
+ EventPattern('dbus-return', method='Set'),
+ )
+ assert account_props.Get(cs.ACCOUNT_IFACE_AVATAR, 'Avatar',
+ byte_arrays=True) == ('AAAA', 'image/jpeg')
+
+ # OK, let's go online. The avatar is set regardless of the CM
+ conn, e = enable_fakecm_account(q, bus, mc, account, params,
+ has_avatars=True, avatars_persist=True,
+ expect_after_connect=[
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_AVATARS, method='SetAvatar',
+ handled=True, args=['AAAA', 'image/jpeg']),
+ ])
+
+ # Change avatar after going online
+ call_async(q, account_props, 'Set', cs.ACCOUNT_IFACE_AVATAR, 'Avatar',
+ (dbus.ByteArray('BBBB'), 'image/png'))
+
+ q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_AVATARS, method='SetAvatar',
+ args=['BBBB', 'image/png'],
+ handled=True),
+ 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) == ('BBBB', 'image/png')
+
+ someone_else = conn.ensure_handle(cs.HT_CONTACT, 'alberto@example.com')
+
+ # Another contact changes their avatar: ignored
+ q.dbus_emit(conn.object_path, cs.CONN_IFACE_AVATARS, 'AvatarUpdated',
+ someone_else, "mardy's avatar token", signature='us')
+
+ # Another client changes our avatar remotely
+ q.dbus_emit(conn.object_path, cs.CONN_IFACE_AVATARS, 'AvatarUpdated',
+ conn.self_handle, 'CCCC', signature='us')
+
+ e = q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_AVATARS, method='RequestAvatars',
+ args=[[conn.self_handle]],
+ handled=False)
+ q.dbus_return(e.message, signature='')
+
+ q.dbus_emit(conn.object_path, cs.CONN_IFACE_AVATARS,
+ 'AvatarRetrieved', conn.self_handle, 'CCCC',
+ dbus.ByteArray('CCCC'), 'image/svg', signature='usays')
+ q.expect('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_AVATAR, signal='AvatarChanged'),
+
+ assert account_props.Get(cs.ACCOUNT_IFACE_AVATAR, 'Avatar',
+ byte_arrays=True) == ('CCCC', 'image/svg')
+
+ # empty avatar tests
+ conn.forget_avatar()
+ q.dbus_emit(conn.object_path, cs.CONN_IFACE_AVATARS, 'AvatarUpdated',
+ conn.self_handle, '', signature='us')
+ q.expect('dbus-method-call', method='GetKnownAvatarTokens')
+ q.expect('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_AVATAR, signal='AvatarChanged')
+
+ assertEquals(account_props.Get(cs.ACCOUNT_IFACE_AVATAR, 'Avatar',
+ byte_arrays=False), ([], ''))
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/bad-cm.py b/tests/twisted/account-manager/bad-cm.py
new file mode 100644
index 00000000..dea5860a
--- /dev/null
+++ b/tests/twisted/account-manager/bad-cm.py
@@ -0,0 +1,79 @@
+# 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
+
+"""Regression test for https://bugs.freedesktop.org/show_bug.cgi?id=20880
+"""
+
+import dbus
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from fakecm import start_fake_connection_manager
+from mctest import exec_test, get_account_manager
+import constants as cs
+
+FakeCM_bus_name = "com.example.FakeCM"
+ConnectionManager_object_path = "/com/example/FakeCM/ConnectionManager"
+
+
+def test(q, bus, mc):
+ # Get the AccountManager interface
+ account_manager = get_account_manager(bus)
+ account_manager_iface = dbus.Interface(account_manager, cs.AM)
+
+ # Create an account with a bad Connection_Manager - it should fail
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ call_async(q, account_manager_iface, 'CreateAccount',
+ 'nonexistent_cm', # Connection_Manager
+ 'fakeprotocol', # Protocol
+ 'fakeaccount', #Display_Name
+ params, # Parameters
+ {}, # Properties
+ )
+ q.expect('dbus-error', method='CreateAccount')
+
+ # Create an account with a bad Protocol - it should fail
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ call_async(q, account_manager_iface, 'CreateAccount',
+ 'fakecm', # Connection_Manager
+ 'nonexistent-protocol', # Protocol
+ 'fakeaccount', #Display_Name
+ params, # Parameters
+ {}, # Properties
+ )
+ q.expect('dbus-error', method='CreateAccount')
+
+ # Create an account with incomplete Parameters - it should fail
+
+ params = dbus.Dictionary({"account": "someguy@example.com"},
+ signature='sv')
+ call_async(q, account_manager_iface, 'CreateAccount',
+ 'fakecm', # Connection_Manager
+ 'fakeprotocol', # Protocol
+ 'fakeaccount', #Display_Name
+ params, # Parameters
+ {}, # Properties
+ )
+ q.expect('dbus-error', method='CreateAccount')
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/create-auto-connect.py b/tests/twisted/account-manager/create-auto-connect.py
new file mode 100644
index 00000000..fa2cb470
--- /dev/null
+++ b/tests/twisted/account-manager/create-auto-connect.py
@@ -0,0 +1,68 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "smcv@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ # Ensure that it's enabled but has offline RP
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'RequestedPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', ''))
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'AutomaticPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Testing automatic presence'))
+ q.expect('dbus-return', method='Set')
+ q.expect('dbus-signal', signal='AccountPropertyChanged',
+ predicate=lambda e:
+ e.args[0].get('AutomaticPresence', (None, None, None))[1]
+ == 'busy')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Enabled', True)
+ q.expect('dbus-return', method='Set')
+ q.expect('dbus-signal', signal='AccountPropertyChanged',
+ predicate=lambda e: e.args[0].get('Enabled'))
+
+ # Go online by telling it to connect automatically
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'ConnectAutomatically',
+ True)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=cs.tp_name_prefix + '.ConnectionManager.fakecm',
+ path=cs.tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=cs.tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/create-twice.py b/tests/twisted/account-manager/create-twice.py
new file mode 100644
index 00000000..2f0c0e0c
--- /dev/null
+++ b/tests/twisted/account-manager/create-twice.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2009 Nokia Corporation
+# Copyright (C) 2009-2010 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account, get_account_manager
+import constants as cs
+
+def test(q, bus, mc):
+ account_manager = get_account_manager(bus)
+ account_manager_iface = dbus.Interface(account_manager, cs.AM)
+
+ # fd.o #25684: creating similarly-named accounts in very quick succession
+ # used to fail
+
+ params = dbus.Dictionary({"account": "create-twice",
+ "password": "secrecy"}, signature='sv')
+
+ cm_name_ref = dbus.service.BusName(cs.tp_name_prefix +
+ '.ConnectionManager.fakecm', bus=bus)
+ account_manager = bus.get_object(cs.AM, cs.AM_PATH)
+ am_iface = dbus.Interface(account_manager, cs.AM)
+
+ call_async(q, am_iface, 'CreateAccount',
+ 'fakecm',
+ 'fakeprotocol',
+ 'fakeaccount',
+ params,
+ {})
+ call_async(q, am_iface, 'CreateAccount',
+ 'fakecm',
+ 'fakeprotocol',
+ 'fakeaccount',
+ params,
+ {})
+
+ ret1 = q.expect('dbus-return', method='CreateAccount')
+ ret2 = q.expect('dbus-return', method='CreateAccount')
+
+ path1 = ret1.value[0]
+ path2 = ret2.value[0]
+ assert path1 != path2
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/create-with-properties.py b/tests/twisted/account-manager/create-with-properties.py
new file mode 100644
index 00000000..5aabfc76
--- /dev/null
+++ b/tests/twisted/account-manager/create-with-properties.py
@@ -0,0 +1,174 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account, get_account_manager
+import constants as cs
+
+def test(q, bus, mc):
+ # Get the AccountManager interface
+ account_manager = get_account_manager(bus)
+ account_manager_iface = dbus.Interface(account_manager, cs.AM)
+
+ # Introspect AccountManager for debugging purpose
+ account_manager_introspected = account_manager.Introspect(
+ dbus_interface=cs.INTROSPECTABLE_IFACE)
+ #print account_manager_introspected
+
+ # Check AccountManager has D-Bus property interface
+ properties = account_manager.GetAll(cs.AM,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('ValidAccounts') == [], \
+ properties.get('ValidAccounts')
+ assert properties.get('InvalidAccounts') == [], \
+ properties.get('InvalidAccounts')
+ interfaces = properties.get('Interfaces')
+ supported = properties.get('SupportedAccountProperties')
+
+ # assert that current functionality exists
+ assert cs.AM_IFACE_NOKIA_QUERY in interfaces, interfaces
+
+ assert (cs.ACCOUNT + '.AutomaticPresence') in supported
+ assert (cs.ACCOUNT + '.Enabled') in supported
+ assert (cs.ACCOUNT + '.Icon') in supported
+ assert (cs.ACCOUNT + '.Nickname') in supported
+ assert (cs.ACCOUNT + '.ConnectAutomatically') in supported
+ assert (cs.ACCOUNT_IFACE_AVATAR + '.Avatar') in supported
+ assert (cs.ACCOUNT_IFACE_NOKIA_COMPAT + '.Profile') in supported
+ assert (cs.ACCOUNT_IFACE_NOKIA_COMPAT + '.SecondaryVCardFields') in supported
+ assert (cs.ACCOUNT_IFACE_NOKIA_CONDITIONS + '.Condition') in supported
+
+ assert (cs.ACCOUNT + '.RequestedPresence') in supported
+
+ params = dbus.Dictionary({"account": "anarki@example.com",
+ "password": "secrecy"}, signature='sv')
+
+ cm_name_ref = dbus.service.BusName(cs.tp_name_prefix +
+ '.ConnectionManager.fakecm', bus=bus)
+ account_manager = bus.get_object(cs.AM, cs.AM_PATH)
+ am_iface = dbus.Interface(account_manager, cs.AM)
+
+ creation_properties = dbus.Dictionary({
+ cs.ACCOUNT + '.Enabled': True,
+ cs.ACCOUNT + '.AutomaticPresence': dbus.Struct((
+ dbus.UInt32(cs.PRESENCE_TYPE_BUSY),
+ 'busy', 'Exploding'), signature='uss'),
+ cs.ACCOUNT + '.RequestedPresence': dbus.Struct((
+ dbus.UInt32(cs.PRESENCE_TYPE_AWAY),
+ 'away', 'Respawning'), signature='uss'),
+ cs.ACCOUNT + '.Icon': 'quake3arena',
+ cs.ACCOUNT + '.Nickname': 'AnArKi',
+ cs.ACCOUNT + '.ConnectAutomatically': True,
+ cs.ACCOUNT_IFACE_AVATAR + '.Avatar': (dbus.ByteArray('foo'),
+ 'image/jpeg'),
+ cs.ACCOUNT_IFACE_NOKIA_COMPAT + '.Profile': 'openarena',
+ cs.ACCOUNT_IFACE_NOKIA_COMPAT + '.SecondaryVCardFields':
+ dbus.Array(['x-ioquake3', 'x-quake3'], signature='s'),
+ cs.ACCOUNT_IFACE_NOKIA_CONDITIONS + '.Condition':
+ dbus.Dictionary({ 'has-quad-damage': ':y' }, signature='ss'),
+ }, signature='sv')
+
+ call_async(q, am_iface, 'CreateAccount',
+ 'fakecm',
+ 'fakeprotocol',
+ 'fakeaccount',
+ params,
+ creation_properties)
+
+ # The spec has no order guarantee here.
+ # FIXME: MC ought to also introspect the CM and find out that the params
+ # are in fact sufficient
+
+ am_signal, ret, rc = q.expect_many(
+ EventPattern('dbus-signal', path=cs.AM_PATH,
+ signal='AccountValidityChanged', interface=cs.AM),
+ EventPattern('dbus-return', method='CreateAccount'),
+ EventPattern('dbus-method-call', method='RequestConnection'),
+ )
+ account_path = ret.value[0]
+ assert am_signal.args == [account_path, True], am_signal.args
+
+ assert account_path is not None
+
+ account = bus.get_object(
+ cs.tp_name_prefix + '.AccountManager',
+ account_path)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ properties = account_props.GetAll(cs.ACCOUNT)
+ assert properties.get('AutomaticPresence') == (cs.PRESENCE_TYPE_BUSY,
+ 'busy', 'Exploding'), \
+ properties.get('AutomaticPresence')
+ assert properties.get('RequestedPresence') == (cs.PRESENCE_TYPE_AWAY,
+ 'away', 'Respawning'), \
+ properties.get('RequestedPresence')
+ assert properties.get('ConnectAutomatically') == True, \
+ properties.get('ConnectAutomatically')
+ assert properties.get('Enabled') == True, \
+ properties.get('Enabled')
+ assert properties.get('Valid') == True, \
+ properties.get('Valid')
+ assert properties.get('Icon') == 'quake3arena', \
+ properties.get('Icon')
+ assert properties.get('Nickname') == 'AnArKi', \
+ properties.get('Nickname')
+
+ properties = account_props.GetAll(cs.ACCOUNT_IFACE_AVATAR)
+ assert properties.get('Avatar') == ([ord('f'), ord('o'), ord('o')],
+ 'image/jpeg')
+
+ properties = account_props.GetAll(cs.ACCOUNT_IFACE_NOKIA_COMPAT)
+ assert properties.get('Profile') == 'openarena'
+ assert sorted(properties.get('SecondaryVCardFields')) == \
+ ['x-ioquake3', 'x-quake3']
+
+ properties = account_props.GetAll(cs.ACCOUNT_IFACE_NOKIA_CONDITIONS)
+ assert properties.get('Condition') == {
+ 'has-quad-damage': ':y',
+ }
+
+ # tests for errors when creating an account
+
+ creation_properties2 = creation_properties.copy()
+ creation_properties2[cs.ACCOUNT + '.NonExistent'] = 'foo'
+ call_async(q, am_iface, 'CreateAccount',
+ 'fakecm',
+ 'fakeprotocol',
+ 'fakeaccount',
+ params,
+ creation_properties2)
+ q.expect('dbus-error', method='CreateAccount')
+
+ params2 = params.copy()
+ params2['fake_param'] = 'foo'
+ call_async(q, am_iface, 'CreateAccount',
+ 'fakecm',
+ 'fakeprotocol',
+ 'fakeaccount',
+ params2,
+ creation_properties)
+ q.expect('dbus-error', method='CreateAccount')
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/device-idle.py b/tests/twisted/account-manager/device-idle.py
new file mode 100644
index 00000000..089a0661
--- /dev/null
+++ b/tests/twisted/account-manager/device-idle.py
@@ -0,0 +1,129 @@
+# 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 config
+
+if config.HAVE_MCE:
+ print "NOTE: built with real MCE support; skipping idleness test"
+ raise SystemExit(77)
+
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, unwrap, sync_dbus
+from mctest import exec_test, create_fakecm_account, SimulatedConnection, \
+ enable_fakecm_account
+import constants as cs
+
+# Fake MCE constants, cloned from mce-slacker.c
+MCE_SERVICE = "org.freedesktop.Telepathy.MissionControl.Tests.MCE"
+
+MCE_SIGNAL_IF = "org.freedesktop.Telepathy.MissionControl.Tests.MCE"
+
+MCE_REQUEST_IF = "org.freedesktop.Telepathy.MissionControl.Tests.MCE"
+MCE_REQUEST_PATH = "/org/freedesktop/Telepathy/MissionControl/Tests/MCE"
+
+class SimulatedMCE(object):
+ def __init__(self, q, bus, inactive=False):
+ self.bus = bus
+ self.q = q
+ self.inactive = inactive
+ self.object_path = MCE_REQUEST_PATH
+ self._name_ref = dbus.service.BusName(MCE_SERVICE, bus)
+
+ q.add_dbus_method_impl(self.GetInactivity,
+ path=self.object_path, interface=MCE_REQUEST_IF,
+ method='GetInactivity')
+
+
+ def GetInactivity(self, e):
+ self.q.dbus_return(e.message, self.inactive, signature='b')
+
+ def InactivityChanged(self, new_value):
+ self.inactive = new_value
+ self.q.dbus_emit(self.object_path, MCE_SIGNAL_IF, "InactivityChanged",
+ self.inactive, signature="b")
+
+ def release_name(self):
+ del self._name_ref
+
+def _create_and_enable(q, bus, mc, account_name, power_saving_supported,
+ expect_after_connect=[]):
+ extra_interfaces = []
+ if power_saving_supported:
+ extra_interfaces = [cs.CONN_IFACE_POWER_SAVING]
+ params = dbus.Dictionary({"account": account_name, "password": "secrecy"},
+ signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params, has_requests=False,
+ extra_interfaces=extra_interfaces,
+ expect_after_connect=expect_after_connect)
+
+ if isinstance(conn, tuple):
+ conn = conn[0]
+
+ return account, conn
+
+def _disable_account(q, bus, mc, account, conn):
+ account.Set(cs.ACCOUNT, 'Enabled', False,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ q.expect('dbus-method-call', method='Disconnect',
+ path=conn.object_path, handled=True)
+
+def test(q, bus, mc):
+ mce = SimulatedMCE(q, bus, True)
+
+ account1, conn1 = _create_and_enable(
+ q, bus, mc, "first@example.com", True,
+ [EventPattern('dbus-method-call', method='SetPowerSaving', args=[True])])
+ account2, conn2 = _create_and_enable(q, bus, mc, "second@example.com", False)
+
+ # Second account does not support PowerSaving interface, don't call SetPowerSaving
+ forbid_no_iface =[EventPattern('dbus-method-call', method='SetPowerSaving',
+ path=conn2.object_path)]
+
+ q.forbid_events(forbid_no_iface)
+
+ for enabled in [False, True, False]:
+ mce.InactivityChanged(enabled)
+ q.expect('dbus-method-call', method='SetPowerSaving',
+ args=[enabled], interface=cs.CONN_IFACE_POWER_SAVING,
+ path=conn1.object_path)
+
+ _disable_account(q, bus, mc, account1, conn1)
+ _disable_account(q, bus, mc, account2, conn2)
+
+ q.unforbid_events(forbid_no_iface)
+
+ # Make sure we don't call SetPowerSaving on a disconnected connection.
+
+ forbid_when_disconnected =[EventPattern('dbus-method-call', method='SetPowerSaving')]
+
+ q.forbid_events(forbid_when_disconnected)
+
+ mce.InactivityChanged(True)
+
+ sync_dbus(bus, q, account1)
+
+ q.unforbid_events(forbid_when_disconnected)
+
+ mce.release_name()
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/enable-auto-connect.py b/tests/twisted/account-manager/enable-auto-connect.py
new file mode 100644
index 00000000..2cb91c31
--- /dev/null
+++ b/tests/twisted/account-manager/enable-auto-connect.py
@@ -0,0 +1,69 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "smcv@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ # Ensure that it's enabled but has offline RP
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'RequestedPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', ''))
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'AutomaticPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Testing automatic presence'))
+ q.expect('dbus-return', method='Set')
+ q.expect('dbus-signal', signal='AccountPropertyChanged',
+ predicate=lambda e:
+ e.args[0].get('AutomaticPresence', (None, None, None))[1]
+ == 'busy')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Enabled', False)
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'ConnectAutomatically',
+ True)
+ q.expect('dbus-return', method='Set')
+
+ # Go online by enabling the account
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Enabled', True)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=cs.tp_name_prefix + '.ConnectionManager.fakecm',
+ path=cs.tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=cs.tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/enable.py b/tests/twisted/account-manager/enable.py
new file mode 100644
index 00000000..50e489d1
--- /dev/null
+++ b/tests/twisted/account-manager/enable.py
@@ -0,0 +1,62 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "smcv@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'RequestedPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', ''))
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Enabled', False)
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'ConnectAutomatically',
+ False)
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'RequestedPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy', 'Testing Enabled'))
+ q.expect('dbus-return', method='Set')
+
+ # Go online by setting Enabled
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Enabled', True)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=cs.tp_name_prefix + '.ConnectionManager.fakecm',
+ path=cs.tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=cs.tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/make-valid.py b/tests/twisted/account-manager/make-valid.py
new file mode 100644
index 00000000..17d58049
--- /dev/null
+++ b/tests/twisted/account-manager/make-valid.py
@@ -0,0 +1,237 @@
+# 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 accounts becoming valid.
+"""
+
+import os
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, create_fakecm_account, \
+ make_mc
+import constants as cs
+
+cm_name_ref = dbus.service.BusName(
+ cs.tp_name_prefix + '.ConnectionManager.fakecm', bus=dbus.SessionBus())
+
+account1_id = 'fakecm/fakeprotocol/jc_2edenton_40unatco_2eint'
+account2_id = 'fakecm/fakeprotocol/jc_2edenton_40example_2ecom'
+
+def preseed():
+
+ accounts_dir = os.environ['MC_ACCOUNT_DIR']
+
+ # The passwords are missing, so the accounts can't connect yet.
+ accounts_cfg = open(accounts_dir + '/accounts.cfg', 'w')
+ accounts_cfg.write("""# Telepathy accounts
+[%s]
+manager=fakecm
+protocol=fakeprotocol
+DisplayName=Work account
+NormalizedName=jc.denton@unatco.int
+param-account=jc.denton@unatco.int
+Enabled=1
+ConnectAutomatically=1
+AutomaticPresenceType=2
+AutomaticPresenceStatus=available
+AutomaticPresenceMessage=My vision is augmented
+Nickname=JC
+AvatarMime=image/jpeg
+
+[%s]
+manager=fakecm
+protocol=fakeprotocol
+DisplayName=Personal account
+NormalizedName=jc.denton@example.com
+param-account=jc.denton@example.com
+Enabled=1
+ConnectAutomatically=0
+AutomaticPresenceType=2
+AutomaticPresenceStatus=available
+AutomaticPresenceMessage=My vision is augmented
+Nickname=JC
+AvatarMime=image/jpeg
+""" % (account1_id, account2_id))
+ accounts_cfg.close()
+
+ os.makedirs(accounts_dir + '/' + account1_id)
+ avatar_bin = open(accounts_dir + '/' + account1_id + '/avatar.bin', 'w')
+ avatar_bin.write('Deus Ex')
+ avatar_bin.close()
+
+ os.makedirs(accounts_dir + '/' + account2_id)
+ avatar_bin = open(accounts_dir + '/' + account2_id + '/avatar.bin', 'w')
+ avatar_bin.write('Invisible War')
+ 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):
+ # make sure RequestConnection doesn't get called yet
+ events = [EventPattern('dbus-method-call', method='RequestConnection')]
+ q.forbid_events(events)
+
+ # Wait for MC to load
+ mc = make_mc(bus, q.append)
+
+ q.expect_many(
+ EventPattern('dbus-signal', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.AM),
+ EventPattern('dbus-signal', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.CD),
+ )
+
+ # Trying to make a channel on account 1 doesn't work, because it's
+ # not valid
+
+ account_path = (cs.tp_path_prefix + '/Account/' + account1_id)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+
+ user_action_time = dbus.Int64(1238582606)
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ call_async(q, cd, 'CreateChannel',
+ account_path, request, user_action_time, "",
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='CreateChannel')
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.CD, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == ""
+ assert request_props['Interfaces'] == []
+
+ sync_dbus(bus, q, mc)
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ # FIXME: error isn't specified (NotAvailable perhaps?)
+ q.expect('dbus-signal', path=cr.object_path,
+ interface=cs.CR, signal='Failed')
+
+ # Make account 1 valid: it should connect automatically
+
+ account_path = (cs.tp_path_prefix + '/Account/' + account1_id)
+ account = bus.get_object(cs.MC, account_path)
+
+ sync_dbus(bus, q, mc)
+ q.unforbid_events(events)
+
+ call_async(q, account, 'UpdateParameters', {'password': 'nanotech'}, [],
+ dbus_interface=cs.ACCOUNT)
+
+ expected_params = {'password': 'nanotech',
+ 'account': 'jc.denton@unatco.int'}
+
+ 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_presence=True)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True, interface=cs.CONN)
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ set_presence, e = q.expect_many(
+ EventPattern('dbus-method-call', path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ handled=True),
+ EventPattern('dbus-signal', signal='AccountPropertyChanged',
+ path=account_path, interface=cs.ACCOUNT,
+ predicate=lambda e: 'CurrentPresence' in e.args[0]
+ and e.args[0]['CurrentPresence'][2] != ''),
+ )
+
+ assert e.args[0]['CurrentPresence'] == (cs.PRESENCE_TYPE_AVAILABLE,
+ 'available', 'My vision is augmented')
+
+ # Request an online presence on account 2, then make it valid
+
+ q.forbid_events(events)
+
+ account_path = (cs.tp_path_prefix + '/Account/' + account2_id)
+ account = bus.get_object(cs.MC, account_path)
+
+ requested_presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_BUSY),
+ 'busy', 'Talking to Illuminati'))
+ account.Set(cs.ACCOUNT, 'RequestedPresence',
+ dbus.Struct(requested_presence, variant_level=1),
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ sync_dbus(bus, q, mc)
+ q.unforbid_events(events)
+
+ # Make the account valid
+ call_async(q, account, 'UpdateParameters', {'password': 'nanotech'}, [],
+ dbus_interface=cs.ACCOUNT)
+
+ expected_params = {'password': 'nanotech',
+ 'account': 'jc.denton@example.com'}
+
+ 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_presence=True)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True, interface=cs.CONN)
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ set_presence = q.expect('dbus-method-call', path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ handled=True)
+
+ e = q.expect('dbus-signal', signal='AccountPropertyChanged',
+ path=account_path, interface=cs.ACCOUNT,
+ predicate=lambda e: 'CurrentPresence' in e.args[0]
+ and e.args[0]['CurrentPresence'][1] == 'busy')
+
+ assert e.args[0]['CurrentPresence'] == (cs.PRESENCE_TYPE_BUSY,
+ 'busy', 'Talking to Illuminati')
+
+if __name__ == '__main__':
+ preseed()
+ exec_test(test, {}, preload_mc=False)
diff --git a/tests/twisted/account-manager/nickname.py b/tests/twisted/account-manager/nickname.py
new file mode 100644
index 00000000..8e2fe799
--- /dev/null
+++ b/tests/twisted/account-manager/nickname.py
@@ -0,0 +1,93 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account, enable_fakecm_account
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "wjt@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Nickname',
+ 'resiak')
+ q.expect_many(
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT,
+ args=[{'Nickname': 'resiak'}]),
+ EventPattern('dbus-return', method='Set'),
+ )
+ assert account_props.Get(cs.ACCOUNT, 'Nickname') == 'resiak'
+
+ # OK, let's go online
+ conn, get_aliases, set_aliases = enable_fakecm_account(q, bus, mc,
+ account, params, has_aliasing=True,
+ expect_after_connect=[
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_ALIASING, method='GetAliases',
+ handled=False),
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_ALIASING, method='SetAliases',
+ handled=False),
+ ])
+
+ assert get_aliases.args[0] == [ conn.self_handle ]
+ q.dbus_return(get_aliases.message, { conn.self_handle: 'wjt@example.com' },
+ signature='a{us}')
+
+ assert set_aliases.args[0] == { conn.self_handle: 'resiak' }
+ q.dbus_return(set_aliases.message, signature='')
+
+ # Change alias after going online
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Nickname',
+ 'Will Thomspon')
+
+ e = q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_ALIASING, method='SetAliases',
+ args=[{ conn.self_handle: 'Will Thomspon' }],
+ handled=False)
+
+ # Set returns immediately; the change happens asynchronously
+ q.expect('dbus-return', method='Set')
+
+ q.dbus_return(e.message, signature='')
+
+ someone_else = conn.ensure_handle(cs.HT_CONTACT, 'alberto@example.com')
+
+ # Another client changes our alias remotely
+ q.dbus_emit(conn.object_path, cs.CONN_IFACE_ALIASING, 'AliasesChanged',
+ dbus.Array([(conn.self_handle, 'wjt'), (someone_else, 'mardy')],
+ signature='(us)'), signature='a(us)')
+
+ q.expect('dbus-signal', path=account.object_path,
+ signal='AccountPropertyChanged', interface=cs.ACCOUNT,
+ args=[{'Nickname': 'wjt'}])
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/param-types.py b/tests/twisted/account-manager/param-types.py
new file mode 100644
index 00000000..8dafd7f1
--- /dev/null
+++ b/tests/twisted/account-manager/param-types.py
@@ -0,0 +1,86 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account, get_account_manager
+import constants as cs
+
+def test(q, bus, mc):
+ cm_name_ref = dbus.service.BusName(
+ cs.tp_name_prefix + '.ConnectionManager.onewitheverything',
+ bus=bus)
+
+ # Get the AccountManager interface
+ account_manager = get_account_manager(bus)
+ account_manager_iface = dbus.Interface(account_manager, cs.AM)
+
+ params = dbus.Dictionary({
+ 's': 'lalala',
+ 'o': dbus.ObjectPath('/lalala'),
+ 'b': False,
+ 'q': dbus.UInt16(42),
+ 'u': dbus.UInt32(0xFFFFFFFFL),
+ 't': dbus.UInt64(0xFFFFffffFFFFffffL),
+ 'n': dbus.Int16(-42),
+ 'i': dbus.Int32(-42),
+ 'x': dbus.Int64(-1 * 0x7FFFffffFFFFffffL),
+ 'd': 4.5,
+ 'y': dbus.Byte(42),
+ 'as': dbus.Array(['one', 'two', 'three'], signature='s')
+ }, signature='sv')
+
+ # Create an account
+ call_async(q, account_manager_iface, 'CreateAccount',
+ 'onewitheverything', # Connection_Manager
+ 'serializable', # Protocol
+ 'fakeaccount', #Display_Name
+ params, # Parameters
+ {}, # Properties
+ )
+
+ am_signal, ret = q.expect_many(
+ EventPattern('dbus-signal', path=cs.AM_PATH,
+ interface=cs.AM, signal='AccountValidityChanged'),
+ EventPattern('dbus-return', method='CreateAccount'),
+ )
+ account_path = ret.value[0]
+ assert am_signal.args == [account_path, True], am_signal.args
+ assert account_path is not None
+
+ account = bus.get_object(
+ cs.tp_name_prefix + '.AccountManager',
+ account_path)
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ stored_params = account_props.Get(cs.ACCOUNT, 'Parameters')
+
+ for k in stored_params:
+ assert k in params, k
+
+ for k in params:
+ assert k in stored_params, k
+ assert stored_params[k] == params[k], (k, stored_params[k], params[k])
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/presence.py b/tests/twisted/account-manager/presence.py
new file mode 100755
index 00000000..abef3b00
--- /dev/null
+++ b/tests/twisted/account-manager/presence.py
@@ -0,0 +1,136 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, create_fakecm_account, enable_fakecm_account
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "jc.denton@example.com",
+ "password": "ionstorm"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ # Go online with a particular presence
+ presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Fighting conspiracies'), signature='uss')
+
+ log = []
+
+ # FIXME: using predicate for its side-effects here is weird
+
+ conn, _, _, _, _, _, _ = enable_fakecm_account(q, bus, mc, account, params,
+ has_presence=True,
+ requested_presence=presence,
+ expect_before_connect=[
+ EventPattern('dbus-method-call',
+ interface=cs.CONN, method='GetInterfaces',
+ args=[],
+ handled=True,
+ predicate=(lambda e: log.append('GetInterfaces') or True)),
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='Get',
+ args=[cs.CONN_IFACE_SIMPLE_PRESENCE, 'Statuses'],
+ handled=True,
+ predicate=(lambda e: log.append('Get(Statuses)[1]') or True)),
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE,
+ method='SetPresence',
+ args=list(presence[1:]),
+ handled=True,
+ predicate=(lambda e: log.append('SetPresence[1]') or True)),
+ ],
+ expect_after_connect=[
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='Get',
+ args=[cs.CONN_IFACE_SIMPLE_PRESENCE, 'Statuses'],
+ handled=True,
+ predicate=(lambda e: log.append('Get(Statuses)[2]') or True)),
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE,
+ method='SetPresence',
+ args=list(presence[1:]),
+ handled=True,
+ predicate=(lambda e: log.append('SetPresence[2]') or True)),
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ predicate=lambda e:
+ e.args[0].get('CurrentPresence') == presence),
+ ])
+
+ # The events before Connect must happen in this order. GetInterfaces() may
+ # be called once or 2 times
+ if len(log) == 5:
+ assert log == ['GetInterfaces', 'Get(Statuses)[1]', 'SetPresence[1]',
+ 'Get(Statuses)[2]', 'SetPresence[2]'], log
+ else:
+ assert log == ['GetInterfaces', 'GetInterfaces', 'Get(Statuses)[1]', 'SetPresence[1]',
+ 'Get(Statuses)[2]', 'SetPresence[2]'], log
+
+ # Change requested presence after going online
+ presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_AWAY), 'away',
+ 'In Hong Kong'), signature='uss')
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'RequestedPresence',
+ presence)
+
+ e, _, _ = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ args=list(presence[1:]),
+ handled=True),
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ predicate=lambda e: e.args[0].get('ChangingPresence') == True and
+ e.args[0].get('RequestedPresence') == presence),
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ predicate=lambda e: e.args[0].get('CurrentPresence') == presence and
+ e.args[0].get('ChangingPresence') == False))
+
+ # Setting RequestedPresence=RequestedPresence causes a (possibly redundant)
+ # call to the CM, so we get any side-effects there might be, either in the
+ # CM or in MC (e.g. asking connectivity services to go online). However,
+ # AccountPropertyChanged is not emitted for RequestedPresence.
+
+ sync_dbus(bus, q, mc)
+ events = [EventPattern('dbus-signal', signal='AccountPropertyChanged',
+ predicate=lambda e: e.args[0].get('RequestedPresence') is not None)]
+ q.forbid_events(events)
+
+ presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_AWAY), 'away',
+ 'In Hong Kong'), signature='uss')
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'RequestedPresence',
+ presence)
+
+ e = q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ args=list(presence[1:]),
+ handled=True)
+
+ sync_dbus(bus, q, mc)
+ q.unforbid_events(events)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/reconnect.py b/tests/twisted/account-manager/reconnect.py
new file mode 100644
index 00000000..0c6ea3ad
--- /dev/null
+++ b/tests/twisted/account-manager/reconnect.py
@@ -0,0 +1,202 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, create_fakecm_account,\
+ SimulatedChannel
+import constants as cs
+
+def test(q, bus, mc):
+ cm_name_ref = dbus.service.BusName(
+ tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ # Create an account
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ # Events that indicate that Reconnect might have done something
+ looks_like_reconnection = [
+ EventPattern('dbus-method-call', method='RequestConnection'),
+ EventPattern('dbus-method-call', method='Disconnect'),
+ ]
+
+ q.forbid_events(looks_like_reconnection)
+
+ # While we want to be online but the account is disabled, Reconnect is a
+ # no-op. Set Enabled to False explicitly, so we're less reliant on initial
+ # state.
+
+ call_async(q, account, 'Set', cs.ACCOUNT, 'Enabled', False,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect('dbus-return', method='Set')
+
+ requested_presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_AVAILABLE),
+ dbus.String(u'available'), dbus.String(u'')))
+ call_async(q, account, 'Set', cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account, 'Reconnect', dbus_interface=cs.ACCOUNT)
+ q.expect('dbus-return', method='Reconnect')
+
+ sync_dbus(bus, q, account)
+
+ # While we want to be offline but the account is enabled, Reconnect is
+ # still a no-op.
+ requested_presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE),
+ dbus.String(u'offline'), dbus.String(u'')))
+ call_async(q, account, 'Set', cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect_many(
+ EventPattern('dbus-return', method='Set'),
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT),
+ )
+
+ # Enable the account
+ call_async(q, account, 'Set', cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect_many(
+ EventPattern('dbus-return', method='Set'),
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT),
+ )
+
+ call_async(q, account, 'Reconnect', dbus_interface=cs.ACCOUNT)
+ q.expect('dbus-return', method='Reconnect')
+
+ sync_dbus(bus, q, account)
+
+ # Actually go online now
+
+ q.unforbid_events(looks_like_reconnection)
+
+ requested_presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_AVAILABLE),
+ dbus.String(u'brb'), dbus.String(u'Be back soon!')))
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself')
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # MC does some setup, including fetching the list of Channels
+
+ q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True),
+ )
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ # Assert that the NormalizedName is harvested from the Connection at some
+ # point
+ while 1:
+ e = q.expect('dbus-signal',
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ path=account.object_path)
+ if 'NormalizedName' in e.args[0]:
+ assert e.args[0]['NormalizedName'] == 'myself', e.args
+ break
+
+ # Check the requested presence is online
+ properties = account.GetAll(cs.ACCOUNT,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('RequestedPresence') == requested_presence, \
+ properties.get('RequestedPresence')
+
+ # Reconnect
+ account.Reconnect(dbus_interface=cs.ACCOUNT)
+
+ q.expect('dbus-method-call', method='Disconnect',
+ path=conn.object_path, handled=True)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself')
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True),
+ )
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ # Put the account offline
+ requested_presence = (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', '')
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ # In response, MC tells us to Disconnect, and we do
+ q.expect('dbus-method-call', method='Disconnect',
+ path=conn.object_path, handled=True)
+
+ # MC terminates the channel
+ # FIXME: it shouldn't do this!
+ #q.expect('dbus-method-call', method='Close',
+ # path=chan.object_path, handled=True)
+
+ properties = account.GetAll(cs.ACCOUNT, dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties['Connection'] == '/'
+ assert properties['ConnectionStatus'] == cs.CONN_STATUS_DISCONNECTED
+ assert properties['CurrentPresence'] == requested_presence
+ assert properties['RequestedPresence'] == requested_presence
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/recover-from-disconnect.py b/tests/twisted/account-manager/recover-from-disconnect.py
new file mode 100644
index 00000000..aba60598
--- /dev/null
+++ b/tests/twisted/account-manager/recover-from-disconnect.py
@@ -0,0 +1,197 @@
+# Copyright (C) 2009 Nokia Corporation
+# Copyright (C) 2009-2010 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
+import dbus
+import dbus.service
+
+from servicetest import (EventPattern, tp_name_prefix, tp_path_prefix,
+ call_async, sync_dbus, assertEquals)
+from mctest import exec_test, SimulatedConnection, create_fakecm_account,\
+ SimulatedChannel
+import constants as cs
+
+def test(q, bus, mc):
+ cm_name_ref = dbus.service.BusName(
+ tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ # Create an account. We're setting register=True here to verify
+ # that after one successful connection, it'll be removed (fd.o #28118).
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy",
+ "register": True}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ call_async(q, account, 'Set', cs.ACCOUNT, 'Enabled', False,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect('dbus-return', method='Set')
+
+ # Enable the account
+ call_async(q, account, 'Set', cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ # Set online presence
+ presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Fixing MC bugs'), signature='uss')
+ call_async(q, account, 'Set', cs.ACCOUNT,
+ 'RequestedPresence', presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself', has_presence=True)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE,
+ method='SetPresence',
+ args=list(presence[1:]),
+ handled=True)
+
+ # Connection falls over for a miscellaneous reason
+ conn.ConnectionError('com.example.My.Network.Is.Full.Of.Eels',
+ {'eels': 23, 'capacity': 23, 'debug-message': 'Too many eels'})
+ conn.StatusChanged(cs.CONN_STATUS_DISCONNECTED,
+ cs.CONN_STATUS_REASON_NETWORK_ERROR)
+
+ # MC reconnects. This time, we expect it to have deleted the 'register'
+ # parameter.
+ del params['register']
+
+ disconnected, connecting, e = q.expect_many(
+ EventPattern('dbus-signal', signal='AccountPropertyChanged',
+ predicate=(lambda e:
+ e.args[0].get('ConnectionStatus') ==
+ cs.CONN_STATUS_DISCONNECTED),
+ ),
+ EventPattern('dbus-signal', signal='AccountPropertyChanged',
+ predicate=(lambda e:
+ e.args[0].get('ConnectionStatus') ==
+ cs.CONN_STATUS_CONNECTING),
+ ),
+ EventPattern('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False),
+ )
+
+ assertEquals('/', disconnected.args[0].get('Connection'))
+ assertEquals('com.example.My.Network.Is.Full.Of.Eels',
+ disconnected.args[0].get('ConnectionError'))
+ assertEquals(
+ {'eels': 23, 'capacity': 23, 'debug-message': 'Too many eels'},
+ disconnected.args[0].get('ConnectionErrorDetails'))
+ assertEquals(cs.CONN_STATUS_DISCONNECTED,
+ disconnected.args[0].get('ConnectionStatus'))
+ assertEquals(cs.CONN_STATUS_REASON_NETWORK_ERROR,
+ disconnected.args[0].get('ConnectionStatusReason'))
+
+ assertEquals('/', connecting.args[0].get('Connection'))
+ assertEquals('com.example.My.Network.Is.Full.Of.Eels',
+ connecting.args[0].get('ConnectionError'))
+ assertEquals(
+ {'eels': 23, 'capacity': 23, 'debug-message': 'Too many eels'},
+ connecting.args[0].get('ConnectionErrorDetails'))
+ assertEquals(cs.CONN_STATUS_CONNECTING,
+ connecting.args[0].get('ConnectionStatus'))
+ assertEquals(cs.CONN_STATUS_REASON_REQUESTED,
+ connecting.args[0].get('ConnectionStatusReason'))
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself', has_presence=True)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ connecting, _ = q.expect_many(
+ EventPattern('dbus-signal', signal='AccountPropertyChanged',
+ predicate=(lambda e:
+ e.args[0].get('ConnectionStatus') ==
+ cs.CONN_STATUS_CONNECTING),
+ ),
+ EventPattern('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True),
+ )
+
+ assertEquals(conn.object_path, connecting.args[0].get('Connection'))
+ assertEquals('com.example.My.Network.Is.Full.Of.Eels',
+ connecting.args[0].get('ConnectionError'))
+ assertEquals(
+ {'eels': 23, 'capacity': 23, 'debug-message': 'Too many eels'},
+ connecting.args[0].get('ConnectionErrorDetails'))
+ assertEquals(cs.CONN_STATUS_CONNECTING,
+ connecting.args[0].get('ConnectionStatus'))
+ assertEquals(cs.CONN_STATUS_REASON_REQUESTED,
+ connecting.args[0].get('ConnectionStatusReason'))
+
+ assertEquals('com.example.My.Network.Is.Full.Of.Eels',
+ account_props.Get(cs.ACCOUNT, 'ConnectionError'))
+ assertEquals(
+ {'eels': 23, 'capacity': 23, 'debug-message': 'Too many eels'},
+ account_props.Get(cs.ACCOUNT, 'ConnectionErrorDetails'))
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ connected, _ = q.expect_many(
+ EventPattern('dbus-signal', signal='AccountPropertyChanged',
+ predicate=(lambda e:
+ e.args[0].get('ConnectionStatus') ==
+ cs.CONN_STATUS_CONNECTED),
+ ),
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE,
+ method='SetPresence',
+ args=list(presence[1:]),
+ handled=True),
+ )
+
+ assertEquals(conn.object_path, connected.args[0].get('Connection'))
+ assertEquals('', connected.args[0].get('ConnectionError'))
+ assertEquals({}, connected.args[0].get('ConnectionErrorDetails'))
+ assertEquals(cs.CONN_STATUS_CONNECTED,
+ connected.args[0].get('ConnectionStatus'))
+ assertEquals(cs.CONN_STATUS_REASON_REQUESTED,
+ connected.args[0].get('ConnectionStatusReason'))
+
+ assertEquals('', account_props.Get(cs.ACCOUNT, 'ConnectionError'))
+ assertEquals({}, account_props.Get(cs.ACCOUNT, 'ConnectionErrorDetails'))
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/request-online.py b/tests/twisted/account-manager/request-online.py
new file mode 100644
index 00000000..83f34f43
--- /dev/null
+++ b/tests/twisted/account-manager/request-online.py
@@ -0,0 +1,170 @@
+# Python is really rubbish. vim: set fileencoding=utf-8 :
+# Copyright © 2009–2010 Nokia Corporation
+# Copyright © 2009–2010 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
+import dbus.service
+
+from servicetest import (
+ EventPattern, tp_name_prefix, tp_path_prefix, assertEquals,
+)
+from mctest import (
+ exec_test, SimulatedConnection, create_fakecm_account,
+ SimulatedChannel, SimulatedClient, expect_client_setup,
+)
+import constants as cs
+
+def test(q, bus, mc):
+ cm_name_ref = dbus.service.BusName(
+ tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ http_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': 1L,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAM_TUBE,
+ cs.CHANNEL_TYPE_STREAM_TUBE + '.Service':
+ 'http'
+ }, signature='sv')
+ caps = dbus.Array([http_fixed_properties], signature='a{sv}')
+
+ # Be a Client
+ client = SimulatedClient(q, bus, 'downloader',
+ observe=[], approve=[], handle=[http_fixed_properties],
+ bypass_approval=False)
+ expect_client_setup(q, [client])
+
+ # Create an account
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ # The account is initially valid but disabled, and hence offline
+ props = account.GetAll(cs.ACCOUNT, dbus_interface=cs.PROPERTIES_IFACE)
+ assert not props['Enabled']
+ assert props['Valid']
+ # The spec says it should be (Offline, "", "") but I don't think the
+ # strings really matter. If anything, the second one should start out at
+ # "offline".
+ assertEquals(cs.PRESENCE_TYPE_OFFLINE, props['CurrentPresence'][0])
+
+ # Enable the account
+ account.Set(cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect('dbus-signal',
+ path=account.object_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT)
+
+ props = account.GetAll(cs.ACCOUNT, dbus_interface=cs.PROPERTIES_IFACE)
+ assert props['Enabled']
+ assert props['Valid']
+ # Ditto above re. string fields.
+ assertEquals(cs.PRESENCE_TYPE_OFFLINE, props['CurrentPresence'][0])
+
+ # Go online
+ requested_presence = dbus.Struct((dbus.UInt32(2L), dbus.String(u'brb'),
+ dbus.String(u'Be back soon!')))
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself')
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # MC does some setup, including fetching the list of Channels
+
+ q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True),
+ )
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ # Assert that the NormalizedName is harvested from the Connection at some
+ # point
+ while 1:
+ e = q.expect('dbus-signal',
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ path=account.object_path)
+ if 'NormalizedName' in e.args[0]:
+ assert e.args[0]['NormalizedName'] == 'myself', e.args
+ break
+
+ # Check the requested presence is online
+ properties = account.GetAll(cs.ACCOUNT,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('HasBeenOnline')
+ assertEquals(requested_presence, properties.get('RequestedPresence'))
+
+ # Since this Connection doesn't support SimplePresence, but it's online,
+ # the spec says that CurrentPresence should be Unset.
+ assertEquals((cs.PRESENCE_TYPE_UNSET, "", ""),
+ properties.get('CurrentPresence'))
+
+ new_channel = http_fixed_properties
+ buddy_handle = conn.ensure_handle(cs.HT_CONTACT, "buddy")
+ new_channel[cs.CHANNEL + '.TargetID'] = "buddy"
+ new_channel[cs.CHANNEL + '.TargetHandle'] = buddy_handle
+ new_channel[cs.CHANNEL + '.Requested'] = False
+ new_channel[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, new_channel)
+ chan.announce()
+
+ e = q.expect('dbus-method-call', method='HandleChannels')
+ q.dbus_return(e.message, signature='')
+
+ # Put the account offline
+ requested_presence = (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', '')
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ # In response, MC tells us to Disconnect, and we do
+ q.expect('dbus-method-call', method='Disconnect',
+ path=conn.object_path, handled=True)
+
+ # MC terminates the channel
+ # FIXME: it shouldn't do this!
+ #q.expect('dbus-method-call', method='Close',
+ # path=chan.object_path, handled=True)
+
+ properties = account.GetAll(cs.ACCOUNT, dbus_interface=cs.PROPERTIES_IFACE)
+ assertEquals('/', properties['Connection'])
+ assertEquals(cs.CONN_STATUS_DISCONNECTED, properties['ConnectionStatus'])
+ assertEquals(requested_presence, properties['CurrentPresence'])
+ assertEquals(requested_presence, properties['RequestedPresence'])
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/server-drops-us.py b/tests/twisted/account-manager/server-drops-us.py
new file mode 100644
index 00000000..b4db423f
--- /dev/null
+++ b/tests/twisted/account-manager/server-drops-us.py
@@ -0,0 +1,128 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus, TimeoutError
+from mctest import exec_test, SimulatedConnection, create_fakecm_account,\
+ SimulatedChannel
+import constants as cs
+
+params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+
+def test(q, bus, mc):
+ cm_name_ref = dbus.service.BusName(
+ tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ # Create an account
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ call_async(q, account, 'Set', cs.ACCOUNT, 'Enabled', False,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect('dbus-return', method='Set')
+
+ # Enable the account
+ call_async(q, account, 'Set', cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ # Set online presence
+ presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Fixing MC bugs'), signature='uss')
+ call_async(q, account, 'Set', cs.ACCOUNT,
+ 'RequestedPresence', presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself', has_presence=True)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ conn = drop_and_expect_reconnect(q, bus, conn)
+ conn = drop_and_expect_reconnect(q, bus, conn)
+ conn = drop_and_expect_reconnect(q, bus, conn)
+
+ forbidden = [EventPattern('dbus-method-call', method='RequestConnection')]
+ q.forbid_events(forbidden)
+
+ # Connection falls over for a miscellaneous reason
+ conn.StatusChanged(cs.CONN_STATUS_DISCONNECTED,
+ cs.CONN_STATUS_REASON_NETWORK_ERROR)
+ # Right, that's it, I'm giving up...
+
+ # This test can be considered to have succeeded if we don't
+ # RequestConnection again before the test fails due to timeout.
+ try:
+ q.expect('the end of the world')
+ except TimeoutError:
+ return
+ else:
+ raise AssertionError('An impossible event happened')
+
+def drop_and_expect_reconnect(q, bus, conn):
+ # Connection falls over for a miscellaneous reason
+ conn.StatusChanged(cs.CONN_STATUS_DISCONNECTED,
+ cs.CONN_STATUS_REASON_NETWORK_ERROR)
+
+ # MC reconnects
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself', has_presence=True)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ return conn
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/service.py b/tests/twisted/account-manager/service.py
new file mode 100644
index 00000000..f7df9903
--- /dev/null
+++ b/tests/twisted/account-manager/service.py
@@ -0,0 +1,112 @@
+# Copyright (C) 2010 Nokia Corporation
+# Copyright (C) 2010 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, call_async, assertEquals
+from mctest import exec_test, create_fakecm_account
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "wjt@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ srv_name = 'fu-bar-42'
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ # defaults to the empty string
+ assertEquals(account_props.Get(cs.ACCOUNT, 'Service'), '');
+
+ # set to a new value after creation
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Service', srv_name);
+ q.expect_many(
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT,
+ args=[{'Service': srv_name}]),
+ EventPattern('dbus-return', method='Set'),
+ )
+ assertEquals(account_props.Get(cs.ACCOUNT, 'Service'), srv_name)
+
+ # set to an invalid value (no actual change should occur)
+
+ # leading non-alphabetic (make sure _ isn't considered alphabetic)
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Service', '_fu-bar');
+ q.expect_many(EventPattern('dbus-error', method='Set'))
+ assertEquals(account_props.Get(cs.ACCOUNT, 'Service'), srv_name)
+
+ # leading non-alphabetic
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Service', '.moose');
+ q.expect_many(EventPattern('dbus-error', method='Set'))
+ assertEquals(account_props.Get(cs.ACCOUNT, 'Service'), srv_name)
+
+ # gregexes have an option to be lenient about trailing newlines:
+ # this makes sure we haven't made that mistake
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Service', srv_name + '\n');
+ q.expect_many(EventPattern('dbus-error', method='Set'))
+ assertEquals(account_props.Get(cs.ACCOUNT, 'Service'), srv_name)
+
+ # set to an empty string
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Service', '');
+ q.expect_many(EventPattern('dbus-return', method='Set'))
+ assertEquals(account_props.Get(cs.ACCOUNT, 'Service'), '')
+
+ # test creation with a service
+ account_manager = bus.get_object(cs.AM, cs.AM_PATH)
+ am_iface = dbus.Interface(account_manager, cs.AM)
+
+ service_prop = dbus.Dictionary({
+ cs.ACCOUNT + '.Service': "moomin-troll",
+ }, signature='sv')
+
+ call_async(q, am_iface, 'CreateAccount',
+ 'fakecm', # Connection_Manager
+ 'fakeprotocol', # Protocol
+ 'fakeaccount', # Display_Name
+ params, # Parameters
+ service_prop, # Properties
+ )
+
+ ret = q.expect('dbus-return', method='CreateAccount')
+ path = ret.value[0]
+ account = bus.get_object(cs.tp_name_prefix + '.AccountManager', path)
+ if_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+ props = if_props.GetAll(cs.ACCOUNT)
+ assertEquals(props.get('Service'), 'moomin-troll')
+
+ # attempt creation with a bogus service
+ service_prop = dbus.Dictionary({cs.ACCOUNT + '.Service': "1337"},
+ signature='sv')
+
+ call_async(q, am_iface, 'CreateAccount',
+ 'fakecm', # Connection_Manager
+ 'fakeprotocol', # Protocol
+ 'fakeaccount', # Display_Name
+ params, # Parameters
+ service_prop, # Properties
+ )
+
+ ret = q.expect('dbus-error', method='CreateAccount')
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-manager/update-parameters.py b/tests/twisted/account-manager/update-parameters.py
new file mode 100644
index 00000000..347a3135
--- /dev/null
+++ b/tests/twisted/account-manager/update-parameters.py
@@ -0,0 +1,194 @@
+# 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 os
+import time
+
+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,\
+ SimulatedChannel
+import constants as cs
+
+def test(q, bus, mc):
+ cm_name_ref = dbus.service.BusName(
+ tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ # Create an account
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy", 'nickname': 'albinoblacksheep'}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ # Enable the account
+ account.Set(cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect('dbus-signal',
+ path=account.object_path,
+ signal='AccountPropertyChanged',
+ interface=cs.ACCOUNT)
+
+ # Go online
+ requested_presence = dbus.Struct((dbus.UInt32(2L), dbus.String(u'brb'),
+ dbus.String(u'Be back soon!')))
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself')
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # MC does some setup, including fetching the list of Channels
+
+ q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True),
+ )
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ # Assert that the NormalizedName is harvested from the Connection at some
+ # point
+ while 1:
+ e = q.expect('dbus-signal',
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ path=account.object_path)
+ if 'NormalizedName' in e.args[0]:
+ assert e.args[0]['NormalizedName'] == 'myself', e.args
+ break
+
+ # Check the requested presence is online
+ properties = account.GetAll(cs.ACCOUNT,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('RequestedPresence') == requested_presence, \
+ properties.get('RequestedPresence')
+
+ # Set some parameters. They include setting account to \\, as a regression
+ # test for part of fd.o #28557.
+ call_async(q, account, 'UpdateParameters',
+ {
+ 'account': r'\\',
+ 'secret-mushroom': '/Amanita muscaria/',
+ 'snakes': dbus.UInt32(42),
+ 'com.example.Badgerable.Badgered': True,
+ },
+ [],
+ dbus_interface=cs.ACCOUNT)
+
+ set_call, ret, _ = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=conn.object_path,
+ interface=cs.PROPERTIES_IFACE, method='Set',
+ args=['com.example.Badgerable', 'Badgered', True],
+ handled=False),
+ EventPattern('dbus-return',
+ method='UpdateParameters'),
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ args=[{'Parameters': {
+ 'account': r'\\',
+ 'com.example.Badgerable.Badgered': True,
+ 'password': 'secrecy',
+ 'nickname': 'albinoblacksheep',
+ 'secret-mushroom': '/Amanita muscaria/',
+ 'snakes': 42,
+ }}]),
+ )
+
+ # the D-Bus property should be set instantly; the others will take effect
+ # on reconnection
+ not_yet = ret.value[0]
+ not_yet.sort()
+ assert not_yet == ['account', 'secret-mushroom', 'snakes'], not_yet
+
+ # Unset some parameters
+ call_async(q, account, 'UpdateParameters',
+ {},
+ ['nickname', 'com.example.Badgerable.Badgered'],
+ dbus_interface=cs.ACCOUNT)
+
+ ret, _ = q.expect_many(
+ EventPattern('dbus-return',
+ method='UpdateParameters'),
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ interface=cs.ACCOUNT, signal='AccountPropertyChanged',
+ args=[{'Parameters': {
+ 'account': r'\\',
+ 'password': 'secrecy',
+ 'secret-mushroom': '/Amanita muscaria/',
+ 'snakes': 42,
+ }}]),
+ )
+
+ # there's no well-defined way to unset a D-Bus property, so it'll go back
+ # to its implied default value only after reconnection
+ #
+ # FIXME: in a perfect implementation, we know that this particular D-Bus
+ # property has a default, so maybe we should set it back to that?
+ not_yet = ret.value[0]
+ not_yet.sort()
+ assert not_yet == ['com.example.Badgerable.Badgered', 'nickname'], not_yet
+
+ accounts_dir = os.environ['MC_ACCOUNT_DIR']
+
+ # fd.o #28557: when the file has been updated, the account parameter
+ # has its two backslashes doubled to 4 (because of the .desktop encoding),
+ # but they are not doubled again.
+ i = 0
+ updated = False
+ while i < 500:
+
+ for line in open(accounts_dir +
+ '/mcp-test-diverted-account-plugin.conf', 'r'):
+ if line.startswith('param-account=') and '\\' in line:
+ assertEquals(r'param-account=\\\\' + '\n', line)
+ updated = True
+
+ if updated:
+ break
+
+ # just to not busy-wait
+ time.sleep(0.1)
+ i += 1
+
+ assert updated
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-requests/cancel.py b/tests/twisted/account-requests/cancel.py
new file mode 100644
index 00000000..d5776ae6
--- /dev/null
+++ b/tests/twisted/account-requests/cancel.py
@@ -0,0 +1,127 @@
+# 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
+"""Regression test for the unofficial Account.Interface.Requests API when
+a channel can be created successfully.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # No Approver should be invoked at any point during this test, because the
+ # Channel was Requested
+ def fail_on_approval(e):
+ raise AssertionError('Approver should not be invoked')
+ q.add_dbus_method_impl(fail_on_approval, path=client.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation')
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ user_action_time = dbus.Int64(1238582606)
+
+ # chat UI calls ChannelDispatcher.CreateChannel
+ # (or in this case, an equivalent non-standard method on the Account)
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, account_requests, 'Create',
+ request, user_action_time, client.bus_name)
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed() - but not
+ # in this non-standard API, which fires off the request instantly
+ ret, cm_request_call, add_request = q.expect_many(
+ EventPattern('dbus-return', method='Create'),
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS, method='CreateChannel',
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS, method='AddRequest',
+ path=client.object_path),
+ )
+
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+
+ assert add_request.args[0] == request_path
+ q.dbus_return(add_request.message, signature='')
+
+ # Actually, never mind.
+ account_requests.Cancel(request_path)
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ # Channel is unwanted now, MC stabs it in the face
+ accsig, stdsig, _ = q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Failed'),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Failed'),
+ EventPattern('dbus-method-call', path=channel.object_path,
+ interface=cs.CHANNEL, method='Close', handled=True),
+ )
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-requests/create-text.py b/tests/twisted/account-requests/create-text.py
new file mode 100644
index 00000000..6488f99c
--- /dev/null
+++ b/tests/twisted/account-requests/create-text.py
@@ -0,0 +1,176 @@
+# 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
+"""Regression test for the unofficial Account.Interface.Requests API when
+a channel can be created successfully.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # No Approver should be invoked at any point during this test, because the
+ # Channel was Requested
+ def fail_on_approval(e):
+ raise AssertionError('Approver should not be invoked')
+ q.add_dbus_method_impl(fail_on_approval, path=client.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation')
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ test_channel_creation(q, bus, account, client, conn, False)
+ test_channel_creation(q, bus, account, client, conn, True)
+
+def test_channel_creation(q, bus, account, client, conn, ensure):
+ user_action_time = dbus.Int64(1238582606)
+
+ # chat UI calls ChannelDispatcher.EnsureChannel or CreateChannel
+ # (or in this case, an equivalent non-standard method on the Account)
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, account_requests,
+ (ensure and 'EnsureChannel' or 'Create'),
+ request, user_action_time, client.bus_name)
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed() - but not
+ # in this non-standard API, which fires off the request instantly
+ ret, cm_request_call, add_request = q.expect_many(
+ EventPattern('dbus-return',
+ method=(ensure and 'EnsureChannel' or 'Create')),
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method=(ensure and 'EnsureChannel' or 'CreateChannel'),
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS, method='AddRequest',
+ path=client.object_path),
+ )
+
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+
+ assert add_request.args[0] == request_path
+ request_props = add_request.args[1]
+ assert request_props[cs.CR + '.Account'] == account.object_path
+ assert request_props[cs.CR + '.Requests'] == [request]
+ assert request_props[cs.CR + '.UserActionTime'] == user_action_time
+ assert request_props[cs.CR + '.PreferredHandler'] == client.bus_name
+
+ q.dbus_return(add_request.message, signature='')
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ if ensure:
+ q.dbus_return(cm_request_call.message, True, # <- Yours
+ channel.object_path, channel.immutable, signature='boa{sv}')
+ else: # Create
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ # Observer should get told, processing waits for it
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert e.args[4] == [request_path], e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+
+ # Observer says "OK, go"
+ q.dbus_return(e.message, signature='')
+
+ # Handler is next
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ assert e.args[3] == [request_path], e.args
+
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ # CR emits Succeeded (or in Mardy's version, Account emits Succeeded)
+ q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded',
+ args=[request_path]),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Succeeded'),
+ )
+
+ # Close the channel
+ channel.close()
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-requests/delete-account-during-request.py b/tests/twisted/account-requests/delete-account-during-request.py
new file mode 100644
index 00000000..dd24fe4c
--- /dev/null
+++ b/tests/twisted/account-requests/delete-account-during-request.py
@@ -0,0 +1,136 @@
+# 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
+"""Regression test for the unofficial Account.Interface.Requests API, when
+an account is deleted while requesting a channel from that account.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ user_action_time = dbus.Int64(1238582606)
+
+ # chat UI calls ChannelDispatcher.CreateChannel
+ # (or in this case, an equivalent non-standard method on the Account)
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, account_requests, 'Create',
+ request, user_action_time, client.bus_name)
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed() - but not
+ # in this non-standard API, which fires off the request instantly
+ ret, cm_request_call, add_request = q.expect_many(
+ EventPattern('dbus-return',
+ method='Create'),
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='CreateChannel',
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS, method='AddRequest',
+ path=client.object_path),
+ )
+
+ request_path = ret.value[0]
+
+ q.dbus_return(add_request.message, signature='')
+
+ # Before the channel is returned, we delete the account
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ assert account_iface.Remove() is None
+ account_event, account_manager_event = q.expect_many(
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ signal='Removed',
+ interface=cs.ACCOUNT,
+ args=[]
+ ),
+ EventPattern('dbus-signal',
+ path=cs.AM_PATH,
+ signal='AccountRemoved',
+ interface=cs.AM,
+ args=[account.object_path]
+ ),
+ )
+
+ # You know that request I told you about? Not going to happen.
+ remove_request = q.expect('dbus-method-call',
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='RemoveRequest',
+ handled=False)
+ assert remove_request.args[0] == request_path
+ # FIXME: the spec should maybe define what error this will be. Currently,
+ # it's Disconnected
+ assert remove_request.args[1].startswith(tp_name_prefix + '.Error.')
+
+ q.expect_many(
+ EventPattern('dbus-signal',
+ path=request_path, interface=cs.CR, signal='Failed',
+ args=remove_request.args[1:]),
+ EventPattern('dbus-signal',
+ path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS,
+ signal='Failed',
+ args=remove_request.args),
+ )
+
+ q.dbus_return(remove_request.message, signature='')
+
+ # ... and the Connection is told to disconnect, hopefully before the
+ # Channel has actually been established
+ e = q.expect('dbus-method-call',
+ path=conn.object_path,
+ interface=cs.CONN,
+ method='Disconnect',
+ args=[],
+ handled=True)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/account-storage/default-keyring-storage.py b/tests/twisted/account-storage/default-keyring-storage.py
new file mode 100644
index 00000000..5d066d6a
--- /dev/null
+++ b/tests/twisted/account-storage/default-keyring-storage.py
@@ -0,0 +1,228 @@
+# Copyright (C) 2009-2010 Nokia Corporation
+# Copyright (C) 2009-2010 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 time
+import os
+import os.path
+import signal
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account, get_account_manager, \
+ get_fakecm_account, make_mc, connect_to_mc, keyfile_read
+import constants as cs
+
+use_keyring = False
+if ('MC_TEST_GNOME_KEYRING' in os.environ and
+ os.environ['MC_TEST_GNOME_KEYRING'] == '1'):
+ use_keyring = True
+
+def create_keyring():
+ if not use_keyring:
+ return
+
+ keyring = os.popen('../keyring-command create').read()
+ if not keyring or keyring.startswith('**'):
+ return None
+ return keyring[:-1]
+
+def remove_keyring(name):
+ if not use_keyring:
+ return
+
+ os.system('../keyring-command remove ' + name)
+
+def account_store(op, backend, key=None, value=None):
+ cmd = [ '../account-store', op, backend,
+ 'fakecm/fakeprotocol/dontdivert_40example_2ecom0' ]
+ if key:
+ cmd.append(key)
+ if value:
+ cmd.append(value)
+
+ lines = os.popen(' '.join(cmd)).read()
+ ret = []
+ for line in lines.split('\n'):
+ if line.startswith('** '):
+ continue
+
+ if line:
+ ret.append(line)
+
+ if len(ret) > 0:
+ return ret[0]
+ else:
+ return None
+
+def start_gnome_keyring_daemon(ctl_dir):
+ if not use_keyring:
+ return
+
+ os.chmod(ctl_dir, 0700)
+ env = os.popen('gnome-keyring-daemon -d --control-directory=' + ctl_dir).read()
+ env_file = open(ctl_dir + '/gnome-keyring-env', 'w')
+
+ for line in env.split('\n'):
+ if line:
+ k, v = line.split('=', 1)
+ print "Adding to env: %s=%s" % (k, v)
+ os.environ[k] = v
+ env_file.write('%s=%s\n' % (k, v))
+
+ keyring_name = create_keyring()
+ assert keyring_name
+ print "Created new keyring name, putting to env", keyring_name
+ os.environ['MC_KEYRING_NAME'] = keyring_name
+ env_file.write('MC_KEYRING_NAME=%s\n' % keyring_name)
+ env_file.close()
+
+def stop_gnome_keyring_daemon():
+ if not use_keyring:
+ return
+
+ keyring_name = os.environ['MC_KEYRING_NAME']
+ keyring_daemon_pid = os.environ['GNOME_KEYRING_PID']
+
+ if keyring_name:
+ print "Removing keyring", keyring_name
+ remove_keyring(keyring_name)
+
+ if keyring_daemon_pid:
+ print "Killing keyring daemon, pid =", keyring_daemon_pid
+ os.kill(int(keyring_daemon_pid), 15)
+
+def test(q, bus, mc):
+ ctl_dir = os.environ['MC_ACCOUNT_DIR']
+ key_file_name = os.path.join(ctl_dir, 'accounts.cfg')
+ group = 'fakecm/fakeprotocol/dontdivert_40example_2ecom0'
+
+ account_manager, properties, interfaces = connect_to_mc(q, bus, mc)
+
+ assert properties.get('ValidAccounts') == [], \
+ properties.get('ValidAccounts')
+ assert properties.get('InvalidAccounts') == [], \
+ properties.get('InvalidAccounts')
+
+ params = dbus.Dictionary({"account": "dontdivert@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_path = account.__dbus_object_path__
+
+ # Check the account is correctly created
+ properties = account_manager.GetAll(cs.AM,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('ValidAccounts') == [account_path], properties
+ account_path = properties['ValidAccounts'][0]
+ assert isinstance(account_path, dbus.ObjectPath), repr(account_path)
+ assert properties.get('InvalidAccounts') == [], properties
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+ nokia_compat = dbus.Interface(account, cs.ACCOUNT_IFACE_NOKIA_COMPAT)
+
+ # Alter some miscellaneous r/w properties
+
+ account_props.Set(cs.ACCOUNT, 'DisplayName', 'Work account')
+ account_props.Set(cs.ACCOUNT, 'Icon', 'im-jabber')
+ account_props.Set(cs.ACCOUNT, 'Nickname', 'Joe Bloggs')
+ nokia_compat.SetHasBeenOnline()
+ account_props.Set(cs.ACCOUNT_IFACE_NOKIA_COMPAT,
+ 'SecondaryVCardFields', ['x-badger', 'x-mushroom'])
+
+ # Restart MC
+
+ secret_debug_api = dbus.Interface(bus.get_object(cs.AM, "/"),
+ 'org.freedesktop.Telepathy.MissionControl5.RegressionTests')
+ secret_debug_api.Abort()
+
+ # Make sure MC exits
+ q.expect('dbus-signal', signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == 'org.freedesktop.Telepathy.AccountManager' and
+ e.args[2] == ''))
+
+ # .. let's check the keyfile
+ kf = keyfile_read(key_file_name)
+ assert group in kf, kf
+ assert kf[group]['manager'] == 'fakecm'
+ assert kf[group]['protocol'] == 'fakeprotocol'
+ assert kf[group]['param-account'] == params['account'], kf
+ assert kf[group]['DisplayName'] == 'Work account', kf
+ assert kf[group]['Icon'] == 'im-jabber', kf
+ assert kf[group]['Nickname'] == 'Joe Bloggs', kf
+ assert kf[group]['HasBeenOnline'] == 'true', kf
+ assert kf[group]['SecondaryVCardFields'] == 'x-badger;x-mushroom;', kf
+
+ # This works wherever the password is stored
+ pwd = account_store('get', 'default', 'param-password')
+ assert pwd == params['password'], pwd
+
+ # If we're using GNOME keyring, the password should not be in the
+ # password file
+ if use_keyring:
+ assert 'param-password' not in kf[group]
+ else:
+ assert kf[group]['param-password'] == params['password'], kf
+
+ # Reactivate MC
+ bus.get_object(cs.MC, "/")
+
+ # Wait until it's up
+ q.expect('dbus-signal', signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == 'org.freedesktop.Telepathy.AccountManager' and
+ e.args[2] != ''))
+
+ mc = make_mc(bus, q.append)
+ account_manager, properties, interfaces = connect_to_mc(q, bus, mc)
+ account = get_fakecm_account(bus, mc, account_path)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+
+ # Delete the account
+ assert account_iface.Remove() is None
+ account_event, account_manager_event = q.expect_many(
+ EventPattern('dbus-signal',
+ path=account_path,
+ signal='Removed',
+ interface=cs.ACCOUNT,
+ args=[]
+ ),
+ EventPattern('dbus-signal',
+ path=cs.AM_PATH,
+ signal='AccountRemoved',
+ interface=cs.AM,
+ args=[account_path]
+ ),
+ )
+
+ # Check the account is correctly deleted
+ kf = keyfile_read(key_file_name)
+ assert group not in kf, kf
+
+
+if __name__ == '__main__':
+ ctl_dir = os.environ['MC_ACCOUNT_DIR']
+ start_gnome_keyring_daemon(ctl_dir)
+ exec_test(test, {}, timeout=10)
+ stop_gnome_keyring_daemon()
diff --git a/tests/twisted/account-storage/diverted-storage.py b/tests/twisted/account-storage/diverted-storage.py
new file mode 100644
index 00000000..46b66b1b
--- /dev/null
+++ b/tests/twisted/account-storage/diverted-storage.py
@@ -0,0 +1,139 @@
+# Copyright (C) 2009-2010 Nokia Corporation
+# Copyright (C) 2009-2010 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 time
+import os
+import os.path
+import signal
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account, get_account_manager, \
+ get_fakecm_account, make_mc, connect_to_mc, keyfile_read
+import constants as cs
+
+def test(q, bus, mc):
+ empty_key_file_name = os.path.join(os.environ['MC_ACCOUNT_DIR'], 'accounts.cfg')
+
+ key_file_name = os.path.join(os.getenv('XDG_CACHE_HOME'),
+ 'mcp-test-diverted-account-plugin.conf')
+ group = 'fakecm/fakeprotocol/someguy_40example_2ecom0'
+
+ account_manager, properties, interfaces = connect_to_mc(q, bus, mc)
+
+ assert properties.get('ValidAccounts') == [], \
+ properties.get('ValidAccounts')
+ assert properties.get('InvalidAccounts') == [], \
+ properties.get('InvalidAccounts')
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_path = account.__dbus_object_path__
+
+ # Check the account is correctly created
+ properties = account_manager.GetAll(cs.AM,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ assert properties.get('ValidAccounts') == [account_path], properties
+ account_path = properties['ValidAccounts'][0]
+ assert isinstance(account_path, dbus.ObjectPath), repr(account_path)
+ assert properties.get('InvalidAccounts') == [], properties
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+ nokia_compat = dbus.Interface(account, cs.ACCOUNT_IFACE_NOKIA_COMPAT)
+
+ # Alter some miscellaneous r/w properties
+
+ account_props.Set(cs.ACCOUNT, 'DisplayName', 'Work account')
+ account_props.Set(cs.ACCOUNT, 'Icon', 'im-jabber')
+ account_props.Set(cs.ACCOUNT, 'Nickname', 'Joe Bloggs')
+ nokia_compat.SetHasBeenOnline()
+ account_props.Set(cs.ACCOUNT_IFACE_NOKIA_COMPAT,
+ 'SecondaryVCardFields', ['x-badger', 'x-mushroom'])
+
+ secret_debug_api = dbus.Interface(bus.get_object(cs.AM, "/"),
+ 'org.freedesktop.Telepathy.MissionControl5.RegressionTests')
+ secret_debug_api.Abort()
+
+ # Make sure MC exits
+ q.expect('dbus-signal', signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == 'org.freedesktop.Telepathy.AccountManager' and
+ e.args[2] == ''))
+
+ # .. let's check the diverted keyfile
+ kf = keyfile_read(key_file_name)
+ assert group in kf, kf
+ assert kf[group]['manager'] == 'fakecm'
+ assert kf[group]['protocol'] == 'fakeprotocol'
+ assert kf[group]['param-account'] == params['account'], kf
+ assert kf[group]['param-password'] == params['password'], kf
+ assert kf[group]['DisplayName'] == 'Work account', kf
+ assert kf[group]['Icon'] == 'im-jabber', kf
+ assert kf[group]['Nickname'] == 'Joe Bloggs', kf
+ assert kf[group]['HasBeenOnline'] == 'true', kf
+ assert kf[group]['SecondaryVCardFields'] == 'x-badger;x-mushroom;', kf
+
+ # default keyfile should be empty
+ ekf = keyfile_read(empty_key_file_name)
+ assert ekf == { None: {} }, ekf
+
+ # Reactivate MC
+ bus.get_object(cs.MC, "/")
+
+ # Wait until it's up
+ q.expect('dbus-signal', signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == 'org.freedesktop.Telepathy.AccountManager' and
+ e.args[2] != ''))
+
+ mc = make_mc(bus, q.append)
+ account_manager, properties, interfaces = connect_to_mc(q, bus, mc)
+ account = get_fakecm_account(bus, mc, account_path)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+
+ # Delete the account
+ assert account_iface.Remove() is None
+ account_event, account_manager_event = q.expect_many(
+ EventPattern('dbus-signal',
+ path=account_path,
+ signal='Removed',
+ interface=cs.ACCOUNT,
+ args=[]
+ ),
+ EventPattern('dbus-signal',
+ path=cs.AM_PATH,
+ signal='AccountRemoved',
+ interface=cs.AM,
+ args=[account_path]
+ ),
+ )
+
+ # Check the account is correctly deleted
+ kf = keyfile_read(key_file_name)
+ assert group not in kf, kf
+
+if __name__ == '__main__':
+ exec_test(test, {}, timeout=10)
diff --git a/tests/twisted/account-storage/libaccounts-sso-storage.py b/tests/twisted/account-storage/libaccounts-sso-storage.py
new file mode 100644
index 00000000..c6b68b6c
--- /dev/null
+++ b/tests/twisted/account-storage/libaccounts-sso-storage.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2009-2010 Nokia Corporation
+# Copyright (C) 2009-2010 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
+
+raise AssertionError('Disabled for 5.6 branch')
+
+import time
+import os
+import os.path
+import signal
+import sys
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, create_fakecm_account, get_account_manager, \
+ get_fakecm_account, make_mc
+import constants as cs
+
+if ('ACCOUNTS' not in os.environ or not os.environ['ACCOUNTS']):
+ print "Not testing accounts-sso storage"
+ sys.exit(0)
+
+def account_store(op, backend, key=None, value=None):
+ cmd = [ '../account-store', op, backend,
+ 'colltest42@googlemail.com' ]
+ if key:
+ cmd.append(key)
+ if value:
+ cmd.append(value)
+
+ lines = os.popen(' '.join(cmd)).read()
+ ret = []
+ for line in lines.split('\n'):
+ if line.startswith('** '):
+ continue
+
+ if line:
+ ret.append(line)
+
+ if len(ret) > 0:
+ return ret[0]
+ else:
+ return None
+
+def prepare_accounts_db(ctl_dir):
+ os.system('cp %s/../tools/example-accounts.db %s/accounts.db' % (ctl_dir, ctl_dir))
+ os.system('cp %s/../tools/accounts-sso-example.service %s/google-talk.service' % (ctl_dir, ctl_dir))
+
+def test(q, bus, mc):
+ account_manager, properties, interfaces = connect_to_mc(q, bus, mc)
+
+ va = properties.get('ValidAccounts')
+ assert va == [], va
+
+ ia = properties.get('InvalidAccounts')
+ assert len(ia) == 1
+
+ account_path = ia[0]
+ print repr(account_path)
+
+ account = get_fakecm_account(bus, mc, account_path)
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ # FIXME at this point MC crashes
+ properties = account_props.GetAll(cs.ACCOUNT)
+
+
+if __name__ == '__main__':
+ ctl_dir = os.environ['ACCOUNTS']
+ prepare_accounts_db(ctl_dir)
+ exec_test(test, {}, timeout=10)
diff --git a/tests/twisted/account/addressing.py b/tests/twisted/account/addressing.py
new file mode 100755
index 00000000..84faae99
--- /dev/null
+++ b/tests/twisted/account/addressing.py
@@ -0,0 +1,85 @@
+# Copyright (C) 2010 Nokia Corporation
+# Copyright (C) 2010 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
+import dbus
+import dbus.service
+
+from servicetest import unwrap, assertContains, assertEquals, assertSameSets
+from mctest import exec_test, create_fakecm_account
+import constants as cs
+
+def get_schemes(props):
+ return unwrap (props.Get (cs.ACCOUNT_IFACE_ADDRESSING, 'URISchemes'))
+
+def test(q, bus, mc):
+ params = dbus.Dictionary ({"account": "jc.denton@example.com",
+ "password": "ionstorm"},
+ signature='sv')
+ (cm_name_ref, account) = create_fakecm_account (q, bus, mc, params)
+
+ account_iface = dbus.Interface (account, cs.ACCOUNT)
+ account_props = dbus.Interface (account, cs.PROPERTIES_IFACE)
+ address_iface = dbus.Interface (account, cs.ACCOUNT_IFACE_ADDRESSING)
+
+ uri_schemes = get_schemes (account_props)
+
+ # initial URI scheme list is empty
+ assertEquals (uri_schemes, [])
+
+ # remove URI from empty list:
+ address_iface.SetURISchemeAssociation ('email', False)
+ uri_schemes = get_schemes (account_props)
+ assertEquals (uri_schemes, [])
+
+ # add association to empty list
+ address_iface.SetURISchemeAssociation ('email', True)
+ uri_schemes = get_schemes (account_props)
+ assertEquals (uri_schemes, ['email'])
+
+ # add association to list where it already resides
+ address_iface.SetURISchemeAssociation ('email', True)
+ uri_schemes = get_schemes (account_props)
+ assertEquals (uri_schemes, ['email'])
+
+ # remove association to produce empty list
+ address_iface.SetURISchemeAssociation ('email', False)
+ uri_schemes = get_schemes (account_props)
+ assertEquals (uri_schemes, [])
+
+ # extend list to 3 schemes, with some redundant additions:
+ address_iface.SetURISchemeAssociation ('scheme-a', True)
+ address_iface.SetURISchemeAssociation ('scheme-b', True)
+ address_iface.SetURISchemeAssociation ('scheme-c', True)
+ address_iface.SetURISchemeAssociation ('scheme-a', True)
+ address_iface.SetURISchemeAssociation ('scheme-c', True)
+ uri_schemes = get_schemes (account_props)
+ assertSameSets (['scheme-a','scheme-b','scheme-c'], uri_schemes)
+
+ # remove a scheme that's not there from a non-empty list
+ address_iface.SetURISchemeAssociation ('scheme-d', False)
+ uri_schemes = get_schemes (account_props)
+ assertSameSets (['scheme-a','scheme-b','scheme-c'], uri_schemes)
+
+ # remove one that is there:
+ address_iface.SetURISchemeAssociation ('scheme-b', False)
+ uri_schemes = get_schemes (account_props)
+ assertSameSets (['scheme-a','scheme-c'], uri_schemes)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/accounts/README b/tests/twisted/accounts/README
new file mode 100644
index 00000000..27bd48fd
--- /dev/null
+++ b/tests/twisted/accounts/README
@@ -0,0 +1,3 @@
+Directory for twisted test to write the accounts.cfg file. $MC_ACCOUNT_DIR is
+set to this directory in the twisted environment. accounts.cfg is deleted before running any test.
+
diff --git a/tests/twisted/capabilities/contact-caps.py b/tests/twisted/capabilities/contact-caps.py
new file mode 100644
index 00000000..7a348df0
--- /dev/null
+++ b/tests/twisted/capabilities/contact-caps.py
@@ -0,0 +1,161 @@
+# 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
+
+"""Regression test for pushing clients' capabilities into a CM with
+ContactCapabilities (final version, which is the same as draft 2).
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ forbidden = [
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CONN_IFACE_CAPS,
+ method='AdvertiseCapabilities'),
+ ]
+ q.forbid_events(forbidden)
+
+ # Two clients want to handle channels: MediaCall is running, and AbiWord
+ # is activatable.
+
+ # this must match the .client file
+ abi_contact_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAM_TUBE,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL_TYPE_STREAM_TUBE + '.Service': 'x-abiword',
+ }, signature='sv')
+ abi_room_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAM_TUBE,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_ROOM,
+ cs.CHANNEL_TYPE_STREAM_TUBE + '.Service': 'x-abiword',
+ }, signature='sv')
+
+ media_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ }, signature='sv')
+ media_call = SimulatedClient(q, bus, 'MediaCall',
+ observe=[], approve=[], handle=[media_fixed_properties],
+ cap_tokens=[cs.CHANNEL_IFACE_MEDIA_SIGNALLING + '/ice-udp',
+ cs.CHANNEL_IFACE_MEDIA_SIGNALLING + '/audio/speex',
+ cs.CHANNEL_IFACE_MEDIA_SIGNALLING + '/video/theora'],
+ bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [media_call])
+
+ def check_contact_caps(e):
+ structs = e.args[0]
+
+ filters = {}
+ tokens = {}
+
+ assert len(structs) == 3
+
+ for struct in structs:
+ assert struct[0] not in filters
+ filters[struct[0]] = sorted(struct[1])
+ tokens[struct[0]] = sorted(struct[2])
+
+ assert media_fixed_properties in filters[cs.CLIENT + '.MediaCall']
+ assert len(filters[cs.CLIENT + '.MediaCall']) == 1
+
+ assert abi_room_fixed_properties in filters[cs.CLIENT + '.AbiWord']
+ assert abi_contact_fixed_properties in filters[cs.CLIENT + '.AbiWord']
+ assert len(filters[cs.CLIENT + '.AbiWord']) == 2
+
+ assert len(tokens[cs.CLIENT + '.MediaCall']) == 3
+ assert cs.CHANNEL_IFACE_MEDIA_SIGNALLING + '/ice-udp' in \
+ tokens[cs.CLIENT + '.MediaCall']
+ assert cs.CHANNEL_IFACE_MEDIA_SIGNALLING + '/audio/speex' in \
+ tokens[cs.CLIENT + '.MediaCall']
+ assert cs.CHANNEL_IFACE_MEDIA_SIGNALLING + '/video/theora' in \
+ tokens[cs.CLIENT + '.MediaCall']
+
+ assert len(tokens[cs.CLIENT + '.AbiWord']) == 2
+ assert 'com.example.Foo' in tokens[cs.CLIENT + '.AbiWord']
+ assert 'com.example.Bar' in tokens[cs.CLIENT + '.AbiWord']
+
+ return True
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn, before = enable_fakecm_account(q, bus, mc, account, params,
+ extra_interfaces=[cs.CONN_IFACE_CONTACT_CAPS,
+ cs.CONN_IFACE_CAPS],
+ expect_before_connect=[
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CONN_IFACE_CONTACT_CAPS,
+ method='UpdateCapabilities',
+ predicate=check_contact_caps),
+ ])
+ q.dbus_return(before.message, signature='')
+
+ irssi_bus = dbus.bus.BusConnection()
+ irssi_bus.set_exit_on_disconnect(False) # we'll disconnect later
+ q.attach_to_bus(irssi_bus)
+ irssi_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_ROOM,
+ }, signature='sv')
+ irssi = SimulatedClient(q, irssi_bus, 'Irssi',
+ observe=[], approve=[], handle=[irssi_fixed_properties],
+ cap_tokens=[],
+ bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [irssi])
+
+ e = q.expect('dbus-method-call', handled=False,
+ interface=cs.CONN_IFACE_CONTACT_CAPS,
+ method='UpdateCapabilities')
+
+ assert len(e.args[0]) == 1
+ struct = e.args[0][0]
+ assert struct[0] == cs.CLIENT + '.Irssi'
+ assert struct[1] == [irssi_fixed_properties]
+ assert struct[2] == []
+
+ # When Irssi exits, the CM is told it has gone
+ irssi.release_name()
+ del irssi
+ irssi_bus.flush()
+ irssi_bus.close()
+
+ e = q.expect('dbus-method-call', handled=False,
+ interface=cs.CONN_IFACE_CONTACT_CAPS,
+ method='UpdateCapabilities')
+
+ assert len(e.args[0]) == 1
+ struct = e.args[0][0]
+ assert struct[0] == cs.CLIENT + '.Irssi'
+ assert struct[1] == []
+ assert struct[2] == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/capabilities/draft-1.py b/tests/twisted/capabilities/draft-1.py
new file mode 100644
index 00000000..4924e74b
--- /dev/null
+++ b/tests/twisted/capabilities/draft-1.py
@@ -0,0 +1,121 @@
+# 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
+
+"""Regression test for pushing clients' capabilities into a CM with
+draft 1 of ContactCapabilities.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ # Two clients want to handle channels: MediaCall is running, and AbiWord
+ # is activatable.
+
+ # this must match the .client file
+ abi_contact_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAM_TUBE,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL_TYPE_STREAM_TUBE + '.Service': 'x-abiword',
+ }, signature='sv')
+ abi_room_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAM_TUBE,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_ROOM,
+ cs.CHANNEL_TYPE_STREAM_TUBE + '.Service': 'x-abiword',
+ }, signature='sv')
+
+ media_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ }, signature='sv')
+ media_call = SimulatedClient(q, bus, 'MediaCall',
+ observe=[], approve=[],
+ handle=[media_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [media_call])
+
+ had = []
+
+ def check_legacy_caps(e):
+ # Because MC has no idea how to map Client capabilities into legacy
+ # capabilities, it assumes that every client has all the flags in
+ # the world. In this example we have (only) a StreamedMedia client
+ # and a stream-tube client, so that's what MC will tell us.
+ add = e.args[0]
+ remove = e.args[1]
+
+ assert (cs.CHANNEL_TYPE_STREAMED_MEDIA, 2L**32-1) in add
+ assert (cs.CHANNEL_TYPE_STREAM_TUBE, 2L**32-1) in add
+
+ # MC puts StreamTube in the list twice - arguably a bug, but
+ # CMs should cope. So, don't assert about the length of the list
+ for item in add:
+ assert item in (
+ (cs.CHANNEL_TYPE_STREAMED_MEDIA, 2L**32-1),
+ (cs.CHANNEL_TYPE_STREAM_TUBE, 2L**32-1),
+ )
+
+ assert len(remove) == 0
+
+ had.append('legacy')
+ return True
+
+ def check_draft_1_caps(e):
+ aasv = e.args[0]
+
+ assert media_fixed_properties in aasv
+ assert abi_room_fixed_properties in aasv
+ assert abi_contact_fixed_properties in aasv
+ assert len(aasv) == 3
+
+ had.append('draft-1')
+ return True
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params,
+ extra_interfaces=[cs.CONN_IFACE_CONTACT_CAPS_DRAFT1,
+ cs.CONN_IFACE_CAPS],
+ expect_after_connect=[
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CONN_IFACE_CONTACT_CAPS_DRAFT1,
+ method='SetSelfCapabilities',
+ predicate=check_draft_1_caps),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CONN_IFACE_CAPS,
+ method='AdvertiseCapabilities',
+ predicate=check_legacy_caps),
+ ])
+
+ # MC currently calls AdvertiseCapabilities first, and changing this
+ # risks causing regressions (the test case in telepathy-gabble assumes
+ # this order).
+ assert had == ['legacy', 'draft-1']
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/capabilities/legacy-caps.py b/tests/twisted/capabilities/legacy-caps.py
new file mode 100644
index 00000000..81d99ab6
--- /dev/null
+++ b/tests/twisted/capabilities/legacy-caps.py
@@ -0,0 +1,85 @@
+# 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
+
+"""Regression test for pushing clients' capabilities into an old CM, with only
+the old Capabilities interface.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ # Two clients want to handle channels: MediaCall is running, and AbiWord
+ # is activatable.
+
+ media_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ }, signature='sv')
+ media_call = SimulatedClient(q, bus, 'MediaCall',
+ observe=[], approve=[],
+ handle=[media_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [media_call])
+
+ def check_legacy_caps(e):
+ # Because MC has no idea how to map Client capabilities into legacy
+ # capabilities, it assumes that every client has all the flags in
+ # the world. In this example we have (only) a StreamedMedia client
+ # and a stream-tube client, so that's what MC will tell us.
+ add = e.args[0]
+ remove = e.args[1]
+
+ assert (cs.CHANNEL_TYPE_STREAMED_MEDIA, 2L**32-1) in add
+ assert (cs.CHANNEL_TYPE_STREAM_TUBE, 2L**32-1) in add
+
+ # MC puts StreamTube in the list twice - arguably a bug, but
+ # CMs should cope. So, don't assert about the length of the list
+ for item in add:
+ assert item in (
+ (cs.CHANNEL_TYPE_STREAMED_MEDIA, 2L**32-1),
+ (cs.CHANNEL_TYPE_STREAM_TUBE, 2L**32-1),
+ )
+
+ assert len(remove) == 0
+
+ return True
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params,
+ extra_interfaces=[cs.CONN_IFACE_CAPS],
+ expect_after_connect=[
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CONN_IFACE_CAPS,
+ method='AdvertiseCapabilities',
+ predicate=check_legacy_caps),
+ ])
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/chandlers/README b/tests/twisted/chandlers/README
new file mode 100644
index 00000000..2980ceba
--- /dev/null
+++ b/tests/twisted/chandlers/README
@@ -0,0 +1,4 @@
+This directory contains .chandler files used by Mission Control when it is run
+by twisted test. $MC_CHANDLERS_DIR is set to this directory in the twisted
+environment.
+
diff --git a/tests/twisted/constants.py b/tests/twisted/constants.py
new file mode 100644
index 00000000..dbedd44b
--- /dev/null
+++ b/tests/twisted/constants.py
@@ -0,0 +1,188 @@
+# 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
+
+"""
+Some handy constants for other tests to share and enjoy.
+"""
+
+from dbus import PROPERTIES_IFACE, INTROSPECTABLE_IFACE
+from servicetest import tp_name_prefix, tp_path_prefix
+
+DBUS_ERROR_NO_REPLY = 'org.freedesktop.DBus.Error.NoReply'
+
+HT_CONTACT = 1
+HT_ROOM = 2
+
+CHANNEL = tp_name_prefix + ".Channel"
+CHANNEL_IFACE_DESTROYABLE = CHANNEL + ".Interface.Destroyable"
+CHANNEL_IFACE_GROUP = CHANNEL + ".Interface.Group"
+CHANNEL_IFACE_HOLD = CHANNEL + ".Interface.Hold"
+CHANNEL_IFACE_MEDIA_SIGNALLING = CHANNEL + ".Interface.MediaSignalling"
+CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text"
+CHANNEL_TYPE_TUBES = CHANNEL + ".Type.Tubes"
+CHANNEL_IFACE_TUBE = CHANNEL + ".Interface.Tube"
+CHANNEL_TYPE_STREAM_TUBE = CHANNEL + ".Type.StreamTube"
+CHANNEL_TYPE_DBUS_TUBE = CHANNEL + ".Type.DBusTube"
+CHANNEL_TYPE_STREAMED_MEDIA = CHANNEL + ".Type.StreamedMedia"
+CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text"
+
+TP_AWKWARD_PROPERTIES = tp_name_prefix + ".Properties"
+PROPERTY_FLAG_READ = 1
+PROPERTY_FLAG_WRITE = 2
+
+CHANNEL_TYPE = CHANNEL + '.ChannelType'
+TARGET_HANDLE_TYPE = CHANNEL + '.TargetHandleType'
+TARGET_HANDLE = CHANNEL + '.TargetHandle'
+TARGET_ID = CHANNEL + '.TargetID'
+REQUESTED = CHANNEL + '.Requested'
+INITIATOR_HANDLE = CHANNEL + '.InitiatorHandle'
+INITIATOR_ID = CHANNEL + '.InitiatorID'
+INTERFACES = CHANNEL + '.Interfaces'
+
+CONN = tp_name_prefix + ".Connection"
+CONN_IFACE_ALIASING = CONN + '.Interface.Aliasing'
+CONN_IFACE_AVATARS = CONN + '.Interface.Avatars'
+CONN_IFACE_CAPS = CONN + '.Interface.Capabilities'
+CONN_IFACE_CONTACTS = CONN + '.Interface.Contacts'
+CONN_IFACE_CONTACT_CAPS_DRAFT1 = CONN + '.Interface.ContactCapabilities.DRAFT'
+CONN_IFACE_CONTACT_CAPS = CONN + '.Interface.ContactCapabilities'
+CONN_IFACE_REQUESTS = CONN + '.Interface.Requests'
+CONN_IFACE_SIMPLE_PRESENCE = CONN + '.Interface.SimplePresence'
+CONN_IFACE_POWER_SAVING = CONN + '.Interface.PowerSaving'
+
+CONN_STATUS_CONNECTED = 0
+CONN_STATUS_CONNECTING = 1
+CONN_STATUS_DISCONNECTED = 2
+
+CONN_STATUS_REASON_NONE = 0
+CONN_STATUS_REASON_REQUESTED = 1
+CONN_STATUS_REASON_NETWORK_ERROR = 2
+
+GROUP_REASON_NONE = 0
+GROUP_REASON_OFFLINE = 1
+GROUP_REASON_KICKED = 2
+GROUP_REASON_BUSY = 3
+GROUP_REASON_INVITED = 4
+GROUP_REASON_BANNED = 5
+GROUP_REASON_ERROR = 6
+GROUP_REASON_INVALID_CONTACT = 7
+GROUP_REASON_NO_ANSWER = 8
+GROUP_REASON_RENAMED = 9
+GROUP_REASON_PERMISSION_DENIED = 10
+GROUP_REASON_SEPARATED = 11
+
+PRESENCE_TYPE_UNSET = 0
+PRESENCE_TYPE_OFFLINE = 1
+PRESENCE_TYPE_AVAILABLE = 2
+PRESENCE_TYPE_AWAY = 3
+PRESENCE_TYPE_XA = 4
+PRESENCE_TYPE_HIDDEN = 5
+PRESENCE_TYPE_BUSY = 6
+PRESENCE_TYPE_UNKNOWN = 7
+PRESENCE_TYPE_ERROR = 8
+
+ERROR = tp_name_prefix + '.Error'
+INVALID_ARGUMENT = ERROR + '.InvalidArgument'
+INVALID_HANDLE = ERROR + '.InvalidHandle'
+NOT_IMPLEMENTED = ERROR + '.NotImplemented'
+NOT_AVAILABLE = ERROR + '.NotAvailable'
+PERMISSION_DENIED = ERROR + '.PermissionDenied'
+CANCELLED = ERROR + '.Cancelled'
+NOT_YOURS = ERROR + '.NotYours'
+DISCONNECTED = ERROR + '.Disconnected'
+NOT_CAPABLE = ERROR + '.NotCapable'
+
+TUBE_PARAMETERS = CHANNEL_IFACE_TUBE + '.Parameters'
+TUBE_STATE = CHANNEL_IFACE_TUBE + '.State'
+STREAM_TUBE_SERVICE = CHANNEL_TYPE_STREAM_TUBE + '.Service'
+DBUS_TUBE_SERVICE_NAME = CHANNEL_TYPE_DBUS_TUBE + '.ServiceName'
+DBUS_TUBE_DBUS_NAMES = CHANNEL_TYPE_DBUS_TUBE + '.DBusNames'
+
+TUBE_CHANNEL_STATE_LOCAL_PENDING = 0
+TUBE_CHANNEL_STATE_REMOTE_PENDING = 1
+TUBE_CHANNEL_STATE_OPEN = 2
+TUBE_CHANNEL_STATE_NOT_OFFERED = 3
+
+MEDIA_STREAM_TYPE_AUDIO = 0
+MEDIA_STREAM_TYPE_VIDEO = 1
+
+SOCKET_ADDRESS_TYPE_UNIX = 0
+SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX = 1
+SOCKET_ADDRESS_TYPE_IPV4 = 2
+SOCKET_ADDRESS_TYPE_IPV6 = 3
+
+SOCKET_ACCESS_CONTROL_LOCALHOST = 0
+SOCKET_ACCESS_CONTROL_PORT = 1
+SOCKET_ACCESS_CONTROL_NETMASK = 2
+SOCKET_ACCESS_CONTROL_CREDENTIALS = 3
+
+TUBE_STATE_LOCAL_PENDING = 0
+TUBE_STATE_REMOTE_PENDING = 1
+TUBE_STATE_OPEN = 2
+TUBE_STATE_NOT_OFFERED = 3
+
+TUBE_TYPE_DBUS = 0
+TUBE_TYPE_STREAM = 1
+
+MEDIA_STREAM_DIRECTION_NONE = 0
+MEDIA_STREAM_DIRECTION_SEND = 1
+MEDIA_STREAM_DIRECTION_RECEIVE = 2
+MEDIA_STREAM_DIRECTION_BIDIRECTIONAL = 3
+
+MEDIA_STREAM_PENDING_LOCAL_SEND = 1
+MEDIA_STREAM_PENDING_REMOTE_SEND = 2
+
+MEDIA_STREAM_TYPE_AUDIO = 0
+MEDIA_STREAM_TYPE_VIDEO = 1
+
+MEDIA_STREAM_STATE_DISCONNECTED = 0
+MEDIA_STREAM_STATE_CONNECTING = 1
+MEDIA_STREAM_STATE_CONNECTED = 2
+
+MEDIA_STREAM_DIRECTION_NONE = 0
+MEDIA_STREAM_DIRECTION_SEND = 1
+MEDIA_STREAM_DIRECTION_RECEIVE = 2
+MEDIA_STREAM_DIRECTION_BIDIRECTIONAL = 3
+
+CLIENT = tp_name_prefix + '.Client'
+CLIENT_PATH = tp_path_prefix + '/Client'
+OBSERVER = tp_name_prefix + '.Client.Observer'
+APPROVER = tp_name_prefix + '.Client.Approver'
+HANDLER = tp_name_prefix + '.Client.Handler'
+CLIENT_IFACE_REQUESTS = CLIENT + '.Interface.Requests'
+
+ACCOUNT = tp_name_prefix + '.Account'
+ACCOUNT_IFACE_AVATAR = ACCOUNT + '.Interface.Avatar'
+ACCOUNT_IFACE_ADDRESSING = ACCOUNT + '.Interface.Addressing'
+ACCOUNT_IFACE_NOKIA_COMPAT = 'com.nokia.Account.Interface.Compat'
+ACCOUNT_IFACE_NOKIA_REQUESTS = 'com.nokia.Account.Interface.ChannelRequests'
+ACCOUNT_IFACE_NOKIA_CONDITIONS = 'com.nokia.Account.Interface.Conditions'
+
+AM = tp_name_prefix + '.AccountManager'
+AM_IFACE_NOKIA_QUERY = 'com.nokia.AccountManager.Interface.Query'
+AM_PATH = tp_path_prefix + '/AccountManager'
+
+CR = tp_name_prefix + '.ChannelRequest'
+CDO = tp_name_prefix + '.ChannelDispatchOperation'
+
+CD = tp_name_prefix + '.ChannelDispatcher'
+CD_IFACE_OP_LIST = tp_name_prefix + '.ChannelDispatcher.Interface.OperationList'
+CD_PATH = tp_path_prefix + '/ChannelDispatcher'
+
+MC = tp_name_prefix + '.MissionControl5'
+MC_PATH = tp_path_prefix + '/MissionControl5'
diff --git a/tests/twisted/crash-recovery/crash-recovery.py b/tests/twisted/crash-recovery/crash-recovery.py
new file mode 100644
index 00000000..da632949
--- /dev/null
+++ b/tests/twisted/crash-recovery/crash-recovery.py
@@ -0,0 +1,135 @@
+# 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
+"""Regression test for recovering from an MC crash.
+"""
+
+import os
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup, make_mc
+import constants as cs
+
+def preseed():
+ accounts_dir = os.environ['MC_ACCOUNT_DIR']
+
+ accounts_cfg = open(accounts_dir + '/accounts.cfg', 'w')
+
+ accounts_cfg.write("""# Telepathy accounts
+[fakecm/fakeprotocol/jc_2edenton_40unatco_2eint]
+manager=fakecm
+protocol=fakeprotocol
+DisplayName=Work account
+NormalizedName=jc.denton@unatco.int
+param-account=jc.denton@unatco.int
+param-password=ionstorm
+Enabled=1
+""")
+
+ accounts_cfg.close()
+
+ account_connections_file = open(accounts_dir + '/.mc_connections', 'w')
+
+ account_connections_file.write("%s\t%s\t%s\n" %
+ (cs.tp_path_prefix + '/Connection/fakecm/fakeprotocol/jc',
+ cs.tp_name_prefix + '.Connection.fakecm.fakeprotocol.jc',
+ 'fakecm/fakeprotocol/jc_2edenton_40unatco_2eint'))
+
+def test(q, bus, unused):
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol',
+ 'jc', 'jc.denton@unatco.int')
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, 0)
+
+ unhandled_properties = dbus.Dictionary(text_fixed_properties, signature='sv')
+ unhandled_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+ unhandled_properties[cs.CHANNEL + '.TargetID'] = 'anna.navarre@unatco.int'
+ unhandled_properties[cs.CHANNEL + '.TargetHandle'] = \
+ dbus.UInt32(conn.ensure_handle(cs.HT_CONTACT, 'anna.navarre@unatco.int'))
+ unhandled_properties[cs.CHANNEL + '.InitiatorHandle'] = dbus.UInt32(conn.self_handle)
+ unhandled_properties[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ unhandled_properties[cs.CHANNEL + '.Requested'] = True
+ unhandled_chan = SimulatedChannel(conn, unhandled_properties)
+ unhandled_chan.announce()
+
+ handled_properties = dbus.Dictionary(text_fixed_properties, signature='sv')
+ handled_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+ handled_properties[cs.CHANNEL + '.TargetID'] = 'gunther.hermann@unatco.int'
+ handled_properties[cs.CHANNEL + '.TargetHandle'] = \
+ dbus.UInt32(conn.ensure_handle(cs.HT_CONTACT, 'gunther.hermann@unatco.int'))
+ handled_properties[cs.CHANNEL + '.InitiatorHandle'] = dbus.UInt32(conn.self_handle)
+ handled_properties[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ handled_properties[cs.CHANNEL + '.Requested'] = True
+ handled_chan = SimulatedChannel(conn, handled_properties)
+ handled_chan.announce()
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ client.handled_channels.append(handled_chan.object_path)
+
+ # Service-activate MC.
+ # We're told about the other channel as an observer...
+ mc = make_mc(bus, q.append)
+ _, _, _, e = q.expect_many(
+ EventPattern('dbus-signal',
+ path='/org/freedesktop/DBus',
+ interface='org.freedesktop.DBus', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.AM and e.args[2]),
+ EventPattern('dbus-signal',
+ path='/org/freedesktop/DBus',
+ interface='org.freedesktop.DBus', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.CD and e.args[2]),
+ EventPattern('dbus-signal',
+ path='/org/freedesktop/DBus',
+ interface='org.freedesktop.DBus', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.MC and e.args[2]),
+ EventPattern('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert channels[0][0] == unhandled_chan.object_path, channels
+ q.dbus_return(e.message, signature='')
+
+ # ... and as a handler
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert channels[0][0] == unhandled_chan.object_path, channels
+ q.dbus_return(e.message, signature='')
+
+if __name__ == '__main__':
+ preseed()
+ exec_test(test, {}, preload_mc=False)
diff --git a/tests/twisted/dispatcher/already-has-channel.py b/tests/twisted/dispatcher/already-has-channel.py
new file mode 100644
index 00000000..e744425a
--- /dev/null
+++ b/tests/twisted/dispatcher/already-has-channel.py
@@ -0,0 +1,223 @@
+# 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
+"""Regression test for dispatching an incoming Text channel that was already
+there before the Connection became ready.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, SimulatedChannel, expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # Enable the account
+ account.Set(cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ requested_presence = dbus.Struct((dbus.UInt32(2L),
+ dbus.String(u'available'), dbus.String(u'')))
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=cs.tp_name_prefix + '.ConnectionManager.fakecm',
+ path=cs.tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=cs.tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ # Don't allow the Connection to become ready until we want it to, by
+ # avoiding a return from GetInterfaces
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself', implement_get_interfaces=False)
+
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # this is the pre-Connect one
+ e = q.expect('dbus-method-call', method='GetInterfaces',
+ path=conn.object_path, handled=False)
+ q.dbus_raise(e.message, cs.DISCONNECTED, 'Not connected yet')
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ get_interfaces_call = q.expect('dbus-method-call', method='GetInterfaces',
+ path=conn.object_path, handled=False)
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # Before returning from GetInterfaces, make a Channel spring into
+ # existence
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # Now reply to GetInterfaces and say we have Requests
+ conn.GetInterfaces(get_interfaces_call)
+ q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True),
+ )
+
+ # A channel dispatch operation is created for the channel we already had
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Empathy first
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/already-has-obsolete.py b/tests/twisted/dispatcher/already-has-obsolete.py
new file mode 100644
index 00000000..05dcb115
--- /dev/null
+++ b/tests/twisted/dispatcher/already-has-obsolete.py
@@ -0,0 +1,221 @@
+# 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
+"""Regression test for dispatching an incoming Text channel that was already
+there before the Connection became ready.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, SimulatedChannel, expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # Enable the account
+ account.Set(cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ requested_presence = dbus.Struct((dbus.UInt32(2L),
+ dbus.String(u'available'), dbus.String(u'')))
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=cs.tp_name_prefix + '.ConnectionManager.fakecm',
+ path=cs.tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=cs.tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ # Don't allow the Connection to become ready until we want it to, by
+ # avoiding a return from GetInterfaces
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
+ 'myself', implement_get_interfaces=False, has_requests=False)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ # this is the pre-Connect one
+ e = q.expect('dbus-method-call', method='GetInterfaces',
+ path=conn.object_path, handled=False)
+ q.dbus_raise(e.message, cs.DISCONNECTED, 'Not connected yet')
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ get_interfaces_call = q.expect('dbus-method-call', method='GetInterfaces',
+ path=conn.object_path, handled=False)
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # Before returning from GetInterfaces, make a Channel spring into
+ # existence
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # Now reply to GetInterfaces and say we don't have Requests
+ conn.GetInterfaces(get_interfaces_call)
+ q.expect('dbus-method-call',
+ interface=cs.CONN, method='ListChannels', args=[],
+ path=conn.object_path, handled=True)
+
+ # A channel dispatch operation is created for the channel we already had
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert len(e.args) == 3
+ assert len(e.args[0]) == 1
+ assert e.args[0][0][0] == chan.object_path
+ assert e.args[1] == cdo_path
+ assert e.args[2] == cdo_properties
+ assert k.args == e.args
+
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Empathy first
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/approver-fails.py b/tests/twisted/dispatcher/approver-fails.py
new file mode 100644
index 00000000..67ce36a3
--- /dev/null
+++ b/tests/twisted/dispatcher/approver-fails.py
@@ -0,0 +1,158 @@
+# 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
+"""Regression test for dispatching an incoming Text channel.
+"""
+
+import dbus
+import dbus.bus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ empathy_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(empathy_bus)
+ empathy = SimulatedClient(q, empathy_bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False)
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+
+ # Empathy rejects the channels
+ q.dbus_raise(e.message, cs.NOT_AVAILABLE, 'Blind drunk', bus=empathy_bus)
+
+ # No approver works, so Empathy-the-Handler is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy rejects the channels
+ q.dbus_raise(e.message, cs.NOT_AVAILABLE, 'Still drunk', bus=empathy_bus)
+
+ q.expect_many(
+ EventPattern('dbus-method-call', path=chan.object_path,
+ interface=cs.CHANNEL, method='Close', handled=True),
+ EventPattern('dbus-signal', path=cdo.object_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cd.object_path,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/bypass-approval.py b/tests/twisted/dispatcher/bypass-approval.py
new file mode 100644
index 00000000..0655a9a9
--- /dev/null
+++ b/tests/twisted/dispatcher/bypass-approval.py
@@ -0,0 +1,293 @@
+# 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
+"""Regression test for dispatching an incoming Text channel with bypassed
+approval.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+contact_text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+urgent_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ 'com.example.Urgency.Urgent': True,
+ }, signature='sv')
+
+def announce_common(q, bus, empathy, kopete, account, conn, cd_props,
+ urgent=False):
+ if urgent:
+ jid = 'friar.lawrence'
+ else:
+ jid = 'juliet'
+
+ channel_properties = dbus.Dictionary(contact_text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = jid
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, jid)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = jid
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, jid)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ if urgent:
+ channel_properties['com.example.Urgency.Urgent'] = True
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+
+ if urgent:
+ # The handler with BypassApproval is first
+ assert handlers[0] == cs.tp_name_prefix + '.Client.Kopete.BypassApproval'
+ # Kopete's filter is more specific than Empathy's, so it comes next
+ assert handlers[1] == cs.tp_name_prefix + '.Client.Kopete'
+ # Empathy's filter is the least specific, so it's last
+ assert handlers[2] == cs.tp_name_prefix + '.Client.Empathy'
+ assert len(handlers) == 3
+ else:
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ return cdo_iface, chan, channel_properties, [e, k]
+
+def expect_and_exercise_approval(q, bus, chan, channel_properties,
+ empathy, kopete, cdo_iface, cd_props):
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args[0] == [(chan.object_path, channel_properties)]
+ assert k.args == e.args
+
+ # Both Approvers indicate that they are ready to proceed
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Empathy first
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ # Two clients want to observe, approve and handle channels. Additionally,
+ # Kopete recognises an "Urgent" flag on certain incoming channels, and
+ # wants to bypass approval for them.
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[contact_text_fixed_properties],
+ approve=[contact_text_fixed_properties],
+ handle=[contact_text_fixed_properties], bypass_approval=False)
+ bypass = SimulatedClient(q, bus, 'Kopete.BypassApproval',
+ observe=[], approve=[],
+ handle=[urgent_fixed_properties], bypass_approval=True)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete, bypass])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # First, a non-urgent channel is created
+
+ cdo_iface, chan, channel_properties, observe_events = announce_common(q,
+ bus, empathy, kopete, account, conn, cd_props, False)
+
+ # Both Observers indicate that they are ready to proceed
+ for e in observe_events:
+ q.dbus_return(e.message, signature='')
+
+ expect_and_exercise_approval(q, bus, chan, channel_properties,
+ empathy, kopete, cdo_iface, cd_props)
+
+ # Now a channel that bypasses approval comes in. During this process,
+ # we should never be asked to approve anything.
+
+ approval = [
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ ]
+
+ q.forbid_events(approval)
+
+ cdo_iface, chan, channel_properties, observe_events = announce_common(q,
+ bus, empathy, kopete, account, conn, cd_props, True)
+
+ # Both Observers indicate that they are ready to proceed
+ for e in observe_events:
+ q.dbus_return(e.message, signature='')
+
+ # Kopete's BypassApproval part is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=bypass.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ # Kopete accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.unforbid_events(approval)
+
+ # Regression test for fd.o #22670
+
+ closure = [
+ EventPattern('dbus-method-call', method='Close'),
+ ]
+
+ q.forbid_events(closure)
+
+ bypass.release_name()
+ sync_dbus(bus, q, mc)
+
+ q.unforbid_events(closure)
+
+ # Bring back that handler
+ del bypass
+ bypass = SimulatedClient(q, bus, 'Kopete.BypassApproval',
+ observe=[], approve=[],
+ handle=[urgent_fixed_properties], bypass_approval=True)
+ expect_client_setup(q, [bypass])
+
+ # Another channel that bypasses approval comes in, but the handler that
+ # bypasses approval fails.
+
+ cdo_iface, chan, channel_properties, observe_events = announce_common(q,
+ bus, empathy, kopete, account, conn, cd_props, True)
+
+ # Both Observers indicate that they are ready to proceed
+ for e in observe_events:
+ q.dbus_return(e.message, signature='')
+
+ # Kopete's BypassApproval part is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=bypass.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ # Kopete's BypassApproval part fails to accept the channels
+ q.dbus_raise(e.message, 'com.example.Broken', 'No way')
+
+ # MC recovers by running the approvers and doing what they say
+ expect_and_exercise_approval(q, bus, chan, channel_properties,
+ empathy, kopete, cdo_iface, cd_props)
+
+if __name__ == '__main__':
+ exec_test(test, {})
+
diff --git a/tests/twisted/dispatcher/bypass-observers.py b/tests/twisted/dispatcher/bypass-observers.py
new file mode 100644
index 00000000..d8b02660
--- /dev/null
+++ b/tests/twisted/dispatcher/bypass-observers.py
@@ -0,0 +1,291 @@
+# Copyright (C) 2009-2010 Nokia Corporation
+# Copyright (C) 2009-2010 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
+"""Regression test for dispatching an incoming Text channel with bypassed
+observers.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus, assertEquals, assertLength, assertContains
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+contact_text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+secret_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ 'com.example.Secrecy.Secret': True,
+ }, signature='sv')
+
+def announce_common(q, bus, empathy, kopete, account, conn, cd_props,
+ secret=False):
+ if secret:
+ jid = 'friar.lawrence'
+ else:
+ jid = 'juliet'
+
+ channel_properties = dbus.Dictionary(contact_text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = jid
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, jid)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = jid
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, jid)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ if secret:
+ channel_properties['com.example.Secrecy.Secret'] = True
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assertEquals(cdo_properties[cs.CDO + '.Account'], account.object_path)
+ assertEquals(cdo_properties[cs.CDO + '.Connection'], conn.object_path)
+ assertContains(cs.CDO + '.Interfaces', cdo_properties)
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+
+ if secret:
+ # The handler with BypassApproval is first
+ assertEquals(cs.tp_name_prefix + '.Client.Kopete.Bypasser',
+ handlers[0])
+ else:
+ handlers.sort()
+ assertEquals([cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers)
+
+ assertContains(cs.CD_IFACE_OP_LIST, cd_props.Get(cs.CD, 'Interfaces'))
+
+ assertEquals([(cdo_path, cdo_properties)],
+ cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations'))
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channel
+
+ if secret:
+ observe_events = []
+ else:
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assertEquals(account.object_path, e.args[0])
+ assertEquals(conn.object_path, e.args[1])
+ assertEquals(cdo_path, e.args[3])
+ assertEquals([], e.args[4]) # no requests satisfied
+ channels = e.args[2]
+ assertLength(1, channels)
+ assertEquals(chan.object_path, channels[0][0])
+ assertEquals(channel_properties, channels[0][1])
+
+ assertEquals(k.args, e.args)
+ observe_events = [e, k]
+
+ return cdo_iface, chan, channel_properties, observe_events
+
+def expect_and_exercise_approval(q, bus, chan, channel_properties,
+ empathy, kopete, cdo_iface, cd_props):
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assertEquals([(chan.object_path, channel_properties)], e.args[0])
+ assertEquals(k.args, e.args)
+
+ # Both Approvers indicate that they are ready to proceed
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Kopete first
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Kopete')
+
+ # Kopete is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Kopete accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assertEquals([], cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations'))
+
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ # Two clients want to observe, approve and handle channels. Additionally,
+ # Kopete recognises a "Secret" flag on certain incoming channels, and
+ # wants to bypass approval and observers for them. Also, Empathy is a
+ # respawnable observer, which wants to get notified of existing channels
+ # if it gets restarted.
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False,
+ wants_recovery=True)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[contact_text_fixed_properties],
+ approve=[contact_text_fixed_properties],
+ handle=[contact_text_fixed_properties], bypass_approval=False)
+ bypass = SimulatedClient(q, bus, 'Kopete.Bypasser',
+ observe=[], approve=[],
+ handle=[secret_fixed_properties],
+ bypass_approval=True, bypass_observers=True)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete, bypass])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assertEquals([], cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations'))
+
+ # First, a non-secret channel is created
+
+ cdo_iface, chan, channel_properties, observe_events = announce_common(q,
+ bus, empathy, kopete, account, conn, cd_props, False)
+
+ # Both Observers indicate that they are ready to proceed
+ for e in observe_events:
+ q.dbus_return(e.message, signature='')
+
+ expect_and_exercise_approval(q, bus, chan, channel_properties,
+ empathy, kopete, cdo_iface, cd_props)
+
+ nonsecret_chan = chan
+
+ # Now a channel that bypasses approval and observers comes in.
+ # During this process, we should never be asked to approve or
+ # observe anything.
+
+ approval = [
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ ]
+
+ q.forbid_events(approval)
+
+ cdo_iface, chan, channel_properties, observe_events = announce_common(q,
+ bus, empathy, kopete, account, conn, cd_props, True)
+
+ # Both Observers indicate that they are ready to proceed
+ for e in observe_events:
+ q.dbus_return(e.message, signature='')
+
+ # Kopete's BypassApproval part is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=bypass.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ # Kopete accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.unforbid_events(approval)
+
+ # Empathy, the observer, crashes
+ empathy.release_name()
+
+ e = q.expect('dbus-signal',
+ signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == empathy.bus_name and e.args[2] == ''),
+ )
+ empathy_unique_name = e.args[1]
+
+ bus.flush()
+
+ # Empathy gets restarted
+ empathy.reacquire_name()
+
+ e = q.expect('dbus-signal',
+ signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == empathy.bus_name and e.args[1] == ''),
+ )
+ empathy_unique_name = e.args[2]
+
+ # Empathy is told to observe only the non-secret channel
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+
+ channels = e.args[2]
+ assertLength(1, channels)
+ assertEquals(nonsecret_chan.object_path, channels[0][0])
+
+if __name__ == '__main__':
+ exec_test(test, {})
+
diff --git a/tests/twisted/dispatcher/cancel.py b/tests/twisted/dispatcher/cancel.py
new file mode 100644
index 00000000..f47a297c
--- /dev/null
+++ b/tests/twisted/dispatcher/cancel.py
@@ -0,0 +1,163 @@
+# 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
+"""Regression test for the unofficial Account.Interface.Requests API when
+a channel can be created successfully.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # No Approver should be invoked at any point during this test, because the
+ # Channel was Requested
+ def fail_on_approval(e):
+ raise AssertionError('Approver should not be invoked')
+ q.add_dbus_method_impl(fail_on_approval, path=client.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation')
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ test_channel_creation(q, bus, account, client, conn, False)
+ test_channel_creation(q, bus, account, client, conn, True)
+
+def test_channel_creation(q, bus, account, client, conn,
+ cancel_before_proceed):
+
+ user_action_time = dbus.Int64(1238582606)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # chat UI calls ChannelDispatcher.CreateChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request, user_action_time, client.bus_name,
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='CreateChannel')
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+
+ if cancel_before_proceed:
+ cr.Cancel(dbus_interface=cs.CR)
+
+ accsig, stdsig = q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS,
+ signal='Failed'),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Failed'),
+ )
+ assert accsig.args[0] == request_path
+ assert accsig.args[1] == cs.CANCELLED
+ assert stdsig.args[0] == cs.CANCELLED
+
+ # the channel request has gone away
+ try:
+ cr.Proceed(dbus_interface=cs.CR)
+ except dbus.DBusException, e:
+ pass
+ else:
+ raise AssertionError('Wanted an error')
+
+ return
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ cm_request_call, add_request_call = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS, method='CreateChannel',
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=client.object_path),
+ )
+
+ assert add_request_call.args[0] == request_path
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Actually, never mind.
+ cr.Cancel(dbus_interface=cs.CR)
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ # Channel is unwanted now, MC stabs it in the face
+ accsig, stdsig, _ = q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Failed'),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Failed'),
+ EventPattern('dbus-method-call', path=channel.object_path,
+ interface=cs.CHANNEL, method='Close', handled=True),
+ )
+
+ assert accsig.args[0] == request_path
+ assert accsig.args[1] == cs.CANCELLED
+ assert stdsig.args[0] == cs.CANCELLED
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/capture-bundle.py b/tests/twisted/dispatcher/capture-bundle.py
new file mode 100644
index 00000000..de773844
--- /dev/null
+++ b/tests/twisted/dispatcher/capture-bundle.py
@@ -0,0 +1,284 @@
+# 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 a VoIP UI stealing closely-related Text channels as
+described in the telepathy-spec rationale.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+ voip_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ }, signature='sv')
+
+ # Two clients want to observe, approve and handle text and VoIP channels.
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties, voip_fixed_properties],
+ approve=[text_fixed_properties, voip_fixed_properties],
+ handle=[text_fixed_properties, voip_fixed_properties],
+ bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties, voip_fixed_properties],
+ approve=[text_fixed_properties, voip_fixed_properties],
+ handle=[text_fixed_properties, voip_fixed_properties],
+ bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # First, a VoIP channel is created
+
+ BUNDLE = '/8de0d29d-83e1-40f5-b24f-748c0aa86c82'
+
+ channel_properties = dbus.Dictionary(voip_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+ channel_properties[cs.CHANNEL + '.FUTURE.Bundle'] = BUNDLE
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Empathy first
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # Now that Empathy has the VoIP channel, it wants to steal incoming Text
+ # channels from the same bundle
+
+ same_bundle_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ same_bundle_properties[cs.CHANNEL + '.FUTURE.Bundle'] = BUNDLE
+
+ bypass = SimulatedClient(q, bus, 'Empathy._1_42.Window2',
+ observe=[],
+ approve=[],
+ handle=[same_bundle_properties],
+ bypass_approval=True)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [bypass])
+
+ # From now on, this test must fail if we're asked to approve anything
+ approval = [
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ ]
+ q.forbid_events(approval)
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetHandleType'] = cs.HT_CONTACT
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.FUTURE.Bundle'] = BUNDLE
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # Again, there's a CDO
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ # The handler with BypassApproval is first
+ assert handlers[0] == bypass.bus_name
+ # The other two handlers are still possibilities
+ assert len(handlers) == 3
+
+ # Observers are invoked as usual
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # Empathy's BypassApproval part is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=bypass.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ # Empathy accepts the channels
+ q.dbus_return(e.message, signature='')
+
+if __name__ == '__main__':
+ exec_test(test, {})
+
diff --git a/tests/twisted/dispatcher/connect-for-request.py b/tests/twisted/dispatcher/connect-for-request.py
new file mode 100644
index 00000000..5cbb9757
--- /dev/null
+++ b/tests/twisted/dispatcher/connect-for-request.py
@@ -0,0 +1,155 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, create_fakecm_account, SimulatedConnection, \
+ SimulatedChannel
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "smcv@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ # Ensure that it's enabled but has offline RP and doesn't connect
+ # automatically
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'RequestedPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', ''))
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'AutomaticPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Testing automatic presence'))
+ q.expect('dbus-return', method='Set')
+ q.expect('dbus-signal', signal='AccountPropertyChanged',
+ predicate=lambda e:
+ e.args[0].get('AutomaticPresence', (None, None, None))[1]
+ == 'busy')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'ConnectAutomatically',
+ False)
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Enabled', True)
+ q.expect('dbus-return', method='Set')
+ q.expect('dbus-signal', signal='AccountPropertyChanged',
+ predicate=lambda e: e.args[0].get('Enabled'))
+
+ # Requesting a channel will put us online
+
+ user_action_time = dbus.Int64(1238582606)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request, user_action_time, "",
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='CreateChannel')
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == ""
+ assert request_props['Interfaces'] == []
+
+ # make sure RequestConnection doesn't get called until we Proceed
+ events = [EventPattern('dbus-method-call', method='RequestConnection')]
+ q.forbid_events(events)
+
+ sync_dbus(bus, q, mc)
+
+ q.unforbid_events(events)
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', 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_requests=True, has_presence=True)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+ conn.presence = dbus.Struct((cs.PRESENCE_TYPE_AVAILABLE, 'available', ''),
+ signature='uss')
+
+ _, cm_request_call = q.expect_many(
+ EventPattern('dbus-method-call', path=conn.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE, method='SetPresence',
+ args=['busy', 'Testing automatic presence'], handled=True),
+ EventPattern('dbus-method-call', path=conn.object_path,
+ interface=cs.CONN_IFACE_REQUESTS, method='CreateChannel',
+ args=[request], handled=False),
+ )
+
+ q.dbus_emit(conn.object_path, cs.CONN_IFACE_SIMPLE_PRESENCE,
+ 'PresencesChanged',
+ {conn.self_handle: (dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Testing automatic presence')},
+ signature='a{u(uss)}')
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ # there's no handler, so it gets shot down
+
+ q.expect('dbus-method-call', path=channel.object_path, method='Close',
+ handled=True)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/create-at-startup.py b/tests/twisted/dispatcher/create-at-startup.py
new file mode 100644
index 00000000..ceec48bd
--- /dev/null
+++ b/tests/twisted/dispatcher/create-at-startup.py
@@ -0,0 +1,232 @@
+# 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
+"""Regression test for activating MC due to, or immediately before, a request.
+"""
+
+import os
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup, make_mc
+import constants as cs
+
+def preseed():
+ accounts_dir = os.environ['MC_ACCOUNT_DIR']
+
+ accounts_cfg = open(accounts_dir + '/accounts.cfg', 'w')
+ accounts_cfg.write("""# Telepathy accounts
+[fakecm/fakeprotocol/jc_2edenton_40unatco_2eint]
+manager=fakecm
+protocol=fakeprotocol
+DisplayName=Work account
+NormalizedName=jc.denton@unatco.int
+param-account=jc.denton@unatco.int
+param-password=ionstorm
+Enabled=1
+ConnectAutomatically=0
+AutomaticPresenceType=2
+AutomaticPresenceStatus=available
+AutomaticPresenceMessage=
+""")
+ accounts_cfg.close()
+
+ account_connections_file = open(accounts_dir + '/.mc_connections', 'w')
+ account_connections_file.write("")
+ account_connections_file.close()
+
+def test(q, bus, unused):
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ user_action_time = dbus.Int64(1238582606)
+
+ # A client and a CM are already running
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False,
+ implement_get_interfaces=False)
+ cm_name_ref = dbus.service.BusName(
+ cs.tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ # service-activate MC and immediately make a request
+ mc = make_mc(bus, q.append)
+
+ account = bus.get_object(cs.MC,
+ cs.tp_path_prefix +
+ '/Account/fakecm/fakeprotocol/jc_2edenton_40unatco_2eint')
+ cd = bus.get_object(cs.MC, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'bob.page@versalife.com',
+ }, signature='sv')
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request, user_action_time, client.bus_name,
+ dbus_interface=cs.CD)
+
+ get_interfaces = q.expect('dbus-method-call', path=client.object_path,
+ interface=cs.PROPERTIES_IFACE, method='Get',
+ args=[cs.CLIENT, 'Interfaces'],
+ handled=False)
+ # don't reply just yet
+
+ ret = q.expect('dbus-return', method='CreateChannel')
+ request_path = ret.value[0]
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed()
+ cr = bus.get_object(cs.MC, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == client.bus_name
+ assert request_props['Interfaces'] == []
+
+ call_async(q, cr, 'Proceed', dbus_interface=cs.CR)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', {
+ 'account': 'jc.denton@unatco.int',
+ 'password': 'ionstorm',
+ }],
+ 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', 'the_conn',
+ 'jc.denton@unatco.int')
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ # A channel appears spontaneously
+
+ announcement_immutable = dbus.Dictionary(text_fixed_properties)
+ announcement_immutable[cs.CHANNEL + '.TargetID'] = 'gunther@unatco.int'
+ announcement_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'gunther@unatco.int')
+ announcement_immutable[cs.CHANNEL + '.InitiatorHandle'] = \
+ announcement_immutable[cs.CHANNEL + '.TargetHandle']
+ announcement_immutable[cs.CHANNEL + '.InitiatorID'] = \
+ announcement_immutable[cs.CHANNEL + '.TargetID']
+ announcement_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ announcement_immutable[cs.CHANNEL + '.Requested'] = False
+ announcement = SimulatedChannel(conn, announcement_immutable)
+ announcement.announce()
+
+ # Now the Client returns its info
+ q.dbus_return(get_interfaces.message,
+ dbus.Array([cs.HANDLER, cs.OBSERVER, cs.APPROVER,
+ cs.CLIENT_IFACE_REQUESTS], signature='s'), signature='v')
+
+ expect_client_setup(q, [client], got_interfaces_already=True)
+
+ # Now that the dispatcher is ready to go, we start looking for channels,
+ # and also make the actual request
+ # Empathy observes the channel we originally requested.
+ _, a, cm_request_call = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True),
+ EventPattern('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS, method='CreateChannel',
+ path=conn.object_path, args=[request], handled=False),
+ )
+
+ assert a.args[0] == account.object_path, a.args
+ assert a.args[1] == conn.object_path, a.args
+ assert a.args[3] != '/', a.args # there is a dispatch operation
+ assert a.args[4] == [], a.args
+ channels = a.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == announcement.object_path, channels
+ assert channels[0][1] == announcement_immutable, channels
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'bob.page@versalife.com')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ # Empathy observes the newly-created channel.
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert e.args[4] == [request_path], e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+
+ # Observer says "OK, go"
+ q.dbus_return(a.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # Empathy is asked to handle the channel
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time
+ assert isinstance(e.args[5], dict)
+ assert len(e.args) == 6
+
+if __name__ == '__main__':
+ preseed()
+ exec_test(test, {}, preload_mc=False)
diff --git a/tests/twisted/dispatcher/create-delayed-by-mini-plugin.py b/tests/twisted/dispatcher/create-delayed-by-mini-plugin.py
new file mode 100644
index 00000000..b99e29f5
--- /dev/null
+++ b/tests/twisted/dispatcher/create-delayed-by-mini-plugin.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2009 Nokia Corporation.
+# Copyright © 2009-2010 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
+"""Regression test for ChannelDispatcher delaying channel creation due to
+a plugin.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+# Rejected by the plugin
+DELAYED_CTYPE = 'com.example.QuestionableChannel'
+
+def test(q, bus, mc):
+ policy_bus_name_ref = dbus.service.BusName('com.example.Policy', bus)
+
+ # For now, we should never actually be asked to make a channel.
+ forbidden = [
+ EventPattern('dbus-method-call', method='CreateChannel'),
+ EventPattern('dbus-method-call', method='EnsureChannel'),
+ EventPattern('dbus-method-call', method='ObserveChannels'),
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ EventPattern('dbus-method-call', method='HandleChannels'),
+ ]
+ q.forbid_events(forbidden)
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': DELAYED_CTYPE,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[fixed_properties], approve=[fixed_properties],
+ handle=[fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ user_action_time = dbus.Int64(1238582606)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # UI calls ChannelDispatcher.CreateChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': DELAYED_CTYPE,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request, user_action_time, client.bus_name,
+ dbus_interface=cs.CD)
+
+ ret = q.expect('dbus-return',
+ method='CreateChannel')
+ request_path = ret.value[0]
+
+ # UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == client.bus_name
+ assert request_props['Interfaces'] == []
+
+ call_async(q, cr, 'Proceed', dbus_interface=cs.CR)
+
+ q.expect('dbus-return', method='Proceed')
+
+ # What does the policy service think?
+ permission = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='RequestRequest')
+
+ # Think about it for a bit, then allow dispatching to continue
+ sync_dbus(bus, q, account)
+ q.unforbid_events(forbidden)
+ q.dbus_return(permission.message, signature='')
+
+ # Only now does the CM's CreateChannel method get called
+ cm_request_call = q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='CreateChannel',
+ path=conn.object_path, args=[request], handled=False)
+ q.dbus_raise(cm_request_call.message, cs.INVALID_ARGUMENT, 'No')
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/create-delayed-by-plugin.py b/tests/twisted/dispatcher/create-delayed-by-plugin.py
new file mode 100644
index 00000000..8a37d228
--- /dev/null
+++ b/tests/twisted/dispatcher/create-delayed-by-plugin.py
@@ -0,0 +1,166 @@
+# 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
+"""Regression test for dispatching a requested Text channel, with a plugin that
+delays dispatching and asks for permission.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+def request_channel_expect_query(q, bus, account, conn, client):
+ # This target is special-cased in test-plugin.c
+ target = 'policy@example.com'
+ request_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ request_properties[cs.CHANNEL + '.TargetID'] = target
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+
+ user_action_time = dbus.Int64(1238582606)
+
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request_properties, user_action_time,
+ client.bus_name, dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='CreateChannel')
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.AM, request_path)
+ cr.Proceed(dbus_interface=cs.CR)
+
+ cm_request_call = q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS, method='CreateChannel',
+ path=conn.object_path, args=[request_properties], handled=False)
+
+ # A channel is returned eventually
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = target
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_properties[cs.CHANNEL + '.Requested'] = True
+ channel_properties[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([cs.CHANNEL_IFACE_GROUP,
+ ],signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties, group=True)
+ q.dbus_return(cm_request_call.message,
+ chan.object_path, chan.immutable, signature='oa{sv}')
+ chan.announce()
+
+ # What does the policy service think?
+ e = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='RequestPermission')
+
+ # Think about it for a bit
+ sync_dbus(bus, q, account)
+
+ # Let the test code decide how to reply
+ return e, chan, cr
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ policy_bus_name_ref = dbus.service.BusName('com.example.Policy', bus)
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ e, chan, cr = request_channel_expect_query(q, bus, account, conn, empathy)
+
+ # No.
+ q.dbus_raise(e.message, 'com.example.Errors.No', 'Denied!')
+
+ # The plugin responds
+ e = q.expect('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ # this error message is from the plugin
+ method='RemoveMembersWithReason', args=[[conn.self_handle],
+ "Computer says no", cs.GROUP_REASON_PERMISSION_DENIED],
+ handled=False)
+ q.dbus_return(e.message, signature='')
+ chan.close()
+
+ # Try again
+ e, chan, cr = request_channel_expect_query(q, bus, account, conn, kopete)
+
+ # Yes.
+ q.dbus_return(e.message, signature='')
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ for _ in ('Kopete', 'Empathy'):
+ e = q.expect('dbus-method-call',
+ interface=cs.HANDLER, method='HandleChannels', handled=False)
+ q.dbus_raise(e.message, cs.INVALID_ARGUMENT, 'Never mind')
+
+ q.expect_many(
+ EventPattern('dbus-signal', path=cr.object_path,
+ interface=cs.CR, signal='Failed'),
+ EventPattern('dbus-method-call', path=chan.object_path,
+ interface=cs.CHANNEL, method='Close', handled=True),
+ )
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/create-handler-fails.py b/tests/twisted/dispatcher/create-handler-fails.py
new file mode 100644
index 00000000..c5ab0967
--- /dev/null
+++ b/tests/twisted/dispatcher/create-handler-fails.py
@@ -0,0 +1,189 @@
+# 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
+"""Regression test for ChannelDispatcher when a channel can be created,
+but the Handler rejects it.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # No Approver should be invoked at any point during this test, because the
+ # Channel was Requested
+ def fail_on_approval(e):
+ raise AssertionError('Approver should not be invoked')
+ q.add_dbus_method_impl(fail_on_approval, path=client.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation')
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ test_channel_creation(q, bus, account, client, conn, False)
+
+def test_channel_creation(q, bus, account, client, conn, ensure):
+ user_action_time = dbus.Int64(1238582606)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # chat UI calls ChannelDispatcher.EnsureChannel or CreateChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, cd,
+ (ensure and 'EnsureChannel' or 'CreateChannel'),
+ account.object_path, request, user_action_time, client.bus_name,
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return',
+ method=(ensure and 'EnsureChannel' or 'CreateChannel'))
+ request_path = ret.value[0]
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == client.bus_name
+ assert request_props['Interfaces'] == []
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ # FIXME: should the EnsureChannel/CreateChannel call, and the AddRequest
+ # call, be in a defined order? Probably not though, since CMs and Clients
+ # aren't meant to be the same process!
+
+ cm_request_call, add_request_call = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method=(ensure and 'EnsureChannel' or 'CreateChannel'),
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=client.object_path),
+ )
+
+ assert add_request_call.args[0] == request_path
+ request_props = add_request_call.args[1]
+ assert request_props[cs.CR + '.Account'] == account.object_path
+ assert request_props[cs.CR + '.Requests'] == [request]
+ assert request_props[cs.CR + '.UserActionTime'] == user_action_time
+ assert request_props[cs.CR + '.PreferredHandler'] == client.bus_name
+ assert request_props[cs.CR + '.Interfaces'] == []
+
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ if ensure:
+ q.dbus_return(cm_request_call.message, True, # <- Yours
+ channel.object_path, channel.immutable, signature='boa{sv}')
+ else: # Create
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ # Observer should get told, processing waits for it
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert e.args[4] == [request_path], e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+
+ # Observer says "OK, go"
+ q.dbus_return(e.message, signature='')
+
+ # Handler is next
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time
+ assert isinstance(e.args[5], dict)
+ assert len(e.args) == 6
+
+ # Handler fails at handling channels
+ q.dbus_raise(e.message, cs.INVALID_ARGUMENT, 'Parlez vous Freezepop?')
+
+ e = q.expect('dbus-method-call', path=client.object_path,
+ interface=cs.CLIENT_IFACE_REQUESTS, method='RemoveRequest',
+ handled=False)
+ q.dbus_raise(e.message, cs.INVALID_ARGUMENT, 'Do you realise?')
+
+ q.expect_many(
+ EventPattern('dbus-signal', path=cr.object_path,
+ interface=cs.CR, signal='Failed'),
+ EventPattern('dbus-method-call', path=channel.object_path,
+ interface=cs.CHANNEL, method='Close', handled=True),
+ )
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/create-hints.py b/tests/twisted/dispatcher/create-hints.py
new file mode 100644
index 00000000..9894276a
--- /dev/null
+++ b/tests/twisted/dispatcher/create-hints.py
@@ -0,0 +1,201 @@
+# Copyright (C) 2009 Nokia Corporation
+# Copyright (C) 2009-2010 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
+"""Test CD.{Create,Ensure}ChannelWithHints
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, assertEquals
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ test_channel_creation(q, bus, account, client, conn, False)
+ test_channel_creation(q, bus, account, client, conn, True)
+
+def test_channel_creation(q, bus, account, client, conn,
+ ensure=False, prefer=None, channel_type=cs.CHANNEL_TYPE_TEXT):
+ user_action_time = dbus.Int64(1238582606)
+ hints = dbus.Dictionary({ 'badger': 42, 'snake': 'pony' },
+ signature='sv')
+
+ if prefer is None:
+ prefer = client
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # chat UI calls ChannelDispatcher.EnsureChannelWithHints or
+ # CreateChannelWithHints
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': channel_type,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ call_async(q, cd,
+ (ensure and 'EnsureChannelWithHints' or 'CreateChannelWithHints'),
+ account.object_path, request, user_action_time, prefer.bus_name,
+ hints, dbus_interface=cs.CD)
+ ret = q.expect('dbus-return',
+ method=(ensure and 'EnsureChannelWithHints' or 'CreateChannelWithHints'))
+ request_path = ret.value[0]
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == prefer.bus_name
+ assert request_props['Interfaces'] == []
+ assertEquals(hints, request_props['Hints'])
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ # FIXME: should the EnsureChannel/CreateChannel call, and the AddRequest
+ # call, be in a defined order? Probably not though, since CMs and Clients
+ # aren't meant to be the same process!
+
+ cm_request_call, add_request_call = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method=(ensure and 'EnsureChannel' or 'CreateChannel'),
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest'),
+ )
+
+ assert add_request_call.args[0] == request_path
+ assert add_request_call.path == prefer.object_path
+ request_props = add_request_call.args[1]
+ assert request_props[cs.CR + '.Account'] == account.object_path
+ assert request_props[cs.CR + '.Requests'] == [request]
+ assert request_props[cs.CR + '.UserActionTime'] == user_action_time
+ assert request_props[cs.CR + '.PreferredHandler'] == prefer.bus_name
+ assert request_props[cs.CR + '.Interfaces'] == []
+
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ if ensure:
+ q.dbus_return(cm_request_call.message, True, # <- Yours
+ channel.object_path, channel.immutable, signature='boa{sv}')
+ else: # Create
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ if channel_type == cs.CHANNEL_TYPE_TEXT:
+ # Observer should get told, processing waits for it
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert e.args[4] == [request_path], e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ info = e.args[5]
+ assert info['request-properties'] == {request_path: request_props}, info
+
+ # Observer says "OK, go"
+ q.dbus_return(e.message, signature='')
+
+ # Handler is next
+ e = q.expect('dbus-method-call',
+ path=prefer.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time
+ assert isinstance(e.args[5], dict)
+ assert len(e.args) == 6
+
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ # SucceededWithChannel is fired first
+ e = q.expect('dbus-signal', path=request_path, interface=cs.CR,
+ signal='SucceededWithChannel')
+
+ assertEquals(conn.object_path, e.args[0])
+ assert isinstance(e.args[1], dict), e.args[1]
+ assertEquals(channel.object_path, e.args[2])
+ assertEquals(channel_immutable, e.args[3])
+
+ # CR emits Succeeded (or in Mardy's version, Account emits Succeeded)
+ q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded',
+ args=[request_path]),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Succeeded'),
+ )
+
+ # Close the channel
+ channel.close()
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/create-no-preferred-handler.py b/tests/twisted/dispatcher/create-no-preferred-handler.py
new file mode 100644
index 00000000..15579b01
--- /dev/null
+++ b/tests/twisted/dispatcher/create-no-preferred-handler.py
@@ -0,0 +1,195 @@
+# 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
+"""Regression test for https://bugs.freedesktop.org/show_bug.cgi?id=22169,
+and for http://bugs.freedesktop.org/show_bug.cgi?id=23935
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+ # Emulate a handler that only wants to get outgoing channels, as a
+ # regression test for <http://bugs.freedesktop.org/show_bug.cgi?id=23935>,
+ # in which those didn't work
+ text_requested = dbus.Dictionary(text_fixed_properties, signature='sv')
+ text_requested[cs.CHANNEL + '.Requested'] = True
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_requested], bypass_approval=False)
+
+ # No Approver should be invoked at any point during this test, because the
+ # Channel was Requested
+ def fail_on_approval(e):
+ raise AssertionError('Approver should not be invoked')
+ q.add_dbus_method_impl(fail_on_approval, path=client.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation')
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ test_channel_creation(q, bus, account, client, conn, False)
+ test_channel_creation(q, bus, account, client, conn, True)
+
+def test_channel_creation(q, bus, account, client, conn, ensure):
+ user_action_time = dbus.Int64(1238582606)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # chat UI calls ChannelDispatcher.EnsureChannel or CreateChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, cd,
+ (ensure and 'EnsureChannel' or 'CreateChannel'),
+ account.object_path, request, user_action_time, "",
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return',
+ method=(ensure and 'EnsureChannel' or 'CreateChannel'))
+ request_path = ret.value[0]
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == ""
+ assert request_props['Interfaces'] == []
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ # FIXME: should the EnsureChannel/CreateChannel call, and the AddRequest
+ # call, be in a defined order? Probably not though, since CMs and Clients
+ # aren't meant to be the same process!
+
+ cm_request_call, add_request_call = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method=(ensure and 'EnsureChannel' or 'CreateChannel'),
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=client.object_path),
+ )
+
+ assert add_request_call.args[0] == request_path
+ request_props = add_request_call.args[1]
+ assert request_props[cs.CR + '.Account'] == account.object_path
+ assert request_props[cs.CR + '.Requests'] == [request]
+ assert request_props[cs.CR + '.UserActionTime'] == user_action_time
+ assert request_props[cs.CR + '.PreferredHandler'] == ""
+ assert request_props[cs.CR + '.Interfaces'] == []
+
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ if ensure:
+ q.dbus_return(cm_request_call.message, True, # <- Yours
+ channel.object_path, channel.immutable, signature='boa{sv}')
+ else: # Create
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ # Observer should get told, processing waits for it
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert e.args[4] == [request_path], e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+
+ # Observer says "OK, go"
+ q.dbus_return(e.message, signature='')
+
+ # Handler is next
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time
+ assert isinstance(e.args[5], dict)
+ assert len(e.args) == 6
+
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ # CR emits Succeeded (or in Mardy's version, Account emits Succeeded)
+ q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded',
+ args=[request_path]),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Succeeded'),
+ )
+
+ # Close the channel
+ channel.close()
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/create-rejected-by-mini-plugin.py b/tests/twisted/dispatcher/create-rejected-by-mini-plugin.py
new file mode 100644
index 00000000..2f517589
--- /dev/null
+++ b/tests/twisted/dispatcher/create-rejected-by-mini-plugin.py
@@ -0,0 +1,102 @@
+# 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
+"""Regression test for ChannelDispatcher rejecting channel creation due to
+a plugin.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+# Rejected by the plugin
+FORBIDDEN_CTYPE = 'com.example.ForbiddenChannel'
+
+def test(q, bus, mc):
+ # For this test, we should never actually be asked to make a channel.
+ forbidden = [
+ EventPattern('dbus-method-call', method='CreateChannel'),
+ EventPattern('dbus-method-call', method='EnsureChannel'),
+ EventPattern('dbus-method-call', method='ObserveChannels'),
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ EventPattern('dbus-method-call', method='HandleChannels'),
+ ]
+ q.forbid_events(forbidden)
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': FORBIDDEN_CTYPE,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[fixed_properties], approve=[fixed_properties],
+ handle=[fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ user_action_time = dbus.Int64(1238582606)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # UI calls ChannelDispatcher.CreateChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': FORBIDDEN_CTYPE,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request, user_action_time, client.bus_name,
+ dbus_interface=cs.CD)
+
+ ret = q.expect('dbus-return',
+ method='CreateChannel')
+ request_path = ret.value[0]
+
+ # UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == client.bus_name
+ assert request_props['Interfaces'] == []
+
+ call_async(q, cr, 'Proceed', dbus_interface=cs.CR)
+
+ q.expect('dbus-return', method='Proceed')
+ q.expect('dbus-signal',
+ path=cr.object_path, interface=cs.CR, signal='Failed',
+ args=[cs.PERMISSION_DENIED, "No, you don't"])
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/create-text.py b/tests/twisted/dispatcher/create-text.py
new file mode 100644
index 00000000..0a05f9d0
--- /dev/null
+++ b/tests/twisted/dispatcher/create-text.py
@@ -0,0 +1,223 @@
+# 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
+"""Regression test for the unofficial Account.Interface.Requests API when
+a channel can be created successfully.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import (EventPattern, tp_name_prefix, tp_path_prefix,
+ call_async, assertEquals, assertContains, assertLength)
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # This client doesn't say it can handle channels, but if it requests one
+ # for itself, we'll at least try dispatching to it.
+ #
+ # A real-world use case for this is if a client wants to request channels,
+ # and handle the channels that it, itself, requested, but not handle
+ # anything requested by others: for instance, nautilus-sendto behaves
+ # like this. See fd.o #23651
+ unsuitable = SimulatedClient(q, bus, 'Unsuitable',
+ observe=[], approve=[], handle=[], is_handler=True,
+ bypass_approval=False)
+
+ # No Approver should be invoked at any point during this test, because the
+ # Channel was Requested
+ def fail_on_approval(e):
+ raise AssertionError('Approver should not be invoked')
+ q.add_dbus_method_impl(fail_on_approval, path=client.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation')
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client, unsuitable])
+
+ test_channel_creation(q, bus, account, client, conn, False)
+ test_channel_creation(q, bus, account, client, conn, True)
+ test_channel_creation(q, bus, account, client, conn, False, unsuitable)
+ test_channel_creation(q, bus, account, client, conn, False, unsuitable,
+ cs.CHANNEL_TYPE_STREAMED_MEDIA)
+
+def test_channel_creation(q, bus, account, client, conn,
+ ensure=False, prefer=None, channel_type=cs.CHANNEL_TYPE_TEXT):
+ user_action_time = dbus.Int64(1238582606)
+
+ if prefer is None:
+ prefer = client
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # chat UI calls ChannelDispatcher.EnsureChannel or CreateChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': channel_type,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, cd,
+ (ensure and 'EnsureChannel' or 'CreateChannel'),
+ account.object_path, request, user_action_time, prefer.bus_name,
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return',
+ method=(ensure and 'EnsureChannel' or 'CreateChannel'))
+ request_path = ret.value[0]
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == prefer.bus_name
+ assert request_props['Interfaces'] == []
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ # FIXME: should the EnsureChannel/CreateChannel call, and the AddRequest
+ # call, be in a defined order? Probably not though, since CMs and Clients
+ # aren't meant to be the same process!
+
+ cm_request_call, add_request_call = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method=(ensure and 'EnsureChannel' or 'CreateChannel'),
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest'),
+ )
+
+ assert add_request_call.args[0] == request_path
+ assert add_request_call.path == prefer.object_path
+ request_props = add_request_call.args[1]
+ assert request_props[cs.CR + '.Account'] == account.object_path
+ assert request_props[cs.CR + '.Requests'] == [request]
+ assert request_props[cs.CR + '.UserActionTime'] == user_action_time
+ assert request_props[cs.CR + '.PreferredHandler'] == prefer.bus_name
+ assert request_props[cs.CR + '.Interfaces'] == []
+
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ if ensure:
+ q.dbus_return(cm_request_call.message, True, # <- Yours
+ channel.object_path, channel.immutable, signature='boa{sv}')
+ else: # Create
+ q.dbus_return(cm_request_call.message,
+ channel.object_path, channel.immutable, signature='oa{sv}')
+ channel.announce()
+
+ if channel_type == cs.CHANNEL_TYPE_TEXT:
+ # Observer should get told, processing waits for it
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert e.args[4] == [request_path], e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+
+ # Observer says "OK, go"
+ q.dbus_return(e.message, signature='')
+
+ # Handler is next
+ e = q.expect('dbus-method-call',
+ path=prefer.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time
+ assert isinstance(e.args[5], dict)
+ assertContains('request-properties', e.args[5])
+ assertContains(request_path, e.args[5]['request-properties'])
+ assertLength(1, e.args[5]['request-properties'])
+ assertEquals(request_props, e.args[5]['request-properties'][request_path])
+ assert len(e.args) == 6
+
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ # SucceededWithChannel is fired first
+ e = q.expect('dbus-signal', path=request_path, interface=cs.CR,
+ signal='SucceededWithChannel')
+
+ assertEquals(conn.object_path, e.args[0])
+ assert isinstance(e.args[1], dict), e.args[1]
+ assertEquals(channel.object_path, e.args[2])
+ assertEquals(channel_immutable, e.args[3])
+
+ # CR emits Succeeded (or in Mardy's version, Account emits Succeeded)
+ q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded',
+ args=[request_path]),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Succeeded'),
+ )
+
+ # Close the channel
+ channel.close()
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/created-behind-our-back.py b/tests/twisted/dispatcher/created-behind-our-back.py
new file mode 100644
index 00000000..55d1b4b0
--- /dev/null
+++ b/tests/twisted/dispatcher/created-behind-our-back.py
@@ -0,0 +1,106 @@
+# 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
+
+"""Regression test for channels created "by going behind MC's back"."""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ # Because the channels are handled by another process, we should never be
+ # asked to approve or handle them.
+ forbidden = [
+ EventPattern('dbus-method-call', method='HandleChannels'),
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ ]
+ q.forbid_events(forbidden)
+
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ expect_client_setup(q, [empathy, kopete])
+
+ # a non-MC-using client goes behind our back to call CreateChannel or
+ # EnsureChannel on the Connection directly
+ #
+ # (This is not simulated here: we just behave as though it had happened)
+
+ channel_immutable = dbus.Dictionary(text_fixed_properties)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_immutable[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ channel.announce()
+
+ # Observer should get told, processing waits for it
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert e.args[4] == [], e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+
+ assert e.args == k.args
+
+ # Observers say "OK, go"
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ sync_dbus(bus, q, mc)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/dispatch-activatable.py b/tests/twisted/dispatcher/dispatch-activatable.py
new file mode 100644
index 00000000..cb7b9cb1
--- /dev/null
+++ b/tests/twisted/dispatcher/dispatch-activatable.py
@@ -0,0 +1,133 @@
+# 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
+"""Regression test for dispatching an incoming AbiWord StreamTube channel.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ abiword_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL_TYPE_STREAM_TUBE + '.Service': 'x-abiword',
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAM_TUBE,
+ }, signature='sv')
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(abiword_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetHandleType'] = cs.HT_CONTACT
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ assert handlers == [cs.tp_name_prefix + '.Client.AbiWord'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ abiword_object_path = cs.tp_path_prefix + '/Client/AbiWord'
+
+ # There are no approvers so the only Handler is asked to handle the
+ # channels
+
+ # The fake AbiWord implementation is run
+ e = q.expect('dbus-signal',
+ path=cs.tp_path_prefix + '/RegressionTests',
+ interface=cs.tp_name_prefix + '.RegressionTests',
+ signal='FakeStartup',
+ args=[cs.tp_name_prefix + '.Client.AbiWord'],
+ )
+ # We take on its identity to be able to continue with the test
+ abiword = SimulatedClient(q, bus, 'AbiWord',
+ handle=[abiword_fixed_properties])
+
+ e = q.expect('dbus-method-call',
+ path=abiword.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ # AbiWord accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ # FIXME: this isn't currently emitted (part of the same bug as emitting
+ # it as soon as HandleChannels is called)
+ #q.expect('dbus-signal', path=cdo_path, signal='Finished')
+ #q.expect('dbus-signal', path=cs.CD_PATH,
+ # signal='DispatchOperationFinished', args=[cdo_path])
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/dispatch-before-connected.py b/tests/twisted/dispatcher/dispatch-before-connected.py
new file mode 100644
index 00000000..9925e0fe
--- /dev/null
+++ b/tests/twisted/dispatcher/dispatch-before-connected.py
@@ -0,0 +1,104 @@
+# Copyright (C) 2010 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
+
+"""Feature test ensuring that MC dispatches channels that are created before
+the connection status is connected.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+CHANNEL_TYPE_SERVER_VERIFICATION = \
+ 'org.freedesktop.Telepathy.Channel.Type.ServerVerification.DRAFT'
+CHANNEL_IFACE_VERIFICATION = \
+ 'org.freedesktop.Telepathy.Channel.Interface.Verification.DRAFT '
+CHANNEL_IFACE_IDENT_EXCHANGE = \
+ 'org.freedesktop.Telepathy.Channel.Interface.IdentityExchange.DRAFT'
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someone@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ # Ensure that it's enabled but has offline RP and doesn't connect
+ # automatically
+
+ verification_filter = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': 0,
+ cs.CHANNEL + '.ChannelType': CHANNEL_TYPE_SERVER_VERIFICATION,
+ }, signature='sv')
+
+ verifier_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(verifier_bus)
+ verifier = SimulatedClient(q, verifier_bus, 'Verifier',
+ handle=[verification_filter])
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [verifier])
+
+ account_props.Set(cs.ACCOUNT, 'RequestedPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_AVAILABLE), 'available', ''))
+
+ account_props.Set(cs.ACCOUNT, 'Enabled', True)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', 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_requests=True, has_presence=True)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ e = q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path,
+ interface=cs.CONN)
+
+ channel_properties = dbus.Dictionary(verification_filter, signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = ''
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = 0
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = ''
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = 0
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array([
+ CHANNEL_IFACE_IDENT_EXCHANGE,
+ CHANNEL_IFACE_VERIFICATION,
+ cs.CHANNEL], signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ e = q.expect('dbus-method-call',
+ path=verifier.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py b/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py
new file mode 100644
index 00000000..50bc8033
--- /dev/null
+++ b/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py
@@ -0,0 +1,261 @@
+# 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
+"""Regression test for dispatching an incoming Text channel.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+def signal_channel_expect_query(q, bus, account, conn, empathy, kopete):
+ # This target is special-cased in test-plugin.c
+ target = 'policy@example.net'
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = target
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = target
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([cs.CHANNEL_IFACE_DESTROYABLE, cs.CHANNEL_IFACE_GROUP,
+ ],signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties, group=True)
+ chan.announce()
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+
+ # What does the policy service think?
+ permission = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='RequestPermission')
+
+ # Think about it for a bit
+ sync_dbus(bus, q, account)
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # Let the test code decide how to reply
+ return permission, chan, cdo_path
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ policy_bus_name_ref = dbus.service.BusName('com.example.Policy', bus)
+
+ # For the beginning of this test, we should never be asked to handle
+ # a channel.
+ forbidden = [
+ EventPattern('dbus-method-call', method='HandleChannels'),
+ ]
+ q.forbid_events(forbidden)
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ e, chan, cdo_path = signal_channel_expect_query(q, bus, account, conn,
+ empathy, kopete)
+
+ # No.
+ q.dbus_raise(e.message, 'com.example.Errors.No', 'Denied!')
+
+ # The plugin responds
+ _, _, e = q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished',
+ args=[cdo_path]),
+ EventPattern('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ # this error message is from the plugin
+ method='RemoveMembersWithReason', args=[[conn.self_handle],
+ "Computer says no", cs.GROUP_REASON_PERMISSION_DENIED],
+ handled=False),
+ )
+ q.dbus_return(e.message, signature='')
+ chan.close()
+
+ # Try again
+ e, chan, cdo_path = signal_channel_expect_query(q, bus, account, conn,
+ empathy, kopete)
+
+ # Yes.
+ q.dbus_return(e.message, signature='')
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ kopete_cdo = bus.get_object(cs.CD, cdo_path)
+ kopete_cdo_iface = dbus.Interface(kopete_cdo, cs.CDO)
+ call_async(q, kopete_cdo_iface, 'Claim')
+
+ q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ signal='DispatchOperationFinished', args=[cdo_path]),
+ EventPattern('dbus-return', method='Claim'),
+ )
+
+ sync_dbus(bus, q, mc)
+
+ # From now on we no longer want to forbid HandleChannels, but we do want
+ # to forbid AddDispatchOperation
+ q.unforbid_events(forbidden)
+ forbidden = [
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ ]
+ q.forbid_events(forbidden)
+
+ # Try yet again
+ policy_request, chan, cdo_path = signal_channel_expect_query(q, bus,
+ account, conn, empathy, kopete)
+
+ # Before the policy service replies, someone requests the same channel
+
+ user_action_time = dbus.Int64(1238582606)
+ call_async(q, cd, 'EnsureChannel',
+ account.object_path, chan.immutable, user_action_time,
+ kopete.bus_name, dbus_interface=cs.CD)
+ ret, add_request_call = q.expect_many(
+ EventPattern('dbus-return', method='EnsureChannel'),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=kopete.object_path),
+ )
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.CD, request_path)
+ cr.Proceed(dbus_interface=cs.CR)
+
+ cm_request_call = q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='EnsureChannel',
+ path=conn.object_path, args=[chan.immutable], handled=False)
+
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Time passes. The CM returns the existing channel, and the policy
+ # service gets round to replying
+
+ q.dbus_return(cm_request_call.message, False,
+ chan.object_path, chan.immutable, signature='boa{sv}')
+
+ q.dbus_return(policy_request.message, signature='')
+
+ e = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == chan.immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time, (e.args[4], user_action_time)
+ assert isinstance(e.args[5], dict)
+ assert len(e.args) == 6
+
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ sync_dbus(bus, q, mc)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/dispatch-delayed-by-plugin.py b/tests/twisted/dispatcher/dispatch-delayed-by-plugin.py
new file mode 100644
index 00000000..24a47092
--- /dev/null
+++ b/tests/twisted/dispatcher/dispatch-delayed-by-plugin.py
@@ -0,0 +1,272 @@
+# 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
+"""Regression test for dispatching an incoming Text channel.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+def signal_channel_expect_query(q, bus, account, conn):
+ # This target is special-cased in test-plugin.c
+ target = 'policy@example.com'
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = target
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = target
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([cs.CHANNEL_IFACE_DESTROYABLE, cs.CHANNEL_IFACE_GROUP,
+ ],signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties, group=True)
+ chan.announce()
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ # What does the policy service think?
+ e = q.expect('dbus-method-call', path='/com/example/Policy',
+ interface='com.example.Policy', method='RequestPermission')
+
+ # Think about it for a bit
+ sync_dbus(bus, q, account)
+
+ # Let the test code decide how to reply
+ return e, chan, cdo_path
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ policy_bus_name_ref = dbus.service.BusName('com.example.Policy', bus)
+
+ # For the beginning of this test, we should never be asked to handle
+ # a channel.
+ forbidden = [
+ EventPattern('dbus-method-call', method='HandleChannels'),
+ ]
+ q.forbid_events(forbidden)
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ e, chan, cdo_path = signal_channel_expect_query(q, bus, account, conn)
+
+ # No.
+ q.dbus_raise(e.message, 'com.example.Errors.No', 'Denied!')
+
+ # The plugin responds
+ _, _, e = q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished',
+ args=[cdo_path]),
+ EventPattern('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ # this error message is from the plugin
+ method='RemoveMembersWithReason', args=[[conn.self_handle],
+ "Computer says no", cs.GROUP_REASON_PERMISSION_DENIED],
+ handled=False),
+ )
+ q.dbus_return(e.message, signature='')
+ chan.close()
+
+ # Try again
+ e, chan, cdo_path = signal_channel_expect_query(q, bus, account, conn)
+
+ # Yes.
+ q.dbus_return(e.message, signature='')
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ kopete_cdo = bus.get_object(cs.CD, cdo_path)
+ kopete_cdo_iface = dbus.Interface(kopete_cdo, cs.CDO)
+ call_async(q, kopete_cdo_iface, 'Claim')
+
+ q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ signal='DispatchOperationFinished', args=[cdo_path]),
+ EventPattern('dbus-return', method='Claim'),
+ )
+
+ sync_dbus(bus, q, mc)
+
+ # From now on we no longer want to forbid HandleChannels, but we do want
+ # to forbid AddDispatchOperation
+ q.unforbid_events(forbidden)
+ forbidden = [
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ ]
+ q.forbid_events(forbidden)
+
+ # Try yet again
+ policy_request, chan, cdo_path = signal_channel_expect_query(q, bus,
+ account, conn)
+
+ # Before the policy service replies, someone requests the same channel
+
+ user_action_time = dbus.Int64(1238582606)
+ call_async(q, cd, 'EnsureChannel',
+ account.object_path, chan.immutable, user_action_time,
+ kopete.bus_name, dbus_interface=cs.CD)
+ ret, add_request_call = q.expect_many(
+ EventPattern('dbus-return', method='EnsureChannel'),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=kopete.object_path),
+ )
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.CD, request_path)
+ cr.Proceed(dbus_interface=cs.CR)
+
+ cm_request_call = q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='EnsureChannel',
+ path=conn.object_path, args=[chan.immutable], handled=False)
+
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Time passes. The CM returns the existing channel, and the policy
+ # service gets round to replying
+
+ q.dbus_return(cm_request_call.message, False,
+ chan.object_path, chan.immutable, signature='boa{sv}')
+
+ q.dbus_return(policy_request.message, signature='')
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ e = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == chan.immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time, (e.args[4], user_action_time)
+ assert isinstance(e.args[5], dict)
+ assert len(e.args) == 6
+
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ sync_dbus(bus, q, mc)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/dispatch-obsolete.py b/tests/twisted/dispatcher/dispatch-obsolete.py
new file mode 100644
index 00000000..4431631a
--- /dev/null
+++ b/tests/twisted/dispatcher/dispatch-obsolete.py
@@ -0,0 +1,191 @@
+# 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
+"""Regression test for dispatching an incoming Text channel from a CM without
+Requests.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params,
+ has_requests=False)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ # the announced channel properties are some subset of what it actually has
+ for key in channels[0][1]:
+ assert channel_properties[key] == channels[0][1][key], key
+ assert cs.CHANNEL + '.TargetHandleType' in channels[0][1]
+ assert cs.CHANNEL + '.ChannelType' in channels[0][1]
+ assert cs.CHANNEL + '.TargetHandle' in channels[0][1]
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args[1:] == [cdo_path, cdo_properties]
+ channels = e.args[0]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ # the announced channel properties are some subset of what it actually has
+ for key in channels[0][1]:
+ assert channel_properties[key] == channels[0][1][key], key
+ assert cs.CHANNEL + '.TargetHandleType' in channels[0][1]
+ assert cs.CHANNEL + '.ChannelType' in channels[0][1]
+ assert cs.CHANNEL + '.TargetHandle' in channels[0][1]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Empathy first
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/dispatch-rejected-by-mini-plugin.py b/tests/twisted/dispatcher/dispatch-rejected-by-mini-plugin.py
new file mode 100644
index 00000000..eeb6e9ec
--- /dev/null
+++ b/tests/twisted/dispatcher/dispatch-rejected-by-mini-plugin.py
@@ -0,0 +1,217 @@
+# 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
+"""Regression test for plugins rejecting an incoming channel immediately.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # Throughout this entire test, we should never be asked to approve or
+ # handle a channel.
+ forbidden = [
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ EventPattern('dbus-method-call', method='HandleChannels'),
+ ]
+ q.forbid_events(forbidden)
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # This ID is special-cased by the mcp-plugin plugin, which rejects
+ # channels to or from it by destroying them, without waiting for observers
+ # to return
+ target = 'rick.astley@example.net'
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = target
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = target
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([cs.CHANNEL_IFACE_DESTROYABLE, cs.CHANNEL_IFACE_GROUP,
+ ],signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties, group=True)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ # The plugin realises we've been rickrolled, and responds. It calls Destroy
+ # even though neither Empathy nor Kopete has returned from ObserveChannels
+ # yet
+ destruction, e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_DESTROYABLE, method='Destroy',
+ args=[], handled=False),
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ # treat the destruction like Close
+ chan.Close(destruction)
+
+ # Both Observers indicate that they are ready to proceed (somewhat late)
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # When the Observers have returned, the CDO finishes
+ q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished',
+ args=[cdo_path]),
+ )
+
+ # This ID is also special-cased
+ target = 'mc.hammer@example.net'
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = target
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = target
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([cs.CHANNEL_IFACE_DESTROYABLE, cs.CHANNEL_IFACE_GROUP,
+ ],signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties, group=True)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ # The plugin realises it's MC Hammer, and responds, but its response waits
+ # for the observers to return
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+
+ sync_dbus(bus, q, account)
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ _, _, e = q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished',
+ args=[cdo_path]),
+ EventPattern('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ method='RemoveMembersWithReason', args=[[conn.self_handle],
+ "Can't touch this", cs.GROUP_REASON_PERMISSION_DENIED],
+ handled=False),
+ )
+ q.dbus_return(e.message, signature='')
+ chan.close()
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/dispatch-rejected-by-plugin.py b/tests/twisted/dispatcher/dispatch-rejected-by-plugin.py
new file mode 100644
index 00000000..272eac37
--- /dev/null
+++ b/tests/twisted/dispatcher/dispatch-rejected-by-plugin.py
@@ -0,0 +1,239 @@
+# 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
+"""Regression test for dispatching an incoming Text channel.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # Throughout this entire test, we should never be asked to approve or
+ # handle a channel.
+ forbidden = [
+ EventPattern('dbus-method-call', method='AddDispatchOperation'),
+ EventPattern('dbus-method-call', method='HandleChannels'),
+ # FIXME: currently we're never asked to ObserveChannels either -
+ # is that right?
+ #
+ # EventPattern('dbus-method-call', method='ObserveChannels'),
+ ]
+ q.forbid_events(forbidden)
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # This ID is special-cased by the test-plugin plugin, which rejects
+ # channels to or from it
+ target = 'rick.astley@example.com'
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = target
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = target
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([cs.CHANNEL_IFACE_DESTROYABLE, cs.CHANNEL_IFACE_GROUP,
+ ],signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties, group=True)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ # The plugin realises we've been rickrolled, and responds
+ e = q.expect('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_DESTROYABLE, method='Destroy', args=[],
+ handled=False)
+ # treat it like Close
+ chan.Close(e)
+
+ q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished',
+ args=[cdo_path]),
+ )
+
+ # This ID is also special-cased
+ target = 'mc.hammer@example.com'
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = target
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = target
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([cs.CHANNEL_IFACE_DESTROYABLE, cs.CHANNEL_IFACE_GROUP,
+ ],signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties, group=True)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ # The plugin realises it's MC Hammer, and responds
+ e = q.expect('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_DESTROYABLE, method='Destroy', args=[],
+ handled=False)
+ # treat it like Close
+ chan.Close(e)
+
+ q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished',
+ args=[cdo_path]),
+ )
+
+ # This ID is *also* special-cased
+ target = 'hammertime@example.com'
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = target
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = target
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, target)
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([cs.CHANNEL_IFACE_DESTROYABLE, cs.CHANNEL_IFACE_GROUP,
+ ],signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties, group=True)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ # The plugin realises it's MC Hammer, and responds
+ _, _, e = q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path,
+ interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished',
+ args=[cdo_path]),
+ EventPattern('dbus-method-call',
+ path=chan.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ method='RemoveMembersWithReason', args=[[conn.self_handle],
+ "Can't touch this", cs.GROUP_REASON_PERMISSION_DENIED],
+ handled=False),
+ )
+ q.dbus_return(e.message, signature='')
+ chan.close()
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/dispatch-text.py b/tests/twisted/dispatcher/dispatch-text.py
new file mode 100644
index 00000000..b1f6ce60
--- /dev/null
+++ b/tests/twisted/dispatcher/dispatch-text.py
@@ -0,0 +1,542 @@
+# 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
+"""Regression test for dispatching an incoming Text channel.
+"""
+
+import dbus
+import dbus.bus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ empathy_bus = dbus.bus.BusConnection()
+ empathy_bus.set_exit_on_disconnect(False) # we'll disconnect later
+ kopete_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(empathy_bus)
+ q.attach_to_bus(kopete_bus)
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, empathy_bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, kopete_bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, bus=empathy_bus, signature='')
+ q.dbus_return(e.message, bus=kopete_bus, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # Using an invalid Handler name should fail
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.The.Moon.On.A.Stick')
+ q.expect('dbus-error', method='HandleWith')
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.the moon on a stick')
+ q.expect('dbus-error', method='HandleWith')
+
+ # The user responds to Empathy first
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # Approve another channel using HandleWithTime()
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'lucien'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'lucien')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'lucien'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'lucien')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, bus=empathy_bus, signature='')
+ q.dbus_return(e.message, bus=kopete_bus, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Empathy first
+ call_async(q, cdo_iface, 'HandleWithTime',
+ cs.tp_name_prefix + '.Client.Empathy', 13)
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ account_path, conn_path, channels, requests, action_time, info = e.args
+ assert action_time == 13
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWithTime'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # From now on, it is an error to get HandleChannels (because we're
+ # testing Claim())
+ forbidden = [
+ EventPattern('dbus-method-call', method='HandleChannels'),
+ ]
+ q.forbid_events(forbidden)
+
+ # Another channel
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'mercutio'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'mercutio')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'mercutio'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'mercutio')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ claimed_chan = SimulatedChannel(conn, channel_properties)
+ claimed_chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == claimed_chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args == [[(claimed_chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Empathy first, and Empathy decides it wants the
+ # channel for itself
+ empathy_cdo = empathy_bus.get_object(cdo.bus_name, cdo.object_path)
+ empathy_cdo_iface = dbus.Interface(empathy_cdo, cs.CDO)
+ call_async(q, empathy_cdo_iface, 'Claim')
+
+ q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ signal='DispatchOperationFinished', args=[cdo_path]),
+ EventPattern('dbus-return', method='Claim'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # A third channel
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'benvolio'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'benvolio')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'benvolio'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'benvolio')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ third_chan = SimulatedChannel(conn, channel_properties)
+ third_chan.announce()
+
+ # third_chan should not be closed
+ q.unforbid_events(forbidden)
+ forbidden.append(EventPattern('dbus-method-call', method='Close',
+ path=third_chan.object_path))
+ q.forbid_events(forbidden)
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ # Kopete closes this one
+ kopete_cdo = kopete_bus.get_object(cdo.bus_name, cdo.object_path)
+ kopete_cdo_iface = dbus.Interface(kopete_cdo, cs.CDO)
+ call_async(q, kopete_cdo_iface, 'Claim')
+
+ q.expect_many(
+ EventPattern('dbus-signal', path=cdo_path, signal='Finished'),
+ EventPattern('dbus-signal', path=cs.CD_PATH,
+ signal='DispatchOperationFinished', args=[cdo_path]),
+ EventPattern('dbus-return', method='Claim'),
+ )
+
+ # Empathy crashes
+ empathy.release_name()
+
+ e = q.expect('dbus-signal',
+ signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == empathy.bus_name and e.args[2] == ''),
+ )
+ empathy_unique_name = e.args[1]
+
+ empathy_bus.flush()
+ empathy_bus.close()
+
+ # In response, the channels that were being handled by Empathy are closed.
+ # Kopete's channel is *not* closed.
+ q.expect_many(
+ EventPattern('dbus-signal',
+ signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == empathy_unique_name and e.args[2] == '')),
+ EventPattern('dbus-method-call',
+ path=chan.object_path, method='Close'),
+ EventPattern('dbus-method-call',
+ path=claimed_chan.object_path, method='Close'),
+ )
+
+ sync_dbus(bus, q, mc)
+ q.unforbid_events(forbidden)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/ensure-and-redispatch.py b/tests/twisted/dispatcher/ensure-and-redispatch.py
new file mode 100644
index 00000000..42e60c5d
--- /dev/null
+++ b/tests/twisted/dispatcher/ensure-and-redispatch.py
@@ -0,0 +1,338 @@
+# 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
+
+"""Feature test ensuring that MC deals correctly with EnsureChannel returning
+a channel that has already been dispatched to a handler.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import (EventPattern, tp_name_prefix, tp_path_prefix,
+ call_async, assertContains, assertLength, assertEquals)
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # We have more than one Client "head" on the same unique name, to test
+ # fd.o #24645 - we want the same "head" to be reinvoked if at all possible
+
+ # This one is first in alphabetical order, is discovered first, and can
+ # handle all channels, but is not the one we want
+ empathy_worse_match = SimulatedClient(q, bus, 'A.Temporary.Handler',
+ observe=[], approve=[],
+ handle=[[]], bypass_approval=False)
+
+ # This is the one we actually want
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # this one is a closer match, but not the preferred handler
+ closer_match = dbus.Dictionary(text_fixed_properties)
+ closer_match[cs.CHANNEL + '.TargetID'] = 'juliet'
+ empathy_better_match = SimulatedClient(q, bus, 'Empathy.BetterMatch',
+ observe=[], approve=[],
+ handle=[closer_match], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy_worse_match, empathy,
+ empathy_better_match])
+
+ channel = test_channel_creation(q, bus, account, empathy, conn)
+
+ # After the channel has been dispatched, a handler that would normally
+ # be a closer match turns up. Regardless, we should not redispatch to it.
+ # For the better client to be treated as if it's in a different process,
+ # it needs its own D-Bus connection.
+ closer_match = dbus.Dictionary(text_fixed_properties)
+ closer_match[cs.CHANNEL + '.TargetID'] = 'juliet'
+ closer_match[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ better_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(better_bus)
+ better = SimulatedClient(q, better_bus, 'BetterMatch',
+ observe=[], approve=[],
+ handle=[closer_match], bypass_approval=False)
+ expect_client_setup(q, [better])
+
+ test_channel_redispatch(q, bus, account, empathy, conn, channel)
+ test_channel_redispatch(q, bus, account, empathy, conn, channel,
+ ungrateful_handler=True)
+ empathy.release_name()
+ empathy_better_match.release_name()
+ empathy_worse_match.release_name()
+ test_channel_redispatch(q, bus, account, empathy, conn, channel,
+ client_gone=True)
+ channel.close()
+
+def test_channel_creation(q, bus, account, client, conn):
+ user_action_time = dbus.Int64(1238582606)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # chat UI calls ChannelDispatcher.EnsureChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, cd, 'EnsureChannel',
+ account.object_path, request, user_action_time, client.bus_name,
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='EnsureChannel')
+ request_path = ret.value[0]
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == client.bus_name
+ assert request_props['Interfaces'] == []
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ cm_request_call, add_request_call = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='EnsureChannel',
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=client.object_path),
+ )
+
+ assert add_request_call.args[0] == request_path
+ request_props = add_request_call.args[1]
+ assert request_props[cs.CR + '.Account'] == account.object_path
+ assert request_props[cs.CR + '.Requests'] == [request]
+ assert request_props[cs.CR + '.UserActionTime'] == user_action_time
+ assert request_props[cs.CR + '.PreferredHandler'] == client.bus_name
+ assert request_props[cs.CR + '.Interfaces'] == []
+
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # this order of events is guaranteed by telepathy-spec (since 0.17.14)
+ q.dbus_return(cm_request_call.message, True, # <- Yours
+ channel.object_path, channel.immutable, signature='boa{sv}')
+ channel.announce()
+
+ # Observer should get told, processing waits for it
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert e.args[4] == [request_path], e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+
+ # Observer says "OK, go"
+ q.dbus_return(e.message, signature='')
+
+ # Handler is next
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time
+ assert isinstance(e.args[5], dict)
+ assert len(e.args) == 6
+
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ # CR emits Succeeded (or in Mardy's version, Account emits Succeeded)
+ q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded',
+ args=[request_path]),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Succeeded'),
+ )
+
+ return channel
+
+def test_channel_redispatch(q, bus, account, client, conn, channel,
+ ungrateful_handler=False, client_gone=False):
+
+ user_action_time = dbus.Int64(1244444444)
+
+ forbidden = [
+ # Because we create no new channels, nothing should be observed.
+ EventPattern('dbus-method-call', method='ObserveChannels'),
+ # Even though there is a better handler on a different unique
+ # name, the channels must not be re-dispatched to it.
+ EventPattern('dbus-method-call', method='HandleChannels',
+ predicate=lambda e: e.path != client.object_path),
+ # If the handler rejects the re-handle call, the channel must not
+ # be closed.
+ EventPattern('dbus-method-call', method='Close'),
+ ]
+
+ if client_gone:
+ # There's nothing to call these methods on any more.
+ forbidden.append(EventPattern('dbus-method-call',
+ method='HandleChannels'))
+ forbidden.append(EventPattern('dbus-method-call',
+ method='AddRequest'))
+
+ q.forbid_events(forbidden)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # UI calls ChannelDispatcher.EnsureChannel again
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ call_async(q, cd, 'EnsureChannel',
+ account.object_path, request, user_action_time, client.bus_name,
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='EnsureChannel')
+ request_path = ret.value[0]
+
+ # UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == client.bus_name
+ assert request_props['Interfaces'] == []
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ cm_request_pattern = EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='EnsureChannel',
+ path=conn.object_path, args=[request], handled=False)
+
+ if client_gone:
+ (cm_request_call,) = q.expect_many(cm_request_pattern)
+ add_request_call = None
+ else:
+ (cm_request_call, add_request_call) = q.expect_many(
+ cm_request_pattern,
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=client.object_path),
+ )
+
+ if add_request_call is not None:
+ assert add_request_call.args[0] == request_path
+ request_props = add_request_call.args[1]
+ assert request_props[cs.CR + '.Account'] == account.object_path
+ assert request_props[cs.CR + '.Requests'] == [request]
+ assert request_props[cs.CR + '.UserActionTime'] == user_action_time
+ assert request_props[cs.CR + '.PreferredHandler'] == client.bus_name
+ assert request_props[cs.CR + '.Interfaces'] == []
+
+ q.dbus_return(add_request_call.message, signature='')
+
+ # Time passes. The same channel is returned.
+ q.dbus_return(cm_request_call.message, False, # <- Yours
+ channel.object_path, channel.immutable, signature='boa{sv}')
+
+ if not client_gone:
+ # Handler is re-invoked. This HandleChannels call is only said to
+ # satisfy the new request, because the earlier request has already
+ # been satisfied.
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel.immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time
+ assert isinstance(e.args[5], dict)
+ assertContains('request-properties', e.args[5])
+ assertContains(request_path, e.args[5]['request-properties'])
+ assertLength(1, e.args[5]['request-properties'])
+ assertEquals(request_props,
+ e.args[5]['request-properties'][request_path])
+ assert len(e.args) == 6
+
+ if ungrateful_handler:
+ q.dbus_raise(e.message, cs.INVALID_ARGUMENT,
+ 'I am very strict in my misunderstanding of telepathy-spec')
+ else:
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ # CR emits Succeeded (or in Mardy's version, Account emits Succeeded)
+ q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded',
+ args=[request_path]),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Succeeded'),
+ )
+
+ q.unforbid_events(forbidden)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/ensure-is-approval.py b/tests/twisted/dispatcher/ensure-is-approval.py
new file mode 100644
index 00000000..a2fb0850
--- /dev/null
+++ b/tests/twisted/dispatcher/ensure-is-approval.py
@@ -0,0 +1,258 @@
+# 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
+"""Regression test for EnsureChannel counting as approval of an incoming text
+channel.
+"""
+
+import dbus
+import dbus.bus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ # One client (Kopete) has less specific filters than the other (Empathy),
+ # to make sure that the dispatcher would normally prefer Empathy; this
+ # means that when we use Kopete as the preferred handler, we know that
+ # if Kopete is invoked, then preferring the preferred handler correctly
+ # took precedence over the normal logic.
+ vague_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ empathy_bus = dbus.bus.BusConnection()
+ kopete_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(empathy_bus)
+ q.attach_to_bus(kopete_bus)
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, empathy_bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, kopete_bus, 'Kopete',
+ observe=[vague_fixed_properties], approve=[vague_fixed_properties],
+ handle=[vague_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ # Empathy has a more specific filter, so it comes first
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, bus=empathy_bus, signature='')
+ q.dbus_return(e.message, bus=kopete_bus, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention. However, the user is busy looking through the address
+ # book, and independently decides to talk to Juliet. The address book
+ # is from KDE so wants Kopete to be used.
+
+ user_action_time = dbus.Int64(1238582606)
+
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ call_async(q, cd, 'EnsureChannel',
+ account.object_path, request, user_action_time, kopete.bus_name,
+ dbus_interface=cs.CD)
+ ret, add_request_call = q.expect_many(
+ EventPattern('dbus-return', method='EnsureChannel'),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=kopete.object_path),
+ )
+ request_path = ret.value[0]
+
+ assert add_request_call.args[0] == request_path
+ request_props = add_request_call.args[1]
+ assert request_props[cs.CR + '.Account'] == account.object_path
+ assert request_props[cs.CR + '.Requests'] == [request]
+ assert request_props[cs.CR + '.UserActionTime'] == user_action_time
+ assert request_props[cs.CR + '.PreferredHandler'] == kopete.bus_name
+ assert request_props[cs.CR + '.Interfaces'] == []
+
+ # UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == kopete.bus_name
+ assert request_props['Interfaces'] == []
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ cm_request_call = q.expect('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='EnsureChannel',
+ path=conn.object_path, args=[request], handled=False)
+
+ q.dbus_return(add_request_call.message, bus=kopete_bus, signature='')
+
+ # Time passes. The CM returns the existing channel
+
+ q.dbus_return(cm_request_call.message, False,
+ chan.object_path, chan.immutable, signature='boa{sv}')
+
+ # EnsureChannel constitutes approval, so Kopete is told to handle the
+ # channel
+
+ e = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == chan.immutable, channels
+ assert e.args[3] == [request_path], e.args
+ assert e.args[4] == user_action_time, (e.args[4], user_action_time)
+ assert isinstance(e.args[5], dict)
+ assert len(e.args) == 6
+
+ q.dbus_return(e.message, bus=kopete_bus, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/ensure-rapidly.py b/tests/twisted/dispatcher/ensure-rapidly.py
new file mode 100644
index 00000000..17be46a5
--- /dev/null
+++ b/tests/twisted/dispatcher/ensure-rapidly.py
@@ -0,0 +1,243 @@
+# 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
+
+"""Feature test ensuring that MC deals correctly with EnsureChannel returning
+a channel that has already been dispatched to a handler.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import (EventPattern, tp_name_prefix, tp_path_prefix,
+ call_async, assertContains, assertLength, assertEquals)
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ channel = test_channel_creation(q, bus, account, client, conn,
+ yours_first=True, swap_requests=False)
+ channel.close()
+
+ channel = test_channel_creation(q, bus, account, client, conn,
+ yours_first=True, swap_requests=True)
+ channel.close()
+
+ channel = test_channel_creation(q, bus, account, client, conn,
+ yours_first=False, swap_requests=False)
+ channel.close()
+
+ channel = test_channel_creation(q, bus, account, client, conn,
+ yours_first=False, swap_requests=True)
+ channel.close()
+
+def test_channel_creation(q, bus, account, client, conn,
+ yours_first=True, swap_requests=False):
+ user_action_time1 = dbus.Int64(1238582606)
+ user_action_time2 = dbus.Int64(1244444444)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # chat UI calls ChannelDispatcher.EnsureChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, cd, 'EnsureChannel',
+ account.object_path, request, user_action_time1, client.bus_name,
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='EnsureChannel')
+ request_path = ret.value[0]
+
+ # chat UI connects to signals and calls ChannelRequest.Proceed()
+
+ cr1 = bus.get_object(cs.AM, request_path)
+ request_props = cr1.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time1
+ assert request_props['PreferredHandler'] == client.bus_name
+ assert request_props['Interfaces'] == []
+
+ cr1.Proceed(dbus_interface=cs.CR)
+
+ cm_request_call1, add_request_call1 = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='EnsureChannel',
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=client.object_path),
+ )
+
+ # Before the first request has succeeded, the user gets impatient and
+ # the UI re-requests.
+ call_async(q, cd, 'EnsureChannel',
+ account.object_path, request, user_action_time2, client.bus_name,
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='EnsureChannel')
+ request_path = ret.value[0]
+ cr2 = bus.get_object(cs.AM, request_path)
+
+ request_props = cr2.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time2
+ assert request_props['PreferredHandler'] == client.bus_name
+ assert request_props['Interfaces'] == []
+
+ cr2.Proceed(dbus_interface=cs.CR)
+
+ cm_request_call2, add_request_call2 = q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.CONN_IFACE_REQUESTS,
+ method='EnsureChannel',
+ path=conn.object_path, args=[request], handled=False),
+ EventPattern('dbus-method-call', handled=False,
+ interface=cs.CLIENT_IFACE_REQUESTS,
+ method='AddRequest', path=client.object_path),
+ )
+
+ assert add_request_call1.args[0] == cr1.object_path
+ request_props1 = add_request_call1.args[1]
+ assert request_props1[cs.CR + '.Account'] == account.object_path
+ assert request_props1[cs.CR + '.Requests'] == [request]
+ assert request_props1[cs.CR + '.UserActionTime'] == user_action_time1
+ assert request_props1[cs.CR + '.PreferredHandler'] == client.bus_name
+ assert request_props1[cs.CR + '.Interfaces'] == []
+
+ assert add_request_call2.args[0] == cr2.object_path
+ request_props2 = add_request_call2.args[1]
+ assert request_props2[cs.CR + '.Account'] == account.object_path
+ assert request_props2[cs.CR + '.Requests'] == [request]
+ assert request_props2[cs.CR + '.UserActionTime'] == user_action_time2
+ assert request_props2[cs.CR + '.PreferredHandler'] == client.bus_name
+ assert request_props2[cs.CR + '.Interfaces'] == []
+
+ q.dbus_return(add_request_call1.message, signature='')
+ q.dbus_return(add_request_call2.message, signature='')
+
+ # Time passes. A channel is returned.
+
+ channel_immutable = dbus.Dictionary(request)
+ channel_immutable[cs.CHANNEL + '.InitiatorID'] = conn.self_ident
+ channel_immutable[cs.CHANNEL + '.InitiatorHandle'] = conn.self_handle
+ channel_immutable[cs.CHANNEL + '.Requested'] = True
+ channel_immutable[cs.CHANNEL + '.Interfaces'] = \
+ dbus.Array([], signature='s')
+ channel_immutable[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel = SimulatedChannel(conn, channel_immutable)
+
+ # Having announce() (i.e. NewChannels) come last is guaranteed by
+ # telepathy-spec (since 0.17.14). There is no other ordering guarantee.
+
+ if swap_requests:
+ m2, m1 = cm_request_call1.message, cm_request_call2.message
+ else:
+ m1, m2 = cm_request_call1.message, cm_request_call2.message
+
+ q.dbus_return(m1, yours_first,
+ channel.object_path, channel.immutable, signature='boa{sv}')
+ q.dbus_return(m2, not yours_first,
+ channel.object_path, channel.immutable, signature='boa{sv}')
+
+ channel.announce()
+
+ # Observer should get told, processing waits for it
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == '/', e.args # no dispatch operation
+ assert sorted(e.args[4]) == sorted([cr1.object_path,
+ cr2.object_path]), e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel.immutable, channels
+
+ # Observer says "OK, go"
+ q.dbus_return(e.message, signature='')
+
+ # Handler is next
+ e = q.expect('dbus-method-call',
+ path=client.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == channel.object_path, channels
+ assert channels[0][1] == channel_immutable, channels
+ assert sorted(e.args[3]) == sorted([cr1.object_path,
+ cr2.object_path]), e.args
+ assert e.args[4] == user_action_time2, (e.args[4], user_action_time2)
+ assert isinstance(e.args[5], dict)
+ assertContains('request-properties', e.args[5])
+ assertContains(cr1.object_path, e.args[5]['request-properties'])
+ assertContains(cr2.object_path, e.args[5]['request-properties'])
+ assertLength(2, e.args[5]['request-properties'])
+ assertEquals(request_props1,
+ e.args[5]['request-properties'][cr1.object_path])
+ assertEquals(request_props2,
+ e.args[5]['request-properties'][cr2.object_path])
+ assert len(e.args) == 6
+
+ # Handler accepts the Channels
+ q.dbus_return(e.message, signature='')
+
+ # CR emits Succeeded (or in Mardy's version, Account emits Succeeded)
+ q.expect_many(
+ EventPattern('dbus-signal', path=account.object_path,
+ interface=cs.ACCOUNT_IFACE_NOKIA_REQUESTS, signal='Succeeded',
+ args=[request_path]),
+ EventPattern('dbus-signal', path=request_path,
+ interface=cs.CR, signal='Succeeded'),
+ )
+
+ return channel
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/exploding-bundles.py b/tests/twisted/dispatcher/exploding-bundles.py
new file mode 100644
index 00000000..a62a7009
--- /dev/null
+++ b/tests/twisted/dispatcher/exploding-bundles.py
@@ -0,0 +1,323 @@
+# 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
+"""Regression test for dispatching several incoming channels.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ media_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ }, signature='sv')
+
+ misc_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': 'com.example.Extension',
+ }, signature='sv')
+
+ # Two clients want to observe, approve and handle channels. Empathy handles
+ # VoIP, Kopete does not.
+ empathy = SimulatedClient(q, bus, 'org.gnome.Empathy',
+ observe=[text_fixed_properties, media_fixed_properties],
+ approve=[text_fixed_properties, media_fixed_properties],
+ handle=[text_fixed_properties, media_fixed_properties],
+ bypass_approval=False)
+
+ kopete = SimulatedClient(q, bus, 'org.kde.Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # Part 1. A bundle that Empathy, but not Kopete, can handle
+
+ text_channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ text_channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ text_channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ text_channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ text_channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ text_channel_properties[cs.CHANNEL + '.Requested'] = False
+ text_channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(
+ [cs.CHANNEL_IFACE_DESTROYABLE], signature='s')
+
+ text_chan = SimulatedChannel(conn, text_channel_properties,
+ destroyable=True)
+
+ media_channel_properties = dbus.Dictionary(media_fixed_properties,
+ signature='sv')
+ media_channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ media_channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ media_channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ media_channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ media_channel_properties[cs.CHANNEL + '.Requested'] = False
+ media_channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(
+ signature='s')
+
+ media_chan = SimulatedChannel(conn, media_channel_properties,
+ destroyable=False)
+
+ conn.NewChannels([text_chan, media_chan])
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ # only Empathy can handle the whole batch
+ assert handlers == [cs.tp_name_prefix + '.Client.org.gnome.Empathy'], \
+ handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channels
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 2, channels
+ assert (text_chan.object_path, text_channel_properties) in channels
+ assert (media_chan.object_path, media_channel_properties) in channels
+
+ # fd.o #21089: telepathy-spec doesn't say whether Kopete observes the whole
+ # batch or just the text channel. In current MC, it only observes the text.
+ assert k.args[0] == e.args[0], k.args
+ assert k.args[1] == e.args[1], e.args
+ assert k.args[2] == [(text_chan.object_path, text_channel_properties)]
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # The Approvers are next
+ # fd.o #21090: telepathy-spec doesn't say whether Kopete is asked to
+ # approve this CDO. In current MC, it is.
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+ assert len(e.args[0]) == 2
+ assert (text_chan.object_path, text_channel_properties) in e.args[0]
+ assert (media_chan.object_path, media_channel_properties) in e.args[0]
+ assert e.args[1:] == [cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, signature='')
+ q.dbus_return(k.message, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user doesn't care which one will handle the channels - because
+ # Empathy is the only possibility, it will be chosen (this is also a
+ # regression test for the ability to leave the handler unspecified).
+ call_async(q, cdo_iface, 'HandleWith', '')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ text_chan.close()
+ media_chan.close()
+
+ # Part 2. A bundle that neither client can handle in its entirety
+
+ respawning_channel_properties = dbus.Dictionary(misc_fixed_properties,
+ signature='sv')
+ respawning_channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ respawning_channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ respawning_channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ respawning_channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ respawning_channel_properties[cs.CHANNEL + '.Requested'] = False
+ respawning_channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(
+ [cs.CHANNEL_IFACE_DESTROYABLE], signature='s')
+
+ ext_channel_properties = dbus.Dictionary(misc_fixed_properties,
+ signature='sv')
+ ext_channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ ext_channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ ext_channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ ext_channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ ext_channel_properties[cs.CHANNEL + '.Requested'] = False
+ ext_channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(
+ signature='s')
+
+ text_chan = SimulatedChannel(conn, text_channel_properties,
+ destroyable=True)
+ media_chan = SimulatedChannel(conn, media_channel_properties,
+ destroyable=False)
+ respawning_chan = SimulatedChannel(conn, respawning_channel_properties,
+ destroyable=True)
+ ext_chan = SimulatedChannel(conn, ext_channel_properties,
+ destroyable=False)
+
+ conn.NewChannels([text_chan, media_chan, ext_chan, respawning_chan])
+
+ # No client can handle all four channels, so the bundle explodes into
+ # two dispatch operations and two failures. We can only match the first
+ # CDO here - we look at the others later.
+ e_observe_media, e_observe_text, k_observe_text, \
+ e_approve_media, e_approve_text, k_approve_text, \
+ _, _, _ = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ predicate=(lambda e: e.args[2][0][0] == media_chan.object_path),
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ predicate=(lambda e: e.args[2][0][0] == text_chan.object_path),
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ predicate=(lambda e: e.args[2][0][0] == text_chan.object_path),
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ predicate=(lambda e:
+ e.args[0][0][0] ==
+ media_chan.object_path),
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ predicate=(lambda e:
+ e.args[0][0][0] ==
+ text_chan.object_path),
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ predicate=(lambda e:
+ e.args[0][0][0] ==
+ text_chan.object_path),
+ handled=False),
+ EventPattern('dbus-method-call',
+ interface=cs.CHANNEL_IFACE_DESTROYABLE,
+ method='Destroy',
+ path=respawning_chan.object_path,
+ handled=True),
+ EventPattern('dbus-method-call',
+ interface=cs.CHANNEL,
+ method='Close',
+ path=ext_chan.object_path,
+ handled=True),
+ # we can't distinguish between the two NewDispatchOperation signals
+ # since we no longer see the Channels property (it's mutable)
+ EventPattern('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation'),
+ )
+
+ q.dbus_return(e_observe_media.message, signature='')
+ q.dbus_return(e_observe_text.message, signature='')
+ q.dbus_return(k_observe_text.message, signature='')
+ q.dbus_return(e_approve_media.message, signature='')
+ q.dbus_return(e_approve_text.message, signature='')
+ q.dbus_return(k_approve_text.message, signature='')
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/fdo-21034.py b/tests/twisted/dispatcher/fdo-21034.py
new file mode 100644
index 00000000..4766631a
--- /dev/null
+++ b/tests/twisted/dispatcher/fdo-21034.py
@@ -0,0 +1,78 @@
+# 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
+"""Regression test for https://bugs.freedesktop.org/show_bug.cgi?id=21034
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ client = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [client])
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+
+ # chat UI calls ChannelDispatcher.EnsureChannel or CreateChannel
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request, dbus.Int64(1234),
+ 'grr.arg', # a valid bus name, but the wrong prefix
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-error', method='CreateChannel')
+ assert ret.error.get_dbus_name() == cs.INVALID_ARGUMENT
+
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request, dbus.Int64(1234),
+ 'can has cheeseburger?', # a totally invalid bus name
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-error', method='CreateChannel')
+ assert ret.error.get_dbus_name() == cs.INVALID_ARGUMENT
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/handle-channels-fails.py b/tests/twisted/dispatcher/handle-channels-fails.py
new file mode 100644
index 00000000..04b51ef7
--- /dev/null
+++ b/tests/twisted/dispatcher/handle-channels-fails.py
@@ -0,0 +1,223 @@
+# 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
+"""Regression test for dispatching an incoming Text channel.
+"""
+
+import dbus
+import dbus.bus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+ vague_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ empathy_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(empathy_bus)
+ empathy = SimulatedClient(q, empathy_bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # Kopete's filter is less specific than Empathy's, so we'll prefer Empathy
+ kopete_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(kopete_bus)
+ kopete = SimulatedClient(q, kopete_bus, 'Kopete',
+ observe=[], approve=[],
+ handle=[vague_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ # In this test Empathy's filter has more things in it than Kopete's, so
+ # MC will prefer Empathy
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False)
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy rejects the channels
+ q.dbus_raise(e.message, cs.NOT_AVAILABLE, 'Blind drunk', bus=empathy_bus)
+
+ e = q.expect('dbus-error', method='HandleWith')
+ assert e.error.get_dbus_name() == cs.NOT_AVAILABLE
+ assert e.error.get_dbus_message() == 'Blind drunk'
+
+ # The channels no longer count as having been approved. Check that MC
+ # doesn't carry on regardless
+ forbidden = [EventPattern('dbus-method-call', method='HandleChannels')]
+ q.forbid_events(forbidden)
+ sync_dbus(bus, q, mc)
+ q.unforbid_events(forbidden)
+
+ # I'm Feeling Lucky. It might work if I try again? Maybe?
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels, again
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy rejects the channels, again
+ q.dbus_raise(e.message, cs.NOT_CAPABLE, 'Still drunk', bus=empathy_bus)
+
+ e = q.expect('dbus-error', method='HandleWith')
+ assert e.error.get_dbus_name() == cs.NOT_CAPABLE
+ assert e.error.get_dbus_message() == 'Still drunk'
+
+ # OK, OK, is anyone else competent enough to handle them?
+ # (Also, assert that MC doesn't offer them back to Empathy, knowing that
+ # it already tried and failed)
+ forbidden = [EventPattern('dbus-method-call', method='HandleChannels',
+ path=empathy.object_path)]
+ q.forbid_events(forbidden)
+ call_async(q, cdo_iface, 'HandleWith', '')
+
+ # Kopete is asked to handle the channels
+ k = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Kopete rejects the channels too
+ q.dbus_raise(k.message, cs.NOT_AVAILABLE, 'Also blind drunk',
+ bus=kopete_bus)
+
+ e = q.expect('dbus-error', method='HandleWith')
+
+ assert e.error.get_dbus_name() == cs.NOT_AVAILABLE
+ assert e.error.get_dbus_message() == 'Also blind drunk'
+
+ # MC gives up and closes the channel. This is the end of the CDO.
+ q.expect_many(
+ EventPattern('dbus-method-call', path=chan.object_path,
+ interface=cs.CHANNEL, method='Close', args=[]),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/lose-text.py b/tests/twisted/dispatcher/lose-text.py
new file mode 100644
index 00000000..d652658c
--- /dev/null
+++ b/tests/twisted/dispatcher/lose-text.py
@@ -0,0 +1,178 @@
+# 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
+"""Regression test for losing a channel while AddDispatchOperation is being
+called.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, signature='')
+ q.dbus_return(e.message, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, signature='')
+
+ # The channel closes before Kopete has said yes. As a result, MC isn't
+ # allowed to emit ChannelLost or Finished yet.
+ chan.close()
+
+ # Empathy wants to handle the channel, but is too late
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+ e = q.expect('dbus-error')
+ # FIXME: e.error.get_dbus_name() == [...Disconnected] which doesn't
+ # seem like the most appropriate thing for MC to do (but at least it's
+ # consistent with ChannelLost)
+
+ # *Now* Kopete is happy...
+
+ q.dbus_return(k.message, signature='')
+
+ # ... and in response, the channel dispatch operation finishes
+
+ e = q.expect('dbus-signal', path=cdo_path, signal='ChannelLost')
+ assert e.args[0] == chan.object_path
+ # FIXME: e.args[1:] == [...Disconnected, 'Channel aborted'] which doesn't
+ # seem like the most appropriate thing for MC to do
+
+ q.expect('dbus-signal', path=cdo_path, signal='Finished')
+ q.expect('dbus-signal', path=cs.CD_PATH,
+ signal='DispatchOperationFinished', args=[cdo_path])
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/recover-from-disconnect.py b/tests/twisted/dispatcher/recover-from-disconnect.py
new file mode 100644
index 00000000..9ae81e51
--- /dev/null
+++ b/tests/twisted/dispatcher/recover-from-disconnect.py
@@ -0,0 +1,274 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, create_fakecm_account,\
+ SimulatedChannel, SimulatedClient, expect_client_setup
+import constants as cs
+
+text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+empathy_bus = dbus.bus.BusConnection()
+kopete_bus = dbus.bus.BusConnection()
+
+def test(q, bus, mc):
+ q.attach_to_bus(empathy_bus)
+ q.attach_to_bus(kopete_bus)
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, empathy_bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+ kopete = SimulatedClient(q, kopete_bus, 'Kopete',
+ observe=[text_fixed_properties], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ cm_name_ref = dbus.service.BusName(
+ tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ # Create an account
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ call_async(q, account, 'Set', cs.ACCOUNT, 'Enabled', False,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ q.expect('dbus-return', method='Set')
+
+ # Enable the account
+ call_async(q, account, 'Set', cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ # Set online presence
+ presence = dbus.Struct((dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Fixing MC bugs'), signature='uss')
+ call_async(q, account, 'Set', cs.ACCOUNT,
+ 'RequestedPresence', presence,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', 'first',
+ 'myself')
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True),
+ )
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ test_dispatching(q, bus, conn, account, empathy, kopete)
+
+ # Connection falls over for a miscellaneous reason
+ conn.StatusChanged(cs.CONN_STATUS_DISCONNECTED,
+ cs.CONN_STATUS_REASON_NETWORK_ERROR)
+
+ # MC reconnects
+
+ e = q.expect('dbus-method-call', method='RequestConnection',
+ args=['fakeprotocol', params],
+ destination=tp_name_prefix + '.ConnectionManager.fakecm',
+ path=tp_path_prefix + '/ConnectionManager/fakecm',
+ interface=tp_name_prefix + '.ConnectionManager',
+ handled=False)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', 'second',
+ 'myself')
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ q.expect_many(
+ EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True),
+ )
+
+ # MC calls GetStatus (maybe) and then Connect
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+
+ # Connect succeeds
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ test_dispatching(q, bus, conn, account, empathy, kopete)
+
+def test_dispatching(q, bus, conn, account, empathy, kopete):
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ ops = cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations')
+ assert ops == [], ops
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Empathy',
+ cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ # Both Observers are told about the new channel
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False),
+ )
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ assert k.args == e.args
+
+ # Both Observers indicate that they are ready to proceed
+ q.dbus_return(k.message, bus=empathy_bus, signature='')
+ q.dbus_return(e.message, bus=kopete_bus, signature='')
+
+ # The Approvers are next
+
+ e, k = q.expect_many(
+ EventPattern('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ EventPattern('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False),
+ )
+
+ assert e.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+ assert k.args == e.args
+
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ # Both Approvers now have a flashing icon or something, trying to get the
+ # user's attention
+
+ # The user responds to Empathy first
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Empathy')
+
+ # Empathy is asked to handle the channels
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Empathy accepts the channels
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/request-disabled-account.py b/tests/twisted/dispatcher/request-disabled-account.py
new file mode 100644
index 00000000..325961db
--- /dev/null
+++ b/tests/twisted/dispatcher/request-disabled-account.py
@@ -0,0 +1,97 @@
+# 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
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, create_fakecm_account, SimulatedConnection, \
+ SimulatedChannel
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "smcv@example.com",
+ "password": "secrecy"}, signature='sv')
+ (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
+
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'RequestedPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', ''))
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'AutomaticPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy',
+ 'Testing automatic presence'))
+ q.expect('dbus-return', method='Set')
+ q.expect('dbus-signal', signal='AccountPropertyChanged',
+ predicate=lambda e:
+ e.args[0].get('AutomaticPresence', (None, None, None))[1]
+ == 'busy')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'Enabled', False)
+ q.expect('dbus-return', method='Set')
+
+ call_async(q, account_props, 'Set', cs.ACCOUNT, 'ConnectAutomatically',
+ False)
+ q.expect('dbus-return', method='Set')
+
+ # Requesting a channel won't put us online, since it's disabled
+
+ # make sure RequestConnection doesn't get called
+ events = [EventPattern('dbus-method-call', method='RequestConnection')]
+ q.forbid_events(events)
+
+ user_action_time = dbus.Int64(1238582606)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+
+ request = dbus.Dictionary({
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.TargetID': 'juliet',
+ }, signature='sv')
+ account_requests = dbus.Interface(account,
+ cs.ACCOUNT_IFACE_NOKIA_REQUESTS)
+ call_async(q, cd, 'CreateChannel',
+ account.object_path, request, user_action_time, "",
+ dbus_interface=cs.CD)
+ ret = q.expect('dbus-return', method='CreateChannel')
+ request_path = ret.value[0]
+
+ cr = bus.get_object(cs.AM, request_path)
+ request_props = cr.GetAll(cs.CR, dbus_interface=cs.PROPERTIES_IFACE)
+ assert request_props['Account'] == account.object_path
+ assert request_props['Requests'] == [request]
+ assert request_props['UserActionTime'] == user_action_time
+ assert request_props['PreferredHandler'] == ""
+ assert request_props['Interfaces'] == []
+
+ sync_dbus(bus, q, mc)
+
+ cr.Proceed(dbus_interface=cs.CR)
+
+ # FIXME: error isn't specified (NotAvailable perhaps?)
+ q.expect('dbus-signal', path=cr.object_path,
+ interface=cs.CR, signal='Failed')
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/respawn-activatable-observers.py b/tests/twisted/dispatcher/respawn-activatable-observers.py
new file mode 100644
index 00000000..8f83ffbb
--- /dev/null
+++ b/tests/twisted/dispatcher/respawn-activatable-observers.py
@@ -0,0 +1,235 @@
+# Copyright (C) 2009,2010 Nokia Corporation
+# Copyright (C) 2009,2010 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
+"""Regression test for respawning crashed Observers.
+"""
+
+import dbus
+import dbus.bus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL + '.Type.RespawnObservers'
+ }, signature='sv')
+
+ # Logger is an activatable Observer who will crash
+ logger_bus = dbus.bus.BusConnection()
+ logger_bus.set_exit_on_disconnect(False) # we'll disconnect later
+
+ # Kopete is an Approver, Handler and will not crash
+ kopete_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(logger_bus)
+ q.attach_to_bus(kopete_bus)
+
+ kopete = SimulatedClient(q, kopete_bus, 'Kopete',
+ observe=[], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ k = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False)
+
+ assert k.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+
+ # The fake Logger implementation is run
+ e = q.expect('dbus-signal',
+ path=cs.tp_path_prefix + '/RegressionTests',
+ interface=cs.tp_name_prefix + '.RegressionTests',
+ signal='FakeStartup',
+ args=[cs.tp_name_prefix + '.Client.Logger'],
+ )
+ # We take on its identity to be able to continue with the test
+ logger = SimulatedClient(q, bus, 'Logger',
+ observe=[text_fixed_properties])
+
+ # Logger is told about the new channel
+ e = q.expect('dbus-method-call',
+ path=logger.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ # Logger indicates that it is ready to proceed
+ q.dbus_return(e.message, bus=logger_bus, signature='')
+
+ # The Approver (Kopete) is next
+
+ # The user responds to Kopete
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Kopete')
+
+ # Kopete is asked to handle the channels
+ k = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Kopete accepts the channels
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # Logger crashes
+ logger.release_name()
+
+ e = q.expect('dbus-signal',
+ signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == logger.bus_name and e.args[2] == ''),
+ )
+ logger_unique_name = e.args[1]
+
+ logger_bus.flush()
+
+ # Logger gets restart request
+ e = q.expect('dbus-signal',
+ path=cs.tp_path_prefix + '/RegressionTests',
+ interface=cs.tp_name_prefix + '.RegressionTests',
+ signal='FakeStartup',
+ args=[cs.tp_name_prefix + '.Client.Logger'],
+ )
+
+ # Logger gets restarted
+ logger.reacquire_name()
+
+ e = q.expect('dbus-signal',
+ signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == logger.bus_name and e.args[1] == ''),
+ )
+ logger_unique_name = e.args[2]
+
+ e = q.expect('dbus-method-call',
+ path=logger.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+
+ # FIXME: assert the same things as before, except CDO (which we don't
+ # have) and account path (which we don't know how to get); also check
+ # that the recovering observer info key is set
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ assert e.args[5]['recovering'] == 1, e.args # due to observer recovery
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ # Logger indicates that it is ready to proceed
+ q.dbus_return(e.message, bus=logger_bus, signature='')
+
+ sync_dbus(bus, q, mc)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/respawn-observers.py b/tests/twisted/dispatcher/respawn-observers.py
new file mode 100644
index 00000000..73847bec
--- /dev/null
+++ b/tests/twisted/dispatcher/respawn-observers.py
@@ -0,0 +1,220 @@
+# Copyright (C) 2009,2010 Nokia Corporation
+# Copyright (C) 2009,2010 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
+"""Regression test for respawning crashed Observers.
+"""
+
+import dbus
+import dbus.bus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # Empathy is an Observers who will crash
+ empathy_bus = dbus.bus.BusConnection()
+ empathy_bus.set_exit_on_disconnect(False) # we'll disconnect later
+
+ # Kopete is an Approver, Handler and will not crash
+ kopete_bus = dbus.bus.BusConnection()
+ q.attach_to_bus(empathy_bus)
+ q.attach_to_bus(kopete_bus)
+
+ # Two clients want to observe, approve and handle channels
+ empathy = SimulatedClient(q, empathy_bus, 'Empathy',
+ observe=[text_fixed_properties], approve=[],
+ handle=[], wants_recovery=True)
+ kopete = SimulatedClient(q, kopete_bus, 'Kopete',
+ observe=[], approve=[text_fixed_properties],
+ handle=[text_fixed_properties], bypass_approval=False)
+
+ # wait for MC to download the properties
+ expect_client_setup(q, [empathy, kopete])
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # A channel dispatch operation is created
+
+ e = q.expect('dbus-signal',
+ path=cs.CD_PATH,
+ interface=cs.CD_IFACE_OP_LIST,
+ signal='NewDispatchOperation')
+
+ cdo_path = e.args[0]
+ cdo_properties = e.args[1]
+
+ assert cdo_properties[cs.CDO + '.Account'] == account.object_path
+ assert cdo_properties[cs.CDO + '.Connection'] == conn.object_path
+ assert cs.CDO + '.Interfaces' in cdo_properties
+
+ handlers = cdo_properties[cs.CDO + '.PossibleHandlers'][:]
+ handlers.sort()
+ assert handlers == [cs.tp_name_prefix + '.Client.Kopete'], handlers
+
+ assert cs.CD_IFACE_OP_LIST in cd_props.Get(cs.CD, 'Interfaces')
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') ==\
+ [(cdo_path, cdo_properties)]
+
+ cdo = bus.get_object(cs.CD, cdo_path)
+ cdo_iface = dbus.Interface(cdo, cs.CDO)
+ cdo_props_iface = dbus.Interface(cdo, cs.PROPERTIES_IFACE)
+
+ assert cdo_props_iface.Get(cs.CDO, 'Interfaces') == \
+ cdo_properties[cs.CDO + '.Interfaces']
+ assert cdo_props_iface.Get(cs.CDO, 'Connection') == conn.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Account') == account.object_path
+ assert cdo_props_iface.Get(cs.CDO, 'Channels') == [(chan.object_path,
+ channel_properties)]
+ assert cdo_props_iface.Get(cs.CDO, 'PossibleHandlers') == \
+ cdo_properties[cs.CDO + '.PossibleHandlers']
+
+ # The Observer (Empathy) is told about the new channel
+
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[3] == cdo_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ # Empathy indicates that it is ready to proceed
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ # The Approver (Kopete) is next
+
+ k = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.APPROVER, method='AddDispatchOperation',
+ handled=False)
+
+ assert k.args == [[(chan.object_path, channel_properties)],
+ cdo_path, cdo_properties]
+
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ # The user responds to Kopete
+ call_async(q, cdo_iface, 'HandleWith',
+ cs.tp_name_prefix + '.Client.Kopete')
+
+ # Kopete is asked to handle the channels
+ k = q.expect('dbus-method-call',
+ path=kopete.object_path,
+ interface=cs.HANDLER, method='HandleChannels',
+ handled=False)
+
+ # Kopete accepts the channels
+ q.dbus_return(k.message, bus=kopete_bus, signature='')
+
+ q.expect_many(
+ EventPattern('dbus-return', method='HandleWith'),
+ EventPattern('dbus-signal', interface=cs.CDO, signal='Finished'),
+ EventPattern('dbus-signal', interface=cs.CD_IFACE_OP_LIST,
+ signal='DispatchOperationFinished'),
+ )
+
+ # Now there are no more active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ # Empathy crashes
+ empathy.release_name()
+
+ e = q.expect('dbus-signal',
+ signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == empathy.bus_name and e.args[2] == ''),
+ )
+ empathy_unique_name = e.args[1]
+
+ empathy_bus.flush()
+
+ # Empathy gets restarted
+ empathy.reacquire_name()
+
+ e = q.expect('dbus-signal',
+ signal='NameOwnerChanged',
+ predicate=(lambda e:
+ e.args[0] == empathy.bus_name and e.args[1] == ''),
+ )
+ empathy_unique_name = e.args[2]
+
+ e = q.expect('dbus-method-call',
+ path=empathy.object_path,
+ interface=cs.OBSERVER, method='ObserveChannels',
+ handled=False)
+
+ # FIXME: assert the same things as before, except CDO (which we don't
+ # have) and account path (which we don't know how to get); also check
+ # that the recovering observer info key is set
+ assert e.args[0] == account.object_path, e.args
+ assert e.args[1] == conn.object_path, e.args
+ assert e.args[4] == [], e.args # no requests satisfied
+ assert e.args[5]['recovering'] == 1, e.args # due to observer recovery
+ channels = e.args[2]
+ assert len(channels) == 1, channels
+ assert channels[0][0] == chan.object_path, channels
+ assert channels[0][1] == channel_properties, channels
+
+ # Empathy indicates that it is ready to proceed
+ q.dbus_return(e.message, bus=empathy_bus, signature='')
+
+ sync_dbus(bus, q, mc)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/undispatchable.py b/tests/twisted/dispatcher/undispatchable.py
new file mode 100644
index 00000000..35b5f44f
--- /dev/null
+++ b/tests/twisted/dispatcher/undispatchable.py
@@ -0,0 +1,75 @@
+# 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
+"""Regression test for dispatching an incoming channel for which there are no
+handlers.
+"""
+
+import dbus
+import dbus.bus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ text_fixed_properties = dbus.Dictionary({
+ cs.CHANNEL + '.TargetHandleType': cs.HT_CONTACT,
+ cs.CHANNEL + '.ChannelType': cs.CHANNEL_TYPE_TEXT,
+ }, signature='sv')
+
+ # subscribe to the OperationList interface (MC assumes that until this
+ # property has been retrieved once, nobody cares)
+
+ cd = bus.get_object(cs.CD, cs.CD_PATH)
+ cd_props = dbus.Interface(cd, cs.PROPERTIES_IFACE)
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+ channel_properties = dbus.Dictionary(text_fixed_properties,
+ signature='sv')
+ channel_properties[cs.CHANNEL + '.TargetID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.TargetHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.InitiatorID'] = 'juliet'
+ channel_properties[cs.CHANNEL + '.InitiatorHandle'] = \
+ conn.ensure_handle(cs.HT_CONTACT, 'juliet')
+ channel_properties[cs.CHANNEL + '.Requested'] = False
+ channel_properties[cs.CHANNEL + '.Interfaces'] = dbus.Array(signature='s')
+
+ chan = SimulatedChannel(conn, channel_properties)
+ chan.announce()
+
+ # In response, MC kills the channel
+ q.expect('dbus-method-call', path=chan.object_path, method='Close',
+ handled=True)
+
+ # There are still no active channel dispatch operations
+ assert cd_props.Get(cs.CD_IFACE_OP_LIST, 'DispatchOperations') == []
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/dispatcher/vanishing-client.py b/tests/twisted/dispatcher/vanishing-client.py
new file mode 100644
index 00000000..92a4079b
--- /dev/null
+++ b/tests/twisted/dispatcher/vanishing-client.py
@@ -0,0 +1,111 @@
+# 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
+"""Regression test for a client crashing when Get is called.
+"""
+
+import dbus
+import dbus.service
+
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
+ call_async, sync_dbus
+from mctest import exec_test, SimulatedConnection, SimulatedClient, \
+ create_fakecm_account, enable_fakecm_account, SimulatedChannel, \
+ expect_client_setup
+import constants as cs
+
+def test(q, bus, mc):
+ params = dbus.Dictionary({"account": "someguy@example.com",
+ "password": "secrecy"}, signature='sv')
+ cm_name_ref, account = create_fakecm_account(q, bus, mc, params)
+ conn = enable_fakecm_account(q, bus, mc, account, params)
+
+ bus_name = '.'.join([tp_name_prefix, 'Client.CrashMe'])
+ bus_name_ref = dbus.service.BusName(bus_name, bus)
+ object_path = '/' + bus_name.replace('.', '/')
+
+ # MC inspects it
+ e = q.expect('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='Get',
+ path=object_path,
+ args=[cs.CLIENT, 'Interfaces'],
+ handled=False)
+ # Simulate a crash
+ del bus_name_ref
+ sync_dbus(bus, q, account)
+ # This might crash MC in sympathy
+ q.dbus_raise(e.message, cs.DBUS_ERROR_NO_REPLY, 'I crashed')
+
+ sync_dbus(bus, q, account)
+
+ # Try again
+ bus_name = '.'.join([tp_name_prefix, 'Client.CrashMeAgain'])
+ bus_name_ref = dbus.service.BusName(bus_name, bus)
+ object_path = '/' + bus_name.replace('.', '/')
+
+ # MC inspects it
+ e = q.expect('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='Get',
+ path=object_path,
+ args=[cs.CLIENT, 'Interfaces'],
+ handled=False)
+ # Don't crash just yet
+ q.dbus_return(e.message, dbus.Array([cs.OBSERVER], signature='s'),
+ signature='v')
+ # MC investigates further
+ e = q.expect('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ path=object_path,
+ args=[cs.OBSERVER],
+ handled=False)
+ # Simulate another crash
+ del bus_name_ref
+ sync_dbus(bus, q, account)
+ q.dbus_raise(e.message, cs.DBUS_ERROR_NO_REPLY, 'I crashed')
+
+ # Try again
+ bus_name = '.'.join([tp_name_prefix, 'Client.CrashMeHarder'])
+ bus_name_ref = dbus.service.BusName(bus_name, bus)
+ object_path = '/' + bus_name.replace('.', '/')
+
+ # MC inspects it
+ e = q.expect('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='Get',
+ path=object_path,
+ args=[cs.CLIENT, 'Interfaces'],
+ handled=False)
+ # Don't crash just yet
+ q.dbus_return(e.message, dbus.Array([cs.OBSERVER], signature='s'),
+ signature='v')
+ # MC investigates further
+ e = q.expect('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ path=object_path,
+ args=[cs.OBSERVER],
+ handled=False)
+ # Simulate a crash with highly unfortunate timing
+ del bus_name_ref
+ sync_dbus(bus, q, account)
+ q.dbus_return(e.message, dbus.Array([dbus.Dictionary({
+ 'x': 'y',
+ }, signature='sv')], signature='a{sv}'), signature='v')
+ sync_dbus(bus, q, account)
+
+if __name__ == '__main__':
+ exec_test(test, {})
diff --git a/tests/twisted/fakeclient.py b/tests/twisted/fakeclient.py
new file mode 100644
index 00000000..d3c43813
--- /dev/null
+++ b/tests/twisted/fakeclient.py
@@ -0,0 +1,99 @@
+# 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
+import dbus.service
+from servicetest import Event
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix
+
+client_iface = "org.freedesktop.Telepathy.Client"
+client_observer_iface = "org.freedesktop.Telepathy.Client.Observer"
+client_approver_iface = "org.freedesktop.Telepathy.Client.Approver"
+client_handler_iface = "org.freedesktop.Telepathy.Client.Handler"
+
+properties_iface = "org.freedesktop.DBus.Properties"
+
+empty_caps = dbus.Array([], signature='a{sv}')
+
+class FakeClient(dbus.service.Object):
+ def __init__(self, object_path, q, bus, bus_name, nameref,
+ caps = empty_caps):
+ self.object_path = object_path
+ self.q = q
+ self.bus = bus
+ self.bus_name = bus_name
+ # keep a reference on nameref, otherwise, the name will be lost!
+ self.nameref = nameref
+ self.caps = caps
+ dbus.service.Object.__init__(self, bus, object_path)
+
+ @dbus.service.method(dbus_interface=properties_iface,
+ in_signature='ss', out_signature='v')
+ def Get(self, interface_name, property_name):
+ self.q.append(Event('dbus-method-call', name="Get",
+ obj=self, interface_name=interface_name,
+ property_name=property_name))
+ if interface_name == client_iface and property_name == "Interfaces":
+ return dbus.Array([
+ client_observer_iface,
+ client_approver_iface,
+ client_handler_iface
+ ], signature='s')
+ if interface_name == client_observer_iface and \
+ property_name == "ObserverChannelFilter":
+ return empty_caps
+ if interface_name == client_approver_iface and \
+ property_name == "ApproverChannelFilter":
+ return empty_caps
+ if interface_name == client_handler_iface and \
+ property_name == "HandlerChannelFilter":
+ return self.caps
+ print "Error: interface_name=%s property_name=%s" % \
+ (interface_name, property_name)
+ return None
+
+ @dbus.service.method(dbus_interface=properties_iface,
+ in_signature='s', out_signature='a{sv}')
+ def GetAll(self, interface_name):
+ self.q.append(Event('dbus-method-call', name="GetAll",
+ obj=self, interface_name=interface_name))
+ if interface_name == client_iface:
+ return dbus.Dictionary({
+ 'Interfaces': dbus.Array([
+ client_observer_iface,
+ client_approver_iface,
+ client_handler_iface
+ ])
+ }, signature='sv')
+ return None
+
+ @dbus.service.method(dbus_interface=client_handler_iface,
+ in_signature='ooa(oa{sv})aot', out_signature='')
+ def HandleChannels(self, account, connection, channels,
+ requests_satisfied, user_action_time):
+ self.q.append(Event('dbus-method-call', name="HandleChannels",
+ obj=self, account=account, connection=connection,
+ channels=channels, requests_satisfied=requests_satisfied,
+ user_action_time=user_action_time))
+
+def start_fake_client(q, bus, bus_name, object_path, caps):
+ nameref = dbus.service.BusName(bus_name, bus=bus)
+ client = FakeClient(object_path, q, bus, bus_name, nameref, caps)
+ return client
+
+
diff --git a/tests/twisted/fakecm.py b/tests/twisted/fakecm.py
new file mode 100644
index 00000000..fb534e7b
--- /dev/null
+++ b/tests/twisted/fakecm.py
@@ -0,0 +1,292 @@
+# 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
+import dbus.service
+from servicetest import Event
+from servicetest import EventPattern, tp_name_prefix, tp_path_prefix
+from twisted.internet import reactor
+
+properties_iface = "org.freedesktop.DBus.Properties"
+cm_iface = "org.freedesktop.Telepathy.ConnectionManager"
+conn_iface = "org.freedesktop.Telepathy.Connection"
+caps_iface = \
+ "org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities.DRAFT"
+requests_iface = "org.freedesktop.Telepathy.Connection.Interface.Requests"
+channel_iface = "org.freedesktop.Telepathy.Channel"
+
+class FakeChannel(dbus.service.Object):
+ def __init__(self, conn, object_path, q, bus, nameref, props):
+ self.conn = conn
+ self.object_path = object_path
+ self.q = q
+ self.bus = bus
+ # keep a reference on nameref, otherwise, the name will be lost!
+ self.nameref = nameref
+ self.props = props
+
+ if channel_iface + '.TargetHandle' not in props:
+ self.props[channel_iface + '.TargetHandle'] = \
+ self.conn.get_handle(props[channel_iface + '.TargetID'])
+
+ dbus.service.Object.__init__(self, bus, object_path)
+
+ def called(self, method):
+ self.q.append(Event('dbus-method-call', name=method, obj=self,
+ path=self.object_path))
+
+ @dbus.service.method(dbus_interface=channel_iface,
+ in_signature='', out_signature='as')
+ def GetInterfaces(self):
+ self.called('GetInterfaces')
+ return [self.props[channel_iface + '.ChannelType']]
+
+ @dbus.service.method(dbus_interface=channel_iface,
+ in_signature='', out_signature='u')
+ def GetHandle(self):
+ return self.props[channel_iface + '.TargetHandle']
+
+ @dbus.service.method(dbus_interface=channel_iface,
+ in_signature='', out_signature='')
+ def Close(self):
+ self.Closed()
+
+ @dbus.service.signal(dbus_interface=channel_iface, signature='')
+ def Closed(self):
+ pass
+
+
+class FakeConn(dbus.service.Object):
+ def __init__(self, object_path, q, bus, nameref):
+ self.object_path = object_path
+ self.q = q
+ self.bus = bus
+ # keep a reference on nameref, otherwise, the name will be lost!
+ self.nameref = nameref
+ self.status = 2 # Connection_Status_Disconnected
+ self.next_channel_id = 1
+ self.channels = []
+ self.handles = {}
+ self.next_handle = 1337 # break people depending on SelfHandle == 1
+ dbus.service.Object.__init__(self, bus, object_path)
+
+ # interface Connection
+
+ @dbus.service.method(dbus_interface=conn_iface,
+ in_signature='', out_signature='')
+ def Connect(self):
+ self.StatusChanged(1, 1)
+ self.StatusChanged(0, 1)
+ self.q.append(Event('dbus-method-call', name="Connect", obj=self,
+ path=self.object_path))
+ return None
+
+ @dbus.service.method(dbus_interface=conn_iface,
+ in_signature='', out_signature='as')
+ def GetInterfaces(self):
+ self.q.append(Event('dbus-method-call', name="GetInterfaces",
+ obj=self, path=self.object_path))
+ return dbus.Array([conn_iface, caps_iface, requests_iface])
+
+ def get_handle(self, id):
+ for handle, id_ in self.handles.iteritems():
+ if id_ == id:
+ return handle
+ handle = self.next_handle
+ self.next_handle += 1
+
+ self.handles[handle] = id
+ return handle
+
+ @dbus.service.method(dbus_interface=conn_iface,
+ in_signature='', out_signature='u')
+ def GetSelfHandle(self):
+ self.q.append(Event('dbus-method-call', name="GetSelfHandle",
+ obj=self, path=self.object_path))
+ return self.get_handle('fakeaccount')
+
+ @dbus.service.method(dbus_interface=conn_iface,
+ in_signature='', out_signature='u')
+ def GetStatus(self):
+ self.q.append(Event('dbus-method-call', name="GetStatus",
+ obj=self, path=self.object_path))
+ return self.status
+
+ @dbus.service.method(dbus_interface=conn_iface,
+ in_signature='uau', out_signature='as')
+ def InspectHandles(self, handle_type, handles):
+ self.q.append(Event('dbus-method-call', name="InspectHandles",
+ obj=self, path=self.object_path, handle_type=handle_type,
+ handles=handles))
+ if handle_type != 1:
+ raise "non-contact handles don't exist"
+
+ ret = []
+ for handle in handles:
+ if handle not in self.handles:
+ raise "%d is not a valid handle" % handle
+ ret.append(self.handles[handle])
+
+ return ret
+
+ @dbus.service.method(dbus_interface=conn_iface,
+ in_signature='uas', out_signature='au')
+ def RequestHandles(self, type, ids):
+ if type != 1:
+ raise "non-contact handles don't exist"
+
+ ret = []
+ for id in ids:
+ ret.append(self.get_handle(id))
+
+ return ret
+
+ @dbus.service.signal(dbus_interface=conn_iface,
+ signature='uu')
+ def StatusChanged(self, status, reason):
+ self.status = status
+
+ # interface Connection.Interface.ContactCapabilities.DRAFT
+
+ @dbus.service.method(dbus_interface=caps_iface,
+ in_signature='aa{sv}', out_signature='')
+ def SetSelfCapabilities(self, caps):
+ self.q.append(Event('dbus-method-call', name="SetSelfCapabilities",
+ obj=self, path=self.object_path, caps=caps))
+ return None
+
+ @dbus.service.signal(dbus_interface=requests_iface,
+ signature='a(oa{sv})')
+ def NewChannels(self, array):
+ self.channels = self.channels + array
+
+ @dbus.service.signal(dbus_interface=conn_iface,
+ signature='osuub')
+ def NewChannel(self, object_path, channel_type, handle_type, handle,
+ suppress_handle):
+ pass
+
+ @dbus.service.method(dbus_interface=properties_iface,
+ in_signature='ss', out_signature='v')
+ def Get(self, interface_name, property_name):
+ self.q.append(Event('dbus-method-call', name="Get",
+ obj=self, interface_name=interface_name,
+ property_name=property_name))
+ if interface_name == requests_iface and \
+ property_name == "Channels":
+ return dbus.Array(self.channels, signature='(oa{sv})')
+ print "Error: interface_name=%s property_name=%s" % \
+ (interface_name, property_name)
+ return None
+
+ @dbus.service.method(dbus_interface=properties_iface,
+ in_signature='s', out_signature='a{sv}')
+ def GetAll(self, interface_name):
+ self.q.append(Event('dbus-method-call', name="GetAll",
+ obj=self, interface_name=interface_name))
+ if interface_name == conn_iface:
+ return dbus.Dictionary({
+ 'SelfHandle': 0L
+ }, signature='sv')
+ if interface_name == requests_iface:
+ return dbus.Dictionary({
+ 'Channels': dbus.Array(self.channels,
+ signature='(oa{sv})')
+ }, signature='sv')
+ return None
+
+ def new_incoming_channel(self, object_path, asv):
+ self.NewChannels(dbus.Array([(object_path, asv)],
+ signature='(oa{sv})'))
+ self.NewChannel(object_path,
+ asv['org.freedesktop.Telepathy.Channel.ChannelType'],
+ asv['org.freedesktop.Telepathy.Channel.TargetHandleType'],
+ asv['org.freedesktop.Telepathy.Channel.TargetHandle'],
+ False)
+
+ # interface Connection.Interface.Requests
+ def make_channel(self, props):
+ path = self.object_path + "/channel%d" % self.next_channel_id
+ self.next_channel_id += 1
+ chan = FakeChannel(self, path, self.q, self.bus, self.nameref, props)
+ reactor.callLater(0, self.NewChannels, [(chan, props)])
+ return chan
+
+ @dbus.service.method(dbus_interface=requests_iface,
+ in_signature='a{sv}', out_signature='oa{sv}')
+ def CreateChannel(self, request):
+ self.q.append(Event('dbus-method-call', name="CreateChannel",
+ obj=self, interface_name=requests_iface))
+ chan = self.make_channel(request)
+ return (chan, request)
+
+ @dbus.service.method(dbus_interface=requests_iface,
+ in_signature='a{sv}', out_signature='boa{sv}')
+ def EnsureChannel(self, request):
+ self.q.append(Event('dbus-method-call', name="EnsureChannel",
+ obj=self, interface_name=requests_iface))
+ chan = self.make_channel(request)
+ self.q
+ return (True, chan, request)
+
+ @dbus.service.signal(dbus_interface=requests_iface,
+ signature="o")
+ def ChannelClosed(self, channel):
+ pass
+
+
+
+class FakeCM(dbus.service.Object):
+ def __init__(self, object_path, q, bus, bus_name, nameref):
+ self.object_path = object_path
+ self.q = q
+ self.bus = bus
+ self.bus_name = bus_name
+ # keep a reference on nameref, otherwise, the name will be lost!
+ self.nameref = nameref
+ dbus.service.Object.__init__(self, bus, object_path)
+
+ @dbus.service.method(dbus_interface=cm_iface,
+ in_signature='s', out_signature='a(susv)')
+ def GetParameters(self, protocol):
+ self.q.append(Event('dbus-method-call', name="GetParameters",
+ protocol=protocol, obj=self))
+ return []
+
+ @dbus.service.method(dbus_interface=cm_iface,
+ in_signature='', out_signature='as')
+ def ListProtocols(self, protocol):
+ self.q.append(Event('dbus-method-call', name="ListProtocols", obj=self))
+ return ['fakeprotocol']
+
+ @dbus.service.method(dbus_interface=cm_iface,
+ in_signature='sa{sv}', out_signature='so')
+ def RequestConnection(self, protocol, parameters):
+ conn_path = tp_path_prefix + "/Connection/fakecm/fakeprotocol/conn1"
+ conn = FakeConn(conn_path, self.q, self.bus, self.nameref)
+ self.q.append(Event('dbus-method-call', name="RequestConnection",
+ protocol=protocol, parameters=parameters,
+ conn=conn, obj=self))
+ return [self.bus_name, conn_path]
+
+def start_fake_connection_manager(q, bus, bus_name, object_path):
+ nameref = dbus.service.BusName(bus_name, bus=bus)
+ cm = FakeCM(object_path, q, bus, bus_name, nameref)
+ return cm
+
+
diff --git a/tests/twisted/mc-debug-server.c b/tests/twisted/mc-debug-server.c
new file mode 100644
index 00000000..3bcbc4d0
--- /dev/null
+++ b/tests/twisted/mc-debug-server.c
@@ -0,0 +1,268 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 8 -*- */
+/*
+ * This file is part of mission-control
+ *
+ * Copyright (C) 2007-2009 Nokia Corporation
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * Contact: Naba Kumar <naba.kumar@nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <telepathy-glib/debug.h>
+#include <telepathy-glib/util.h>
+
+#if ENABLE_GNOME_KEYRING
+#include <gnome-keyring.h>
+#endif
+
+#include "mcd-service.h"
+
+static McdService *mcd = NULL;
+
+static gboolean
+the_end (gpointer data)
+{
+ g_main_loop_quit (data);
+
+ return FALSE;
+}
+
+static void
+on_abort (gpointer unused G_GNUC_UNUSED)
+{
+ g_debug ("McdService aborted, unreffing it");
+ mcd_debug_print_tree (mcd);
+ tp_clear_object (&mcd);
+}
+
+static gboolean
+delayed_abort (gpointer data G_GNUC_UNUSED)
+{
+ g_message ("Aborting by popular request");
+ mcd_mission_abort ((McdMission *) mcd);
+ return FALSE;
+}
+
+#define MCD_SYSTEM_MEMORY_CONSERVED (1 << 1)
+#define MCD_SYSTEM_IDLE (1 << 5)
+
+static DBusHandlerResult
+dbus_filter_function (DBusConnection *connection,
+ DBusMessage *message,
+ void *user_data)
+{
+ if (dbus_message_is_signal (message, DBUS_INTERFACE_LOCAL, "Disconnected") &&
+ !tp_strdiff (dbus_message_get_path (message), DBUS_PATH_LOCAL))
+ {
+ /* MC initialization sets exit on disconnect - turn it off again, so we
+ * get a graceful exit instead (to keep gcov happy) */
+ dbus_connection_set_exit_on_disconnect (connection, FALSE);
+
+ g_message ("Got disconnected from the session bus");
+
+ mcd_mission_abort ((McdMission *) mcd);
+ }
+ else if (dbus_message_is_method_call (message,
+ "org.freedesktop.Telepathy.MissionControl5.RegressionTests",
+ "Abort"))
+ {
+ DBusMessage *reply;
+
+ g_idle_add (delayed_abort, NULL);
+
+ reply = dbus_message_new_method_return (message);
+
+ if (reply == NULL || !dbus_connection_send (connection, reply, NULL))
+ g_error ("Out of memory");
+
+ dbus_message_unref (reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ else if (dbus_message_is_method_call (message,
+ "org.freedesktop.Telepathy.MissionControl5.RegressionTests",
+ "ChangeSystemFlags"))
+ {
+ DBusMessage *reply;
+ DBusError e;
+ dbus_uint32_t set, unset;
+
+ dbus_error_init (&e);
+
+ if (!dbus_message_get_args (message, &e,
+ 'u', &set,
+ 'u', &unset,
+ DBUS_TYPE_INVALID))
+ {
+ reply = dbus_message_new_error (message, e.name, e.message);
+ dbus_error_free (&e);
+ }
+ else
+ {
+ McdMaster *master = mcd_master_get_default ();
+
+ if (set & MCD_SYSTEM_IDLE)
+ {
+ mcd_master_set_idle (master, TRUE);
+ }
+
+ if (set & MCD_SYSTEM_MEMORY_CONSERVED)
+ {
+ mcd_master_set_low_memory (master, TRUE);
+ }
+
+ if (unset & MCD_SYSTEM_IDLE)
+ {
+ mcd_master_set_idle (master, FALSE);
+ }
+
+ if (unset & MCD_SYSTEM_MEMORY_CONSERVED)
+ {
+ mcd_master_set_low_memory (master, FALSE);
+ }
+
+ reply = dbus_message_new_method_return (message);
+ }
+
+ if (reply == NULL || !dbus_connection_send (connection, reply, NULL))
+ g_error ("Out of memory");
+
+ dbus_message_unref (reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int
+main (int argc, char **argv)
+{
+ TpDBusDaemon *bus_daemon = NULL;
+ GError *error = NULL;
+ DBusConnection *connection = NULL;
+ int ret = 1;
+ GMainLoop *teardown_loop;
+ guint linger_time = 5;
+#if ENABLE_GNOME_KEYRING
+ GnomeKeyringResult result;
+#endif
+
+ g_type_init ();
+
+ g_set_application_name ("Mission Control regression tests");
+
+ mcd_debug_init ();
+ tp_debug_set_flags (g_getenv ("MC_TP_DEBUG"));
+
+ /* Not all warnings are fatal due to MC spamming warnings (fd.o #23486),
+ * but GLib and GObject warnings are pretty serious */
+ g_log_set_fatal_mask ("GLib",
+ G_LOG_FATAL_MASK | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING);
+ g_log_set_fatal_mask ("GLib-GObject",
+ G_LOG_FATAL_MASK | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING);
+
+ bus_daemon = tp_dbus_daemon_dup (&error);
+
+ if (bus_daemon == NULL)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ error = NULL;
+ goto out;
+ }
+
+ /* It appears that dbus-glib registers a filter that wrongly returns
+ * DBUS_HANDLER_RESULT_HANDLED for signals, so for *our* filter to have any
+ * effect, we need to install it as soon as possible */
+ connection = dbus_g_connection_get_connection (
+ ((TpProxy *) bus_daemon)->dbus_connection);
+ dbus_connection_add_filter (connection, dbus_filter_function, NULL, NULL);
+
+#if ENABLE_GNOME_KEYRING
+ if (g_getenv ("MC_KEYRING_NAME") != NULL)
+ {
+ const gchar *keyring_name = g_getenv ("MC_KEYRING_NAME");
+
+ if ((result = gnome_keyring_set_default_keyring_sync (keyring_name)) ==
+ GNOME_KEYRING_RESULT_OK)
+ {
+ g_debug ("Successfully set up temporary keyring %s for tests",
+ keyring_name);
+ }
+ else
+ {
+ g_warning ("Failed to set %s as the default keyring: %s",
+ keyring_name, gnome_keyring_result_to_message (result));
+ }
+ }
+#endif
+
+ mcd = mcd_service_new ();
+
+ /* Listen for suicide notification */
+ g_signal_connect_after (mcd, "abort", G_CALLBACK (on_abort), NULL);
+
+ /* connect */
+ mcd_mission_connect (MCD_MISSION (mcd));
+
+ dbus_connection_set_exit_on_disconnect (connection, FALSE);
+
+ mcd_service_run (MCD_OBJECT (mcd));
+
+ ret = 0;
+
+ teardown_loop = g_main_loop_new (NULL, FALSE);
+
+ if (g_getenv ("MC_LINGER_TIME") != NULL)
+ {
+ linger_time = g_ascii_strtoull (g_getenv ("MC_LINGER_TIME"), NULL, 10);
+ }
+
+ /* Keep running in the background until it's all over. This means valgrind
+ * and refdbg can get complete information. */
+ g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, linger_time, the_end,
+ teardown_loop, (GDestroyNotify) g_main_loop_unref);
+
+ g_main_loop_run (teardown_loop);
+
+out:
+
+ if (connection != NULL)
+ {
+ dbus_connection_flush (connection);
+ }
+
+ tp_clear_object (&bus_daemon);
+
+ dbus_shutdown ();
+
+ g_message ("Exiting with %d", ret);
+
+ return ret;
+}
diff --git a/tests/twisted/mcp-account-diversion.c b/tests/twisted/mcp-account-diversion.c
new file mode 100644
index 00000000..0994e25d
--- /dev/null
+++ b/tests/twisted/mcp-account-diversion.c
@@ -0,0 +1,286 @@
+/*
+ * A demonstration plugin that diverts account storage to an alternate location.
+ *
+ * Copyright © 2010 Nokia Corporation
+ * Copyright © 2010 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
+ */
+
+#include <mission-control-plugins/mission-control-plugins.h>
+
+#define DONT_DIVERT "fakecm/fakeprotocol/dontdivert"
+#define CONFFILE "mcp-test-diverted-account-plugin.conf"
+
+#define PLUGIN_NAME "diverted-keyfile"
+#define PLUGIN_PRIORITY MCP_ACCOUNT_STORAGE_PLUGIN_PRIO_NORMAL
+#define PLUGIN_DESCRIPTION \
+ "Test plugin that grabs all accounts it receives (except '" \
+ DONT_DIVERT "*') and diverts them to '" CONFFILE \
+ "' in g_get_user_cache_dir () instead of the usual location."
+
+#define DEBUG g_debug
+
+typedef struct {
+ GObject parent;
+ GKeyFile *keyfile;
+ gboolean save;
+ gboolean loaded;
+} AccountDiversionPlugin;
+
+typedef struct {
+ GObjectClass parent_class;
+} AccountDiversionPluginClass;
+
+GType account_diversion_plugin_get_type (void) G_GNUC_CONST;
+static void account_storage_iface_init (McpAccountStorageIface *,
+ gpointer);
+
+
+#define ACCOUNT_DIVERSION_PLUGIN(o) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((o), account_diversion_plugin_get_type (), \
+ AccountDiversionPlugin))
+
+G_DEFINE_TYPE_WITH_CODE (AccountDiversionPlugin, account_diversion_plugin,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_ACCOUNT_STORAGE,
+ account_storage_iface_init));
+
+static void
+account_diversion_plugin_init (AccountDiversionPlugin *self)
+{
+ DEBUG ("account_diversion_plugin_init");
+ self->keyfile = g_key_file_new ();
+ self->save = FALSE;
+ self->loaded = FALSE;
+}
+
+static void
+account_diversion_plugin_class_init (AccountDiversionPluginClass *cls)
+{
+ DEBUG ("account_diversion_plugin_class_init");
+}
+
+static gchar *
+_conf_filename (void)
+{
+ static gchar *file = NULL;
+
+ if (file == NULL)
+ {
+ const gchar *dir = g_get_user_cache_dir ();
+
+ file = g_build_path (G_DIR_SEPARATOR_S, dir, CONFFILE, NULL);
+ }
+
+ return file;
+}
+
+static gboolean
+_have_config (void)
+{
+ const gchar *file = _conf_filename ();
+
+ DEBUG ("checking for %s", file);
+ return g_file_test (file, G_FILE_TEST_EXISTS);
+}
+
+static void
+_create_config (void)
+{
+ gchar *file = _conf_filename ();
+ gchar *dir = g_path_get_dirname (file);
+
+ g_mkdir_with_parents (dir, 0700);
+ g_free (dir);
+
+ g_file_set_contents (file, "# diverted accounts\n", -1, NULL);
+ DEBUG ("created %s", file);
+}
+
+static gboolean
+_set (const McpAccountStorage *self,
+ const McpAccountManager *am,
+ const gchar *account,
+ const gchar *key,
+ const gchar *val)
+{
+ AccountDiversionPlugin *adp = ACCOUNT_DIVERSION_PLUGIN (self);
+
+ if (g_str_has_prefix (account, DONT_DIVERT))
+ return FALSE;
+
+ adp->save = TRUE;
+ g_key_file_set_value (adp->keyfile, account, key, val);
+
+ return TRUE;
+}
+
+static gboolean
+_get (const McpAccountStorage *self,
+ const McpAccountManager *am,
+ const gchar *account,
+ const gchar *key)
+{
+ AccountDiversionPlugin *adp = ACCOUNT_DIVERSION_PLUGIN (self);
+
+ if (key != NULL)
+ {
+ gchar *v = g_key_file_get_value (adp->keyfile, account, key, NULL);
+
+ if (v == NULL)
+ return FALSE;
+
+ mcp_account_manager_set_value (am, account, key, v);
+ g_free (v);
+ }
+ else
+ {
+ gsize i;
+ gsize n;
+ GStrv keys = g_key_file_get_keys (adp->keyfile, account, &n, NULL);
+
+ if (keys == NULL)
+ n = 0;
+
+ for (i = 0; i < n; i++)
+ {
+ gchar *v = g_key_file_get_value (adp->keyfile, account, keys[i], NULL);
+
+ if (v != NULL)
+ mcp_account_manager_set_value (am, account, keys[i], v);
+
+ g_free (v);
+ }
+
+ g_strfreev (keys);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_delete (const McpAccountStorage *self,
+ const McpAccountManager *am,
+ const gchar *account,
+ const gchar *key)
+{
+ AccountDiversionPlugin *adp = ACCOUNT_DIVERSION_PLUGIN (self);
+
+ if (key == NULL)
+ {
+ if (g_key_file_remove_group (adp->keyfile, account, NULL))
+ adp->save = TRUE;
+ }
+ else
+ {
+ gsize n;
+ GStrv keys;
+
+ if (g_key_file_remove_key (adp->keyfile, account, key, NULL))
+ adp->save = TRUE;
+
+ keys = g_key_file_get_keys (adp->keyfile, account, &n, NULL);
+
+ if (keys == NULL || n == 0)
+ g_key_file_remove_group (adp->keyfile, account, NULL);
+
+ g_strfreev (keys);
+ }
+
+ return TRUE;
+}
+
+
+static gboolean
+_commit (const McpAccountStorage *self,
+ const McpAccountManager *am)
+{
+ gsize n;
+ gchar *data;
+ AccountDiversionPlugin *adp = ACCOUNT_DIVERSION_PLUGIN (self);
+ gboolean rval = FALSE;
+
+ if (!adp->save)
+ return TRUE;
+
+ if (!_have_config ())
+ _create_config ();
+
+ data = g_key_file_to_data (adp->keyfile, &n, NULL);
+ rval = g_file_set_contents (_conf_filename (), data, n, NULL);
+ adp->save = !rval;
+ g_free (data);
+
+ return rval;
+}
+
+static GList *
+_list (const McpAccountStorage *self,
+ const McpAccountManager *am)
+{
+ gsize i;
+ gsize n;
+ GStrv accounts;
+ GList *rval = NULL;
+ AccountDiversionPlugin *adp = ACCOUNT_DIVERSION_PLUGIN (self);
+
+ if (!_have_config ())
+ _create_config ();
+
+ if (!adp->loaded)
+ adp->loaded = g_key_file_load_from_file (adp->keyfile, _conf_filename (),
+ G_KEY_FILE_KEEP_COMMENTS, NULL);
+
+ accounts = g_key_file_get_groups (adp->keyfile, &n);
+
+ for (i = 0; i < n; i++)
+ rval = g_list_prepend (rval, g_strdup (accounts[i]));
+
+ g_strfreev (accounts);
+
+ return rval;
+}
+
+static void
+account_storage_iface_init (McpAccountStorageIface *iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+ mcp_account_storage_iface_set_name (iface, PLUGIN_NAME);
+ mcp_account_storage_iface_set_desc (iface, PLUGIN_DESCRIPTION);
+ mcp_account_storage_iface_set_priority (iface, PLUGIN_PRIORITY);
+
+ mcp_account_storage_iface_implement_get (iface, _get);
+ mcp_account_storage_iface_implement_set (iface, _set);
+ mcp_account_storage_iface_implement_delete (iface, _delete);
+ mcp_account_storage_iface_implement_commit (iface, _commit);
+ mcp_account_storage_iface_implement_list (iface, _list);
+}
+
+
+GObject *
+mcp_plugin_ref_nth_object (guint n)
+{
+ DEBUG ("Initializing mcp-account-diversion-plugin (n=%u)", n);
+
+ switch (n)
+ {
+ case 0:
+ return g_object_new (account_diversion_plugin_get_type (), NULL);
+
+ default:
+ return NULL;
+ }
+}
diff --git a/tests/twisted/mcp-dbus-caller-permission.c b/tests/twisted/mcp-dbus-caller-permission.c
new file mode 100644
index 00000000..96162baf
--- /dev/null
+++ b/tests/twisted/mcp-dbus-caller-permission.c
@@ -0,0 +1,264 @@
+/*
+ * A demonstration plugin that checks a DBus caller's md5sum
+ *
+ * Copyright © 2010 Nokia Corporation
+ * Copyright © 2010 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
+ */
+
+#include <mission-control-plugins/mission-control-plugins.h>
+#include <sys/types.h>
+
+#define CONFFILE "mcp-dbus-caller-permissions.conf"
+
+#define DEBUG g_debug
+
+#define PLUGIN_NAME "dbus-caller-permission-checker"
+#define PLUGIN_DESCRIPTION \
+ "Test plugin that checks the md5 checksum of a DBus caller. " \
+ "gkeyfile g_get_user_cache_dir()/" CONFFILE " holds the [paths " \
+ "to] the binaries, and the permission tokens associated with each."
+
+/* Example conf file:
+[/usr/local/bin/mc-tool]
+org.freedesktop.Telepathy.AccountManager=1
+*=1
+*/
+
+static void dbus_acl_iface_init (McpDBusAclIface *,
+ gpointer);
+
+typedef struct {
+ GObject parent;
+ GKeyFile *permits;
+ gboolean loaded;
+} DBusCallerPermission;
+
+typedef struct {
+ GObjectClass parent_class;
+} DBusCallerPermissionClass;
+
+GType dbus_caller_permission_get_type (void) G_GNUC_CONST;
+
+#define DBUS_CALLER_PERMISSION(o) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((o), dbus_caller_permission_get_type (), \
+ DBusCallerPermission))
+
+G_DEFINE_TYPE_WITH_CODE (DBusCallerPermission, dbus_caller_permission,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_DBUS_ACL, dbus_acl_iface_init));
+
+static void
+dbus_caller_permission_init (DBusCallerPermission *self)
+{
+ const gchar *dir;
+ gchar *file = NULL;
+
+ self->permits = g_key_file_new ();
+
+ dir = g_get_user_cache_dir ();
+ file = g_build_path (G_DIR_SEPARATOR_S, dir, CONFFILE, NULL);
+
+ if (!g_file_test (file, G_FILE_TEST_EXISTS))
+ {
+ g_mkdir_with_parents (dir, 0700);
+ g_file_set_contents (file, "# MC DBus permissions\n", -1, NULL);
+ }
+
+ DEBUG ("conf file %s", file);
+ g_key_file_load_from_file (self->permits, file, G_KEY_FILE_NONE, NULL);
+
+ g_free (file);
+}
+
+static void
+dbus_caller_permission_class_init (DBusCallerPermissionClass *cls)
+{
+}
+
+static gboolean is_filtered (const McpDBusAcl *self,
+ DBusAclType type,
+ const gchar *name)
+{
+ DBusCallerPermission *plugin = DBUS_CALLER_PERMISSION (self);
+ GKeyFile *permits = plugin->permits;
+
+ switch (type)
+ {
+ case DBUS_ACL_TYPE_METHOD:
+ return g_key_file_get_boolean (permits, "methods", name, NULL);
+ case DBUS_ACL_TYPE_GET_PROPERTY:
+ return g_key_file_get_boolean (permits, "get-property", name, NULL);
+ case DBUS_ACL_TYPE_SET_PROPERTY:
+ return g_key_file_get_boolean (permits, "set-property", name, NULL);
+ default:
+ return FALSE;
+ }
+}
+
+static gboolean
+pid_is_permitted (const McpDBusAcl *self, const gchar *name, pid_t pid)
+{
+ gboolean ok = FALSE;
+
+ if (pid != 0)
+ {
+ gchar *path = g_strdup_printf ("/proc/%d/exe", pid);
+ gchar *executable = g_file_read_link (path, NULL);
+
+ if (executable != NULL)
+ {
+ DBusCallerPermission *plugin = DBUS_CALLER_PERMISSION (self);
+ GKeyFile *permits = plugin->permits;
+
+ DEBUG ("executable to check for permission is %s", executable);
+ ok = g_key_file_get_boolean (permits, executable, name, NULL);
+ DEBUG ("%s:%s = %s", executable, name, ok ? "TRUE" : "FALSE");
+
+ g_free (executable);
+ }
+
+ g_free (path);
+ }
+
+ return ok;
+}
+
+static gboolean
+caller_authorised (const McpDBusAcl *self,
+ const TpDBusDaemon *dbus,
+ const DBusGMethodInvocation *call,
+ DBusAclType type,
+ const gchar *name,
+ const GHashTable *params)
+{
+ DBusGConnection *dgc = tp_proxy_get_dbus_connection ((TpDBusDaemon *)dbus);
+ gboolean ok = TRUE;
+
+ if (is_filtered (self, type, name))
+ {
+ pid_t pid = 0;
+ GError *error = NULL;
+ gchar *caller = dbus_g_method_get_sender ((DBusGMethodInvocation *) call);
+ DBusGProxy *proxy = dbus_g_proxy_new_for_name (dgc,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS);
+
+ dbus_g_proxy_call (proxy, "GetConnectionUnixProcessID", &error,
+ G_TYPE_STRING, caller,
+ G_TYPE_INVALID,
+ G_TYPE_UINT, &pid,
+ G_TYPE_INVALID);
+
+ ok = pid_is_permitted (self, name, pid);
+
+ g_free (caller);
+ g_object_unref (proxy);
+ }
+
+ DEBUG ("sync caller-permission ACL check [%s]", ok ? "Allowed" : "Forbidden");
+
+ return ok;
+}
+
+static void
+async_authorised_cb (DBusGProxy *proxy,
+ DBusGProxyCall *call,
+ gpointer data)
+{
+ GError *error = NULL;
+ DBusAclAuthData *ad = data;
+ pid_t pid = 0;
+ const McpDBusAcl *self = ad->acl;
+ gboolean permitted = FALSE;
+
+ /* if this returns FALSE, there's no PID, which means something bizarre *
+ * and untrustowrthy is going on, which in turn means we must deny: can't *
+ * authorise without first authenticating */
+ permitted = dbus_g_proxy_end_call (proxy, call, &error,
+ G_TYPE_UINT, &pid,
+ G_TYPE_INVALID);
+
+ if (permitted)
+ permitted = pid_is_permitted (self, ad->name, pid);
+ else
+ g_error_free (error);
+
+ DEBUG ("finished async caller-permission ACL check [%u -> %s]",
+ pid, permitted ? "Allowed" : "Forbidden");
+
+ mcp_dbus_acl_authorised_async_step (ad, permitted);
+}
+
+static void
+caller_async_authorised (const McpDBusAcl *self,
+ DBusAclAuthData *data)
+{
+ DBusGConnection *dgc = tp_proxy_get_dbus_connection (data->dbus);
+ DBusGProxy *proxy = dbus_g_proxy_new_for_name (dgc,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS);
+
+ DEBUG ("starting async caller-permission ACL check");
+
+ if (is_filtered (self, data->type, data->name))
+ {
+ gchar *caller = dbus_g_method_get_sender (data->context);
+
+ dbus_g_proxy_begin_call (proxy, "GetConnectionUnixProcessID",
+ async_authorised_cb,
+ data,
+ NULL,
+ G_TYPE_STRING, caller,
+ G_TYPE_INVALID);
+
+ g_free (caller);
+ }
+ else /* not filtered, so the call is allowed: */
+ {
+ mcp_dbus_acl_authorised_async_step (data, TRUE);
+ }
+}
+
+
+static void
+dbus_acl_iface_init (McpDBusAclIface *iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+ mcp_dbus_acl_iface_set_name (iface, PLUGIN_NAME);
+ mcp_dbus_acl_iface_set_desc (iface, PLUGIN_DESCRIPTION);
+
+ mcp_dbus_acl_iface_implement_authorised (iface, caller_authorised);
+ mcp_dbus_acl_iface_implement_authorised_async (iface, caller_async_authorised);
+}
+
+GObject *
+mcp_plugin_ref_nth_object (guint n)
+{
+ DEBUG ("Initializing mcp-dbus-caller-id plugin (n=%u)", n);
+
+ switch (n)
+ {
+ case 0:
+ return g_object_new (dbus_caller_permission_get_type (), NULL);
+
+ default:
+ return NULL;
+ }
+}
+
diff --git a/tests/twisted/mcp-plugin.c b/tests/twisted/mcp-plugin.c
new file mode 100644
index 00000000..53aa1731
--- /dev/null
+++ b/tests/twisted/mcp-plugin.c
@@ -0,0 +1,442 @@
+/*
+ * A demonstration plugin that acts as a channel filter.
+ *
+ * Copyright © 2008-2009 Nokia Corporation
+ * Copyright © 2009-2010 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
+ */
+
+#include <mission-control-plugins/mission-control-plugins.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#define DEBUG g_debug
+
+/* ------ TestPermissionPlugin -------------------------------------- */
+
+typedef struct {
+ GObject parent;
+} TestPermissionPlugin;
+
+typedef struct {
+ GObjectClass parent_class;
+} TestPermissionPluginClass;
+
+GType test_permission_plugin_get_type (void) G_GNUC_CONST;
+static void cdo_policy_iface_init (McpDispatchOperationPolicyIface *,
+ gpointer);
+static void req_policy_iface_init (McpRequestPolicyIface *, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (TestPermissionPlugin, test_permission_plugin,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_REQUEST_POLICY,
+ req_policy_iface_init);
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_DISPATCH_OPERATION_POLICY,
+ cdo_policy_iface_init))
+
+static void
+test_permission_plugin_init (TestPermissionPlugin *self)
+{
+}
+
+static void
+test_permission_plugin_class_init (TestPermissionPluginClass *cls)
+{
+}
+
+typedef struct {
+ McpDispatchOperation *dispatch_operation;
+ McpDispatchOperationDelay *delay;
+} PermissionContext;
+
+static void
+permission_context_free (gpointer p)
+{
+ PermissionContext *ctx = p;
+
+ mcp_dispatch_operation_end_delay (ctx->dispatch_operation, ctx->delay);
+ g_object_unref (ctx->dispatch_operation);
+ g_slice_free (PermissionContext, ctx);
+}
+
+static void
+permission_cb (DBusPendingCall *pc,
+ gpointer data)
+{
+ PermissionContext *ctx = data;
+ DBusMessage *message = dbus_pending_call_steal_reply (pc);
+
+ if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR)
+ {
+ DEBUG ("Permission denied");
+ mcp_dispatch_operation_leave_channels (ctx->dispatch_operation,
+ TRUE, TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED,
+ "Computer says no");
+ }
+ else
+ {
+ DEBUG ("Permission granted");
+ }
+
+ dbus_message_unref (message);
+ dbus_pending_call_unref (pc);
+}
+
+static void
+test_permission_plugin_check_cdo (McpDispatchOperationPolicy *policy,
+ McpDispatchOperation *dispatch_operation)
+{
+ GHashTable *properties = mcp_dispatch_operation_ref_nth_channel_properties (
+ dispatch_operation, 0);
+ PermissionContext *ctx = NULL;
+
+ DEBUG ("enter");
+
+ if (properties == NULL)
+ {
+ DEBUG ("no channels!?");
+ return;
+ }
+
+ /* currently this example just checks the first channel */
+
+ if (!tp_strdiff (tp_asv_get_string (properties,
+ TP_IFACE_CHANNEL ".TargetID"),
+ "policy@example.net"))
+ {
+ TpDBusDaemon *dbus_daemon = tp_dbus_daemon_dup (NULL);
+ DBusGConnection *gconn = tp_proxy_get_dbus_connection (dbus_daemon);
+ DBusConnection *libdbus = dbus_g_connection_get_connection (gconn);
+ DBusPendingCall *pc = NULL;
+ DBusMessage *message;
+
+ ctx = g_slice_new0 (PermissionContext);
+ ctx->dispatch_operation = g_object_ref (dispatch_operation);
+ ctx->delay = mcp_dispatch_operation_start_delay (dispatch_operation);
+
+ /* in a real policy-mechanism you'd give some details, like the
+ * channel's properties or object path */
+ message = dbus_message_new_method_call ("com.example.Policy",
+ "/com/example/Policy", "com.example.Policy", "RequestPermission");
+
+ if (!dbus_connection_send_with_reply (libdbus, message,
+ &pc, -1))
+ {
+ g_error ("out of memory");
+ }
+
+ dbus_message_unref (message);
+
+ if (pc == NULL)
+ {
+ DEBUG ("got disconnected from D-Bus...");
+
+ goto finally;
+ }
+
+ /* pc is unreffed by permission_cb */
+
+ DEBUG ("Waiting for permission");
+
+ if (dbus_pending_call_get_completed (pc))
+ {
+ permission_cb (pc, ctx);
+ goto finally;
+ }
+
+ if (!dbus_pending_call_set_notify (pc, permission_cb, ctx,
+ permission_context_free))
+ {
+ g_error ("Out of memory");
+ }
+
+ /* ctx will be freed later */
+ ctx = NULL;
+ }
+
+finally:
+ if (ctx != NULL)
+ permission_context_free (ctx);
+
+ g_hash_table_unref (properties);
+}
+
+static void
+cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+ mcp_dispatch_operation_policy_iface_implement_check (iface,
+ test_permission_plugin_check_cdo);
+}
+
+typedef struct {
+ McpRequest *request;
+ McpRequestDelay *delay;
+} RequestPermissionContext;
+
+static void
+request_permission_context_free (gpointer p)
+{
+ RequestPermissionContext *ctx = p;
+
+ mcp_request_end_delay (ctx->request, ctx->delay);
+ g_object_unref (ctx->request);
+ g_slice_free (RequestPermissionContext, ctx);
+}
+
+static void
+request_permission_cb (DBusPendingCall *pc,
+ gpointer data)
+{
+ RequestPermissionContext *ctx = data;
+ DBusMessage *message = dbus_pending_call_steal_reply (pc);
+
+ if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR)
+ {
+ DEBUG ("Permission denied");
+ mcp_request_deny (ctx->request,
+ TP_ERRORS, TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED,
+ "Computer says no");
+ }
+ else
+ {
+ DEBUG ("Permission granted");
+ }
+
+ dbus_message_unref (message);
+ dbus_pending_call_unref (pc);
+}
+
+static void
+test_permission_plugin_check_request (McpRequestPolicy *policy,
+ McpRequest *request)
+{
+ GHashTable *properties = mcp_request_ref_nth_request (request, 0);
+ RequestPermissionContext *ctx = NULL;
+
+ DEBUG ("%s", G_STRFUNC);
+
+ if (mcp_request_find_request_by_type (request,
+ 0, g_quark_from_static_string ("com.example.QuestionableChannel"),
+ NULL, NULL))
+ {
+ TpDBusDaemon *dbus_daemon = tp_dbus_daemon_dup (NULL);
+ DBusGConnection *gconn = tp_proxy_get_dbus_connection (dbus_daemon);
+ DBusConnection *libdbus = dbus_g_connection_get_connection (gconn);
+ DBusPendingCall *pc = NULL;
+ DBusMessage *message;
+
+ DEBUG ("Questionable channel detected, asking for permission");
+
+ ctx = g_slice_new0 (RequestPermissionContext);
+ ctx->request = g_object_ref (request);
+ ctx->delay = mcp_request_start_delay (request);
+
+ /* in a real policy-mechanism you'd give some details, like the
+ * channel's properties or object path */
+ message = dbus_message_new_method_call ("com.example.Policy",
+ "/com/example/Policy", "com.example.Policy", "RequestRequest");
+
+ if (!dbus_connection_send_with_reply (libdbus, message,
+ &pc, -1))
+ {
+ g_error ("out of memory");
+ }
+
+ dbus_message_unref (message);
+
+ if (pc == NULL)
+ {
+ DEBUG ("got disconnected from D-Bus...");
+
+ goto finally;
+ }
+
+ /* pc is unreffed by permission_cb */
+
+ DEBUG ("Waiting for permission");
+
+ if (dbus_pending_call_get_completed (pc))
+ {
+ request_permission_cb (pc, ctx);
+ goto finally;
+ }
+
+ if (!dbus_pending_call_set_notify (pc, request_permission_cb, ctx,
+ request_permission_context_free))
+ {
+ g_error ("Out of memory");
+ }
+
+ /* ctx will be freed later */
+ ctx = NULL;
+ }
+
+finally:
+ if (ctx != NULL)
+ request_permission_context_free (ctx);
+
+ g_hash_table_unref (properties);
+}
+
+static void
+req_policy_iface_init (McpRequestPolicyIface *iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+ mcp_request_policy_iface_implement_check (iface,
+ test_permission_plugin_check_request);
+}
+
+/* ------ TestRejectionPlugin --------------------------------------- */
+
+typedef struct {
+ GObject parent;
+} TestRejectionPlugin;
+
+typedef struct {
+ GObjectClass parent_class;
+} TestRejectionPluginClass;
+
+GType test_rejection_plugin_get_type (void) G_GNUC_CONST;
+static void rej_cdo_policy_iface_init (McpDispatchOperationPolicyIface *,
+ gpointer);
+static void rej_req_policy_iface_init (McpRequestPolicyIface *,
+ gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (TestRejectionPlugin, test_rejection_plugin,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_REQUEST_POLICY,
+ rej_req_policy_iface_init);
+ G_IMPLEMENT_INTERFACE (MCP_TYPE_DISPATCH_OPERATION_POLICY,
+ rej_cdo_policy_iface_init))
+
+static void
+test_rejection_plugin_init (TestRejectionPlugin *self)
+{
+}
+
+static void
+test_rejection_plugin_class_init (TestRejectionPluginClass *cls)
+{
+}
+
+static void
+test_rejection_plugin_check_cdo (McpDispatchOperationPolicy *policy,
+ McpDispatchOperation *dispatch_operation)
+{
+ GHashTable *properties = mcp_dispatch_operation_ref_nth_channel_properties (
+ dispatch_operation, 0);
+ const gchar *target_id;
+
+ DEBUG ("enter");
+
+ if (properties == NULL)
+ {
+ DEBUG ("no channels!?");
+ return;
+ }
+
+ /* currently this example just checks the first channel */
+
+ target_id = tp_asv_get_string (properties, TP_IFACE_CHANNEL ".TargetID");
+
+ if (!tp_strdiff (target_id, "rick.astley@example.net"))
+ {
+ DEBUG ("rickrolling detected, destroying channels immediately!");
+ mcp_dispatch_operation_destroy_channels (dispatch_operation, FALSE);
+ }
+ else if (!tp_strdiff (target_id, "mc.hammer@example.net"))
+ {
+ DEBUG ("MC Hammer detected, leaving channels when observers have run");
+ mcp_dispatch_operation_leave_channels (dispatch_operation, TRUE,
+ TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED,
+ "Can't touch this");
+ }
+
+ g_hash_table_unref (properties);
+}
+
+static void
+rej_cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+ mcp_dispatch_operation_policy_iface_implement_check (iface,
+ test_rejection_plugin_check_cdo);
+}
+
+static void
+test_rejection_plugin_check_request (McpRequestPolicy *policy,
+ McpRequest *request)
+{
+ GHashTable *properties = mcp_request_ref_nth_request (request, 0);
+
+ DEBUG ("%s", G_STRFUNC);
+
+ if (!tp_strdiff (
+ tp_asv_get_string (properties, TP_IFACE_CHANNEL ".ChannelType"),
+ "com.example.ForbiddenChannel"))
+ {
+ DEBUG ("Forbidden channel detected, denying request");
+ mcp_request_deny (request, TP_ERRORS, TP_ERROR_PERMISSION_DENIED,
+ "No, you don't");
+ }
+
+ if (mcp_request_find_request_by_type (request,
+ 0, g_quark_from_static_string ("com.example.ForbiddenChannel"),
+ NULL, NULL))
+ {
+ DEBUG ("Forbidden channel detected, denying request");
+ mcp_request_deny (request, TP_ERRORS, TP_ERROR_PERMISSION_DENIED,
+ "No, you don't");
+ }
+
+ g_hash_table_unref (properties);
+}
+
+static void
+rej_req_policy_iface_init (McpRequestPolicyIface *iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+ mcp_request_policy_iface_implement_check (iface,
+ test_rejection_plugin_check_request);
+}
+
+/* ------ Initialization -------------------------------------------- */
+
+GObject *
+mcp_plugin_ref_nth_object (guint n)
+{
+ DEBUG ("Initializing mcp-plugin (n=%u)", n);
+
+ switch (n)
+ {
+ case 0:
+ return g_object_new (test_permission_plugin_get_type (),
+ NULL);
+
+ case 1:
+ return g_object_new (test_rejection_plugin_get_type (),
+ NULL);
+
+ default:
+ return NULL;
+ }
+}
diff --git a/tests/twisted/mctest.py b/tests/twisted/mctest.py
new file mode 100644
index 00000000..e67b8cd1
--- /dev/null
+++ b/tests/twisted/mctest.py
@@ -0,0 +1,1054 @@
+# 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
+
+"""
+Infrastructure code for testing Mission Control
+"""
+
+import base64
+import os
+import sys
+
+import constants as cs
+import servicetest
+import twisted
+from twisted.internet import reactor
+
+import dbus
+import dbus.service
+
+def make_mc(bus, event_func, params=None):
+ default_params = {
+ }
+
+ if params:
+ default_params.update(params)
+
+ return servicetest.make_mc(bus, event_func, default_params)
+
+def install_colourer():
+ def red(s):
+ return '\x1b[31m%s\x1b[0m' % s
+
+ def green(s):
+ return '\x1b[32m%s\x1b[0m' % s
+
+ patterns = {
+ 'handled': green,
+ 'not handled': red,
+ }
+
+ class Colourer:
+ def __init__(self, fh, patterns):
+ self.fh = fh
+ self.patterns = patterns
+
+ def write(self, s):
+ f = self.patterns.get(s, lambda x: x)
+ self.fh.write(f(s))
+
+ sys.stdout = Colourer(sys.stdout, patterns)
+ return sys.stdout
+
+
+def exec_test_deferred (fun, params, protocol=None, timeout=None,
+ preload_mc=True):
+ colourer = None
+
+ if sys.stdout.isatty():
+ colourer = install_colourer()
+
+ queue = servicetest.IteratingEventQueue(timeout)
+ queue.verbose = (
+ os.environ.get('CHECK_TWISTED_VERBOSE', '') != ''
+ or '-v' in sys.argv)
+
+ bus = dbus.SessionBus()
+ queue.attach_to_bus(bus)
+ if preload_mc:
+ mc = make_mc(bus, queue.append, params)
+
+ try:
+ bus.get_name_owner(cs.AM)
+ except dbus.DBusException, e:
+ queue.expect('dbus-signal', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.AM and e.args[2])
+
+ try:
+ bus.get_name_owner(cs.CD)
+ except dbus.DBusException, e:
+ queue.expect('dbus-signal', signal='NameOwnerChanged',
+ predicate=lambda e: e.args[0] == cs.CD and e.args[2])
+ else:
+ mc = None
+ error = None
+
+ try:
+ fun(queue, bus, mc)
+ except Exception, e:
+ import traceback
+ traceback.print_exc()
+ error = e
+
+ try:
+ am_props_iface = dbus.Interface(get_account_manager(bus),
+ cs.PROPERTIES_IFACE)
+ am_props = am_props_iface.GetAll(cs.AM)
+
+ for a in (am_props.get('ValidAccounts', []) +
+ am_props.get('InvalidAccounts', [])):
+ try:
+ account_props_iface = dbus.Interface(bus.get_object(cs.AM, a),
+ cs.PROPERTIES_IFACE)
+ account_props_iface.Set(cs.ACCOUNT, 'RequestedPresence',
+ (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline',
+ ''))
+ except dbus.DBusException, e:
+ print >> sys.stderr, e
+
+ try:
+ account_props_iface = dbus.Interface(bus.get_object(cs.AM, a),
+ cs.PROPERTIES_IFACE)
+ account_props_iface.Set(cs.ACCOUNT, 'Enabled', False)
+ except dbus.DBusException, e:
+ print >> sys.stderr, e
+
+ try:
+ account_iface = dbus.Interface(bus.get_object(cs.AM, a),
+ cs.ACCOUNT)
+ account_iface.Remove()
+ except dbus.DBusException, e:
+ print >> sys.stderr, e
+
+ servicetest.sync_dbus(bus, queue, am_props_iface)
+
+ except dbus.DBusException, e:
+ print >> sys.stderr, e
+
+ queue.cleanup()
+
+ if error is None:
+ reactor.callLater(0, reactor.stop)
+ else:
+ # please ignore the POSIX behind the curtain
+ os._exit(1)
+
+ if colourer:
+ sys.stdout = colourer.fh
+
+def exec_test(fun, params=None, protocol=None, timeout=None, preload_mc=True):
+ reactor.callWhenRunning (exec_test_deferred, fun, params, protocol, timeout,
+ preload_mc)
+ reactor.run()
+
+class SimulatedConnection(object):
+
+ def ensure_handle(self, type, identifier):
+ if (type, identifier) in self._handles:
+ return self._handles[(type, identifier)]
+
+ self._last_handle += 1
+ self._handles[(type, identifier)] = self._last_handle
+ self._identifiers[(type, self._last_handle)] = identifier
+ return self._last_handle
+
+ def __init__(self, q, bus, cmname, protocol, account_part, self_ident,
+ implement_get_interfaces=True, has_requests=True,
+ has_presence=False, has_aliasing=False, has_avatars=False,
+ avatars_persist=True, extra_interfaces=[], has_hidden=False):
+ self.q = q
+ self.bus = bus
+
+ self.bus_name = '.'.join([cs.tp_name_prefix, 'Connection',
+ cmname, protocol.replace('-', '_'), account_part])
+ self._bus_name_ref = dbus.service.BusName(self.bus_name, self.bus)
+ self.object_path = '/' + self.bus_name.replace('.', '/')
+
+ self._last_handle = 41
+ self._handles = {}
+ self._identifiers = {}
+ self.status = cs.CONN_STATUS_DISCONNECTED
+ self.reason = cs.CONN_STATUS_CONNECTING
+ self.self_ident = self_ident
+ self.self_handle = self.ensure_handle(cs.HT_CONTACT, self_ident)
+ self.channels = []
+ self.has_requests = has_requests
+ self.has_presence = has_presence
+ self.has_aliasing = has_aliasing
+ self.has_avatars = has_avatars
+ self.avatars_persist = avatars_persist
+ self.extra_interfaces = extra_interfaces[:]
+
+ self.interfaces = []
+
+ if self.has_requests:
+ self.interfaces.append(cs.CONN_IFACE_REQUESTS)
+ if self.has_aliasing:
+ self.interfaces.append(cs.CONN_IFACE_ALIASING)
+ if self.has_avatars:
+ self.interfaces.append(cs.CONN_IFACE_AVATARS)
+ if self.has_presence:
+ self.interfaces.append(cs.CONN_IFACE_SIMPLE_PRESENCE)
+ if self.extra_interfaces:
+ self.interfaces.extend(self.extra_interfaces)
+
+ if self.avatars_persist:
+ self.avatar = dbus.Struct((dbus.ByteArray('my old avatar'),
+ 'text/plain'), signature='ays')
+ else:
+ self.avatar = None
+
+ q.add_dbus_method_impl(self.Connect,
+ path=self.object_path, interface=cs.CONN, method='Connect')
+ q.add_dbus_method_impl(self.Disconnect,
+ path=self.object_path, interface=cs.CONN, method='Disconnect')
+ q.add_dbus_method_impl(self.GetSelfHandle,
+ path=self.object_path,
+ interface=cs.CONN, method='GetSelfHandle')
+ q.add_dbus_method_impl(self.GetStatus,
+ path=self.object_path, interface=cs.CONN, method='GetStatus')
+
+ q.add_dbus_method_impl(self.GetAll_Connection,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN])
+
+ if implement_get_interfaces:
+ q.add_dbus_method_impl(self.GetInterfaces,
+ path=self.object_path, interface=cs.CONN,
+ method='GetInterfaces')
+
+ q.add_dbus_method_impl(self.InspectHandles,
+ path=self.object_path, interface=cs.CONN,
+ method='InspectHandles')
+ q.add_dbus_method_impl(self.HoldHandles,
+ path=self.object_path, interface=cs.CONN,
+ method='HoldHandles')
+ q.add_dbus_method_impl(self.GetAll_Requests,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS])
+
+ q.add_dbus_method_impl(self.GetContactAttributes,
+ path=self.object_path,
+ interface=cs.CONN_IFACE_CONTACTS, method='GetContactAttributes')
+ q.add_dbus_method_impl(self.GetAll_Contacts,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_CONTACTS])
+
+ if not has_requests:
+ q.add_dbus_method_impl(self.ListChannels,
+ path=self.object_path, interface=cs.CONN,
+ method='ListChannels')
+
+ if has_presence:
+ q.add_dbus_method_impl(self.SetPresence, path=self.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE,
+ method='SetPresence')
+ q.add_dbus_method_impl(self.GetPresences, path=self.object_path,
+ interface=cs.CONN_IFACE_SIMPLE_PRESENCE,
+ method='GetPresences')
+ q.add_dbus_method_impl(self.Get_SimplePresenceStatuses,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get',
+ args=[cs.CONN_IFACE_SIMPLE_PRESENCE, 'Statuses'])
+ q.add_dbus_method_impl(self.GetAll_SimplePresence,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='GetAll',
+ args=[cs.CONN_IFACE_SIMPLE_PRESENCE])
+
+ if has_aliasing:
+ q.add_dbus_method_impl(self.GetAliasFlags,
+ path=self.object_path, interface=cs.CONN_IFACE_ALIASING,
+ method='GetAliasFlags',
+ args=[])
+
+ if has_avatars:
+ q.add_dbus_method_impl(self.GetAvatarRequirements,
+ path=self.object_path, interface=cs.CONN_IFACE_AVATARS,
+ method='GetAvatarRequirements', args=[])
+ q.add_dbus_method_impl(self.GetAll_Avatars,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='GetAll', args=[cs.CONN_IFACE_AVATARS])
+ q.add_dbus_method_impl(self.GetKnownAvatarTokens,
+ path=self.object_path, interface=cs.CONN_IFACE_AVATARS,
+ method='GetKnownAvatarTokens')
+ q.add_dbus_method_impl(self.SetAvatar,
+ path=self.object_path, interface=cs.CONN_IFACE_AVATARS,
+ method='SetAvatar')
+
+ self.statuses = dbus.Dictionary({
+ 'available': (cs.PRESENCE_TYPE_AVAILABLE, True, True),
+ 'away': (cs.PRESENCE_TYPE_AWAY, True, True),
+ 'lunch': (cs.PRESENCE_TYPE_XA, True, True),
+ 'busy': (cs.PRESENCE_TYPE_BUSY, True, True),
+ 'phone': (cs.PRESENCE_TYPE_BUSY, True, True),
+ 'offline': (cs.PRESENCE_TYPE_OFFLINE, False, False),
+ 'error': (cs.PRESENCE_TYPE_ERROR, False, False),
+ 'unknown': (cs.PRESENCE_TYPE_UNKNOWN, False, False),
+ }, signature='s(ubb)')
+
+ if has_hidden:
+ self.statuses['hidden'] = (cs.PRESENCE_TYPE_HIDDEN, True, True)
+
+ self.presence = dbus.Struct((cs.PRESENCE_TYPE_OFFLINE, 'offline', ''),
+ signature='uss')
+
+ def GetAll_Connection(self, e):
+ self.q.dbus_return(e.message, {
+ 'Interfaces': dbus.Array(self.interfaces, signature='s'),
+ 'SelfHandle': dbus.UInt32(self.self_handle),
+ 'Status': dbus.UInt32(self.status),
+ 'HasImmortalHandles': dbus.Boolean(True),
+ }, signature='a{sv}')
+
+ def forget_avatar(self):
+ self.avatar = (dbus.ByteArray(''), '')
+
+ # not actually very relevant for MC so hard-code 0 for now
+ def GetAliasFlags(self, e):
+ self.q.dbus_return(e.message, 0, signature='u')
+
+ # mostly for the UI's benefit; for now hard-code the requirements from XMPP
+ def GetAvatarRequirements(self, e):
+ self.q.dbus_return(e.message, ['image/jpeg'], 0, 0, 96, 96, 8192,
+ signature='asqqqqu')
+
+ def GetAll_Avatars(self, e):
+ self.q.dbus_return(e.message, {
+ 'SupportedAvatarMIMETypes': ['image/jpeg'],
+ 'MinimumAvatarWidth': 0,
+ 'RecommendedAvatarWidth': 64,
+ 'MaximumAvatarWidth': 96,
+ 'MinimumAvatarHeight': 0,
+ 'RecommendedAvatarHeight': 64,
+ 'MaximumAvatarHeight': 96,
+ 'MaximumAvatarBytes': 8192,
+ }, signature='a{sv}')
+
+ def GetKnownAvatarTokens(self, e):
+ ret = dbus.Dictionary(signature='us')
+
+ # 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:
+ # we just stringify the avatar as the token
+ # (also, empty avatar => no avatar => empty token)
+ ret[self.self_handle] = str(self.avatar[0])
+
+ self.q.dbus_return(e.message, ret, signature='a{us}')
+
+ def SetAvatar(self, e):
+ self.avatar = dbus.Struct(e.args, signature='ays')
+
+ # we just stringify the avatar as the token
+ self.q.dbus_return(e.message, str(self.avatar[0]), signature='s')
+ self.q.dbus_emit(self.object_path, cs.CONN_IFACE_AVATARS,
+ 'AvatarRetrieved', self.self_handle, str(self.avatar[0]),
+ self.avatar[0], self.avatar[1], signature='usays')
+
+ def GetPresences(self, e):
+ ret = dbus.Dictionary(signature='u(uss)')
+ contacts = e.args[0]
+ for contact in contacts:
+ if contact == self.self_handle:
+ ret[contact] = self.presence
+ else:
+ # stub - MC doesn't care
+ ret[contact] = dbus.Struct(
+ (cs.PRESENCE_TYPE_UNKNOWN, 'unknown', ''),
+ signature='uss')
+ self.q.dbus_return(e.message, ret, signature='a{u(uss)}')
+
+ def SetPresence(self, e):
+ if e.args[0] in self.statuses:
+ presence = dbus.Struct((self.statuses[e.args[0]][0],
+ e.args[0], e.args[1]), signature='uss')
+
+ old_presence = self.presence
+
+ if presence != old_presence:
+ self.presence = presence
+
+ if self.status == cs.CONN_STATUS_CONNECTED:
+ self.q.dbus_emit(self.object_path,
+ cs.CONN_IFACE_SIMPLE_PRESENCE, 'PresencesChanged',
+ { self.self_handle : presence },
+ signature='a{u(uss)}')
+
+ self.q.dbus_return(e.message, signature='')
+ else:
+ self.q.dbus_raise(cs.INVALID_ARGUMENT, 'Unknown status')
+
+ def Get_SimplePresenceStatuses(self, e):
+ self.q.dbus_return(e.message, self.statuses, signature='v')
+
+ def GetAll_SimplePresence(self, e):
+ self.q.dbus_return(e.message,
+ {'Statuses': self.statuses}, signature='a{sv}')
+
+ def GetInterfaces(self, e):
+ self.q.dbus_return(e.message, self.interfaces, signature='as')
+
+ def Connect(self, e):
+ self.StatusChanged(cs.CONN_STATUS_CONNECTING,
+ cs.CONN_STATUS_REASON_REQUESTED)
+ self.q.dbus_return(e.message, signature='')
+
+ def Disconnect(self, e):
+ self.StatusChanged(cs.CONN_STATUS_DISCONNECTED,
+ cs.CONN_STATUS_REASON_REQUESTED)
+ self.q.dbus_return(e.message, signature='')
+ for c in self.channels:
+ c.close()
+
+ def inspect_handles(self, handles, htype=cs.HT_CONTACT):
+ ret = []
+
+ for h in handles:
+ if (htype, h) in self._identifiers:
+ ret.append(self._identifiers[(htype, h)])
+ else:
+ raise Exception(h)
+
+ return ret
+
+ def InspectHandles(self, e):
+ htype, hs = e.args
+
+ try:
+ ret = self.inspect_handles(hs, htype)
+ self.q.dbus_return(e.message, ret, signature='as')
+ except e:
+ self.q.dbus_raise(e.message, INVALID_HANDLE, str(e.args[0]))
+
+ def GetStatus(self, e):
+ self.q.dbus_return(e.message, self.status, signature='u')
+
+ def ConnectionError(self, error, details):
+ self.q.dbus_emit(self.object_path, cs.CONN, 'ConnectionError',
+ error, details, signature='sa{sv}')
+
+ def StatusChanged(self, status, reason):
+ self.status = status
+ self.reason = reason
+ self.q.dbus_emit(self.object_path, cs.CONN, 'StatusChanged',
+ status, reason, signature='uu')
+ if self.status == cs.CONN_STATUS_CONNECTED and self.has_presence:
+ if self.presence[0] == cs.PRESENCE_TYPE_OFFLINE:
+ self.presence = dbus.Struct((cs.PRESENCE_TYPE_AVAILABLE,
+ 'available', ''), signature='uss')
+
+ self.q.dbus_emit(self.object_path,
+ cs.CONN_IFACE_SIMPLE_PRESENCE, 'PresencesChanged',
+ { self.self_handle : self.presence },
+ signature='a{u(uss)}')
+
+ def ListChannels(self, e):
+ arr = dbus.Array(signature='(osuu)')
+
+ for c in self.channels:
+ arr.append(dbus.Struct(
+ (c.object_path,
+ c.immutable[cs.CHANNEL + '.ChannelType'],
+ c.immutable.get(cs.CHANNEL + '.TargetHandleType', 0),
+ c.immutable.get(cs.CHANNEL + '.TargetHandle', 0)
+ ), signature='osuu'))
+
+ self.q.dbus_return(e.message, arr, signature='a(osuu)')
+
+ def get_channel_details(self):
+ return dbus.Array([(c.object_path, c.immutable)
+ for c in self.channels], signature='(oa{sv})')
+
+ def GetAll_Requests(self, e):
+ if self.has_requests:
+ self.q.dbus_return(e.message, {
+ 'Channels': self.get_channel_details(),
+ }, signature='a{sv}')
+ else:
+ self.q.dbus_raise(e.message, cs.NOT_IMPLEMENTED, 'no Requests')
+
+ def GetSelfHandle(self, e):
+ self.q.dbus_return(e.message, self.self_handle, signature='u')
+
+ def HoldHandles(self, e):
+ # do nothing
+ self.q.dbus_return(e.message, signature='')
+
+ def NewChannels(self, channels):
+ for channel in channels:
+ assert not channel.announced
+ channel.announced = True
+ self.channels.append(channel)
+
+ self.q.dbus_emit(self.object_path, cs.CONN,
+ 'NewChannel',
+ channel.object_path,
+ channel.immutable[cs.CHANNEL + '.ChannelType'],
+ channel.immutable.get(cs.CHANNEL + '.TargetHandleType', 0),
+ channel.immutable.get(cs.CHANNEL + '.TargetHandle', 0),
+ channel.immutable.get(cs.CHANNEL + '.Requested', False),
+ signature='osuub')
+
+ if self.has_requests:
+ self.q.dbus_emit(self.object_path, cs.CONN_IFACE_REQUESTS,
+ 'NewChannels',
+ [(channel.object_path, channel.immutable)
+ for channel in channels],
+ signature='a(oa{sv})')
+
+ def GetContactAttributes(self, e):
+ ret = {}
+
+ try:
+ for h in e.args[0]:
+ id = self.inspect_handles(h)[0]
+ ret[dbus.UInt32(h)] = dbus.Dictionary({telepathy.CONN_IFACE + '/contact-id': id},
+ signature='sv')
+
+ q.dbus_return(e.message, ret, signature='a{ua{sv}}')
+ except e:
+ self.q.dbus_raise(e.message, INVALID_HANDLE, str(e.args[0]))
+
+ def GetAll_Contacts(self, e):
+ self.q.dbus_return(e.message, {
+ 'ContactAttributeInterfaces': []
+ }, signature='a{sv}')
+
+class SimulatedChannel(object):
+ def __init__(self, conn, immutable, mutable={},
+ destroyable=False, group=False):
+ self.conn = conn
+ self.q = conn.q
+ self.bus = conn.bus
+ self.object_path = conn.object_path + ('/_%x' % id(self))
+ self.immutable = immutable
+ self.properties = dbus.Dictionary({}, signature='sv')
+ self.properties.update(immutable)
+ self.properties.update(mutable)
+
+ self.q.add_dbus_method_impl(self.GetAll,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='GetAll')
+ self.q.add_dbus_method_impl(self.Get,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='Get')
+ self.q.add_dbus_method_impl(self.Close,
+ path=self.object_path,
+ interface=cs.CHANNEL, method='Close')
+ self.q.add_dbus_method_impl(self.GetInterfaces,
+ path=self.object_path,
+ interface=cs.CHANNEL, method='GetInterfaces')
+
+ if destroyable:
+ self.q.add_dbus_method_impl(self.Close,
+ path=self.object_path,
+ interface=cs.CHANNEL_IFACE_DESTROYABLE,
+ method='Destroy')
+
+ if group:
+ self.q.add_dbus_method_impl(self.GetGroupFlags,
+ path=self.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ method='GetGroupFlags')
+ self.q.add_dbus_method_impl(self.GetSelfHandle,
+ path=self.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ method='GetSelfHandle')
+ self.q.add_dbus_method_impl(self.GetAllMembers,
+ path=self.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ method='GetAllMembers')
+ self.q.add_dbus_method_impl(self.GetLocalPendingMembersWithInfo,
+ path=self.object_path,
+ interface=cs.CHANNEL_IFACE_GROUP,
+ method='GetLocalPendingMembersWithInfo')
+ self.properties[cs.CHANNEL_IFACE_GROUP + '.SelfHandle'] \
+ = self.conn.self_handle
+
+ self.announced = False
+ self.closed = False
+
+ def GetGroupFlags(self, e):
+ self.q.dbus_return(e.message, 0, signature='u')
+
+ def GetSelfHandle(self, e):
+ self.q.dbus_return(e.message,
+ self.properties[cs.CHANNEL_IFACE_GROUP + '.SelfHandle'],
+ signature='u')
+
+ def GetAllMembers(self, e):
+ # stub
+ self.q.dbus_return(e.message,
+ [self.properties[cs.CHANNEL_IFACE_GROUP + '.SelfHandle']],
+ [], [],
+ signature='auauau')
+
+ def GetLocalPendingMembersWithInfo(self, e):
+ # stub
+ self.q.dbus_return(e.message, [], signature='a(uuus)')
+
+ def announce(self):
+ self.conn.NewChannels([self])
+
+ def Close(self, e):
+ if not self.closed:
+ self.close()
+ self.q.dbus_return(e.message, signature='')
+
+ def close(self):
+ assert self.announced
+ assert not self.closed
+ self.closed = True
+ self.conn.channels.remove(self)
+ self.q.dbus_emit(self.object_path, cs.CHANNEL, 'Closed', signature='')
+ self.q.dbus_emit(self.conn.object_path, cs.CONN_IFACE_REQUESTS,
+ 'ChannelClosed', self.object_path, signature='o')
+
+ def GetInterfaces(self, e):
+ self.q.dbus_return(e.message,
+ self.properties[cs.CHANNEL + '.Interfaces'], signature='as')
+
+ def GetAll(self, e):
+ iface = e.args[0] + '.'
+
+ ret = dbus.Dictionary({}, signature='sv')
+ for k in self.properties:
+ if k.startswith(iface):
+ tail = k[len(iface):]
+ if '.' not in tail:
+ ret[tail] = self.properties[k]
+ assert ret # die on attempts to get unimplemented interfaces
+ self.q.dbus_return(e.message, ret, signature='a{sv}')
+
+ def Get(self, e):
+ prop = e.args[0] + '.' + e.args[1]
+ self.q.dbus_return(e.message, self.properties[prop],
+ signature='v')
+
+def aasv(x):
+ return dbus.Array([dbus.Dictionary(d, signature='sv') for d in x],
+ signature='a{sv}')
+
+class SimulatedClient(object):
+ def __init__(self, q, bus, clientname,
+ observe=[], approve=[], handle=[],
+ cap_tokens=[], bypass_approval=False, wants_recovery=False,
+ request_notification=True, implement_get_interfaces=True,
+ is_handler=None, bypass_observers=False):
+ self.q = q
+ self.bus = bus
+ self.bus_name = '.'.join([cs.tp_name_prefix, 'Client', clientname])
+ self._bus_name_ref = dbus.service.BusName(self.bus_name, self.bus)
+ self.object_path = '/' + self.bus_name.replace('.', '/')
+ self.observe = aasv(observe)
+ self.approve = aasv(approve)
+ self.handle = aasv(handle)
+ self.bypass_approval = bool(bypass_approval)
+ self.bypass_observers = bool(bypass_observers)
+ self.wants_recovery = bool(wants_recovery)
+ self.request_notification = bool(request_notification)
+ self.handled_channels = dbus.Array([], signature='o')
+ self.cap_tokens = dbus.Array(cap_tokens, signature='s')
+ self.is_handler = is_handler
+
+ if self.is_handler is None:
+ self.is_handler = bool(handle)
+
+ if implement_get_interfaces:
+ q.add_dbus_method_impl(self.Get_Interfaces,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get', args=[cs.CLIENT, 'Interfaces'])
+ q.add_dbus_method_impl(self.GetAll_Client,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CLIENT])
+
+ q.add_dbus_method_impl(self.Get_ObserverChannelFilter,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get', args=[cs.OBSERVER, 'ObserverChannelFilter'])
+ q.add_dbus_method_impl(self.GetAll_Observer,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.OBSERVER])
+
+ q.add_dbus_method_impl(self.Get_ApproverChannelFilter,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get', args=[cs.APPROVER, 'ApproverChannelFilter'])
+ q.add_dbus_method_impl(self.GetAll_Approver,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.APPROVER])
+
+ q.add_dbus_method_impl(self.Get_HandlerChannelFilter,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get', args=[cs.HANDLER, 'HandlerChannelFilter'])
+ q.add_dbus_method_impl(self.Get_Capabilities,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get', args=[cs.HANDLER, 'Capabilities'])
+ q.add_dbus_method_impl(self.Get_HandledChannels,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get', args=[cs.HANDLER, 'HandledChannels'])
+ q.add_dbus_method_impl(self.Get_BypassApproval,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get', args=[cs.HANDLER, 'BypassApproval'])
+ q.add_dbus_method_impl(self.Get_Recover,
+ path=self.object_path, interface=cs.PROPERTIES_IFACE,
+ method='Get', args=[cs.OBSERVER, 'Recover'])
+ q.add_dbus_method_impl(self.GetAll_Handler,
+ path=self.object_path,
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.HANDLER])
+
+ def release_name(self):
+ del self._bus_name_ref
+
+ def reacquire_name(self):
+ self._bus_name_ref = dbus.service.BusName(self.bus_name, self.bus)
+
+ def get_interfaces(self):
+ ret = dbus.Array([], signature='s', variant_level=1)
+
+ if self.observe:
+ ret.append(cs.OBSERVER)
+
+ if self.approve:
+ ret.append(cs.APPROVER)
+
+ if self.is_handler:
+ ret.append(cs.HANDLER)
+
+ if self.request_notification:
+ ret.append(cs.CLIENT_IFACE_REQUESTS)
+
+ return ret
+
+ def Get_Interfaces(self, e):
+ self.q.dbus_return(e.message, self.get_interfaces(), signature='v',
+ bus=self.bus)
+
+ def GetAll_Client(self, e):
+ self.q.dbus_return(e.message, {'Interfaces': self.get_interfaces()},
+ signature='a{sv}', bus=self.bus)
+
+ def GetAll_Observer(self, e):
+ assert self.observe
+ self.q.dbus_return(e.message, {
+ 'ObserverChannelFilter': self.observe,
+ 'Recover': self.wants_recovery,
+ },
+ signature='a{sv}', bus=self.bus)
+
+ def Get_ObserverChannelFilter(self, e):
+ assert self.observe
+ self.q.dbus_return(e.message, self.observe, signature='v',
+ bus=self.bus)
+
+ def GetAll_Approver(self, e):
+ assert self.approve
+ self.q.dbus_return(e.message, {'ApproverChannelFilter': self.approve},
+ signature='a{sv}', bus=self.bus)
+
+ def Get_ApproverChannelFilter(self, e):
+ assert self.approve
+ self.q.dbus_return(e.message, self.approve, signature='v',
+ bus=self.bus)
+
+ def GetAll_Handler(self, e):
+ assert self.is_handler
+ self.q.dbus_return(e.message, {
+ 'HandlerChannelFilter': self.handle,
+ 'BypassApproval': self.bypass_approval,
+ 'BypassObservers': self.bypass_observers,
+ 'HandledChannels': self.handled_channels,
+ 'Capabilities': self.cap_tokens,
+ },
+ signature='a{sv}', bus=self.bus)
+
+ def Get_Capabilities(self, e):
+ self.q.dbus_return(e.message, self.cap_tokens, signature='v',
+ bus=self.bus)
+
+ def Get_HandledChannels(self, e):
+ self.q.dbus_return(e.message, self.handled_channels, signature='v',
+ bus=self.bus)
+
+ def Get_HandlerChannelFilter(self, e):
+ assert self.handle
+ self.q.dbus_return(e.message, self.handle, signature='v',
+ bus=self.bus)
+
+ def Get_BypassApproval(self, e):
+ assert self.handle
+ self.q.dbus_return(e.message, self.bypass_approval, signature='v',
+ bus=self.bus)
+
+ def Get_BypassApproval(self, e):
+ assert self.handle
+ self.q.dbus_return(e.message, self.bypass_observers, signature='v',
+ bus=self.bus)
+
+ def Get_Recover(self, e):
+ assert self.handle
+ self.q.dbus_return(e.message, self.recover, signature='v',
+ bus=self.bus)
+
+def create_fakecm_account(q, bus, mc, params):
+ """Create a fake connection manager and an account that uses it.
+ """
+ cm_name_ref = dbus.service.BusName(
+ cs.tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
+
+ # Get the AccountManager interface
+ account_manager = get_account_manager(bus)
+ account_manager_iface = dbus.Interface(account_manager, cs.AM)
+
+ # Create an account
+ servicetest.call_async(q, account_manager_iface, 'CreateAccount',
+ 'fakecm', # Connection_Manager
+ 'fakeprotocol', # Protocol
+ 'fakeaccount', #Display_Name
+ params, # Parameters
+ {}, # Properties
+ )
+ # The spec has no order guarantee here.
+ # FIXME: MC ought to also introspect the CM and find out that the params
+ # are in fact sufficient
+
+ a_signal, am_signal, ret = q.expect_many(
+ servicetest.EventPattern('dbus-signal',
+ signal='AccountPropertyChanged', interface=cs.ACCOUNT,
+ predicate=(lambda e: 'Valid' in e.args[0])),
+ servicetest.EventPattern('dbus-signal', path=cs.AM_PATH,
+ signal='AccountValidityChanged', interface=cs.AM),
+ servicetest.EventPattern('dbus-return', method='CreateAccount'),
+ )
+ account_path = ret.value[0]
+ assert am_signal.args == [account_path, True], am_signal.args
+ assert a_signal.args[0]['Valid'] == True, a_signal.args
+
+ assert account_path is not None
+
+ # Get the Account interface
+ account = bus.get_object(
+ cs.tp_name_prefix + '.AccountManager',
+ account_path)
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+ # Introspect Account for debugging purpose
+ account_introspected = account.Introspect(
+ dbus_interface=cs.INTROSPECTABLE_IFACE)
+ #print account_introspected
+
+ return (cm_name_ref, account)
+
+def get_fakecm_account(bus, mc, account_path):
+ # Get the Account interface
+ account = bus.get_object(
+ cs.tp_name_prefix + '.AccountManager',
+ account_path)
+ account_iface = dbus.Interface(account, cs.ACCOUNT)
+ account_props = dbus.Interface(account, cs.PROPERTIES_IFACE)
+ # Introspect Account for debugging purpose
+ account_introspected = account.Introspect(
+ dbus_interface=cs.INTROSPECTABLE_IFACE)
+ #print account_introspected
+ return account
+
+
+def enable_fakecm_account(q, bus, mc, account, expected_params,
+ has_requests=True, has_presence=False, has_aliasing=False,
+ has_avatars=False, avatars_persist=True,
+ extra_interfaces=[],
+ requested_presence=(2, 'available', ''),
+ expect_before_connect=[], expect_after_connect=[],
+ has_hidden=False):
+ # Enable the account
+ account.Set(cs.ACCOUNT, 'Enabled', True,
+ dbus_interface=cs.PROPERTIES_IFACE)
+
+ if requested_presence is not None:
+ requested_presence = dbus.Struct(
+ (dbus.UInt32(requested_presence[0]),) +
+ tuple(requested_presence[1:]),
+ signature='uss')
+ account.Set(cs.ACCOUNT,
+ 'RequestedPresence', requested_presence,
+ 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)
+
+ conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol',
+ account.object_path.split('/')[-1],
+ 'myself', has_requests=has_requests, has_presence=has_presence,
+ has_aliasing=has_aliasing, has_avatars=has_avatars,
+ avatars_persist=avatars_persist, extra_interfaces=extra_interfaces,
+ has_hidden=has_hidden)
+
+ q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
+
+ if has_requests:
+ expect_before_connect.append(
+ servicetest.EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE, method='GetAll',
+ args=[cs.CONN_IFACE_REQUESTS],
+ path=conn.object_path, handled=True))
+
+ if expect_before_connect:
+ events = list(q.expect_many(*expect_before_connect))
+ if has_requests:
+ del events[-1]
+ else:
+ events = []
+
+ q.expect('dbus-method-call', method='Connect',
+ path=conn.object_path, handled=True)
+ conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
+
+ expect_after_connect = list(expect_after_connect)
+
+ if not has_requests:
+ expect_after_connect.append(
+ servicetest.EventPattern('dbus-method-call',
+ interface=cs.CONN, method='ListChannels', args=[],
+ path=conn.object_path, handled=True))
+
+ events = events + list(q.expect_many(*expect_after_connect))
+
+ if not has_requests:
+ del events[-1]
+
+ if events:
+ return (conn,) + tuple(events)
+
+ return conn
+
+def expect_client_setup(q, clients, got_interfaces_already=False):
+ patterns = []
+
+ def is_client_setup(e):
+ if e.method == 'Get' and e.args == [cs.CLIENT, 'Interfaces']:
+ return True
+ if e.method == 'GetAll' and e.args == [cs.CLIENT]:
+ return True
+ return False
+
+ def is_approver_setup(e):
+ if e.method == 'Get' and \
+ e.args == [cs.APPROVER, 'ApproverChannelFilter']:
+ return True
+ if e.method == 'GetAll' and e.args == [cs.APPROVER]:
+ return True
+ return False
+
+ def is_observer_setup(e):
+ if e.method == 'Get' and \
+ e.args == [cs.OBSERVER, 'ObserverChannelFilter']:
+ return True
+ if e.method == 'GetAll' and e.args == [cs.OBSERVER]:
+ return True
+ return False
+
+ def is_handler_setup(e):
+ if e.method == 'Get' and \
+ e.args == [cs.HANDLER, 'HandlerChannelFilter']:
+ return True
+ if e.method == 'GetAll' and e.args == [cs.HANDLER]:
+ return True
+ return False
+
+ for client in clients:
+ if not got_interfaces_already:
+ patterns.append(servicetest.EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE,
+ path=client.object_path, handled=True,
+ predicate=is_client_setup))
+
+ if client.observe:
+ patterns.append(servicetest.EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE,
+ path=client.object_path, handled=True,
+ predicate=is_observer_setup))
+
+ if client.approve:
+ patterns.append(servicetest.EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE,
+ path=client.object_path, handled=True,
+ predicate=is_approver_setup))
+
+ if client.handle:
+ patterns.append(servicetest.EventPattern('dbus-method-call',
+ interface=cs.PROPERTIES_IFACE,
+ path=client.object_path, predicate=is_handler_setup))
+
+ q.expect_many(*patterns)
+
+def get_account_manager(bus):
+ return bus.get_object(cs.AM, cs.AM_PATH,
+ follow_name_owner_changes=True)
+
+def connect_to_mc(q, bus, mc):
+ # Get the AccountManager interface
+ account_manager = get_account_manager(bus)
+ account_manager_iface = dbus.Interface(account_manager, cs.AM)
+
+ # Introspect AccountManager for debugging purpose
+ account_manager_introspected = account_manager.Introspect(
+ dbus_interface=cs.INTROSPECTABLE_IFACE)
+ #print account_manager_introspected
+
+ # Check AccountManager has D-Bus property interface
+ properties = account_manager.GetAll(cs.AM,
+ dbus_interface=cs.PROPERTIES_IFACE)
+ assert properties is not None
+ interfaces = properties.get('Interfaces')
+
+ # assert that current functionality exists
+ assert cs.AM_IFACE_NOKIA_QUERY in interfaces, interfaces
+
+ return account_manager, properties, interfaces
+
+def keyfile_read(fname):
+ groups = { None: {} }
+ group = None
+ for line in open(fname):
+ line = line[:-1].decode('utf-8').strip()
+ if not line or line.startswith('#'):
+ continue
+
+ if line.startswith('[') and line.endswith(']'):
+ group = line[1:-1]
+ groups[group] = {}
+ continue
+
+ if '=' in line:
+ k, v = line.split('=', 1)
+ else:
+ k = line
+ v = None
+
+ groups[group][k] = v
+ return groups
+
diff --git a/tests/twisted/servicetest.py b/tests/twisted/servicetest.py
new file mode 100644
index 00000000..700c05ad
--- /dev/null
+++ b/tests/twisted/servicetest.py
@@ -0,0 +1,655 @@
+# 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
+
+"""
+Infrastructure code for testing Mission Control
+"""
+
+from twisted.internet import glib2reactor
+from twisted.internet.protocol import Protocol, Factory, ClientFactory
+glib2reactor.install()
+import sys
+
+import pprint
+import unittest
+
+import dbus
+import dbus.lowlevel
+import dbus.glib
+
+from twisted.internet import reactor
+
+tp_name_prefix = 'org.freedesktop.Telepathy'
+tp_path_prefix = '/org/freedesktop/Telepathy'
+
+class Event:
+ def __init__(self, type, **kw):
+ self.__dict__.update(kw)
+ self.type = type
+
+def format_event(event):
+ ret = ['- type %s' % event.type]
+
+ for key in dir(event):
+ if key != 'type' and not key.startswith('_'):
+ ret.append('- %s: %s' % (
+ key, pprint.pformat(getattr(event, key))))
+
+ if key == 'error':
+ ret.append('%s' % getattr(event, key))
+
+ return ret
+
+class EventPattern:
+ def __init__(self, type, **properties):
+ self.type = type
+ self.predicate = lambda x: True
+ if 'predicate' in properties:
+ self.predicate = properties['predicate']
+ del properties['predicate']
+ self.properties = properties
+
+ def __repr__(self):
+ properties = dict(self.properties)
+
+ if self.predicate:
+ properties['predicate'] = self.predicate
+
+ return '%s(%r, **%r)' % (
+ self.__class__.__name__, self.type, properties)
+
+ def match(self, event):
+ if event.type != self.type:
+ return False
+
+ for key, value in self.properties.iteritems():
+ try:
+ if getattr(event, key) != value:
+ return False
+ except AttributeError:
+ return False
+
+ if self.predicate(event):
+ return True
+
+ return False
+
+
+class TimeoutError(Exception):
+ pass
+
+class BaseEventQueue:
+ """Abstract event queue base class.
+
+ Implement the wait() method to have something that works.
+ """
+
+ def __init__(self, timeout=None):
+ self.verbose = False
+ self.past_events = []
+ self.forbidden_events = set()
+
+ if timeout is None:
+ self.timeout = 5
+ else:
+ self.timeout = timeout
+
+ def log(self, s):
+ if self.verbose:
+ print s
+
+ def log_event(self, event):
+ if self.verbose:
+ self.log('got event:')
+
+ if self.verbose:
+ map(self.log, format_event(event))
+
+ def flush_past_events(self):
+ self.past_events = []
+
+ def expect_racy(self, type, **kw):
+ pattern = EventPattern(type, **kw)
+
+ for event in self.past_events:
+ if pattern.match(event):
+ self.log('past event handled')
+ map(self.log, format_event(event))
+ self.log('')
+ self.past_events.remove(event)
+ return event
+
+ return self.expect(type, **kw)
+
+ def forbid_events(self, patterns):
+ """
+ Add patterns (an iterable of EventPattern) to the set of forbidden
+ events. If a forbidden event occurs during an expect or expect_many,
+ the test will fail.
+ """
+ self.forbidden_events.update(set(patterns))
+
+ def unforbid_events(self, patterns):
+ """
+ Remove 'patterns' (an iterable of EventPattern) from the set of
+ forbidden events. These must be the same EventPattern pointers that
+ were passed to forbid_events.
+ """
+ self.forbidden_events.difference_update(set(patterns))
+
+ def _check_forbidden(self, event):
+ for e in self.forbidden_events:
+ if e.match(event):
+ print "forbidden event occurred:"
+ for x in format_event(event):
+ print x
+ assert False
+
+ def expect(self, type, **kw):
+ pattern = EventPattern(type, **kw)
+
+ while True:
+ event = self.wait()
+ self.log_event(event)
+ self._check_forbidden(event)
+
+ if pattern.match(event):
+ self.log('handled')
+ self.log('')
+ return event
+
+ self.past_events.append(event)
+ self.log('not handled')
+ self.log('')
+
+ def expect_many(self, *patterns):
+ ret = [None] * len(patterns)
+
+ while None in ret:
+ try:
+ event = self.wait()
+ except TimeoutError:
+ self.log('timeout')
+ self.log('still expecting:')
+ for i, pattern in enumerate(patterns):
+ if ret[i] is None:
+ self.log(' - %r' % pattern)
+ raise
+ self.log_event(event)
+ self._check_forbidden(event)
+
+ for i, pattern in enumerate(patterns):
+ if ret[i] is None and pattern.match(event):
+ self.log('handled')
+ self.log('')
+ ret[i] = event
+ break
+ else:
+ self.past_events.append(event)
+ self.log('not handled')
+ self.log('')
+
+ return ret
+
+ def demand(self, type, **kw):
+ pattern = EventPattern(type, **kw)
+
+ event = self.wait()
+ self.log_event(event)
+
+ if pattern.match(event):
+ self.log('handled')
+ self.log('')
+ return event
+
+ self.log('not handled')
+ raise RuntimeError('expected %r, got %r' % (pattern, event))
+
+class IteratingEventQueue(BaseEventQueue):
+ """Event queue that works by iterating the Twisted reactor."""
+
+ def __init__(self, timeout=None):
+ BaseEventQueue.__init__(self, timeout)
+ self.events = []
+ self._dbus_method_impls = []
+ self._buses = []
+ # a message filter which will claim we handled everything
+ self._dbus_dev_null = \
+ lambda bus, message: dbus.lowlevel.HANDLER_RESULT_HANDLED
+
+ def wait(self):
+ stop = [False]
+
+ def later():
+ stop[0] = True
+
+ delayed_call = reactor.callLater(self.timeout, later)
+
+ while (not self.events) and (not stop[0]):
+ reactor.iterate(0.1)
+
+ if self.events:
+ delayed_call.cancel()
+ return self.events.pop(0)
+ else:
+ raise TimeoutError
+
+ def append(self, event):
+ self.events.append(event)
+
+ # compatibility
+ handle_event = append
+
+ def add_dbus_method_impl(self, cb, bus=None, **kwargs):
+ if bus is None:
+ bus = self._buses[0]
+
+ self._dbus_method_impls.append(
+ (EventPattern('dbus-method-call', **kwargs), cb))
+
+ def dbus_emit(self, path, iface, name, *a, **k):
+ bus = k.pop('bus', self._buses[0])
+ assert 'signature' in k, k
+ message = dbus.lowlevel.SignalMessage(path, iface, name)
+ message.append(*a, **k)
+ bus.send_message(message)
+
+ def dbus_return(self, in_reply_to, *a, **k):
+ bus = k.pop('bus', self._buses[0])
+ assert 'signature' in k, k
+ reply = dbus.lowlevel.MethodReturnMessage(in_reply_to)
+ reply.append(*a, **k)
+ bus.send_message(reply)
+
+ def dbus_raise(self, in_reply_to, name, message=None, bus=None):
+ if bus is None:
+ bus = self._buses[0]
+
+ reply = dbus.lowlevel.ErrorMessage(in_reply_to, name, message)
+ bus.send_message(reply)
+
+ def attach_to_bus(self, bus):
+ if not self._buses:
+ # first-time setup
+ self._dbus_filter_bound_method = self._dbus_filter
+
+ self._buses.append(bus)
+
+ # Only subscribe to messages on the first bus connection (assumed to
+ # be the shared session bus connection used by the simulated connection
+ # manager and most of the test suite), not on subsequent bus
+ # connections (assumed to represent extra clients).
+ #
+ # When we receive a method call on the other bus connections, ignore
+ # it - the eavesdropping filter installed on the first bus connection
+ # will see it too.
+ #
+ # This is highly counter-intuitive, but it means our messages are in
+ # a guaranteed order (we don't have races between messages arriving on
+ # various connections).
+ if len(self._buses) > 1:
+ bus.add_message_filter(self._dbus_dev_null)
+ return
+
+ bus.add_match_string("") # eavesdrop, like dbus-monitor does
+
+ bus.add_message_filter(self._dbus_filter_bound_method)
+
+ bus.add_signal_receiver(
+ lambda *args, **kw:
+ self.append(
+ Event('dbus-signal',
+ path=unwrap(kw['path']),
+ signal=kw['member'],
+ args=map(unwrap, args),
+ interface=kw['interface'])),
+ None,
+ None,
+ None,
+ path_keyword='path',
+ member_keyword='member',
+ interface_keyword='interface',
+ byte_arrays=True,
+ )
+
+ def cleanup(self):
+ if self._buses:
+ self._buses[0].remove_message_filter(self._dbus_filter_bound_method)
+ for bus in self._buses[1:]:
+ bus.remove_message_filter(self._dbus_dev_null)
+
+ self._buses = []
+ self._dbus_method_impls = []
+
+ def _dbus_filter(self, bus, message):
+ if isinstance(message, dbus.lowlevel.MethodCallMessage):
+
+ destination = message.get_destination()
+ sender = message.get_sender()
+
+ if (destination == 'org.freedesktop.DBus' or
+ sender == self._buses[0].get_unique_name()):
+ # suppress reply and don't make an Event
+ return dbus.lowlevel.HANDLER_RESULT_HANDLED
+
+ e = Event('dbus-method-call', message=message,
+ interface=message.get_interface(), path=message.get_path(),
+ args=map(unwrap, message.get_args_list(byte_arrays=True)),
+ destination=str(destination),
+ method=message.get_member(),
+ sender=message.get_sender(),
+ handled=False)
+
+ for pair in self._dbus_method_impls:
+ pattern, cb = pair
+ if pattern.match(e):
+ cb(e)
+ e.handled = True
+ break
+
+ self.append(e)
+
+ return dbus.lowlevel.HANDLER_RESULT_HANDLED
+
+ return dbus.lowlevel.HANDLER_RESULT_NOT_YET_HANDLED
+
+class TestEventQueue(BaseEventQueue):
+ def __init__(self, events):
+ BaseEventQueue.__init__(self)
+ self.events = events
+
+ def wait(self):
+ if self.events:
+ return self.events.pop(0)
+ else:
+ raise TimeoutError
+
+class EventQueueTest(unittest.TestCase):
+ def test_expect(self):
+ queue = TestEventQueue([Event('foo'), Event('bar')])
+ assert queue.expect('foo').type == 'foo'
+ assert queue.expect('bar').type == 'bar'
+
+ def test_expect_many(self):
+ queue = TestEventQueue([Event('foo'), Event('bar')])
+ bar, foo = queue.expect_many(
+ EventPattern('bar'),
+ EventPattern('foo'))
+ assert bar.type == 'bar'
+ assert foo.type == 'foo'
+
+ def test_expect_many2(self):
+ # Test that events are only matched against patterns that haven't yet
+ # been matched. This tests a regression.
+ queue = TestEventQueue([Event('foo', x=1), Event('foo', x=2)])
+ foo1, foo2 = queue.expect_many(
+ EventPattern('foo'),
+ EventPattern('foo'))
+ assert foo1.type == 'foo' and foo1.x == 1
+ assert foo2.type == 'foo' and foo2.x == 2
+
+ def test_timeout(self):
+ queue = TestEventQueue([])
+ self.assertRaises(TimeoutError, queue.expect, 'foo')
+
+ def test_demand(self):
+ queue = TestEventQueue([Event('foo'), Event('bar')])
+ foo = queue.demand('foo')
+ assert foo.type == 'foo'
+
+ def test_demand_fail(self):
+ queue = TestEventQueue([Event('foo'), Event('bar')])
+ self.assertRaises(RuntimeError, queue.demand, 'bar')
+
+def unwrap(x):
+ """Hack to unwrap D-Bus values, so that they're easier to read when
+ printed."""
+
+ if isinstance(x, list):
+ return map(unwrap, x)
+
+ if isinstance(x, tuple):
+ return tuple(map(unwrap, x))
+
+ if isinstance(x, dict):
+ return dict([(unwrap(k), unwrap(v)) for k, v in x.iteritems()])
+
+ if isinstance(x, dbus.Boolean):
+ return bool(x)
+
+ for t in [unicode, str, long, int, float]:
+ if isinstance(x, t):
+ return t(x)
+
+ return x
+
+def call_async(test, proxy, method, *args, **kw):
+ """Call a D-Bus method asynchronously and generate an event for the
+ resulting method return/error."""
+
+ def reply_func(*ret):
+ test.handle_event(Event('dbus-return', method=method,
+ value=unwrap(ret)))
+
+ def error_func(err):
+ test.handle_event(Event('dbus-error', method=method, error=err,
+ name=err.get_dbus_name(), message=str(err)))
+
+ method_proxy = getattr(proxy, method)
+ kw.update({'reply_handler': reply_func, 'error_handler': error_func})
+ method_proxy(*args, **kw)
+
+def sync_dbus(bus, q, proxy):
+ # Dummy D-Bus method call
+ call_async(q, dbus.Interface(proxy, dbus.PEER_IFACE), "Ping")
+ event = q.expect('dbus-return', method='Ping')
+
+class ProxyWrapper:
+ def __init__(self, object, default, others):
+ self.object = object
+ self.default_interface = dbus.Interface(object, default)
+ self.Properties = dbus.Interface(object, dbus.PROPERTIES_IFACE)
+ self.TpProperties = \
+ dbus.Interface(object, tp_name_prefix + '.Properties')
+ self.interfaces = dict([
+ (name, dbus.Interface(object, iface))
+ for name, iface in others.iteritems()])
+
+ def __getattr__(self, name):
+ if name in self.interfaces:
+ return self.interfaces[name]
+
+ if name in self.object.__dict__:
+ return getattr(self.object, name)
+
+ return getattr(self.default_interface, name)
+
+def make_mc(bus, event_func, params):
+ mc = bus.get_object(
+ tp_name_prefix + '.MissionControl5',
+ tp_path_prefix + '/MissionControl5',
+ follow_name_owner_changes=True)
+ assert mc is not None
+
+ return mc
+
+
+
+def wrap_channel(chan, type_, extra=None):
+ interfaces = {
+ type_: tp_name_prefix + '.Channel.Type.' + type_,
+ 'Group': tp_name_prefix + '.Channel.Interface.Group',
+ }
+
+ if extra:
+ interfaces.update(dict([
+ (name, tp_name_prefix + '.Channel.Interface.' + name)
+ for name in extra]))
+
+ return ProxyWrapper(chan, tp_name_prefix + '.Channel', interfaces)
+
+def make_connection(bus, event_func, name, proto, params):
+ cm = bus.get_object(
+ tp_name_prefix + '.ConnectionManager.%s' % name,
+ tp_path_prefix + '/ConnectionManager/%s' % name)
+ cm_iface = dbus.Interface(cm, tp_name_prefix + '.ConnectionManager')
+
+ connection_name, connection_path = cm_iface.RequestConnection(
+ proto, params)
+ conn = wrap_connection(bus.get_object(connection_name, connection_path))
+
+ return conn
+
+def make_channel_proxy(conn, path, iface):
+ bus = dbus.SessionBus()
+ chan = bus.get_object(conn.object.bus_name, path)
+ chan = dbus.Interface(chan, tp_name_prefix + '.' + iface)
+ return chan
+
+# block_reading can be used if the test want to choose when we start to read
+# data from the socket.
+class EventProtocol(Protocol):
+ def __init__(self, queue=None, block_reading=False):
+ self.queue = queue
+ self.block_reading = block_reading
+
+ def dataReceived(self, data):
+ if self.queue is not None:
+ self.queue.handle_event(Event('socket-data', protocol=self,
+ data=data))
+
+ def sendData(self, data):
+ self.transport.write(data)
+
+ def connectionMade(self):
+ if self.block_reading:
+ self.transport.stopReading()
+
+ def connectionLost(self, reason=None):
+ if self.queue is not None:
+ self.queue.handle_event(Event('socket-disconnected', protocol=self))
+
+class EventProtocolFactory(Factory):
+ def __init__(self, queue, block_reading=False):
+ self.queue = queue
+ self.block_reading = block_reading
+
+ def _create_protocol(self):
+ return EventProtocol(self.queue, self.block_reading)
+
+ def buildProtocol(self, addr):
+ proto = self._create_protocol()
+ self.queue.handle_event(Event('socket-connected', protocol=proto))
+ return proto
+
+class EventProtocolClientFactory(EventProtocolFactory, ClientFactory):
+ pass
+
+def watch_tube_signals(q, tube):
+ def got_signal_cb(*args, **kwargs):
+ q.handle_event(Event('tube-signal',
+ path=kwargs['path'],
+ signal=kwargs['member'],
+ args=map(unwrap, args),
+ tube=tube))
+
+ tube.add_signal_receiver(got_signal_cb,
+ path_keyword='path', member_keyword='member',
+ byte_arrays=True)
+
+def pretty(x):
+ return pprint.pformat(unwrap(x))
+
+def assertEquals(expected, value):
+ if expected != value:
+ raise AssertionError(
+ "expected:\n%s\ngot:\n%s" % (pretty(expected), pretty(value)))
+
+def assertNotEquals(expected, value):
+ if expected == value:
+ raise AssertionError(
+ "expected something other than:\n%s" % pretty(value))
+
+def assertContains(element, value):
+ if element not in value:
+ raise AssertionError(
+ "expected:\n%s\nin:\n%s" % (pretty(element), pretty(value)))
+
+def assertDoesNotContain(element, value):
+ if element in value:
+ raise AssertionError(
+ "expected:\n%s\nnot in:\n%s" % (pretty(element), pretty(value)))
+
+def assertLength(length, value):
+ if len(value) != length:
+ raise AssertionError("expected: length %d, got length %d:\n%s" % (
+ length, len(value), pretty(value)))
+
+def assertFlagsSet(flags, value):
+ masked = value & flags
+ if masked != flags:
+ raise AssertionError(
+ "expected flags %u, of which only %u are set in %u" % (
+ flags, masked, value))
+
+def assertFlagsUnset(flags, value):
+ masked = value & flags
+ if masked != 0:
+ raise AssertionError(
+ "expected none of flags %u, but %u are set in %u" % (
+ flags, masked, value))
+
+def assertSameSets(expected, value):
+ exp_set = set(expected)
+ val_set = set(value)
+
+ if exp_set != val_set:
+ raise AssertionError(
+ "expected contents:\n%s\ngot:\n%s" % (
+ pretty(exp_set), pretty(val_set)))
+
+
+def install_colourer():
+ def red(s):
+ return '\x1b[31m%s\x1b[0m' % s
+
+ def green(s):
+ return '\x1b[32m%s\x1b[0m' % s
+
+ patterns = {
+ 'handled': green,
+ 'not handled': red,
+ }
+
+ class Colourer:
+ def __init__(self, fh, patterns):
+ self.fh = fh
+ self.patterns = patterns
+
+ def write(self, s):
+ f = self.patterns.get(s, lambda x: x)
+ self.fh.write(f(s))
+
+ sys.stdout = Colourer(sys.stdout, patterns)
+ return sys.stdout
+
+
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/tests/twisted/telepathy/clients/AbiWord.client b/tests/twisted/telepathy/clients/AbiWord.client
new file mode 100644
index 00000000..2ec84949
--- /dev/null
+++ b/tests/twisted/telepathy/clients/AbiWord.client
@@ -0,0 +1,16 @@
+[org.freedesktop.Telepathy.Client]
+Interfaces=org.freedesktop.Telepathy.Client.Handler
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 0]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.StreamTube
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+org.freedesktop.Telepathy.Channel.Type.StreamTube.Service s=x-abiword
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 1]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.StreamTube
+org.freedesktop.Telepathy.Channel.TargetHandleType u=2
+org.freedesktop.Telepathy.Channel.Type.StreamTube.Service s=x-abiword
+
+[org.freedesktop.Telepathy.Client.Handler.Capabilities]
+com.example.Foo=true
+com.example.Bar=true
diff --git a/tests/twisted/telepathy/clients/Logger.client b/tests/twisted/telepathy/clients/Logger.client
new file mode 100644
index 00000000..b46d0d51
--- /dev/null
+++ b/tests/twisted/telepathy/clients/Logger.client
@@ -0,0 +1,10 @@
+[org.freedesktop.Telepathy.Client]
+Interfaces=org.freedesktop.Telepathy.Client.Observer
+
+[org.freedesktop.Telepathy.Client.Observer]
+Recover=true
+
+[org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter 0]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.RespawnObservers
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+
diff --git a/tests/twisted/telepathy/clients/README b/tests/twisted/telepathy/clients/README
new file mode 100644
index 00000000..7b725bf9
--- /dev/null
+++ b/tests/twisted/telepathy/clients/README
@@ -0,0 +1,4 @@
+This directory contains .clients files used by Mission Control when it is run
+by twisted test. $MC_CLIENTS_DIR is set to this directory in the twisted
+environment.
+
diff --git a/tests/twisted/telepathy/managers/README b/tests/twisted/telepathy/managers/README
new file mode 100644
index 00000000..d77437cb
--- /dev/null
+++ b/tests/twisted/telepathy/managers/README
@@ -0,0 +1,4 @@
+This directory contains .manager files used by Mission Control when it is run
+by twisted test. $MC_MANAGER_DIR is set to this directory in the twisted
+environment.
+
diff --git a/tests/twisted/telepathy/managers/fakecm.manager b/tests/twisted/telepathy/managers/fakecm.manager
new file mode 100644
index 00000000..23a074e5
--- /dev/null
+++ b/tests/twisted/telepathy/managers/fakecm.manager
@@ -0,0 +1,15 @@
+[ConnectionManager]
+BusName=com.example.FakeCM
+ObjectPath=/com/example/FakeCM/ConnectionManager
+
+[Protocol fakeprotocol]
+param-account=s register required
+param-password=s register required secret
+param-nickname=s register
+param-register=b
+param-com.example.Badgerable.Badgered=b dbus-property
+param-secret-mushroom=s
+param-snakes=u
+default-register=0
+default-com.example.Badgerable.Badgered=0
+default-snakes=1
diff --git a/tests/twisted/telepathy/managers/onewitheverything.manager b/tests/twisted/telepathy/managers/onewitheverything.manager
new file mode 100644
index 00000000..222daddb
--- /dev/null
+++ b/tests/twisted/telepathy/managers/onewitheverything.manager
@@ -0,0 +1,48 @@
+[Protocol serializable]
+# All the types for which telepathy-spec defines a serialization
+param-s=s required
+param-o=o
+param-b=b
+param-q=q
+param-u=u
+param-t=t
+param-n=n
+param-i=i
+param-x=x
+param-d=d
+param-as=as
+param-y=y
+
+[Protocol defaults]
+# All the types, plus a default for each
+param-s=s
+default-s=foo
+param-o=o
+default-o=/foo
+param-b=b
+default-b=true
+param-q=q
+default-q=1
+param-u=u
+default-u=1
+param-t=t
+default-t=1
+param-n=n
+default-n=-1
+param-i=i
+default-i=-1
+param-x=x
+default-x=-1
+param-d=d
+default-d=1.5
+param-as=as
+default-as=foo;bar;baz;
+param-y=y
+default-y=1
+
+[Protocol flags]
+# All the flags
+param-account=s required register
+param-name=s register
+param-key=s required register secret
+param-com.example.Badgerable.Badgers=s dbus-property
diff --git a/tests/twisted/test-plugin.c b/tests/twisted/test-plugin.c
new file mode 100644
index 00000000..89171a32
--- /dev/null
+++ b/tests/twisted/test-plugin.c
@@ -0,0 +1,227 @@
+/*
+ * A demonstration plugin that acts as a channel filter.
+ *
+ * Copyright (C) 2008-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
+ */
+
+#include "mcd-dispatcher-context.h"
+#include "mcd-plugin.h"
+#include "mcd-debug.h"
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+/* forward declaration to keep the compiler happy */
+void mcd_plugin_init (McdPlugin *);
+
+static void
+reject_rickrolling (McdDispatcherContext *ctx,
+ gpointer user_data)
+{
+ McdChannel *channel = mcd_dispatcher_context_get_channel (ctx);
+ const gchar *inviter = mcd_channel_get_inviter (channel);
+ GQuark channel_type = mcd_channel_get_channel_type_quark (channel);
+ const gchar *object_path = mcd_channel_get_object_path (channel);
+
+ DEBUG ("called");
+
+ /* we don't actually use the user_data here, so just assert that it's
+ * passed to the callback correctly */
+ g_assert (!tp_strdiff (user_data, "Never gonna give you up"));
+
+ /* the McdChannel had better have a TpChannel, otherwise something is badly
+ * wrong */
+ g_assert (channel_type != 0);
+ g_assert (object_path != NULL);
+
+ if (!tp_strdiff (inviter, "rick.astley@example.com")
+ && (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA ||
+ channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_TEXT))
+ {
+ DEBUG ("rickrolling detected, closing channel %s", object_path);
+ mcd_dispatcher_context_destroy_all (ctx);
+ }
+
+ mcd_dispatcher_context_proceed (ctx);
+}
+
+static void
+reject_with_reason (McdDispatcherContext *ctx,
+ gpointer user_data)
+{
+ McdChannel *channel = mcd_dispatcher_context_get_channel (ctx);
+ const gchar *inviter = mcd_channel_get_inviter (channel);
+ GQuark channel_type = mcd_channel_get_channel_type_quark (channel);
+ const gchar *object_path = mcd_channel_get_object_path (channel);
+
+ DEBUG ("called");
+
+ /* we don't actually use the user_data here, so just assert that it's
+ * passed to the callback correctly */
+ g_assert (!tp_strdiff (user_data, "Can't touch this"));
+
+ /* the McdChannel had better have a TpChannel, otherwise something is badly
+ * wrong */
+ g_assert (channel_type != 0);
+ g_assert (object_path != NULL);
+
+ if (!tp_strdiff (inviter, "hammertime@example.com")
+ && (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA ||
+ channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_TEXT))
+ {
+ DEBUG ("MC Hammer detected, closing channel %s", object_path);
+ mcd_dispatcher_context_close_all (ctx,
+ TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED,
+ "Can't touch this");
+ }
+
+ mcd_dispatcher_context_proceed (ctx);
+}
+
+/* An older API for terminating unwanted channels */
+static void
+reject_mc_hammer (McdDispatcherContext *ctx,
+ gpointer user_data)
+{
+ McdChannel *channel = mcd_dispatcher_context_get_channel (ctx);
+ const gchar *inviter = mcd_channel_get_inviter (channel);
+ GQuark channel_type = mcd_channel_get_channel_type_quark (channel);
+ const gchar *object_path = mcd_channel_get_object_path (channel);
+
+ DEBUG ("called");
+
+ /* we don't actually use the user_data here, so just assert that it's
+ * passed to the callback correctly */
+ g_assert (!tp_strdiff (user_data, "Stop! Hammer time"));
+
+ /* the McdChannel had better have a TpChannel, otherwise something is badly
+ * wrong */
+ g_assert (channel_type != 0);
+ g_assert (object_path != NULL);
+
+ if (!tp_strdiff (inviter, "mc.hammer@example.com")
+ && (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA ||
+ channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_TEXT))
+ {
+ DEBUG ("MC Hammer detected, closing channel %s", object_path);
+ mcd_dispatcher_context_process (ctx, FALSE);
+ return;
+ }
+
+ mcd_dispatcher_context_process (ctx, TRUE);
+}
+
+static void
+permission_cb (DBusPendingCall *pc,
+ gpointer data)
+{
+ McdDispatcherContext *ctx = data;
+ DBusMessage *message = dbus_pending_call_steal_reply (pc);
+
+ if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR)
+ {
+ DEBUG ("Permission denied for %p", ctx);
+ mcd_dispatcher_context_close_all (ctx,
+ TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED,
+ "Computer says no");
+ }
+ else
+ {
+ DEBUG ("Permission granted for %p", ctx);
+ }
+
+ dbus_message_unref (message);
+ dbus_pending_call_unref (pc);
+}
+
+static void
+ask_for_permission (McdDispatcherContext *ctx, gpointer user_data)
+{
+ McdChannel *channel = mcd_dispatcher_context_get_channel (ctx);
+
+ DEBUG ("%p", ctx);
+
+ if (!tp_strdiff (mcd_channel_get_name (channel), "policy@example.com"))
+ {
+ TpDBusDaemon *dbus_daemon = tp_dbus_daemon_dup (NULL);
+ DBusGConnection *gconn = tp_proxy_get_dbus_connection (dbus_daemon);
+ DBusConnection *libdbus = dbus_g_connection_get_connection (gconn);
+ DBusPendingCall *pc = NULL;
+ DBusMessage *message;
+
+ /* in a real policy-mechanism you'd give some details, like the
+ * channel's properties or object path */
+ message = dbus_message_new_method_call ("com.example.Policy",
+ "/com/example/Policy", "com.example.Policy", "RequestPermission");
+
+ if (!dbus_connection_send_with_reply (libdbus, message,
+ &pc, -1))
+ g_error ("out of memory");
+
+ dbus_message_unref (message);
+
+ if (pc == NULL)
+ {
+ DEBUG ("got disconnected from D-Bus...");
+ goto proceed;
+ }
+
+ /* pc is unreffed by permission_cb */
+
+ DEBUG ("Waiting for permission for %p", ctx);
+
+ if (dbus_pending_call_get_completed (pc))
+ {
+ permission_cb (pc, ctx);
+ goto proceed;
+ }
+
+ if (!dbus_pending_call_set_notify (pc, permission_cb, ctx,
+ (DBusFreeFunction) mcd_dispatcher_context_proceed))
+ g_error ("Out of memory");
+
+ return;
+ }
+
+proceed:
+ mcd_dispatcher_context_proceed (ctx);
+}
+
+static const McdFilter my_filters[] = {
+ { reject_rickrolling, MCD_FILTER_PRIORITY_CRITICAL,
+ "Never gonna give you up" },
+ { reject_with_reason, MCD_FILTER_PRIORITY_CRITICAL,
+ "Can't touch this" },
+ { reject_mc_hammer, MCD_FILTER_PRIORITY_CRITICAL,
+ "Stop! Hammer time" },
+ { ask_for_permission, MCD_FILTER_PRIORITY_SYSTEM, "May I?" },
+ { NULL }
+};
+
+void
+mcd_plugin_init (McdPlugin *plugin)
+{
+ McdDispatcher *dispatcher = mcd_plugin_get_dispatcher (plugin);
+
+ DEBUG ("Initializing test-plugin");
+
+ mcd_dispatcher_add_filters (dispatcher, my_filters);
+}
diff --git a/tests/twisted/tools/Client.AbiWord.service.in b/tests/twisted/tools/Client.AbiWord.service.in
new file mode 100644
index 00000000..300bd5b0
--- /dev/null
+++ b/tests/twisted/tools/Client.AbiWord.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.Client.AbiWord
+Exec=/bin/sh @abs_top_srcdir@/tests/twisted/tools/fake-startup.sh org.freedesktop.Telepathy.Client.AbiWord
diff --git a/tests/twisted/tools/Client.Logger.service.in b/tests/twisted/tools/Client.Logger.service.in
new file mode 100644
index 00000000..9fd4bfb1
--- /dev/null
+++ b/tests/twisted/tools/Client.Logger.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.Client.Logger
+Exec=/bin/sh @abs_top_srcdir@/tests/twisted/tools/fake-startup.sh org.freedesktop.Telepathy.Client.Logger
diff --git a/tests/twisted/tools/Makefile.am b/tests/twisted/tools/Makefile.am
new file mode 100644
index 00000000..57ba5045
--- /dev/null
+++ b/tests/twisted/tools/Makefile.am
@@ -0,0 +1,40 @@
+exec-with-log.sh: exec-with-log.sh.in Makefile
+ sed -e "s|[@]abs_top_srcdir[@]|@abs_top_srcdir@|g" \
+ -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" \
+ $< > $@
+ chmod +x $@
+
+%.conf: %.conf.in
+ sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
+
+# We don't use the full filename for the .in because > 99 character filenames
+# in tarballs are non-portable (and automake 1.8 doesn't let us build
+# non-archaic tarballs)
+org.freedesktop.Telepathy.%.service: %.service.in
+ sed \
+ -e "s|[@]abs_top_srcdir[@]|@abs_top_srcdir@|g" \
+ -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" \
+ $< > $@
+
+# D-Bus service file for testing
+service_in_files = MissionControl5.service.in Client.AbiWord.service.in Client.Logger.service.in
+service_files = $(patsubst %.in,org.freedesktop.Telepathy.%, $(service_in_files))
+
+# D-Bus config file for testing
+conf_in_files = tmp-session-bus.conf.in
+conf_files = $(conf_in_files:.conf.in=.conf)
+
+BUILT_SOURCES = $(service_files) $(conf_files) exec-with-log.sh
+
+EXTRA_DIST = \
+ $(service_in_files) \
+ $(conf_in_files) \
+ exec-with-log.sh.in \
+ fake-startup.sh \
+ valgrind.supp \
+ with-session-bus.sh
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ missioncontrol.log \
+ missioncontrol-*.log
diff --git a/tests/twisted/tools/MissionControl5.service.in b/tests/twisted/tools/MissionControl5.service.in
new file mode 100644
index 00000000..e8f41c41
--- /dev/null
+++ b/tests/twisted/tools/MissionControl5.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.MissionControl5
+Exec=@abs_top_builddir@/tests/twisted/tools/exec-with-log.sh
diff --git a/tests/twisted/tools/exec-with-log.sh.in b/tests/twisted/tools/exec-with-log.sh.in
new file mode 100644
index 00000000..0b40a24b
--- /dev/null
+++ b/tests/twisted/tools/exec-with-log.sh.in
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+# 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
+
+cd "@abs_top_builddir@/tests/twisted/tools"
+
+ulimit -c unlimited
+exec > missioncontrol-$$.log 2>&1
+ln -f missioncontrol-$$.log missioncontrol.log
+
+if test -z "$MC_ACCOUNT_DIR"; then
+ echo "MC_ACCOUNT_DIR must be set"
+ exit 1
+fi
+
+if test -n "$MISSIONCONTROL_TEST_VALGRIND"; then
+ G_DEBUG="$G_DEBUG,gc-friendly"
+ export G_DEBUG
+ G_SLICE=always-malloc
+ export G_SLICE
+ MISSIONCONTROL_WRAPPER="valgrind --leak-check=full --num-callers=20"
+ MISSIONCONTROL_WRAPPER="$MISSIONCONTROL_WRAPPER --track-origins=yes"
+ MISSIONCONTROL_WRAPPER="$MISSIONCONTROL_WRAPPER -v"
+ MISSIONCONTROL_WRAPPER="$MISSIONCONTROL_WRAPPER --suppressions=@abs_top_srcdir@/tests/twisted/tools/valgrind.supp"
+ MISSIONCONTROL_WRAPPER="$MISSIONCONTROL_WRAPPER --gen-suppressions=all"
+ MISSIONCONTROL_WRAPPER="$MISSIONCONTROL_WRAPPER --show-reachable=yes"
+elif test -n "$MISSIONCONTROL_TEST_REFDBG"; then
+ if test -z "$REFDBG_OPTIONS" ; then
+ REFDBG_OPTIONS="btnum=10"
+ export REFDBG_OPTIONS
+ fi
+ if test -z "$MISSIONCONTROL_WRAPPER" ; then
+ MISSIONCONTROL_WRAPPER="refdbg"
+ fi
+fi
+
+if test "z$MC_EXECUTABLE" = z; then
+ MC_EXECUTABLE=@abs_top_builddir@/tests/twisted/mc-debug-server
+fi
+
+if test -f "$MC_ACCOUNT_DIR/gnome-keyring-env"; then
+ cat "$MC_ACCOUNT_DIR/gnome-keyring-env"
+ . "$MC_ACCOUNT_DIR/gnome-keyring-env"
+ export GNOME_KEYRING_CONTROL GNOME_KEYRING_PID MC_KEYRING_NAME SSH_AUTH_SOCK
+fi
+
+exec @abs_top_builddir@/libtool --mode=execute \
+ $MISSIONCONTROL_WRAPPER \
+ $MC_EXECUTABLE
diff --git a/tests/twisted/tools/fake-startup.sh b/tests/twisted/tools/fake-startup.sh
new file mode 100644
index 00000000..d571c015
--- /dev/null
+++ b/tests/twisted/tools/fake-startup.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+dbus-send --session --type=signal --print-reply \
+/org/freedesktop/Telepathy/RegressionTests \
+org.freedesktop.Telepathy.RegressionTests.FakeStartup \
+string:"$1"
diff --git a/tests/twisted/tools/tmp-session-bus.conf.in b/tests/twisted/tools/tmp-session-bus.conf.in
new file mode 100644
index 00000000..84d8d656
--- /dev/null
+++ b/tests/twisted/tools/tmp-session-bus.conf.in
@@ -0,0 +1,30 @@
+<!-- This configuration file controls the per-user-login-session message bus.
+ Add a session-local.conf and edit that rather than changing this
+ file directly. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <!-- Our well-known bus type, don't change this -->
+ <type>session</type>
+
+ <listen>unix:tmpdir=/tmp</listen>
+
+ <servicedir>@abs_top_builddir@/tests/twisted/tools</servicedir>
+
+ <policy context="default">
+ <!-- Allow everything to be sent -->
+ <allow send_destination="*" eavesdrop="true"/>
+ <!-- Allow everything to be received -->
+ <allow eavesdrop="true"/>
+ <!-- Allow anyone to own anything -->
+ <allow own="*"/>
+ </policy>
+
+ <!-- This is included last so local configuration can override what's
+ in this standard file -->
+
+
+
+
+</busconfig>
diff --git a/tests/twisted/tools/valgrind.supp b/tests/twisted/tools/valgrind.supp
new file mode 100644
index 00000000..1a87dd31
--- /dev/null
+++ b/tests/twisted/tools/valgrind.supp
@@ -0,0 +1,809 @@
+# Valgrind error suppression file
+{
+ dbus-glib, https://bugs.freedesktop.org/show_bug.cgi?id=14125
+ Memcheck:Addr4
+ fun:g_hash_table_foreach
+ obj:/usr/lib/libdbus-glib-1.so.2.1.0
+ fun:g_object_run_dispose
+}
+
+{
+ libdbus shared connection via tp_get_bus (1)
+ Memcheck:Leak
+ ...
+ fun:dbus_malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (1)
+ Memcheck:Leak
+ ...
+ fun:dbus_malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (2)
+ Memcheck:Leak
+ ...
+ fun:dbus_malloc0
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (2)
+ Memcheck:Leak
+ ...
+ fun:dbus_malloc0
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (3)
+ Memcheck:Leak
+ ...
+ fun:dbus_realloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (3)
+ Memcheck:Leak
+ ...
+ fun:dbus_realloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (4)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (4)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (5)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (5)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (6)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (6)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (7)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (7)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (8)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (8)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (9)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (9)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (10)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (10)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (11)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (11)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (12)
+ Memcheck:Leak
+ fun:realloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (12)
+ Memcheck:Leak
+ fun:realloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (13. sigh.)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (13. sigh.)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (14)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (14)
+ Memcheck:Leak
+ fun:malloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ libdbus shared connection via tp_get_bus (15)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_get_bus
+}
+
+{
+ libdbus shared connection via tp_dbus_daemon_dup (15)
+ Memcheck:Leak
+ fun:calloc
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ obj:*/libdbus-1.so.*
+ fun:dbus_g_bus_get
+ fun:tp_dbus_daemon_dup
+}
+
+{
+ anything called by g_type_init_with_debug_flags
+ Memcheck:Leak
+ ...
+ fun:g_type_init_with_debug_flags
+}
+
+{
+ static GType registration
+ Memcheck:Leak
+ ...
+ fun:g_realloc
+ obj:/usr/lib/libgobject-2.0.so.*
+ obj:/usr/lib/libgobject-2.0.so.*
+ fun:g_type_register_static
+}
+
+{
+ g_slice_init_nomessage (1)
+ Memcheck:Leak
+ fun:calloc
+ fun:g_malloc0
+ obj:/usr/lib/libglib-2.0.so.*
+ fun:g_slice_alloc
+}
+
+{
+ creating param specs in tp_proxy_class_intern_init
+ Memcheck:Leak
+ fun:memalign
+ fun:posix_memalign
+ fun:slab_allocator_alloc_chunk
+ fun:g_slice_alloc
+ fun:g_slice_alloc0
+ fun:g_type_create_instance
+ fun:g_param_spec_internal
+ fun:g_param_spec_string
+}
+
+{
+ shared global default g_main_context (1)
+ Memcheck:Leak
+ fun:calloc
+ fun:g_malloc0
+ fun:g_main_context_new
+ fun:g_main_context_default
+}
+
+{
+ shared global default g_main_context (2)
+ Memcheck:Leak
+ fun:memalign
+ fun:posix_memalign
+ fun:slab_allocator_alloc_chunk
+ fun:g_slice_alloc
+ fun:g_ptr_array_sized_new
+ fun:g_ptr_array_new
+ fun:g_main_context_new
+ fun:g_main_context_default
+}
+
+{
+ shared global default g_main_context (2) (with G_SLICE_DEBUG=always-malloc)
+ Memcheck:Leak
+ fun:malloc
+ fun:g_malloc
+ fun:g_slice_alloc
+ fun:g_ptr_array_sized_new
+ fun:g_ptr_array_new
+ fun:g_main_context_new
+ fun:g_main_context_default
+}
+
+{
+ GQuarks can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_quark_from_static_string
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code on glibc 2.7
+ Memcheck:Leak
+ fun:malloc
+ fun:local_strdup
+ fun:_dl_map_object
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:do_dlopen
+ fun:_dl_catch_error
+ fun:dlerror_run
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code on older glibc
+ Memcheck:Leak
+ fun:malloc
+ fun:_dl_map_object
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:do_dlopen
+ fun:_dl_catch_error
+ fun:dlerror_run
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code (2)
+ Memcheck:Leak
+ fun:malloc
+ fun:_dl_new_object
+ fun:_dl_map_object_from_fd
+ fun:_dl_map_object
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:do_dlopen
+ fun:_dl_catch_error
+ fun:dlerror_run
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code (3)
+ Memcheck:Leak
+ fun:malloc
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:do_dlopen
+ fun:_dl_catch_error
+ fun:dlerror_run
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code (4)
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_check_map_versions
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:do_dlopen
+ fun:_dl_catch_error
+ fun:dlerror_run
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+ fun:pretend_connected
+ fun:g_timeout_dispatch
+ fun:g_main_context_dispatch
+ fun:g_main_context_iterate
+ fun:g_main_loop_run
+ fun:tp_connection_run_until_ready
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code (5)
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_new_object
+ fun:_dl_map_object_from_fd
+ fun:_dl_map_object
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:do_dlopen
+ fun:_dl_catch_error
+ fun:dlerror_run
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code (6)
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_check_map_versions
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:do_dlopen
+ fun:_dl_catch_error
+ fun:dlerror_run
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+{
+ ld.so initialization + selinux
+ Memcheck:Leak
+ fun:malloc
+ obj:/lib/libselinux.so.*
+ obj:/lib/libselinux.so.*
+ obj:/lib/libselinux.so.*
+ fun:call_init
+ fun:_dl_init
+ obj:/lib/ld-*.so
+}
+
+{
+ ld.so initialization + selinux
+ Memcheck:Leak
+ fun:malloc
+ fun:vasprintf
+ fun:asprintf
+ obj:/lib/libselinux.so.*
+ obj:/lib/libselinux.so.*
+ obj:/lib/libselinux.so.*
+ fun:call_init
+ fun:_dl_init
+ obj:/lib/ld-*.so
+}
+
+{
+ ld.so initialization + selinux
+ Memcheck:Leak
+ fun:realloc
+ fun:vasprintf
+ fun:asprintf
+ obj:/lib/libselinux.so.*
+ obj:/lib/libselinux.so.*
+ obj:/lib/libselinux.so.*
+ fun:call_init
+ fun:_dl_init
+ obj:/lib/ld-*.so
+}
+
+{
+ ld.so initialization on glibc 2.9
+ Memcheck:Cond
+ fun:_dl_relocate_object
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-2.9.so
+}
+
+{
+ ld.so initialization on glibc 2.9
+ Memcheck:Cond
+ fun:strlen
+ fun:_dl_init_paths
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-2.9.so
+}
+
+{
+ GTest initialization
+ Memcheck:Leak
+ ...
+ fun:g_test_init
+}
+
+{
+ g_random_int makes default GRand
+ Memcheck:Leak
+ ...
+ fun:g_rand_new
+ fun:g_random_int
+}
+
+{
+ GTest logging
+ Memcheck:Leak
+ fun:malloc
+ fun:realloc
+ fun:g_realloc
+ fun:g_array_maybe_expand
+ fun:g_array_set_size
+ fun:g_static_private_set
+ fun:g_get_charset
+ fun:g_print
+ fun:g_test_log
+ fun:g_test_run_suite_internal
+ fun:g_test_run_suite_internal
+ fun:g_test_run_suite
+ fun:g_test_run
+ fun:main
+}
+
+{
+ dlopen initialization
+ Memcheck:Leak
+ fun:calloc
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+ fun:mcd_master_constructor
+ fun:g_object_newv
+ fun:g_object_new_valist
+ fun:g_object_new
+ fun:mcd_service_new
+ fun:main
+}
+
+{
+ more dlopen initialization
+ Memcheck:Leak
+ fun:malloc
+ fun:add_to_global
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.1
+ fun:g_module_open
+ fun:mcd_master_constructor
+ fun:g_object_newv
+ fun:g_object_new_valist
+ fun:g_object_new
+ fun:mcd_service_new
+ fun:main
+}
diff --git a/tests/twisted/tools/with-session-bus.sh b/tests/twisted/tools/with-session-bus.sh
new file mode 100644
index 00000000..987b9cde
--- /dev/null
+++ b/tests/twisted/tools/with-session-bus.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+# with-session-bus.sh - run a program with a temporary D-Bus session daemon
+#
+# The canonical location of this program is the telepathy-glib tools/
+# directory, please synchronize any changes with that copy.
+#
+# Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+set -e
+
+me=with-session-bus
+
+dbus_daemon_args="--print-address=5 --print-pid=6 --fork"
+
+usage ()
+{
+ echo "usage: $me [options] -- program [program_options]" >&2
+ echo "Requires write access to the current directory." >&2
+ echo "" >&2
+ echo "If \$WITH_SESSION_BUS_FORK_DBUS_MONITOR is set, fork dbus-monitor" >&2
+ echo "with the arguments in \$WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT." >&2
+ echo "The output of dbus-monitor is saved in $me-<pid>.dbus-monitor-logs" >&2
+ exit 2
+}
+
+while test "z$1" != "z--"; do
+ case "$1" in
+ --session)
+ dbus_daemon_args="$dbus_daemon_args --session"
+ shift
+ ;;
+ --config-file=*)
+ # FIXME: assumes config file doesn't contain any special characters
+ dbus_daemon_args="$dbus_daemon_args $1"
+ shift
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+shift
+if test "z$1" = "z"; then usage; fi
+
+exec 5> $me-$$.address
+exec 6> $me-$$.pid
+
+cleanup ()
+{
+ pid=`head -n1 $me-$$.pid`
+ if test -n "$pid" ; then
+ echo "Killing temporary bus daemon: $pid" >&2
+ kill -INT "$pid"
+ fi
+ rm -f $me-$$.address
+ rm -f $me-$$.pid
+}
+
+trap cleanup INT HUP TERM
+dbus-daemon $dbus_daemon_args
+
+{ echo -n "Temporary bus daemon is "; cat $me-$$.address; } >&2
+{ echo -n "Temporary bus daemon PID is "; head -n1 $me-$$.pid; } >&2
+
+e=0
+DBUS_SESSION_BUS_ADDRESS="`cat $me-$$.address`"
+export DBUS_SESSION_BUS_ADDRESS
+
+if [ -n "$WITH_SESSION_BUS_FORK_DBUS_MONITOR" ] ; then
+ echo -n "Forking dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT" >&2
+ dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT \
+ > $me-$$.dbus-monitor-logs 2>&1 &
+fi
+
+"$@" || e=$?
+
+trap - INT HUP TERM
+cleanup
+
+exit $e
diff --git a/tests/value-is-same.c b/tests/value-is-same.c
new file mode 100644
index 00000000..0b64032f
--- /dev/null
+++ b/tests/value-is-same.c
@@ -0,0 +1,209 @@
+/* vi: set et sw=4 ts=8 cino=t0,(0: */
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 8 -*- */
+/*
+ * Regression test for value_is_same()
+ *
+ * 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
+ *
+ */
+
+/* Yes, this is a hack */
+#include "mcd-account.c"
+
+static inline void
+assert_and_unset (GValue *value, GValue *same, GValue *different)
+{
+ g_assert (value_is_same (value, same));
+ g_assert (value_is_same (same, value));
+ g_assert (value_is_same (value, value));
+ g_assert (!value_is_same (value, different));
+ g_assert (!value_is_same (different, value));
+
+ g_value_unset (value);
+ g_value_unset (same);
+ g_value_unset (different);
+}
+
+static inline void
+assert_and_reset (GValue *value, GValue *same, GValue *different)
+{
+ g_assert (value_is_same (value, same));
+ g_assert (value_is_same (same, value));
+ g_assert (value_is_same (value, value));
+ g_assert (!value_is_same (value, different));
+ g_assert (!value_is_same (different, value));
+
+ g_value_reset (value);
+ g_value_reset (same);
+ g_value_reset (different);
+}
+
+static void
+test_numeric (void)
+{
+ GValue value = { 0 }, same = { 0 }, different = { 0 };
+
+ g_value_set_int (g_value_init (&value, G_TYPE_INT), -42);
+ g_value_set_int (g_value_init (&same, G_TYPE_INT), -42);
+ g_value_set_int (g_value_init (&different, G_TYPE_INT), -23);
+ assert_and_unset (&value, &same, &different);
+
+ g_value_set_uint (g_value_init (&value, G_TYPE_UINT), 42);
+ g_value_set_uint (g_value_init (&same, G_TYPE_UINT), 42);
+ g_value_set_uint (g_value_init (&different, G_TYPE_UINT), 23);
+ assert_and_unset (&value, &same, &different);
+
+ g_value_set_int64 (g_value_init (&value, G_TYPE_INT64), -42);
+ g_value_set_int64 (g_value_init (&same, G_TYPE_INT64), -42);
+ g_value_set_int64 (g_value_init (&different, G_TYPE_INT64), -23);
+ assert_and_unset (&value, &same, &different);
+
+ g_value_set_uint64 (g_value_init (&value, G_TYPE_UINT64), -42);
+ g_value_set_uint64 (g_value_init (&same, G_TYPE_UINT64), -42);
+ g_value_set_uint64 (g_value_init (&different, G_TYPE_UINT64), -23);
+ assert_and_unset (&value, &same, &different);
+
+ /* FIXME: why is this supported? char doesn't exist on D-Bus */
+ g_value_set_char (g_value_init (&value, G_TYPE_CHAR), 42);
+ g_value_set_char (g_value_init (&same, G_TYPE_CHAR), 42);
+ g_value_set_char (g_value_init (&different, G_TYPE_CHAR), 23);
+ assert_and_unset (&value, &same, &different);
+
+ g_value_set_uchar (g_value_init (&value, G_TYPE_UCHAR), 42);
+ g_value_set_uchar (g_value_init (&same, G_TYPE_UCHAR), 42);
+ g_value_set_uchar (g_value_init (&different, G_TYPE_UCHAR), 23);
+ assert_and_unset (&value, &same, &different);
+
+ g_value_set_double (g_value_init (&value, G_TYPE_DOUBLE), 4.5);
+ g_value_set_double (g_value_init (&same, G_TYPE_DOUBLE), 4.5);
+ g_value_set_double (g_value_init (&different, G_TYPE_DOUBLE), -1.25);
+ assert_and_unset (&value, &same, &different);
+
+ g_value_set_boolean (g_value_init (&value, G_TYPE_BOOLEAN), TRUE);
+ g_value_set_boolean (g_value_init (&same, G_TYPE_BOOLEAN), TRUE);
+ g_value_set_boolean (g_value_init (&different, G_TYPE_BOOLEAN), FALSE);
+ assert_and_unset (&value, &same, &different);
+
+ g_value_set_boolean (g_value_init (&value, G_TYPE_BOOLEAN), FALSE);
+ g_value_set_boolean (g_value_init (&same, G_TYPE_BOOLEAN), FALSE);
+ g_value_set_boolean (g_value_init (&different, G_TYPE_BOOLEAN), TRUE);
+ assert_and_unset (&value, &same, &different);
+}
+
+static void
+test_string (void)
+{
+ GValue value = { 0 }, same = { 0 }, different = { 0 };
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_init (&same, G_TYPE_STRING);
+ g_value_init (&different, G_TYPE_STRING);
+
+ g_value_set_static_string (&value, NULL);
+ g_value_set_static_string (&same, NULL);
+ g_value_set_static_string (&different, "");
+ assert_and_reset (&value, &same, &different);
+
+ g_value_set_static_string (&value, "");
+ g_value_set_static_string (&same, "");
+ g_value_set_static_string (&different, NULL);
+ assert_and_reset (&value, &same, &different);
+
+ g_value_set_static_string (&value, "foo");
+ g_value_take_string (&same, g_strdup ("foo"));
+ g_value_set_static_string (&different, "bar");
+ assert_and_reset (&value, &same, &different);
+
+ g_value_unset (&value);
+ g_value_unset (&same);
+ g_value_unset (&different);
+}
+
+static void
+test_object_path (void)
+{
+ GValue value = { 0 }, same = { 0 }, different = { 0 };
+
+ g_value_init (&value, DBUS_TYPE_G_OBJECT_PATH);
+ g_value_init (&same, DBUS_TYPE_G_OBJECT_PATH);
+ g_value_init (&different, DBUS_TYPE_G_OBJECT_PATH);
+
+ g_value_set_static_boxed (&value, "/foo");
+ g_value_take_boxed (&same, g_strdup ("/foo"));
+ g_value_set_static_boxed (&different, "/bar");
+ assert_and_reset (&value, &same, &different);
+
+ g_value_unset (&value);
+ g_value_unset (&same);
+ g_value_unset (&different);
+}
+
+static void
+test_strv (void)
+{
+ const gchar * const empty[] = { NULL };
+ const gchar * const small[] = { "foo", "bar", NULL };
+ const gchar * const large[] = { "foo", "bar", "baz", NULL };
+ GValue value = { 0 }, same = { 0 }, different = { 0 };
+
+ g_value_init (&value, G_TYPE_STRV);
+ g_value_init (&same, G_TYPE_STRV);
+ g_value_init (&different, G_TYPE_STRV);
+
+ g_value_set_static_boxed (&value, (GStrv) small);
+ g_value_take_boxed (&same, g_strdupv ((GStrv) small));
+ g_value_set_static_boxed (&different, (GStrv) large);
+ assert_and_reset (&value, &same, &different);
+
+ g_value_set_static_boxed (&value, (GStrv) large);
+ g_value_take_boxed (&same, g_strdupv ((GStrv) large));
+ g_value_set_static_boxed (&different, (GStrv) small);
+ assert_and_reset (&value, &same, &different);
+
+ g_value_set_static_boxed (&value, NULL);
+ g_value_set_static_boxed (&same, (GStrv) empty);
+ g_value_set_static_boxed (&different, (GStrv) small);
+ assert_and_reset (&value, &same, &different);
+
+ g_value_set_static_boxed (&value, (GStrv) empty);
+ g_value_set_static_boxed (&same, NULL);
+ g_value_set_static_boxed (&different, (GStrv) large);
+ assert_and_reset (&value, &same, &different);
+
+ g_value_unset (&value);
+ g_value_unset (&same);
+ g_value_unset (&different);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
+
+ g_type_init ();
+
+ g_test_add_func ("/value-is-same/numeric", test_numeric);
+ g_test_add_func ("/value-is-same/string", test_string);
+ g_test_add_func ("/value-is-same/object-path", test_object_path);
+ g_test_add_func ("/value-is-same/strv", test_strv);
+
+ return g_test_run ();
+}