diff options
author | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2010-12-22 15:54:58 +0000 |
---|---|---|
committer | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2010-12-22 15:54:58 +0000 |
commit | 5b34520e25c014377a27f22d35b958ded994a2dd (patch) | |
tree | b3c3c0eb6e0d39d85ce823db1a35d3c317571c7e /tests | |
parent | 1d9f17613ac2ce1dc3928ab0848d7b9b987b6330 (diff) |
tests: move from test/ to tests/
So so annoying.
Signed-off-by: Jonny Lamb <jonny.lamb@collabora.co.uk>
Diffstat (limited to 'tests')
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 ¶meter; +} + + +/* 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 (); +} |