From 1f3f85537d501a1ff720b4ef726ac99e7b5d8571 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 20 Mar 2013 16:42:35 +0000 Subject: [PATCH] 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(-) create mode 100644 tests/eds/helper-create-many-contacts.vala create mode 100644 tests/eds/helper-delete-contacts.vala create mode 100644 tests/eds/helper-prepare-aggregator.vala 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 . + * + * Authors: + * Simon McVittie + */ + +/** + * 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 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 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 . + * + * Authors: + * Simon McVittie + */ + +/** + * 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 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 . + * + * Authors: + * Simon McVittie + */ + +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 -- 2.7.4