[folks] eds test: add some helper programs for performance testing
- From: Travis Reitter <treitter src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] eds test: add some helper programs for performance testing
- Date: Wed, 20 Mar 2013 23:27:25 +0000 (UTC)
commit 1f3f85537d501a1ff720b4ef726ac99e7b5d8571
Author: Simon McVittie <simon mcvittie collabora co uk>
Date: Wed Mar 20 16:42:35 2013 +0000
eds test: add some helper programs for performance testing
tests/eds/Makefile.am | 22 +++-
tests/eds/helper-create-many-contacts.vala | 168 +++++++++++++++++++++++++
tests/eds/helper-delete-contacts.vala | 92 ++++++++++++++
tests/eds/helper-prepare-aggregator.vala | 184 ++++++++++++++++++++++++++++
tests/lib/test-utils.vala | 41 ++++++
5 files changed, 505 insertions(+), 2 deletions(-)
---
diff --git a/tests/eds/Makefile.am b/tests/eds/Makefile.am
index bf22cea..ef0b130 100644
--- a/tests/eds/Makefile.am
+++ b/tests/eds/Makefile.am
@@ -29,6 +29,7 @@ AM_VALAFLAGS += \
--vapidir=$(top_srcdir)/backends/eds/lib \
--vapidir=$(top_srcdir)/tests/lib \
--vapidir=$(top_srcdir)/tests/lib/eds \
+ --pkg posix \
--pkg gobject-2.0 \
--pkg gio-2.0 \
--pkg gee-0.8 \
@@ -43,7 +44,7 @@ AM_VALAFLAGS += \
$(NULL)
# in order from least to most complex
-noinst_PROGRAMS = \
+TESTS = \
persona-store-tests \
individual-retrieval \
phone-details \
@@ -87,7 +88,12 @@ TESTS_ENVIRONMENT = \
--session \
--
-TESTS = $(noinst_PROGRAMS)
+noinst_PROGRAMS = \
+ $(TESTS) \
+ helper-create-many-contacts \
+ helper-delete-contacts \
+ helper-prepare-aggregator \
+ $(NULL)
anti_linking_SOURCES = \
anti-linking.vala \
@@ -221,6 +227,18 @@ store_removed_SOURCES = \
store-removed.vala \
$(NULL)
+helper_create_many_contacts_SOURCES = \
+ helper-create-many-contacts.vala \
+ $(NULL)
+
+helper_delete_contacts_SOURCES = \
+ helper-delete-contacts.vala \
+ $(NULL)
+
+helper_prepare_aggregator_SOURCES = \
+ helper-prepare-aggregator.vala \
+ $(NULL)
+
CLEANFILES = \
*.pid \
*.address \
diff --git a/tests/eds/helper-create-many-contacts.vala b/tests/eds/helper-create-many-contacts.vala
new file mode 100644
index 0000000..d02439c
--- /dev/null
+++ b/tests/eds/helper-create-many-contacts.vala
@@ -0,0 +1,168 @@
+/*
+ * Copyright © 2013 Intel Corporation
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Simon McVittie <simon mcvittie collabora co uk>
+ */
+
+/**
+ * helper-create-many-contacts --uid=UID [--n-contacts=N]
+ *
+ * Add N contacts (default 2000) to the Evolution Data Server address book
+ * with the given UID.
+ *
+ * This utility must be called in a Folks test environment, with
+ * DBUS_SESSION_BUS_ADDRESS pointing to a temporary D-Bus session and
+ * XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME pointing into a temporary
+ * directory.
+ *
+ * By default, we use N numbered contacts with some easy test data (currently
+ * full name, one email address and one ICQ number).
+ *
+ * If the environment variable $FOLKS_TESTS_REAL_VCARDS is set, it is a file
+ * containing multiple vCards in text format. Put a source of test data there
+ * (e.g. a copy of your personal address book). The first contacts in that
+ * file will be used as the first contacts in the address book: if there
+ * are more than N only the first N will be used, and if there are fewer than
+ * N, the difference will be made up with numbered contacts.
+ */
+public class Main
+{
+ private static void _add_many (uint n_contacts, string uid) throws GLib.Error
+ {
+ var registry = new E.SourceRegistry.sync ();
+ var source = registry.ref_source (uid);
+ assert (source.uid == uid);
+ var book_client = new E.BookClient (source);
+ book_client.open_sync (false, null);
+ SList<E.Contact> contacts = null;
+
+ var envvar = Environment.get_variable ("FOLKS_TESTS_REAL_VCARDS");
+
+ uint n = 0;
+
+ if (envvar != null)
+ {
+ /* Split at the boundaries between END:VCARD and BEGIN_VCARD,
+ * without removing those tokens from the segments. */
+ string[] cards;
+
+ try
+ {
+ string text;
+ FileUtils.get_contents ((!) envvar, out text);
+
+ var regex = new Regex ("(?<=\r\nEND:VCARD)\r\n+(?=BEGIN:VCARD\r\n)",
+ RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS |
+ RegexCompileFlags.NEWLINE_LF);
+ cards = regex.split_full (text);
+ }
+ catch (Error e)
+ {
+ error ("%s", e.message);
+ }
+
+ foreach (var card in cards)
+ {
+ if (n >= n_contacts)
+ break;
+
+ var contact = new E.Contact.from_vcard (card);
+
+ /* If we got the contact from an e-d-s export, give it a new
+ * unique uid. */
+ contact.id = null;
+
+ contacts.prepend (contact);
+ n++;
+ }
+ }
+
+ while (n < n_contacts)
+ {
+ var contact = new E.Contact ();
+
+ contact.full_name = "Contact %u".printf (n);
+ contact.email_1 = "contact%u example com".printf (n);
+ contact.im_icq_home_1 = "%u".printf (n);
+
+ contacts.prepend (contact);
+ n++;
+ }
+
+ debug ("Importing %u contacts", n);
+
+ SList<string> uids;
+ try
+ {
+ book_client.add_contacts_sync (contacts, out uids, null);
+ }
+ catch (Error e)
+ {
+ error ("%s", e.message);
+ }
+
+ debug ("Imported %u contacts", uids.length ());
+ }
+
+ private static int _n_contacts = 2000;
+ private static string _uid = "";
+ private const GLib.OptionEntry[] _options = {
+ { "n-contacts", 'n', 0, OptionArg.INT, ref Main._n_contacts,
+ "Number of contacts", "CONTACTS" },
+ { "uid", 'u', 0, OptionArg.STRING, ref Main._uid,
+ "Address book uid", "UID" },
+ { null }
+ };
+
+ public static int main (string[] args)
+ {
+ if (Environment.get_variable ("FOLKS_TESTS_SANDBOXED_DBUS") != "eds")
+ error ("e-d-s helpers must be run in a private D-Bus session with " +
+ "e-d-s services");
+
+ try
+ {
+ var context = new OptionContext ("- Create many e-d-s contacts");
+ context.set_help_enabled (true);
+ context.add_main_entries (Main._options, null);
+ context.parse (ref args);
+ }
+ catch (OptionError e)
+ {
+ stderr.printf ("Error parsing arguments: %s\n", e.message);
+ return 2;
+ }
+
+ if (Main._uid == "")
+ {
+ stderr.printf ("The --uid=UID option is required\n");
+ return 2;
+ }
+
+ try
+ {
+ Main._add_many (Main._n_contacts, Main._uid);
+ }
+ catch (Error e)
+ {
+ stderr.printf ("Error: %s\n", e.message);
+ return 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/tests/eds/helper-delete-contacts.vala b/tests/eds/helper-delete-contacts.vala
new file mode 100644
index 0000000..560f93b
--- /dev/null
+++ b/tests/eds/helper-delete-contacts.vala
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2013 Intel Corporation
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Simon McVittie <simon mcvittie collabora co uk>
+ */
+
+/**
+ * helper-delete-contacts --uid=UID
+ *
+ * Delete every contact from the Evolution Data Server address book
+ * with the given UID.
+ *
+ * This utility must be called in a Folks test environment, with
+ * DBUS_SESSION_BUS_ADDRESS pointing to a temporary D-Bus session and
+ * XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME pointing into a temporary
+ * directory.
+ */
+public class Main
+{
+ private static void _remove_all (string uid) throws GLib.Error
+ {
+ var registry = new E.SourceRegistry.sync ();
+ var source = registry.ref_source (uid);
+ assert (source.uid == uid);
+ var book_client = new E.BookClient (source);
+ book_client.open_sync (false, null);
+
+ SList<string> uids;
+ book_client.get_contacts_uids_sync (
+ "(contains \"x-evolution-any-field\" \"\")", out uids);
+ book_client.remove_contacts_sync (uids);
+ }
+
+ private static string _uid = "";
+ private const GLib.OptionEntry[] _options = {
+ { "uid", 'u', 0, OptionArg.STRING, ref Main._uid,
+ "Address book uid", "UID" },
+ { null }
+ };
+
+ public static int main (string[] args)
+ {
+ if (Environment.get_variable ("FOLKS_TESTS_SANDBOXED_DBUS") != "eds")
+ error ("e-d-s helpers must be run in a private D-Bus session with " +
+ "e-d-s services");
+
+ try
+ {
+ var context = new OptionContext ("- Delete all e-d-s contacts");
+ context.set_help_enabled (true);
+ context.add_main_entries (Main._options, null);
+ context.parse (ref args);
+ }
+ catch (OptionError e)
+ {
+ stderr.printf ("Error parsing arguments: %s\n", e.message);
+ return 2;
+ }
+
+ if (Main._uid == "")
+ {
+ stderr.printf ("The --uid=UID option is required\n");
+ return 2;
+ }
+
+ try
+ {
+ Main._remove_all (Main._uid);
+ }
+ catch (Error e)
+ {
+ stderr.printf ("Error: %s\n", e.message);
+ return 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/tests/eds/helper-prepare-aggregator.vala b/tests/eds/helper-prepare-aggregator.vala
new file mode 100644
index 0000000..af639dd
--- /dev/null
+++ b/tests/eds/helper-prepare-aggregator.vala
@@ -0,0 +1,184 @@
+/*
+ * Copyright © 2013 Intel Corporation
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Simon McVittie <simon mcvittie collabora co uk>
+ */
+
+using Folks;
+
+/**
+ * helper-prepare-aggregator [--print-an-individual-id] [--print-a-persona-uid]
+ *
+ * Prepare a Folks IndividualAggregator and iterate through all individuals,
+ * emulating an "ordinary" Folks client application like gnome-contacts.
+ *
+ * If --print-an-individual-id is given, output a representative individual's
+ * globally-unique identifier (Individual.id) to stdout. This can be used
+ * as input for a search, to benchmark how long it takes to search for a
+ * representative individual.
+ *
+ * If --print-a-persona-uid is given, output the globally-unique identifier
+ * (Persona.uid) of a representative one of that individual's personas
+ * to stdout. This can be used as input for a search, to benchmark how long
+ * it takes to search for a representative persona.
+ *
+ * The Individual chosen is the one halfway through iteration, in an attempt
+ * to avoid pathologically good or bad performance. Similarly, the Persona
+ * chosen is the one halfway through when iterating that Individual's
+ * personas.
+ */
+public class Main
+{
+ private const uint _TIMEOUT = 20; /* seconds */
+
+ private static bool _print_an_individual_id = false;
+ private static bool _print_a_persona_uid = false;
+
+ private const GLib.OptionEntry[] _options = {
+ { "print-a-persona-uid", 0, 0, OptionArg.NONE,
+ ref Main._print_a_persona_uid,
+ "Print a more or less arbitrary Persona UID", null },
+ { "print-an-individual-id", 0, 0, OptionArg.NONE,
+ ref Main._print_an_individual_id,
+ "Print a more or less arbitrary Individual ID", null },
+ { null }
+ };
+
+ public static int main (string[] args)
+ {
+ if (Environment.get_variable ("FOLKS_TESTS_SANDBOXED_DBUS") != "eds" ||
+ Environment.get_variable ("FOLKS_BACKENDS_ALLOWED") != "eds" ||
+ Environment.get_variable ("FOLKS_PRIMARY_STORE") == null)
+ error ("e-d-s helpers must be run in a private D-Bus session with " +
+ "e-d-s services");
+
+ try
+ {
+ var context = new OptionContext ("- Create many e-d-s contacts");
+ context.set_help_enabled (true);
+ context.add_main_entries (Main._options, null);
+ context.parse (ref args);
+ }
+ catch (OptionError e)
+ {
+ stderr.printf ("Error parsing arguments: %s\n", e.message);
+ return 2;
+ }
+
+ var loop = new MainLoop (null, false);
+ AsyncResult? result = null;
+ Main._main_async.begin ((nil, res) =>
+ {
+ result = res;
+ loop.quit ();
+ });
+
+ TestUtils.loop_run_with_timeout (loop, 60);
+
+ try
+ {
+ Main._main_async.end ((!) result);
+ }
+ catch (Error e)
+ {
+ error ("%s #%d: %s", e.domain.to_string (), e.code, e.message);
+ }
+
+ return 0;
+ }
+
+ public static async void _main_async () throws GLib.Error
+ {
+ /* g_log() can print to stdout (if the level is less than MESSAGE)
+ * which would spoil our machine-readable output, so we need to
+ * remember the original stdout, then make stdout a copy of stderr.
+ *
+ * Analogous to "3>&1 >&2" in a shell. */
+ var original_stdout = FileStream.fdopen (Posix.dup (1), "w");
+ assert (original_stdout != null);
+ if (Posix.dup2 (2, 1) != 1)
+ error ("dup2(stderr, stdout) failed: %s", GLib.strerror (GLib.errno));
+
+ message ("running helper-prepare-aggregator");
+
+ Test.timer_start ();
+
+ message ("%.6f Setting up test backend", Test.timer_elapsed ());
+
+ var store = BackendStore.dup ();
+
+ yield store.prepare ();
+
+ yield store.load_backends ();
+
+ var eds = store.dup_backend_by_name ("eds");
+ assert (eds != null);
+
+ message ("%.6f Waiting for EDS backend", Test.timer_elapsed ());
+
+ yield TestUtils.backend_prepare_and_wait_for_quiescence ((!) eds);
+
+ message ("%.6f Waiting for aggregator", Test.timer_elapsed ());
+ var aggregator = new IndividualAggregator ();
+ yield TestUtils.aggregator_prepare_and_wait_for_quiescence (aggregator);
+
+ var map = aggregator.individuals;
+ message ("%.6f Aggregated into %d individuals", Test.timer_elapsed (),
+ map.size);
+
+ var iter = map.map_iterator ();
+ int i = 0;
+
+ while (iter.next ())
+ {
+ var individual = iter.get_value ();
+
+ debug ("%s → %s", iter.get_key (), individual.full_name);
+
+ /* We use the individual ID that's halfway through in iteration
+ * order, in the hope that that'll avoid pathologically good or bad
+ * performance. */
+ if (Main._print_an_individual_id && i == map.size / 2)
+ {
+ message ("choosing individual %s - %s\n", individual.id,
+ iter.get_key ());
+ original_stdout.printf ("%s\n", iter.get_key ());
+ }
+
+ if (Main._print_a_persona_uid && i == map.size / 2)
+ {
+ var personas = individual.personas;
+ int j = 0;
+
+ foreach (var persona in personas)
+ {
+ /* We use the persona that's halfway through in iteration
+ * order, for the same reason. */
+ if (j == personas.size / 2)
+ {
+ message ("choosing persona %s\n", persona.uid);
+ original_stdout.printf ("%s\n", persona.uid);
+ }
+
+ j++;
+ }
+ }
+
+ i++;
+ }
+ }
+}
diff --git a/tests/lib/test-utils.vala b/tests/lib/test-utils.vala
index dbb1ecf..7c95d6c 100644
--- a/tests/lib/test-utils.vala
+++ b/tests/lib/test-utils.vala
@@ -178,6 +178,47 @@ public class Folks.TestUtils
}
/**
+ * Prepare a backend and wait for it to reach quiescence.
+ *
+ * This will prepare the given { link Backend} then yield until it reaches
+ * quiescence. No timeout is used, so if the backend never reaches quiescence,
+ * this function will never return; callers must add their own timeout to
+ * avoid this if necessary.
+ *
+ * When this returns, the backend is guaranteed to be quiescent.
+ *
+ * @param backend the backend to prepare
+ */
+ public static async void backend_prepare_and_wait_for_quiescence (
+ Backend backend) throws GLib.Error
+ {
+ var has_yielded = false;
+ var signal_id = backend.notify["is-quiescent"].connect ((obj, pspec) =>
+ {
+ if (has_yielded == true)
+ {
+ TestUtils.backend_prepare_and_wait_for_quiescence.callback ();
+ }
+ });
+
+ try
+ {
+ yield backend.prepare ();
+
+ if (backend.is_quiescent == false)
+ {
+ has_yielded = true;
+ yield;
+ }
+ }
+ finally
+ {
+ backend.disconnect (signal_id);
+ assert (backend.is_quiescent == true);
+ }
+ }
+
+ /**
* Prepare an aggregator and wait for it to reach quiescence.
*
* This will prepare the given { link IndividualAggregator} then yield until
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]