[evolution-data-server/cursor-staging: 20/22] Adding test-client-cursor-operations.c test.
- From: Tristan Van Berkom <tvb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/cursor-staging: 20/22] Adding test-client-cursor-operations.c test.
- Date: Tue, 30 Jul 2013 21:42:27 +0000 (UTC)
commit e2874aad6b47a7f12e2323507faec9b9ea0f58eb
Author: Tristan Van Berkom <tristanvb openismus com>
Date: Thu Jul 4 17:38:24 2013 +0900
Adding test-client-cursor-operations.c test.
tests/libebook/client/Makefile.am | 6 +
.../client/test-client-cursor-operations.c | 2562 ++++++++++++++++++++
2 files changed, 2568 insertions(+), 0 deletions(-)
---
diff --git a/tests/libebook/client/Makefile.am b/tests/libebook/client/Makefile.am
index 1dd7c0e..92d4460 100644
--- a/tests/libebook/client/Makefile.am
+++ b/tests/libebook/client/Makefile.am
@@ -12,6 +12,8 @@ libclient_test_utils_la_CPPFLAGS = \
-I$(top_builddir)/calendar \
-I$(top_srcdir)/tests/test-server-utils \
-I$(top_builddir)/tests/test-server-utils \
+ -I$(top_srcdir)/private \
+ -I$(top_builddir)/private \
-DSRCDIR=\""$(abs_srcdir)"\" \
$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
$(CAMEL_CFLAGS) \
@@ -20,6 +22,7 @@ libclient_test_utils_la_CPPFLAGS = \
libclient_test_utils_la_LIBADD = \
$(top_builddir)/addressbook/libebook/libebook-1.2.la \
$(top_builddir)/tests/test-server-utils/libetestserverutils.la \
+ $(top_builddir)/private/libedbus-private.la \
$(EVOLUTION_ADDRESSBOOK_LIBS) \
$(CAMEL_LIBS) \
$(NULL)
@@ -49,6 +52,7 @@ TESTS = \
test-client-view-operations \
test-client-suppress-notifications \
test-client-cursor-create \
+ test-client-cursor-operations \
$(NULL)
# The noinst tests are functional tests, not unit tests.
@@ -117,5 +121,7 @@ test_client_self_LDADD=$(TEST_LIBS)
test_client_self_CPPFLAGS=$(TEST_CPPFLAGS)
test_client_cursor_create_LDADD=$(TEST_LIBS)
test_client_cursor_create_CPPFLAGS=$(TEST_CPPFLAGS)
+test_client_cursor_operations_LDADD=$(TEST_LIBS)
+test_client_cursor_operations_CPPFLAGS=$(TEST_CPPFLAGS)
-include $(top_srcdir)/git.mk
diff --git a/tests/libebook/client/test-client-cursor-operations.c
b/tests/libebook/client/test-client-cursor-operations.c
new file mode 100644
index 0000000..5a68d47
--- /dev/null
+++ b/tests/libebook/client/test-client-cursor-operations.c
@@ -0,0 +1,2562 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Authors: Tristan Van Berkom <tristanvb openismus com>
+ */
+#include <libebook/libebook.h>
+
+#include "e-test-server-utils.h"
+#include "client-test-utils.h"
+#include "e-dbus-localed.h"
+
+
+/* This is based on the sorted-%02d.vcf contacts from 1-20,
+ * a full table describing the expected order under various
+ * locales can be found in tests/libedata-book/data-test-utils.h
+ */
+#define N_SORTED_CONTACTS 20
+
+/* 13 contacts in the test data have an email address ending with ".com" */
+#define N_FILTERED_CONTACTS 13
+
+/* How long to wait before aborting an async test */
+#define TIMEOUT 5 * 1000
+
+/* Standard number of iterations for a parallelized operation */
+#define THREAD_ITERATIONS 20
+
+/* Number of threads to use in the concurrent thread tests */
+#define CONCURRENT_THREADS 5
+
+typedef struct _CursorClosure CursorClosure;
+typedef struct _CursorFixture CursorFixture;
+typedef enum _CursorTestType CursorTestType;
+typedef struct _CursorTest CursorTest;
+typedef struct _CursorThread CursorThread;
+typedef struct _CursorThreadTest CursorThreadTest;
+
+typedef void (*CursorThreadFunc) (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorThread *thread,
+ gpointer user_data);
+
+/* Main Cursor Test Fixture */
+static void cursor_fixture_setup (CursorFixture *fixture,
+ gconstpointer user_data);
+static void cursor_fixture_teardown (CursorFixture *fixture,
+ gconstpointer user_data);
+static void cursor_fixture_set_locale (CursorFixture *fixture,
+ const gchar *locale);
+static void cursor_fixture_timeout_start (CursorFixture *fixture,
+ const gchar *error_message);
+static void cursor_fixture_timeout_cancel (CursorFixture *fixture);
+
+/* Main Cursor Test Closure */
+static CursorClosure *cursor_closure_new (gboolean async,
+ gboolean dra,
+ const gchar *locale);
+static void cursor_closure_free (CursorClosure *closure);
+static void cursor_test_free (CursorTest *test);
+static void cursor_closure_add (CursorClosure *closure,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+/* Move By Tests */
+static void cursor_closure_move_by (CursorClosure *closure,
+ EBookCursorOrigin origin,
+ gint count,
+ gint expected,
+ ...);
+static void cursor_test_move_by (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_move_by_free (CursorTest *test);
+
+/* Set Sexp Tests */
+static void cursor_closure_set_sexp (CursorClosure *closure,
+ EBookQuery *query,
+ gboolean expect_success);
+static void cursor_test_set_sexp (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_set_sexp_free (CursorTest *test);
+
+/* Position Tests */
+static void cursor_closure_position (CursorClosure *closure,
+ gint total,
+ gint position,
+ gboolean immediate);
+static void cursor_test_position (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_position_free (CursorTest *test);
+
+/* Add / Remove Tests */
+static void cursor_closure_add_contact (CursorClosure *closure,
+ const gchar *case_name);
+static void cursor_closure_remove_contact (CursorClosure *closure,
+ const gchar *case_name);
+static void cursor_test_add_remove (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_add_remove_free (CursorTest *test);
+
+/* Alphabet Index Tests */
+static void cursor_closure_alphabet_index (CursorClosure *closure,
+ gint index);
+static void cursor_test_alphabet_index (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_alphabet_index_free (CursorTest *test);
+
+/* Change Locale Tests */
+static void cursor_closure_change_locale (CursorClosure *closure,
+ const gchar *locale);
+static void cursor_test_change_locale (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_change_locale_free (CursorTest *test);
+
+
+/* Alphabet Tests */
+static void cursor_closure_alphabet (CursorClosure *closure,
+ gboolean immediate,
+ const gchar *letter0,
+ const gchar *letter1,
+ const gchar *letter2,
+ const gchar *letter3,
+ const gchar *letter4);
+static void cursor_test_alphabet (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_alphabet_free (CursorTest *test);
+
+/* Thread Tests */
+static CursorThread *cursor_closure_thread (CursorClosure *closure,
+ gboolean create_cursor,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (3, 4);
+static void cursor_test_thread (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_thread_free (CursorTest *test);
+static void cursor_thread_add_test (CursorThread *thread,
+ CursorThreadFunc test_func,
+ gpointer data,
+ GDestroyNotify destroy_data);
+
+/* Thread Join Tests */
+static void cursor_closure_thread_join (CursorClosure *closure,
+ CursorThread *thread);
+static void cursor_test_thread_join (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_thread_join_free (CursorTest *test);
+
+
+/* Spinning the main loop */
+static void cursor_closure_spin_loop (CursorClosure *closure,
+ guint ms);
+static void cursor_test_spin_loop (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test);
+static void cursor_test_spin_loop_free (CursorTest *test);
+
+
+struct _CursorFixture {
+ ETestServerFixture parent_fixture;
+
+ EBookClientCursor *cursor;
+ EContact *contacts[N_SORTED_CONTACTS];
+ EBookQuery *query;
+ GSource *timeout_source;
+
+ EDBusLocale1 *locale1;
+ guint own_id;
+};
+
+struct _CursorClosure {
+ ETestServerClosure parent_closure;
+ gchar *locale;
+ gboolean async;
+ GList *tests;
+};
+
+enum _CursorTestType {
+ CURSOR_TEST_MOVE_BY,
+ CURSOR_TEST_SET_SEXP,
+ CURSOR_TEST_POSITION,
+ CURSOR_TEST_ADD_REMOVE,
+ CURSOR_TEST_ALPHABET_INDEX,
+ CURSOR_TEST_CHANGE_LOCALE,
+ CURSOR_TEST_ALPHABET,
+ CURSOR_TEST_THREAD,
+ CURSOR_TEST_THREAD_JOIN,
+ CURSOR_TEST_SPIN_LOOP
+};
+
+struct _CursorTest {
+ CursorTestType type;
+};
+
+/******************************************************
+ * Main Cursor Test Fixture *
+ ******************************************************/
+#define N_SORT_FIELDS 2
+static EContactField sort_fields[] = { E_CONTACT_FAMILY_NAME, E_CONTACT_GIVEN_NAME };
+static EBookSortType sort_types[] = { E_BOOK_SORT_ASCENDING, E_BOOK_SORT_ASCENDING };
+
+static gboolean
+cursor_timeout (const gchar *error_message)
+{
+ g_error ("%s", error_message);
+
+ return FALSE;
+}
+
+static void
+timeout_start (GMainContext *context,
+ GSource **source,
+ const gchar *error_message)
+{
+ g_assert (source && *source == NULL);
+
+ if (!context)
+ context = g_main_context_default ();
+
+ *source = g_timeout_source_new (TIMEOUT);
+ g_source_set_callback (*source, (GSourceFunc)cursor_timeout,
+ g_strdup (error_message),
+ (GDestroyNotify)g_free);
+ g_source_attach (*source, context);
+}
+
+static void
+timeout_cancel (GSource **source)
+{
+ g_assert (source && *source != NULL);
+
+ g_source_destroy (*source);
+ g_source_unref (*source);
+ *source = NULL;
+}
+
+static void
+cursor_fixture_timeout_start (CursorFixture *fixture,
+ const gchar *error_message)
+{
+ timeout_start (NULL, &fixture->timeout_source, error_message);
+}
+
+static void
+cursor_fixture_timeout_cancel (CursorFixture *fixture)
+{
+ timeout_cancel (&fixture->timeout_source);
+}
+
+static void
+cursor_fixture_add_contacts (CursorFixture *fixture,
+ ETestServerClosure *closure)
+{
+ EBookClient *book_client;
+ GError *error = NULL;
+ GSList *contacts = NULL;
+ gint i;
+
+ book_client = E_TEST_SERVER_UTILS_SERVICE (fixture, EBookClient);
+
+ for (i = 0; i < N_SORTED_CONTACTS; i++) {
+ gchar *case_name = g_strdup_printf ("sorted-%d", i + 1);
+ gchar *vcard;
+ EContact *contact;
+
+ vcard = new_vcard_from_test_case (case_name);
+ contact = e_contact_new_from_vcard (vcard);
+ contacts = g_slist_prepend (contacts, contact);
+ g_free (vcard);
+ g_free (case_name);
+
+ fixture->contacts[i] = contact;
+ }
+
+ if (!e_book_client_add_contacts_sync (book_client, contacts, NULL, NULL, &error)) {
+
+ /* Dont complain here, we re-use the same addressbook for multiple tests
+ * and we can't add the same contacts twice
+ */
+ if (g_error_matches (error, E_BOOK_CLIENT_ERROR,
+ E_BOOK_CLIENT_ERROR_CONTACT_ID_ALREADY_EXISTS))
+ g_clear_error (&error);
+ else
+ g_error ("Failed to add test contacts: %s", error->message);
+ }
+
+ g_slist_free (contacts);
+
+
+}
+
+static void
+cursor_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CursorFixture *fixture = (CursorFixture *)user_data;
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ GError *error = NULL;
+
+ if (!e_book_client_get_cursor_finish (E_BOOK_CLIENT (source_object),
+ result, &(fixture->cursor), &error))
+ g_error ("Failed to create a cursor: %s", error->message);
+
+ g_main_loop_quit (server_fixture->loop);
+}
+
+static void
+cursor_fixture_setup (CursorFixture *fixture,
+ gconstpointer user_data)
+{
+ CursorClosure *closure = (CursorClosure *)user_data;
+ ETestServerClosure *server_closure = (ETestServerClosure *)user_data;
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ EBookClient *book_client;
+ GError *error = NULL;
+ gchar *sexp = NULL;
+
+ e_test_server_utils_setup (server_fixture, user_data);
+
+ /* Set the initial locale before adding the contacts */
+ if (closure->locale)
+ cursor_fixture_set_locale (fixture, closure->locale);
+ else
+ cursor_fixture_set_locale (fixture, "en_US.UTF-8");
+
+ cursor_fixture_add_contacts (fixture, server_closure);
+
+ book_client = E_TEST_SERVER_UTILS_SERVICE (fixture, EBookClient);
+
+ /* Allow a surrounding fixture setup
+ * to add a query here
+ */
+ if (fixture->query) {
+ sexp = e_book_query_to_string (fixture->query);
+ e_book_query_unref (fixture->query);
+ fixture->query = NULL;
+ }
+
+ if (server_closure->use_async_connect) {
+
+ e_book_client_get_cursor (book_client,
+ sexp,
+ sort_fields,
+ sort_types,
+ N_SORT_FIELDS,
+ NULL,
+ cursor_ready_cb,
+ fixture);
+
+ cursor_fixture_timeout_start (fixture, "Timeout reached while trying to create a cursor");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+
+ } else {
+
+ if (!e_book_client_get_cursor_sync (book_client,
+ sexp,
+ sort_fields,
+ sort_types,
+ N_SORT_FIELDS,
+ &fixture->cursor,
+ NULL, &error))
+ g_error ("Failed to create a cursor with an empty query: %s", error->message);
+ }
+
+ g_free (sexp);
+}
+
+static void
+cursor_fixture_teardown (CursorFixture *fixture,
+ gconstpointer user_data)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ gint i;
+
+ g_object_unref (fixture->cursor);
+ for (i = 0; i < N_SORTED_CONTACTS; i++) {
+ if (fixture->contacts[i])
+ g_clear_object (&(fixture->contacts[i]));
+ }
+
+ if (fixture->locale1)
+ g_object_unref (fixture->locale1);
+
+ if (fixture->own_id > 0)
+ g_bus_unown_name (fixture->own_id);
+
+ e_test_server_utils_teardown (server_fixture, user_data);
+}
+
+typedef struct {
+ CursorFixture *fixture;
+ const gchar *locale;
+} ChangeLocaleData;
+
+static void
+book_client_locale_change (EBookClient *book,
+ GParamSpec *pspec,
+ ChangeLocaleData *data)
+{
+ ETestServerFixture *base_fixture = (ETestServerFixture *)data->fixture;
+
+ if (!g_strcmp0 (e_book_client_get_locale (book), data->locale))
+ g_main_loop_quit (base_fixture->loop);
+}
+
+static void
+cursor_fixture_set_locale (CursorFixture *fixture,
+ const gchar *locale)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ EBookClient *book_client;
+ ChangeLocaleData data = { fixture, locale };
+ gulong handler_id;
+ gchar *strv[2] = { NULL, NULL };
+
+ book_client = E_TEST_SERVER_UTILS_SERVICE (fixture, EBookClient);
+
+ /* We're already in the right locale */
+ if (g_strcmp0 (locale, e_book_client_get_locale (book_client)) == 0)
+ return;
+
+ if (!fixture->locale1) {
+ GDBusConnection *bus;
+ GError *error = NULL;
+
+ /* We use the 'org.freedesktop.locale1 on the session bus instead
+ * of the system bus only for testing purposes... in real life
+ * this service is on the system bus.
+ */
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (!bus)
+ g_error ("Failed to get system bus: %s", error->message);
+
+ fixture->locale1 = e_dbus_locale1_skeleton_new ();
+
+ /* Set initial locale before exporting on the bus */
+ strv[0] = g_strdup_printf ("LANG=%s", locale);
+ e_dbus_locale1_set_locale (fixture->locale1, (const gchar * const *)strv);
+ g_free (strv[0]);
+
+ if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (fixture->locale1),
+ bus, "/org/freedesktop/locale1", &error))
+ g_error ("Failed to export org.freedesktop.locale1: %s", error->message);
+
+ fixture->own_id =
+ g_bus_own_name_on_connection (bus,
+ "org.freedesktop.locale1",
+ G_BUS_NAME_OWNER_FLAGS_REPLACE,
+ NULL, NULL, NULL, NULL);
+
+ g_object_unref (bus);
+ } else {
+ /* Send locale change message */
+ strv[0] = g_strdup_printf ("LANG=%s", locale);
+ e_dbus_locale1_set_locale (fixture->locale1, (const gchar * const *)strv);
+ g_free (strv[0]);
+ }
+
+ handler_id = g_signal_connect (book_client, "notify::locale",
+ G_CALLBACK (book_client_locale_change), &data);
+
+
+ cursor_fixture_timeout_start (fixture, "Timeout reached while waiting for locale change");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+
+ g_signal_handler_disconnect (book_client, handler_id);
+}
+
+static void
+cursor_fixture_test (CursorFixture *fixture,
+ gconstpointer user_data)
+{
+ CursorClosure *closure = (CursorClosure *)user_data;
+ GList *l;
+
+ /* Run all the tests */
+ for (l = closure->tests; l; l = l->next) {
+ CursorTest *test = l->data;
+
+ switch (test->type) {
+ case CURSOR_TEST_MOVE_BY:
+ cursor_test_move_by (fixture, closure, test);
+ break;
+ case CURSOR_TEST_SET_SEXP:
+ cursor_test_set_sexp (fixture, closure, test);
+ break;
+ case CURSOR_TEST_POSITION:
+ cursor_test_position (fixture, closure, test);
+ break;
+ case CURSOR_TEST_ADD_REMOVE:
+ cursor_test_add_remove (fixture, closure, test);
+ break;
+ case CURSOR_TEST_ALPHABET_INDEX:
+ cursor_test_alphabet_index (fixture, closure, test);
+ break;
+ case CURSOR_TEST_CHANGE_LOCALE:
+ cursor_test_change_locale (fixture, closure, test);
+ break;
+ case CURSOR_TEST_ALPHABET:
+ cursor_test_alphabet (fixture, closure, test);
+ break;
+ case CURSOR_TEST_THREAD:
+ cursor_test_thread (fixture, closure, test);
+ break;
+ case CURSOR_TEST_THREAD_JOIN:
+ cursor_test_thread_join (fixture, closure, test);
+ break;
+ case CURSOR_TEST_SPIN_LOOP:
+ cursor_test_spin_loop (fixture, closure, test);
+ break;
+ }
+ }
+}
+
+/******************************************************
+ * Main Cursor Test Closure *
+ ******************************************************/
+static void
+cursor_test_free (CursorTest *test)
+{
+ switch (test->type) {
+ case CURSOR_TEST_MOVE_BY:
+ cursor_test_move_by_free (test);
+ break;
+ case CURSOR_TEST_SET_SEXP:
+ cursor_test_set_sexp_free (test);
+ break;
+ case CURSOR_TEST_POSITION:
+ cursor_test_position_free (test);
+ break;
+ case CURSOR_TEST_ADD_REMOVE:
+ cursor_test_add_remove_free (test);
+ break;
+ case CURSOR_TEST_ALPHABET_INDEX:
+ cursor_test_alphabet_index_free (test);
+ break;
+ case CURSOR_TEST_CHANGE_LOCALE:
+ cursor_test_change_locale_free (test);
+ break;
+ case CURSOR_TEST_ALPHABET:
+ cursor_test_alphabet_free (test);
+ break;
+ case CURSOR_TEST_THREAD:
+ cursor_test_thread_free (test);
+ break;
+ case CURSOR_TEST_THREAD_JOIN:
+ cursor_test_thread_join_free (test);
+ break;
+ case CURSOR_TEST_SPIN_LOOP:
+ cursor_test_spin_loop_free (test);
+ break;
+ }
+}
+
+static void
+cursor_closure_free (CursorClosure *closure)
+{
+ if (closure) {
+ g_list_free_full (closure->tests, (GDestroyNotify)cursor_test_free);
+ g_free (closure->locale);
+ g_slice_free (CursorClosure, closure);
+ }
+}
+
+static CursorClosure *
+cursor_closure_new (gboolean async,
+ gboolean dra,
+ const gchar *locale)
+{
+ CursorClosure *closure = g_slice_new0 (CursorClosure);
+ ETestServerClosure *server_closure = (ETestServerClosure *)closure;
+
+ if (dra)
+ server_closure->type = E_TEST_SERVER_DIRECT_ADDRESS_BOOK;
+ else
+ server_closure->type = E_TEST_SERVER_ADDRESS_BOOK;
+
+ server_closure->use_async_connect = async;
+ server_closure->destroy_closure_func = (GDestroyNotify)cursor_closure_free;
+
+ closure->locale = g_strdup (locale);
+ closure->async = async;
+
+ return closure;
+}
+
+static void
+cursor_closure_add (CursorClosure *closure,
+ const gchar *format,
+ ...)
+{
+ gchar *test_path;
+ va_list args;
+
+ va_start (args, format);
+ test_path = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_test_add (test_path, CursorFixture, closure,
+ cursor_fixture_setup,
+ cursor_fixture_test,
+ cursor_fixture_teardown);
+
+ g_free (test_path);
+}
+
+/******************************************************
+ * Move By Tests *
+ ******************************************************/
+typedef struct {
+ CursorTestType type;
+
+ EBookCursorOrigin origin; /* The origin to move from */
+ gint count; /* The amount to move the cursor by (positive or negative) */
+ gint expected; /* The amount of actual results expected */
+ gint expected_order[N_SORTED_CONTACTS]; /* The expected results */
+} CursorTestMoveBy;
+
+typedef struct {
+ CursorFixture *fixture;
+ CursorTestMoveBy *test;
+ gboolean out_of_sync;
+} MoveByReadyData;
+
+static gint
+find_contact_link (EContact *contact,
+ const gchar *uid)
+{
+ const gchar *contact_uid =
+ (const gchar *)e_contact_get_const (contact, E_CONTACT_UID);
+
+ return g_strcmp0 (contact_uid, uid);
+}
+
+static void
+assert_contacts_order_slist (GSList *results,
+ GSList *uids)
+{
+ gint position = -1;
+ GSList *link, *l;
+
+ /* Assert that all passed UIDs are found in the
+ * results, and that those UIDs are in the
+ * specified order.
+ */
+ for (l = uids; l; l = l->next) {
+ const gchar *uid = l->data;
+ gint new_position;
+
+ link = g_slist_find_custom (results, uid, (GCompareFunc)find_contact_link);
+ if (!link)
+ g_error ("Specified uid '%s' was not found in results", uid);
+
+ new_position = g_slist_position (results, link);
+ g_assert_cmpint (new_position, >, position);
+ position = new_position;
+ }
+}
+
+static void
+move_by_print_results (GSList *results,
+ GSList *uids)
+{
+ GSList *l;
+
+ if (g_getenv ("TEST_DEBUG") == NULL)
+ return;
+
+ g_print ("\nPRINTING EXPECTED RESULTS:\n");
+ for (l = uids; l; l = l->next) {
+ gchar *uid = l->data;
+
+ g_print ("\t%s\n", uid);
+ }
+ g_print ("\nEXPECTED RESULTS FINISHED\n");
+
+ g_print ("\nPRINTING RESULTS:\n");
+ for (l = results; l; l = l->next) {
+ EContact *contact = l->data;
+ gchar *vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+
+ g_print ("\n%s\n", vcard);
+ g_free (vcard);
+ }
+ g_print ("\nRESULT LIST FINISHED\n");
+}
+
+static void
+cursor_test_assert_results (CursorFixture *fixture,
+ CursorTestMoveBy *test,
+ GSList *results)
+{
+ GSList *uids = NULL;
+ gint i;
+
+ for (i = 0; i < test->expected; i++) {
+ gchar *uid;
+ gint index = test->expected_order[i];
+
+ uid = (gchar *)e_contact_get_const (fixture->contacts[index], E_CONTACT_UID);
+ uids = g_slist_append (uids, uid);
+ }
+
+ move_by_print_results (results, uids);
+
+ /* Assert the exact amount of requested results */
+ g_assert_cmpint (g_slist_length (results), ==, test->expected);
+ assert_contacts_order_slist (results, uids);
+ g_slist_free (uids);
+}
+
+static void
+cursor_test_move_by_free (CursorTest *test)
+{
+ CursorTestMoveBy *move_by = (CursorTestMoveBy *)test;
+
+ g_slice_free (CursorTestMoveBy, move_by);
+}
+
+static void
+cursor_test_move_by_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ MoveByReadyData *data = (MoveByReadyData *)user_data;
+ ETestServerFixture *server_fixture = (ETestServerFixture *)data->fixture;
+ GSList *results = NULL;
+ GError *error = NULL;
+
+ if (!e_book_client_cursor_move_by_finish (E_BOOK_CLIENT_CURSOR (source_object),
+ result, &results, &error)) {
+ if (g_error_matches (error,
+ E_CLIENT_ERROR,
+ E_CLIENT_ERROR_OUT_OF_SYNC)) {
+ data->out_of_sync = TRUE;
+ g_clear_error (&error);
+ } else {
+ g_error ("Error calling e_book_client_cursor_move_by_finish(): %s",
+ error->message);
+ }
+ }
+
+ if (!data->out_of_sync)
+ cursor_test_assert_results (data->fixture, data->test, results);
+
+ g_slist_free_full (results, (GDestroyNotify)g_object_unref);
+ g_main_loop_quit (server_fixture->loop);
+}
+
+static gboolean
+cursor_test_try_move_by (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ CursorTestMoveBy *move_by = (CursorTestMoveBy *)test;
+ gboolean out_of_sync = FALSE;
+
+ if (closure->async) {
+ MoveByReadyData data = { fixture, move_by, FALSE };
+
+ e_book_client_cursor_move_by (fixture->cursor,
+ move_by->origin,
+ move_by->count,
+ TRUE,
+ NULL,
+ cursor_test_move_by_ready_cb,
+ &data);
+
+ /* Wait for result with an error timeout */
+ cursor_fixture_timeout_start (fixture, "Timeout reached while moving the cursor");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+
+ } else {
+ GError *error = NULL;
+ GSList *results = NULL;
+
+ if (!e_book_client_cursor_move_by_sync (fixture->cursor,
+ move_by->origin,
+ move_by->count,
+ &results,
+ NULL, &error)) {
+
+ if (g_error_matches (error,
+ E_CLIENT_ERROR,
+ E_CLIENT_ERROR_OUT_OF_SYNC)) {
+ out_of_sync = TRUE;
+ g_clear_error (&error);
+ } else {
+ g_error ("Error calling e_book_client_cursor_move_by_sync(): %s",
+ error->message);
+ }
+
+ } else {
+ cursor_test_assert_results (fixture, move_by, results);
+ }
+
+ g_slist_free_full (results, (GDestroyNotify)g_object_unref);
+ }
+
+ return out_of_sync == FALSE;
+}
+
+static void
+move_by_refreshed (EBookClientCursor *cursor,
+ GMainLoop *loop)
+{
+ g_main_loop_quit (loop);
+}
+
+static void
+cursor_test_move_by (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ gint i;
+ gboolean success = FALSE;
+
+ /* Should only ever really be once, but in practice code
+ * should be written to continually fallback until a synchronized
+ * move_by() call passes */
+ for (i = 0; i < 3 && success == FALSE; i++) {
+ gulong refresh_id;
+
+ /* Wait for a refresh which follows an out-of-sync move by */
+ refresh_id = g_signal_connect (fixture->cursor, "refresh",
+ G_CALLBACK (move_by_refreshed),
+ server_fixture->loop);
+
+ /* Stop trying after the first move_by() is not out-of-sync */
+ success = cursor_test_try_move_by (fixture, closure, test);
+
+ if (!success) {
+ cursor_fixture_timeout_start (fixture, "Timeout reached while waiting for refresh
event");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+ }
+
+ g_signal_handler_disconnect (fixture->cursor, refresh_id);
+ }
+}
+
+static void
+cursor_closure_move_by (CursorClosure *closure,
+ EBookCursorOrigin origin,
+ gint count,
+ gint expected,
+ ...)
+{
+ CursorTestMoveBy *test = g_slice_new0 (CursorTestMoveBy);
+ va_list args;
+ gint i;
+
+ g_assert (expected <= N_SORTED_CONTACTS);
+ g_assert (ABS (count) <= N_SORTED_CONTACTS);
+
+ test->type = CURSOR_TEST_MOVE_BY;
+ test->origin = origin;
+ test->count = count;
+ test->expected = expected;
+
+ va_start (args, expected);
+ for (i = 0; i < expected; i++) {
+ gint expected_position = va_arg (args, gint);
+
+ /* Sanity check while building the test case */
+ g_assert_cmpint (expected_position, >, 0);
+
+ test->expected_order[i] = expected_position - 1;
+ }
+ va_end (args);
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+/******************************************************
+ * Set Sexp Tests *
+ ******************************************************/
+typedef struct {
+ CursorTestType type;
+
+ gchar *sexp;
+ gboolean expect_success;
+} CursorTestSetSexp;
+
+typedef struct {
+ CursorFixture *fixture;
+ CursorTestSetSexp *test;
+} SetSexpReadyData;
+
+static void
+cursor_closure_set_sexp (CursorClosure *closure,
+ EBookQuery *query,
+ gboolean expect_success)
+{
+ CursorTestSetSexp *test = g_slice_new0 (CursorTestSetSexp);
+
+ g_assert (query != NULL);
+
+ test->type = CURSOR_TEST_SET_SEXP;
+ test->sexp = e_book_query_to_string (query);
+ test->expect_success = expect_success;
+
+ e_book_query_unref (query);
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+static void
+cursor_test_set_sexp_assert (CursorTestSetSexp *test,
+ gboolean success,
+ GError *error)
+{
+ if (test->expect_success) {
+ if (!success)
+ g_error ("Failed to set sexp '%s': %s",
+ test->sexp, error->message);
+ } else {
+
+ if (success)
+ g_error ("Unexpected success setting sexp '%s'",
+ test->sexp);
+ else if (!g_error_matches (error,
+ E_CLIENT_ERROR,
+ E_CLIENT_ERROR_INVALID_QUERY))
+ g_error ("Wrong error message when failing to set a search expression: "
+ "Domain '%s' Code '%d' Message: %s",
+ g_quark_to_string (error->domain),
+ error->code,
+ error->message);
+ }
+}
+
+static void
+cursor_test_set_sexp_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ SetSexpReadyData *data = (SetSexpReadyData *)user_data;
+ ETestServerFixture *server_fixture = (ETestServerFixture *)data->fixture;
+ gboolean success;
+ GError *error = NULL;
+
+ success = e_book_client_cursor_set_sexp_finish (E_BOOK_CLIENT_CURSOR (source_object),
+ result, &error);
+ cursor_test_set_sexp_assert (data->test, success, error);
+ g_clear_error (&error);
+
+ g_main_loop_quit (server_fixture->loop);
+}
+
+static void
+cursor_test_set_sexp (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ CursorTestSetSexp *set_sexp = (CursorTestSetSexp *)test;
+
+ if (closure->async) {
+ SetSexpReadyData data = { fixture, set_sexp };
+
+ e_book_client_cursor_set_sexp (fixture->cursor,
+ set_sexp->sexp,
+ NULL,
+ cursor_test_set_sexp_ready_cb,
+ &data);
+
+ /* Wait for result with an error timeout */
+ cursor_fixture_timeout_start (fixture, "Timeout reached while setting search expression");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+ } else {
+ gboolean success;
+ GError *error = NULL;
+
+ success = e_book_client_cursor_set_sexp_sync (fixture->cursor,
+ set_sexp->sexp,
+ NULL,
+ &error);
+ cursor_test_set_sexp_assert (set_sexp, success, error);
+ g_clear_error (&error);
+ }
+}
+
+static void
+cursor_test_set_sexp_free (CursorTest *test)
+{
+ CursorTestSetSexp *set_sexp = (CursorTestSetSexp *)test;
+
+ g_free (set_sexp->sexp);
+ g_slice_free (CursorTestSetSexp, set_sexp);
+}
+
+
+/******************************************************
+ * Position Tests *
+ ******************************************************/
+typedef struct {
+ CursorTestType type;
+
+ gint total;
+ gint position;
+ gboolean immediate;
+
+ gulong total_id;
+ gulong position_id;
+} CursorTestPosition;
+
+typedef struct {
+ CursorFixture *fixture;
+ CursorTestPosition *test;
+} PositionData;
+
+static void
+cursor_closure_position (CursorClosure *closure,
+ gint total,
+ gint position,
+ gboolean immediate)
+{
+ CursorTestPosition *test = g_slice_new0 (CursorTestPosition);
+
+ test->type = CURSOR_TEST_POSITION;
+ test->total = total;
+ test->position = position;
+ test->immediate = immediate;
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+static void
+position_or_total_changed (EBookClientCursor *cursor,
+ GParamSpec *pspec,
+ PositionData *data)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)data->fixture;
+
+ if (g_getenv ("TEST_DEBUG") != NULL) {
+ g_print ("Position changed, total: %d position: %d (expecting total: %d position: %d)\n",
+ e_book_client_cursor_get_total (cursor),
+ e_book_client_cursor_get_position (cursor),
+ data->test->total,
+ data->test->position);
+ }
+
+ if (data->test->total == e_book_client_cursor_get_total (cursor) &&
+ data->test->position == e_book_client_cursor_get_position (cursor))
+ g_main_loop_quit (server_fixture->loop);
+}
+
+static void
+cursor_test_position (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ CursorTestPosition *position = (CursorTestPosition *)test;
+ PositionData data = { fixture, position };
+
+ if (g_getenv ("TEST_DEBUG") != NULL) {
+ g_print ("Actual total: %d position: %d, Waiting for total: %d position: %d\n",
+ e_book_client_cursor_get_total (fixture->cursor),
+ e_book_client_cursor_get_position (fixture->cursor),
+ position->total,
+ position->position);
+ }
+
+ /* If testing immediate mode, just assert the immediate position and don't wait for asynchronous
updates*/
+ if (position->immediate) {
+ g_assert_cmpint (e_book_client_cursor_get_total (fixture->cursor), ==, position->total);
+ g_assert_cmpint (e_book_client_cursor_get_position (fixture->cursor), ==, position->position);
+ }
+
+ /* Position is already correct */
+ if (position->total == e_book_client_cursor_get_total (fixture->cursor) &&
+ position->position == e_book_client_cursor_get_position (fixture->cursor))
+ return;
+
+ /* Position & Total is notified asynchronously, connect to signals and
+ * timeout error if the correct position / total is never reached.
+ */
+ position->total_id = g_signal_connect (fixture->cursor, "notify::total",
+ G_CALLBACK (position_or_total_changed),
+ &data);
+ position->position_id = g_signal_connect (fixture->cursor, "notify::position",
+ G_CALLBACK (position_or_total_changed),
+ &data);
+
+ cursor_fixture_timeout_start (fixture, "Timeout waiting for expected position and total");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+
+ g_signal_handler_disconnect (fixture->cursor, position->total_id);
+ g_signal_handler_disconnect (fixture->cursor, position->position_id);
+}
+
+static void
+cursor_test_position_free (CursorTest *test)
+{
+ CursorTestPosition *position = (CursorTestPosition *)test;
+
+ g_slice_free (CursorTestPosition, position);
+}
+
+/******************************************************
+ * Add / Remove Tests *
+ ******************************************************/
+typedef struct {
+ CursorTestType type;
+
+ gchar *case_name;
+ gboolean add;
+} CursorTestAddRemove;
+
+typedef struct {
+ CursorFixture *fixture;
+ CursorTestAddRemove *test;
+ EContact *contact;
+} AddRemoveReadyData;
+
+static void
+cursor_closure_add_remove_contact (CursorClosure *closure,
+ const gchar *case_name,
+ gboolean add)
+{
+ CursorTestAddRemove *test = g_slice_new0 (CursorTestAddRemove);
+
+ test->type = CURSOR_TEST_ADD_REMOVE;
+ test->case_name = g_strdup (case_name);
+ test->add = add;
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+static void
+cursor_closure_add_contact (CursorClosure *closure,
+ const gchar *case_name)
+{
+ cursor_closure_add_remove_contact (closure, case_name, TRUE);
+}
+
+static void
+cursor_closure_remove_contact (CursorClosure *closure,
+ const gchar *case_name)
+{
+ cursor_closure_add_remove_contact (closure, case_name, FALSE);
+}
+
+static void
+cursor_test_add_remove_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+
+ AddRemoveReadyData *data = (AddRemoveReadyData *)user_data;
+ ETestServerFixture *server_fixture = (ETestServerFixture *)data->fixture;
+ gchar *new_uid = NULL;
+ const gchar *contact_uid;
+ gboolean success;
+ GError *error = NULL;
+
+
+ if (data->test->add) {
+ success = e_book_client_add_contact_finish (E_BOOK_CLIENT (source_object),
+ result, &new_uid, &error);
+
+ if (!success)
+ g_error ("Error adding contact: %s", error->message);
+ } else {
+ success = e_book_client_remove_contact_finish (E_BOOK_CLIENT (source_object),
+ result, &error);
+
+ if (!success)
+ g_error ("Error adding contact: %s", error->message);
+ }
+
+ g_clear_error (&error);
+
+ if (data->test->add) {
+ contact_uid = e_contact_get_const (data->contact, E_CONTACT_UID);
+ if (contact_uid)
+ g_assert_cmpstr (contact_uid, ==, new_uid);
+ }
+
+ g_free (new_uid);
+
+ g_main_loop_quit (server_fixture->loop);
+}
+
+static void
+cursor_test_add_remove (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ CursorTestAddRemove *modify = (CursorTestAddRemove *)test;
+ EBookClient *book_client;
+ EContact *contact;
+ gchar *vcard;
+
+ vcard = new_vcard_from_test_case (modify->case_name);
+ contact = e_contact_new_from_vcard (vcard);
+ g_free (vcard);
+
+ book_client = E_TEST_SERVER_UTILS_SERVICE (server_fixture, EBookClient);
+
+ if (closure->async) {
+ AddRemoveReadyData data = { fixture, modify, contact };
+
+ if (modify->add)
+ e_book_client_add_contact (book_client, contact,
+ NULL, cursor_test_add_remove_ready_cb, &data);
+ else
+ e_book_client_remove_contact (book_client, contact,
+ NULL, cursor_test_add_remove_ready_cb, &data);
+
+ /* Wait for result with an error timeout */
+ cursor_fixture_timeout_start (fixture, "Timeout reached while adding a contact");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+ } else {
+ gboolean success;
+ GError *error = NULL;
+ gchar *new_uid = NULL;
+ const gchar *contact_uid;
+
+ if (modify->add)
+ success = e_book_client_add_contact_sync (book_client,
+ contact,
+ &new_uid,
+ NULL, &error);
+ else
+ success = e_book_client_remove_contact_sync (book_client,
+ contact,
+ NULL, &error);
+
+ if (!success)
+ g_error ("Error adding contact: %s", error->message);
+ g_clear_error (&error);
+
+ if (modify->add) {
+ contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+ if (contact_uid)
+ g_assert_cmpstr (contact_uid, ==, new_uid);
+ }
+
+ g_free (new_uid);
+ }
+
+ g_object_unref (contact);
+}
+
+static void
+cursor_test_add_remove_free (CursorTest *test)
+{
+ CursorTestAddRemove *modify = (CursorTestAddRemove *)test;
+
+ g_free (modify->case_name);
+ g_slice_free (CursorTestAddRemove, modify);
+}
+
+
+/******************************************************
+ * Alphabet Index Tests *
+ ******************************************************/
+typedef struct {
+ CursorTestType type;
+
+ gint index;
+} CursorTestAlphabetIndex;
+
+typedef struct {
+ CursorFixture *fixture;
+ CursorTestAlphabetIndex *test;
+} AlphabetIndexReadyData;
+
+static void
+cursor_closure_alphabet_index (CursorClosure *closure,
+ gint index)
+{
+ CursorTestAlphabetIndex *test = g_slice_new0 (CursorTestAlphabetIndex);
+
+ test->type = CURSOR_TEST_ALPHABET_INDEX;
+ test->index = index;
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+static void
+cursor_test_alphabet_index_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+
+ AlphabetIndexReadyData *data = (AlphabetIndexReadyData *)user_data;
+ ETestServerFixture *server_fixture = (ETestServerFixture *)data->fixture;
+ GError *error = NULL;
+
+ if (!e_book_client_cursor_set_alphabetic_index_finish (E_BOOK_CLIENT_CURSOR (source_object),
+ result, &error))
+ g_error ("Error setting alphabet index: %s", error->message);
+
+ g_main_loop_quit (server_fixture->loop);
+}
+
+static void
+cursor_test_alphabet_index (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ CursorTestAlphabetIndex *alphabet_index = (CursorTestAlphabetIndex *)test;
+
+ if (closure->async) {
+
+ AlphabetIndexReadyData data = { fixture, alphabet_index };
+
+ e_book_client_cursor_set_alphabetic_index (fixture->cursor, alphabet_index->index,
+ NULL, cursor_test_alphabet_index_ready_cb, &data);
+
+ /* Wait for result with an error timeout */
+ cursor_fixture_timeout_start (fixture, "Timeout reached while setting alphabet index");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+ } else {
+ GError *error = NULL;
+
+ if (!e_book_client_cursor_set_alphabetic_index_sync (fixture->cursor,
+ alphabet_index->index,
+ NULL, &error))
+ g_error ("Error setting alphabet index: %s", error->message);
+ }
+}
+
+static void
+cursor_test_alphabet_index_free (CursorTest *test)
+{
+ CursorTestAlphabetIndex *alphabet_index = (CursorTestAlphabetIndex *)test;
+
+ g_slice_free (CursorTestAlphabetIndex, alphabet_index);
+}
+
+/******************************************************
+ * Change Locale Tests *
+ ******************************************************/
+typedef struct {
+ CursorTestType type;
+
+ gchar *locale;
+} CursorTestChangeLocale;
+
+static void
+cursor_closure_change_locale (CursorClosure *closure,
+ const gchar *locale)
+{
+ CursorTestChangeLocale *test = g_slice_new0 (CursorTestChangeLocale);
+
+ test->type = CURSOR_TEST_CHANGE_LOCALE;
+ test->locale = g_strdup (locale);
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+static void
+cursor_test_change_locale (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ CursorTestChangeLocale *change_locale = (CursorTestChangeLocale *)test;
+
+ /* There is no sync/async for this */
+ cursor_fixture_set_locale (fixture, change_locale->locale);
+}
+
+static void
+cursor_test_change_locale_free (CursorTest *test)
+{
+ CursorTestChangeLocale *change_locale = (CursorTestChangeLocale *)test;
+
+ g_free (change_locale->locale);
+ g_slice_free (CursorTestChangeLocale, change_locale);
+}
+
+/******************************************************
+ * Alphabet Tests *
+ ******************************************************/
+#define ALPHABET_TEST_LETTERS 5
+
+typedef struct {
+ CursorTestType type;
+
+ gchar *letters[ALPHABET_TEST_LETTERS];
+ gboolean immediate;
+
+ gulong alphabet_id;
+} CursorTestAlphabet;
+
+typedef struct {
+ CursorFixture *fixture;
+ CursorTestAlphabet *test;
+} AlphabetData;
+
+static void
+cursor_closure_alphabet (CursorClosure *closure,
+ gboolean immediate,
+ const gchar *letter0,
+ const gchar *letter1,
+ const gchar *letter2,
+ const gchar *letter3,
+ const gchar *letter4)
+{
+ CursorTestAlphabet *test = g_slice_new0 (CursorTestAlphabet);
+
+ test->type = CURSOR_TEST_ALPHABET;
+ test->immediate = immediate;
+ test->letters[0] = g_strdup (letter0);
+ test->letters[1] = g_strdup (letter1);
+ test->letters[2] = g_strdup (letter2);
+ test->letters[3] = g_strdup (letter3);
+ test->letters[4] = g_strdup (letter4);
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+static gboolean
+expected_alpabet_check (EBookClientCursor *cursor,
+ CursorTestAlphabet *test,
+ gboolean assert_letters)
+{
+ const gchar * const * alphabet;
+ gint i, n_labels = 0;
+ gboolean expected = TRUE;
+
+ alphabet = e_book_client_cursor_get_alphabet (cursor, &n_labels, NULL, NULL, NULL);
+
+ for (i = 0; i < ALPHABET_TEST_LETTERS && expected; i++) {
+
+ /* We compare letters[i] with alphabet[i + 1]
+ * so 'i' must fit into 'n_labels - 2'
+ */
+ if (test->letters[i] != NULL && i > (n_labels - 2)) {
+
+ if (assert_letters)
+ g_error ("Not enough letters in the expected alphabet");
+ else
+ expected = FALSE;
+ } else if (test->letters[i] != NULL) {
+
+ if (assert_letters)
+ g_assert_cmpstr (test->letters[i], ==, alphabet[i + 1]);
+ else
+ expected = (g_strcmp0 (test->letters[i], alphabet[i + 1]) == 0);
+ }
+ }
+
+ return expected;
+}
+
+static void
+alphabet_changed (EBookClientCursor *cursor,
+ GParamSpec *pspec,
+ AlphabetData *data)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)data->fixture;
+
+ if (expected_alpabet_check (cursor, data->test, FALSE))
+ g_main_loop_quit (server_fixture->loop);
+}
+
+static void
+cursor_test_alphabet (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ CursorTestAlphabet *alphabet = (CursorTestAlphabet *)test;
+ AlphabetData data = { fixture, alphabet };
+
+
+ /* Alphabet is already correct */
+ if (expected_alpabet_check (fixture->cursor, alphabet, alphabet->immediate))
+ return;
+
+ /* Alphabet is notified asynchronously, connect to signal and
+ * timeout error if the correct alphabet is never reached.
+ */
+ alphabet->alphabet_id = g_signal_connect (fixture->cursor, "notify::alphabet",
+ G_CALLBACK (alphabet_changed),
+ &data);
+
+ cursor_fixture_timeout_start (fixture, "Timeout waiting for expected alphabet");
+ g_main_loop_run (server_fixture->loop);
+ cursor_fixture_timeout_cancel (fixture);
+
+ g_signal_handler_disconnect (fixture->cursor, alphabet->alphabet_id);
+}
+
+static void
+cursor_test_alphabet_free (CursorTest *test)
+{
+ CursorTestAlphabet *alphabet = (CursorTestAlphabet *)test;
+ gint i;
+
+ for (i = 0; i < ALPHABET_TEST_LETTERS; i++)
+ g_free (alphabet->letters[i]);
+
+ g_slice_free (CursorTestAlphabet, alphabet);
+}
+
+
+
+/******************************************************
+ * Thread Tests *
+ ******************************************************/
+
+struct _CursorThreadTest {
+ CursorThreadFunc test_func;
+ GDestroyNotify destroy_data;
+ gpointer data;
+};
+
+struct _CursorThread {
+ CursorTestType type;
+
+ CursorFixture *fixture;
+ CursorClosure *closure;
+ gchar *thread_name;
+
+ GThread *thread;
+ GMutex mutex;
+ GCond cond;
+ GMainContext *context;
+ GMainLoop *loop;
+ GSource *timeout_source;
+
+ EBookClientCursor *cursor;
+
+ GList *tests;
+
+ guint create_cursor : 1;
+ guint running : 1;
+};
+
+static CursorThread *
+cursor_closure_thread (CursorClosure *closure,
+ gboolean create_cursor,
+ const gchar *format,
+ ...)
+{
+ CursorThread *test = g_slice_new0 (CursorThread);
+ va_list args;
+
+ test->type = CURSOR_TEST_THREAD;
+
+ va_start (args, format);
+ test->thread_name = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ test->create_cursor = create_cursor;
+ g_mutex_init (&test->mutex);
+ g_cond_init (&test->cond);
+
+ closure->tests = g_list_append (closure->tests, test);
+
+ return test;
+}
+
+static gpointer
+cursor_thread_func (gpointer data)
+{
+ CursorThread *thread = (CursorThread *)data;
+ GList *l;
+
+ /* Allocate thread local resources */
+ thread->context = g_main_context_new ();
+ thread->loop = g_main_loop_new (thread->context, FALSE);
+ g_main_context_push_thread_default (thread->context);
+
+ if (thread->create_cursor) {
+ /* Use a separate cursor from a different thread */
+ ETestServerFixture *server_fixture = (ETestServerFixture *)thread->fixture;
+ EBookClient *book_client;
+ GError *error;
+
+ book_client = E_TEST_SERVER_UTILS_SERVICE (server_fixture, EBookClient);
+
+ if (!e_book_client_get_cursor_sync (book_client,
+ NULL,
+ sort_fields,
+ sort_types,
+ N_SORT_FIELDS,
+ &thread->cursor,
+ NULL, &error))
+ g_error ("Error creating cursor in thread: %s", error->message);
+
+ } else {
+ /* Use the same cursor from a different thread */
+ thread->cursor = g_object_ref (thread->fixture->cursor);
+ }
+
+ /* Signal that we're running */
+ g_mutex_lock (&thread->mutex);
+ thread->running = TRUE;
+ g_cond_signal (&thread->cond);
+ g_mutex_unlock (&thread->mutex);
+
+ /* Run tests */
+ for (l = thread->tests; l; l = l->next) {
+ CursorThreadTest *test = l->data;
+
+ test->test_func (thread->fixture,
+ thread->closure,
+ thread,
+ test->data);
+ }
+
+ /* Cleanup and return */
+ g_mutex_lock (&thread->mutex);
+
+ g_object_unref (thread->cursor);
+ g_main_context_pop_thread_default (thread->context);
+ g_main_loop_unref (thread->loop);
+ g_main_context_unref (thread->context);
+
+ thread->context = NULL;
+ thread->loop = NULL;
+ thread->cursor = NULL;
+
+ g_mutex_unlock (&thread->mutex);
+
+
+ return NULL;
+}
+
+static void
+cursor_test_thread (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ CursorThread *thread = (CursorThread *)test;
+
+ thread->fixture = fixture;
+ thread->closure = closure;
+ thread->thread = g_thread_new (thread->thread_name,
+ cursor_thread_func,
+ thread);
+
+ /* Wait for the thread to start */
+ g_mutex_lock (&thread->mutex);
+ while (!thread->running)
+ g_cond_wait (&thread->cond, &thread->mutex);
+ g_mutex_unlock (&thread->mutex);
+}
+
+static void
+cursor_thread_test_free (CursorThreadTest *thread_test)
+{
+ if (thread_test->destroy_data)
+ thread_test->destroy_data (thread_test->data);
+
+ g_slice_free (CursorThreadTest, thread_test);
+}
+
+static void
+cursor_test_thread_free (CursorTest *test)
+{
+ CursorThread *thread = (CursorThread *)test;
+
+ if (thread->thread)
+ g_error ("Freeing thread test before the thread has been joined");
+
+ g_free (thread->thread_name);
+
+ g_mutex_clear (&thread->mutex);
+ g_cond_clear (&thread->cond);
+
+ g_list_free_full (thread->tests, (GDestroyNotify)cursor_thread_test_free);
+
+ g_slice_free (CursorThread, thread);
+}
+
+static void
+cursor_thread_add_test (CursorThread *thread,
+ CursorThreadFunc test_func,
+ gpointer data,
+ GDestroyNotify destroy_data)
+{
+ CursorThreadTest *thread_test = g_slice_new0 (CursorThreadTest);
+
+ thread_test->test_func = test_func;
+ thread_test->destroy_data = destroy_data;
+ thread_test->data = data;
+
+ thread->tests = g_list_append (thread->tests, thread_test);
+}
+
+static void
+cursor_thread_timeout_start (CursorThread *thread,
+ const gchar *error_message)
+{
+ timeout_start (thread->context, &thread->timeout_source, error_message);
+}
+
+static void
+cursor_thread_timeout_cancel (CursorThread *thread)
+{
+ timeout_cancel (&thread->timeout_source);
+}
+
+/******************************************************
+ * Thread Join Tests *
+ ******************************************************/
+typedef struct {
+ CursorTestType type;
+
+ CursorThread *thread;
+} CursorThreadJoin;
+
+static void
+cursor_closure_thread_join (CursorClosure *closure,
+ CursorThread *thread)
+{
+ CursorThreadJoin *test = g_slice_new0 (CursorThreadJoin);
+
+ test->type = CURSOR_TEST_THREAD_JOIN;
+ test->thread = thread;
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+static gboolean
+quit_thread_cb (gpointer data)
+{
+ CursorThread *thread = (CursorThread *)data;
+
+ g_main_loop_quit (thread->loop);
+
+ return FALSE;
+}
+
+static void
+cursor_test_thread_join (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ CursorThreadJoin *join = (CursorThreadJoin *)test;
+ GSource *source;
+
+ g_mutex_lock (&(join->thread->mutex));
+ if (join->thread->context) {
+ source = g_idle_source_new ();
+ g_source_set_callback (source, quit_thread_cb, join->thread, NULL);
+ g_source_attach (source, join->thread->context);
+ g_source_unref (source);
+ }
+ g_mutex_unlock (&(join->thread->mutex));
+
+ g_thread_join (join->thread->thread);
+ join->thread->thread = NULL;
+}
+
+static void
+cursor_test_thread_join_free (CursorTest *test)
+{
+ CursorThreadJoin *join = (CursorThreadJoin *)test;
+
+ g_slice_free (CursorThreadJoin, join);
+}
+
+/******************************************************
+ * Tests *
+ ******************************************************/
+typedef struct {
+ CursorTestType type;
+
+ guint ms;
+} CursorTestSpinLoop;
+
+static void
+cursor_closure_spin_loop (CursorClosure *closure,
+ guint ms)
+{
+ CursorTestSpinLoop *test = g_slice_new0 (CursorTestSpinLoop);
+
+ test->type = CURSOR_TEST_SPIN_LOOP;
+ test->ms = ms;
+
+ closure->tests = g_list_append (closure->tests, test);
+}
+
+static gboolean
+quit_loop_cb (gpointer data)
+{
+ GMainLoop *loop = (GMainLoop *)data;
+
+ g_main_loop_quit (loop);
+ return FALSE;
+}
+
+static void
+cursor_test_spin_loop (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorTest *test)
+{
+ ETestServerFixture *server_fixture = (ETestServerFixture *)fixture;
+ CursorTestSpinLoop *spin = (CursorTestSpinLoop *)test;
+
+ if (spin->ms > 0)
+ g_timeout_add (spin->ms, quit_loop_cb, server_fixture->loop);
+ else
+ g_idle_add (quit_loop_cb, server_fixture->loop);
+
+ g_main_loop_run (server_fixture->loop);
+}
+
+static void
+cursor_test_spin_loop_free (CursorTest *test)
+{
+ CursorTestSpinLoop *spin = (CursorTestSpinLoop *)test;
+
+ g_slice_free (CursorTestSpinLoop, spin);
+}
+
+/******************************************************
+ * Threaded Test Functions *
+ ******************************************************/
+typedef struct {
+ CursorFixture *fixture;
+ CursorClosure *closure;
+ CursorThread *thread;
+
+ gboolean async;
+ gboolean out_of_sync;
+} MoveByLoopData;
+
+static void
+move_by_loop_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ MoveByLoopData *data = (MoveByLoopData *)user_data;
+ GSList *results = NULL;
+ GError *error = NULL;
+
+ if (!e_book_client_cursor_move_by_finish (E_BOOK_CLIENT_CURSOR (source_object),
+ result, &results, &error)) {
+
+ if (g_error_matches (error,
+ E_CLIENT_ERROR,
+ E_CLIENT_ERROR_OUT_OF_SYNC)) {
+ data->out_of_sync = TRUE;
+ g_clear_error (&error);
+ } else {
+ g_error ("Error calling e_book_client_cursor_move_by_finish(): %s",
+ error->message);
+ }
+ }
+
+ g_slist_free_full (results, (GDestroyNotify)g_object_unref);
+ g_main_loop_quit (data->thread->loop);
+}
+
+static void
+move_by_loop_refreshed (EBookClientCursor *cursor,
+ MoveByLoopData *data)
+{
+ g_main_loop_quit (data->thread->loop);
+}
+
+static void
+move_by_loop_iteration (MoveByLoopData *data)
+{
+ GError *error = NULL;
+ GSList *results = NULL;
+
+ if (data->async) {
+ e_book_client_cursor_move_by (data->thread->cursor,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 3,
+ TRUE,
+ NULL,
+ move_by_loop_ready_cb,
+ data);
+
+ cursor_thread_timeout_start (data->thread, "Timeout reached waiting for move_by() reply in a
thread");
+ g_main_loop_run (data->thread->loop);
+ cursor_thread_timeout_cancel (data->thread);
+ } else {
+
+ if (!e_book_client_cursor_move_by_sync (data->thread->cursor,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 3,
+ &results,
+ NULL, &error)) {
+
+ if (g_error_matches (error,
+ E_CLIENT_ERROR,
+ E_CLIENT_ERROR_OUT_OF_SYNC)) {
+ data->out_of_sync = TRUE;
+ g_clear_error (&error);
+ } else {
+ g_error ("Error calling e_book_client_cursor_move_by_sync(): %s",
+ error->message);
+ }
+ }
+
+ g_slist_free_full (results, (GDestroyNotify)g_object_unref);
+ }
+
+ /* Pause here and wait for a refresh if we're out of sync */
+ if (data->out_of_sync) {
+
+ gulong refresh_id = g_signal_connect (data->thread->cursor, "refresh",
+ G_CALLBACK (move_by_loop_refreshed),
+ data);
+
+
+ cursor_thread_timeout_start (data->thread,
+ "Timeout reached waiting for a refresh "
+ "event after an out-of-sync return by move_by()");
+ g_main_loop_run (data->thread->loop);
+ cursor_thread_timeout_cancel (data->thread);
+
+ g_signal_handler_disconnect (data->thread->cursor, refresh_id);
+
+ data->out_of_sync = FALSE;
+ }
+}
+
+static void
+move_by_loop (CursorFixture *fixture,
+ CursorClosure *closure,
+ CursorThread *thread,
+ gpointer user_data)
+{
+ gboolean async = GPOINTER_TO_INT (user_data);
+ MoveByLoopData data = { fixture, closure, thread, async, FALSE };
+ gint i;
+
+ for (i = 0; i < THREAD_ITERATIONS; i++) {
+ move_by_loop_iteration (&data);
+ }
+}
+
+
+/******************************************************
+ * Main, tests defined here *
+ ******************************************************/
+typedef struct {
+ const gchar *base_path;
+ gboolean async;
+ gboolean dra;
+} BaseParams;
+
+typedef struct {
+ const gchar *base_path;
+ gboolean async;
+ gboolean dedicated_cursor;
+} ThreadParams;
+
+static BaseParams base_params[] = {
+ { "/Standard/Sync", FALSE, FALSE },
+ { "/Standard/Async", TRUE, FALSE },
+ { "/Direct/Sync", FALSE, TRUE },
+ { "/Direct/Async", TRUE, TRUE },
+};
+
+static ThreadParams thread_params[] = {
+ { "/SharedCursor/ThreadSync", FALSE, FALSE },
+ { "/SharedCursor/ThreadAsync", TRUE, FALSE },
+ { "/DedicatedCursor/ThreadSync", FALSE, TRUE },
+ { "/DedicatedCursor/ThreadAsync", TRUE, TRUE },
+};
+
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ CursorClosure *closure;
+ CursorThread *thread;
+ CursorThread *threads[CONCURRENT_THREADS];
+ gint i, j, k;
+
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugzilla.gnome.org/");
+
+ for (i = 0; i < G_N_ELEMENTS (base_params); i++) {
+
+ /****************************************************
+ * BASIC SORT ORDERING TESTS *
+ ****************************************************
+ *
+ * Note that all sort ordering can be confirmed with the
+ * chart found in tests/libedata-book/data-test-utils.h
+ */
+
+ /* POSIX Order */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 12, 13, 9, 19, 20);
+ cursor_closure_position (closure, 20, 20, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Order/POSIX%s", base_params[i].base_path);
+
+ /* en_US Order */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "en_US.utf8");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 1, 2, 5, 6, 4, 3, 7, 8, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 12, 13, 9, 19, 20);
+ cursor_closure_position (closure, 20, 20, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Order/en_US%s", base_params[i].base_path);
+
+ /* fr_CA Order */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "fr_CA.utf8");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 1, 2, 5, 6, 4, 3, 7, 8, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 13, 12, 9, 19, 20);
+ cursor_closure_position (closure, 20, 20, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Order/fr_CA%s", base_params[i].base_path);
+
+ /* de_DE Order */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "de_DE.utf8");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 1, 2, 5, 6, 7, 8, 4, 3, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 12, 13, 9, 20, 19);
+ cursor_closure_position (closure, 20, 20, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Order/de_DE%s", base_params[i].base_path);
+
+ /****************************************************
+ * Move By / Origins Test *
+ ****************************************************/
+
+ /* Overshooting the contact list causes position to become 0 */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 11, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 12, 13, 9, 19, 20);
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Move/Overshoot%s", base_params[i].base_path);
+
+ /* Undershooting the contact list (in reverse) causes position to become
+ * (moving -20 should give us position 1) */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ -10, /* Count */
+ 10, /* Expected results */
+ 20, 19, 9, 13, 12, 14, 10, 18, 16, 17);
+ cursor_closure_position (closure, 20, 11, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ -11, /* Count */
+ 10, /* Expected results */
+ 15, 7, 4, 5, 1, 8, 3, 6, 2, 11);
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Move/Undershoot%s",
base_params[i].base_path);
+
+ /* Resetting query to get the beginning of the results */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_RESET,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Move/Reset/Forward%s",
base_params[i].base_path);
+
+ /* Resetting query to get the ending of the results (backwards) */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ -10, /* Count */
+ 10, /* Expected results */
+ 20, 19, 9, 13, 12, 14, 10, 18, 16, 17);
+ cursor_closure_position (closure, 20, 11, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_RESET,
+ -10, /* Count */
+ 10, /* Expected results */
+ 20, 19, 9, 13, 12, 14, 10, 18, 16, 17);
+ cursor_closure_position (closure, 20, 11, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Move/Reset/Backwards%s",
base_params[i].base_path);
+
+ /* Move twice and then repeat query */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 5, /* Count */
+ 5, /* Expected results */
+ 17, 16, 18, 10, 14);
+ cursor_closure_position (closure, 20, 15, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_PREVIOUS,
+ 5, /* Count */
+ 5, /* Expected results */
+ 17, 16, 18, 10, 14);
+ cursor_closure_position (closure, 20, 15, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Move/RepeatPrevious/Forward%s",
base_params[i].base_path);
+
+ /* Move twice and then repeat query */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ -10, /* Count */
+ 10, /* Expected results */
+ 20, 19, 9, 13, 12, 14, 10, 18, 16, 17);
+ cursor_closure_position (closure, 20, 11, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ -5, /* Count */
+ 5, /* Expected results */
+ 15, 7, 4, 5, 1);
+ cursor_closure_position (closure, 20, 6, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_PREVIOUS,
+ -5, /* Count */
+ 5, /* Expected results */
+ 15, 7, 4, 5, 1);
+ cursor_closure_position (closure, 20, 6, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/Move/RepeatPrevious/Backwards%s",
base_params[i].base_path);
+
+ /****************************************************
+ * BASIC SEARCH EXPRESSION TESTS *
+ ****************************************************/
+
+ /* Invalid Query */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_set_sexp (closure,
+ e_book_query_field_test (
+ E_CONTACT_TEL,
+ E_BOOK_QUERY_BEGINS_WITH,
+ "1-800"),
+ FALSE);
+ cursor_closure_add (closure, "/EBookClientCursor/SearchExpression/Invalid%s",
base_params[i].base_path);
+
+ /* Valid Query */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_set_sexp (closure,
+ e_book_query_field_test (
+ E_CONTACT_FULL_NAME,
+ E_BOOK_QUERY_BEGINS_WITH,
+ "Bubba"),
+ TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/SearchExpression/Valid%s",
base_params[i].base_path);
+
+ /* Query Changes Position */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_set_sexp (closure,
+ e_book_query_field_test (
+ E_CONTACT_EMAIL,
+ E_BOOK_QUERY_ENDS_WITH,
+ ".com"),
+ TRUE);
+
+ /* In POSIX Locale, the 10th contact out of 20 becomes the 6th contact out of
+ * 13 contacts bearing a '.com' email address
+ */
+ cursor_closure_position (closure, 13, 6, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/SearchExpression/EffectsPosition%s",
base_params[i].base_path);
+
+ /****************************************************
+ * Address Book Modifications *
+ ****************************************************/
+
+ /* Test that adding a contact changes the total / position appropriately */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_add_contact (closure, "custom-3");
+ cursor_closure_position (closure, 21, 11, FALSE);
+ cursor_closure_add (closure, "/EBookClientCursor/AddContact/TestForward%s",
base_params[i].base_path);
+
+ /* Test that adding a contact changes the total / position appropriately after having moved
from the end */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ -10, /* Count */
+ 10, /* Expected results */
+ 20, 19, 9, 13, 12, 14, 10, 18, 16, 17);
+ cursor_closure_position (closure, 20, 11, TRUE);
+ cursor_closure_add_contact (closure, "custom-3");
+ cursor_closure_position (closure, 21, 12, FALSE);
+ cursor_closure_add (closure, "/EBookClientCursor/AddContact/TestBackwards%s",
base_params[i].base_path);
+
+ /* Test that removing a contact changes the total / position appropriately */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_remove_contact (closure, "sorted-14");
+ cursor_closure_position (closure, 19, 10, FALSE);
+ cursor_closure_add (closure, "/EBookClientCursor/RemoveContact/TestForward%s",
base_params[i].base_path);
+
+ /* Test that removing a contact changes the total / position appropriately after having moved
from the end */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ -10, /* Count */
+ 10, /* Expected results */
+ 20, 19, 9, 13, 12, 14, 10, 18, 16, 17);
+ cursor_closure_position (closure, 20, 11, TRUE);
+ cursor_closure_remove_contact (closure, "sorted-14");
+ cursor_closure_position (closure, 19, 11, FALSE);
+ cursor_closure_add (closure, "/EBookClientCursor/RemoveContact/TestBackwards%s",
base_params[i].base_path);
+
+ /****************************************************
+ * Alphabet Index Tests *
+ ****************************************************/
+
+ /* POSIX locale & Latin indexes */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_alphabet_index (closure, 2);
+ cursor_closure_position (closure, 20, 1, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/AlphabetIndex/Position/B%s",
base_params[i].base_path);
+
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_alphabet_index (closure, 3);
+ cursor_closure_position (closure, 20, 13, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/AlphabetIndex/Position/C%s",
base_params[i].base_path);
+
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_alphabet_index (closure, 13);
+ cursor_closure_position (closure, 20, 18, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/AlphabetIndex/Position/M%s",
base_params[i].base_path);
+
+ /****************************************************
+ * Dynamic Locale Changes *
+ ****************************************************
+ *
+ * Locale changes take effect asynchronously, so we have to
+ * check that the total/position automatically resets itself
+ * asynchronously, not synchronously.
+ *
+ * Other direct e_book_client_cursor_() apis however result
+ * in synchronous updates of the position / total values.
+ *
+ * These tests add an idle pause after setting the locale
+ * using 'cursor_closure_spin_loop (closure, 0)'.
+ *
+ * The locale setting test only blocks until the EBookClient
+ * receives a new locale, but the EBookClientCursor::alphabet
+ * notification may have not been notified, so we add this
+ * idle to ensure that it is before adding any assertions.
+ */
+
+ /* Start in POSIX */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_position (closure, 20, 0, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 2, 6, 3, 8, 1, 5, 4, 7, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 12, 13, 9, 19, 20);
+ cursor_closure_position (closure, 20, 20, TRUE);
+
+ /* Now in en_US */
+ cursor_closure_change_locale (closure, "en_US.utf8");
+ cursor_closure_spin_loop (closure, 0);
+ cursor_closure_position (closure, 20, 0, FALSE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 1, 2, 5, 6, 4, 3, 7, 8, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 12, 13, 9, 19, 20);
+ cursor_closure_position (closure, 20, 20, TRUE);
+
+ /* Now in fr_CA */
+ cursor_closure_change_locale (closure, "fr_CA.utf8");
+ cursor_closure_spin_loop (closure, 0);
+ cursor_closure_position (closure, 20, 0, FALSE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 1, 2, 5, 6, 4, 3, 7, 8, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 13, 12, 9, 19, 20);
+ cursor_closure_position (closure, 20, 20, TRUE);
+
+ /* Now in de_DE */
+ cursor_closure_change_locale (closure, "de_DE.utf8");
+ cursor_closure_spin_loop (closure, 0);
+ cursor_closure_position (closure, 20, 0, FALSE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 11, 1, 2, 5, 6, 7, 8, 4, 3, 15);
+ cursor_closure_position (closure, 20, 10, TRUE);
+ cursor_closure_move_by (closure,
+ E_BOOK_CURSOR_ORIGIN_CURRENT,
+ 10, /* Count */
+ 10, /* Expected results */
+ 17, 16, 18, 10, 14, 12, 13, 9, 20, 19);
+ cursor_closure_position (closure, 20, 20, TRUE);
+ cursor_closure_add (closure, "/EBookClientCursor/ChangeLocale/Order%s",
base_params[i].base_path);
+
+ /*
+ * Check that alphabets are updated after changing locale
+ *
+ * For a variety of locales, check first that the alphabet is latin for
+ * "POSIX" locale (synchronously), and then check that the alphabet changes
+ * to the new expected alphabet for the new locale after dynamically changing
+ * the locale (allow a timeout, alphabet changes are delivered only asynchronously).
+ */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_change_locale (closure, "en_US.utf8");
+ cursor_closure_alphabet (closure, FALSE, "A", "B", "C", "D", "E");
+ cursor_closure_add (closure, "/EBookClientCursor/ChangeLocale/Alphabet/en_US%s",
base_params[i].base_path);
+
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_change_locale (closure, "el_GR.utf8");
+ cursor_closure_alphabet (closure, FALSE, "Α", "Β", "Γ", "Δ", "Ε");
+ cursor_closure_add (closure, "/EBookClientCursor/ChangeLocale/Alphabet/el_GR%s",
base_params[i].base_path);
+
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_change_locale (closure, "ru_RU.utf8");
+ cursor_closure_alphabet (closure, FALSE, "А", "Б", "В", "Г", "Д");
+ cursor_closure_add (closure, "/EBookClientCursor/ChangeLocale/Alphabet/ru_RU%s",
base_params[i].base_path);
+
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_change_locale (closure, "ja_JP.utf8");
+ cursor_closure_alphabet (closure, FALSE, "あ", "か", "さ", "た", "な");
+ cursor_closure_add (closure, "/EBookClientCursor/ChangeLocale/Alphabet/ja_JP%s",
base_params[i].base_path);
+
+ /* The alphabet doesnt change for chinese */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_change_locale (closure, "zh_CN.utf8");
+ cursor_closure_alphabet (closure, FALSE, "A", "B", "C", "D", "E");
+ cursor_closure_add (closure, "/EBookClientCursor/ChangeLocale/Alphabet/zh_CN%s",
base_params[i].base_path);
+
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_change_locale (closure, "ko_KR.utf8");
+ cursor_closure_alphabet (closure, FALSE, "ᄀ", "ᄂ", "ᄃ", "ᄅ", "ᄆ");
+ cursor_closure_add (closure, "/EBookClientCursor/ChangeLocale/Alphabet/ko_KR%s",
base_params[i].base_path);
+
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ cursor_closure_alphabet (closure, TRUE, "A", "B", "C", "D", "E");
+ cursor_closure_change_locale (closure, "ar_TN.utf8");
+ cursor_closure_alphabet (closure, FALSE, "ا", "ب", "ت", "ث", "ج");
+ cursor_closure_add (closure, "/EBookClientCursor/ChangeLocale/Alphabet/ar_TN%s",
base_params[i].base_path);
+
+ /****************************************************
+ * Threaded Tests *
+ ****************************************************/
+
+ for (j = 0; j < G_N_ELEMENTS (thread_params); j++) {
+
+ /*
+ * This test performs a series of cursor moves in a dedicated
+ * thread while the main thread is repeatedly modifying the addressbook
+ *
+ * The move_by_loop() will encounter races where it finds that the addressbook
+ * has been modified, when E_CLIENT_ERROR_OUT_OF_SYNC is reported then it will
+ * wait for a refresh and then continue.
+ *
+ * The test will fail if it crashes from unprotected concurrent memory accesses,
+ * and it will fail if ever an out-of-sync error is not followed by an asynchronous
+ * "refresh" signal.
+ */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ thread = cursor_closure_thread (closure, thread_params[j].dedicated_cursor,
"move-by-thread");
+ cursor_thread_add_test (thread, move_by_loop,
+ GINT_TO_POINTER (thread_params[j].async), NULL);
+
+ for (k = 0; k < THREAD_ITERATIONS; k++) {
+ cursor_closure_add_contact (closure, "custom-3");
+ cursor_closure_spin_loop (closure, 0);
+ cursor_closure_remove_contact (closure, "custom-3");
+ cursor_closure_spin_loop (closure, 0);
+ }
+ cursor_closure_thread_join (closure, thread);
+ cursor_closure_add (closure, "/EBookClientCursor/Thread/MoveBy/AddingAndRemoving%s%s",
+ base_params[i].base_path,
+ thread_params[j].base_path);
+
+ /* The same test as above, except that the addressbook is changing locale instead
+ * of adding / removing contacts
+ */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+ thread = cursor_closure_thread (closure, thread_params[j].dedicated_cursor,
"move-by-thread");
+ cursor_thread_add_test (thread, move_by_loop,
+ GINT_TO_POINTER (thread_params[j].async), NULL);
+
+ for (k = 0; k < THREAD_ITERATIONS; k++) {
+ cursor_closure_change_locale (closure, "de_DE.utf8");
+ cursor_closure_spin_loop (closure, 0);
+ cursor_closure_change_locale (closure, "fr_CA.utf8");
+ cursor_closure_spin_loop (closure, 0);
+ }
+ cursor_closure_thread_join (closure, thread);
+ cursor_closure_add (closure, "/EBookClientCursor/Thread/MoveBy/ChangingLocale%s%s",
+ base_params[i].base_path,
+ thread_params[j].base_path);
+
+
+
+ /* Add / Remove contacts with multiple threaded move_by() operations going on
concurrently */
+ closure = cursor_closure_new (base_params[i].async, base_params[i].dra, "POSIX");
+
+ for (k = 0; k < CONCURRENT_THREADS; k++) {
+ threads[k] = cursor_closure_thread (closure,
thread_params[j].dedicated_cursor, "move-by-thread");
+ cursor_thread_add_test (threads[k], move_by_loop,
+ GINT_TO_POINTER (thread_params[j].async), NULL);
+ }
+
+ for (k = 0; k < THREAD_ITERATIONS; k++) {
+ cursor_closure_add_contact (closure, "custom-3");
+ cursor_closure_spin_loop (closure, 0);
+ cursor_closure_remove_contact (closure, "custom-3");
+ cursor_closure_spin_loop (closure, 0);
+ }
+
+ for (k = 0; k < CONCURRENT_THREADS; k++)
+ cursor_closure_thread_join (closure, threads[k]);
+
+ cursor_closure_add (closure,
"/EBookClientCursor/MultipleThreads/MoveBy/AddingAndRemoving%s%s",
+ base_params[i].base_path,
+ thread_params[j].base_path);
+
+ }
+ }
+
+ return e_test_server_utils_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]