[folks] eds test: add some helper programs for performance testing



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]