[evolution-data-server/openismus-work: 11/52] Adding test case proving there is a write-write race condition to fix



commit 6e84c664992af33e6d167267379f4ce9cd78bf0a
Author: Tristan Van Berkom <tristanvb openismus com>
Date:   Thu Oct 25 16:51:32 2012 +0900

    Adding test case proving there is a write-write race condition to fix
    
    The test case creates a number of threads all trying to modify the
    same contact in the same book each concurrently with a different field,
    if the modification fails it retries. If all the fields (one for each
    thread) are not properly set at the end of all thread execution, then
    the test is a failure.

 tests/libebook/client/Makefile.am               |    3 +
 tests/libebook/client/test-client-write-write.c |  273 +++++++++++++++++++++++
 2 files changed, 276 insertions(+), 0 deletions(-)
---
diff --git a/tests/libebook/client/Makefile.am b/tests/libebook/client/Makefile.am
index edb1920..611281b 100644
--- a/tests/libebook/client/Makefile.am
+++ b/tests/libebook/client/Makefile.am
@@ -37,6 +37,7 @@ TESTS =								\
 	test-client-remove-contact-by-uid			\
 	test-client-remove-contacts				\
 	test-client-photo-is-uri				\
+	test-client-write-write					\
 	test-client-stress-factory--serial			\
 	test-client-stress-factory--fifo			\
 	test-client-stress-factory--single-book			\
@@ -100,6 +101,8 @@ test_client_remove_contacts_LDADD=$(TEST_LIBS)
 test_client_remove_contacts_CPPFLAGS=$(TEST_CPPFLAGS)
 test_client_photo_is_uri_LDADD=$(TEST_LIBS)
 test_client_photo_is_uri_CPPFLAGS=$(TEST_CPPFLAGS)
+test_client_write_write_LDADD=$(TEST_LIBS)
+test_client_write_write_CPPFLAGS=$(TEST_CPPFLAGS)
 test_client_stress_factory__fifo_LDADD=$(TEST_LIBS)
 test_client_stress_factory__fifo_CPPFLAGS=$(TEST_CPPFLAGS)
 test_client_stress_factory__serial_LDADD=$(TEST_LIBS)
diff --git a/tests/libebook/client/test-client-write-write.c b/tests/libebook/client/test-client-write-write.c
new file mode 100644
index 0000000..ddbe0f6
--- /dev/null
+++ b/tests/libebook/client/test-client-write-write.c
@@ -0,0 +1,273 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+#include <stdlib.h>
+#include <libebook/libebook.h>
+
+#include "client-test-utils.h"
+
+
+typedef struct {
+	EContactField field;
+	const gchar  *value;
+} TestData;
+
+typedef struct {
+	GThread       *thread;
+	const gchar   *book_uid;
+	const gchar   *contact_uid;
+	EContactField  field;
+	const gchar   *value;
+	EBookClient   *client;
+	GMainLoop     *loop;
+} ThreadData;
+
+static const TestData field_tests[] = {
+	{ E_CONTACT_GIVEN_NAME,          "Elvis" },
+	{ E_CONTACT_FAMILY_NAME,         "Presley" },
+	{ E_CONTACT_NICKNAME,            "The King" },
+	{ E_CONTACT_EMAIL_1,             "elvis presley com" },
+	{ E_CONTACT_EMAIL_2,             "theking elvispresley com" },
+	{ E_CONTACT_EMAIL_3,             "elvispresley theking com" },
+	{ E_CONTACT_EMAIL_4,             "another email com" },
+	{ E_CONTACT_ADDRESS_LABEL_HOME,  "3764 Elvis Presley Boulevard, Graceland" },
+	{ E_CONTACT_ADDRESS_LABEL_WORK,  "Workin on the road again..." },
+	{ E_CONTACT_ADDRESS_LABEL_OTHER, "Another address to reach the king" },
+	{ E_CONTACT_PHONE_ASSISTANT,     "+1234567890" },
+	{ E_CONTACT_PHONE_BUSINESS,      "+99-123-4352-9943" },
+	{ E_CONTACT_PHONE_BUSINESS_2,    "+99-123-4352-9943" },
+	{ E_CONTACT_PHONE_BUSINESS_FAX,  "+44-123456789" },
+	{ E_CONTACT_PHONE_CALLBACK,      "+11-222-3333-4444" },
+	{ E_CONTACT_PHONE_CAR,           "555-123-4567" },
+	{ E_CONTACT_PHONE_COMPANY,       "666-666-6666" },
+	{ E_CONTACT_PHONE_HOME,          "333-4444-5678" },
+	{ E_CONTACT_PHONE_HOME_2,        "444-555-66666" },
+	{ E_CONTACT_PHONE_HOME_FAX,      "+993355556666" },
+	{ E_CONTACT_PHONE_ISDN,          "+88-777-6666-5555" },
+	{ E_CONTACT_PHONE_MOBILE,        "333-3333" },
+	{ E_CONTACT_PHONE_OTHER,         "0987654321" }
+};
+
+static gboolean try_write_field_thread_idle (ThreadData *data);
+
+
+static void
+test_write_thread_contact_modified (GObject *source_object,
+				    GAsyncResult *res,
+				    ThreadData *data)
+{
+	GError   *error = NULL;
+
+	if (!e_book_client_modify_contact_finish (E_BOOK_CLIENT (source_object), res, &error)) {
+		g_error_free (error);
+
+		try_write_field_thread_idle (data);
+	} else {
+		g_main_loop_quit (data->loop);
+	}
+}
+
+static void
+test_write_thread_contact_fetched (GObject *source_object,
+				   GAsyncResult *res,
+				   ThreadData *data)
+{
+	EContact *contact = NULL;
+	GError   *error = NULL;
+
+	if (!e_book_client_get_contact_finish (E_BOOK_CLIENT (source_object), res, &contact, &error))
+		g_error ("Failed to fetch contact in thread '%s': %s",
+			 e_contact_field_name (data->field), error->message);
+
+	e_contact_set (contact, data->field, data->value);
+
+	e_book_client_modify_contact (data->client, contact, NULL, 
+				      (GAsyncReadyCallback)test_write_thread_contact_modified, data);
+
+	g_object_unref (contact);
+}
+
+static gboolean
+try_write_field_thread_idle (ThreadData *data)
+{
+	e_book_client_get_contact (data->client, data->contact_uid, NULL,
+				   (GAsyncReadyCallback)test_write_thread_contact_fetched, data);
+
+	return FALSE;
+}
+
+static void
+test_write_thread_client_opened (GObject *source_object,
+				 GAsyncResult *res,
+				 ThreadData *data)
+{
+	GMainContext *context;
+	GSource      *gsource;
+	GError       *error = NULL;
+
+	if (!e_client_open_finish (E_CLIENT (source_object), res, &error))
+		g_error ("Error opening client for thread '%s': %s",
+			 e_contact_field_name (data->field),
+			 error->message);
+
+	context = g_main_loop_get_context (data->loop);
+	gsource = g_idle_source_new ();
+	g_source_set_callback (gsource, (GSourceFunc)try_write_field_thread_idle, data, NULL);
+	g_source_attach (gsource, context);
+}
+
+static gboolean
+test_write_thread_open_idle (ThreadData *data)
+{
+	/* Open the book client, only if it exists, it should be the same book created by the main thread */
+	e_client_open (E_CLIENT (data->client), TRUE, NULL, (GAsyncReadyCallback)test_write_thread_client_opened, data);
+
+	return FALSE;
+}
+
+static gpointer
+test_write_thread (ThreadData *data)
+{
+	GMainContext    *context;
+	ESourceRegistry *registry;
+	GSource         *gsource;
+	ESource         *source;
+	GError          *error = NULL;
+
+	context    = g_main_context_new ();
+	data->loop = g_main_loop_new (context, FALSE);
+	g_main_context_push_thread_default (context);
+
+	/* Open the test book client in this thread */
+	registry = e_source_registry_new_sync (NULL, &error);
+	if (!registry)
+		g_error ("Unable to create the registry: %s", error->message);
+
+	source = e_source_registry_ref_source (registry, data->book_uid);
+	if (!source)
+		g_error ("Unable to fetch source uid '%s' from the registry", data->book_uid);
+
+	data->client = e_book_client_new (source, &error);
+	if (!data->client)
+		g_error ("Unable to create EBookClient for uid '%s': %s", data->book_uid, error->message);
+
+	/* Retry setting the contact field until we succeed setting the field
+	 */
+	gsource = g_idle_source_new ();
+	g_source_set_callback (gsource, (GSourceFunc)test_write_thread_open_idle, data, NULL);
+	g_source_attach (gsource, context);
+	g_main_loop_run (data->loop);
+
+	g_object_unref (source);
+	g_object_unref (registry);
+
+	g_object_unref (data->client);
+	g_main_context_pop_thread_default (context);
+	g_main_loop_unref (data->loop);
+	g_main_context_unref (context);
+
+	return NULL;
+}
+
+static ThreadData *
+create_test_thread (const gchar   *book_uid,
+		    const gchar   *contact_uid,
+		    EContactField  field,
+		    const gchar   *value)
+{
+	ThreadData  *data = g_slice_new0 (ThreadData);
+	const gchar *name = e_contact_field_name (field);
+
+	data->book_uid    = book_uid;
+	data->contact_uid = contact_uid;
+	data->field       = field;
+	data->value       = value;
+
+	data->thread = g_thread_new (name, (GThreadFunc)test_write_thread, data);
+
+	return data;
+}
+
+static void
+wait_thread_test (ThreadData *data)
+{
+	g_thread_join (data->thread);
+	g_slice_free (ThreadData, data);
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	EBookClient *main_client;
+	EContact *contact;
+	GError *error = NULL;
+	gchar *book_uid = NULL;
+	gchar *contact_uid = NULL;
+	ThreadData **tests;
+	gint i;
+
+	main_initialize ();
+
+	/* Open the book */
+	main_client = new_temp_client (&book_uid);
+	g_return_val_if_fail (main_client != NULL, 1);
+
+	if (!e_client_open_sync (E_CLIENT (main_client), FALSE, NULL, &error)) {
+		report_error ("client open sync", &error);
+		g_object_unref (main_client);
+		return 1;
+	}
+
+	/* Create out test contact */
+	if (!add_contact_from_test_case_verify (main_client, "simple-1", &contact)) {
+		g_object_unref (main_client);
+		return 1;
+	}
+
+	contact_uid = e_contact_get (contact, E_CONTACT_UID);
+	g_object_unref (contact);
+
+
+	/* Create all concurrent threads accessing the same addressbook */
+	tests = g_new0 (ThreadData *, G_N_ELEMENTS (field_tests));
+	for (i = 0; i < G_N_ELEMENTS (field_tests); i++)
+		tests[i] = create_test_thread (book_uid, contact_uid,
+					       field_tests[i].field,
+					       field_tests[i].value);
+
+	/* Wait for all threads to complete */
+	for (i = 0; i < G_N_ELEMENTS (field_tests); i++)
+		wait_thread_test (tests[i]);
+
+	/* Fetch the updated contact */
+	if (!e_book_client_get_contact_sync (main_client, contact_uid, &contact, NULL, &error))
+		g_error ("Failed to fetch test contact after updates: %s", error->message);
+
+	/* Ensure that every value written to the contact concurrently was actually updated in
+	 * the final contact
+	 */
+	for (i = 0; i < G_N_ELEMENTS (field_tests); i++) {
+		gchar *value = e_contact_get (contact, field_tests[i].field);
+
+		if (g_strcmp0 (field_tests[i].value, value) != 0)
+			g_error ("Lost data in concurrent writes, expected value for field '%s' was '%s', actual value is '%s'",
+				 e_contact_field_name (field_tests[i].field), field_tests[i].value, value);
+
+		g_free (value);
+	}
+	g_object_unref (contact);
+
+	g_object_unref (main_client);
+
+	g_free (book_uid);
+	g_free (contact_uid);
+
+	/* Remove the book, test complete */
+	if (!e_client_remove_sync (E_CLIENT (main_client), NULL, &error)) {
+		report_error ("client remove sync", &error);
+		g_object_unref (main_client);
+		return 1;
+	}
+
+	return 0;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]