[folks/648811-dummy-backend-non-dbus] dummy: UNFINISHED work to document backend and port tests to it



commit 747bc2741ac513fd1866238012e052e988780cac
Author: Philip Withnall <philip tecnocode co uk>
Date:   Wed May 22 23:57:15 2013 +0100

    dummy: UNFINISHED work to document backend and port tests to it

 backends/dummy/dummy-backend-factory.vala   |    2 +-
 backends/dummy/lib/dummy-backend.vala       |   77 ++++++++-
 backends/dummy/lib/dummy-fat-persona.vala   |  233 +++++++++++++++++++++++++--
 backends/dummy/lib/dummy-persona-store.vala |  232 ++++++++++++++++++++++++---
 backends/dummy/lib/dummy-persona.vala       |    8 +
 configure.ac                                |    1 +
 tests/data/Makefile.am                      |    1 +
 tests/data/backend-store-all.ini            |    3 +
 tests/folks/Makefile.am                     |   10 +-
 tests/folks/aggregation.vala                |  125 +++++----------
 tests/lib/Makefile.am                       |    8 +-
 tests/lib/test-case.vala                    |   36 ++++-
 tests/lib/test-utils.vala                   |   70 ++++++++
 13 files changed, 674 insertions(+), 132 deletions(-)
---
diff --git a/backends/dummy/dummy-backend-factory.vala b/backends/dummy/dummy-backend-factory.vala
index 9b7a6f6..08df1cd 100644
--- a/backends/dummy/dummy-backend-factory.vala
+++ b/backends/dummy/dummy-backend-factory.vala
@@ -43,5 +43,5 @@ public void module_init (BackendStore backend_store)
  */
 public void module_finalize (BackendStore backend_store)
 {
-  /* TODO */
+  /* TODO: No backend_store.remove_backend() API exists. */
 }
diff --git a/backends/dummy/lib/dummy-backend.vala b/backends/dummy/lib/dummy-backend.vala
index 6646649..d76463c 100644
--- a/backends/dummy/lib/dummy-backend.vala
+++ b/backends/dummy/lib/dummy-backend.vala
@@ -25,8 +25,23 @@ using Folks;
 extern const string BACKEND_NAME;
 
 /**
- * A backend which connects to EDS and creates a { link PersonaStore}
- * for each service. TODO
+ * A backend which allows { link Dummyf.PersonaStore}s and
+ * { link Dummyf.Persona}s to be programmatically created and manipulated, for
+ * the purposes of testing the core of libfolks itself.
+ *
+ * This backend is not meant to be enabled in production use. The methods on
+ * { link Dummyf.Backend} (and other classes) for programmatically manipulating
+ * the backend's state are considered internal to libfolks and are not stable.
+ *
+ * This backend maintains two sets of persona stores: the set of all persona
+ * stores, and the set of enabled persona stores (which must be a subset of the
+ * former). { link Dummyf.Backend.register_persona_stores} adds persona stores
+ * to the set of all stores. Optionally it also enables them, adding them to the
+ * set of enabled stores. The set of persona stores advertised by the backend as
+ * { link Backend.persona_stores} is the set of enabled stores. libfolks may
+ * internally enable or disable stores using
+ * { link Backend.enable_persona_store}, { link Backend.disable_persona_store}
+ * and { link Backend.set_persona_stores}.
  *
  * @since UNRELEASED
  */
@@ -278,13 +293,42 @@ public class Dummyf.Backend : Folks.Backend
         }
     }
 
+
+  /*
+   * All the functions below here are to be used by testing code rather than by
+   * libfolks clients. They form the interface which would normally be between
+   * the Backend and a web service or backing store of some kind.
+   */
+
+
   /**
-   * TODO
+   * Register and enable some { link Dummyf.PersonaStore}s.
    *
+   * For each of the persona stores in ``stores``, register it with this
+   * backend. If ``enable_stores`` is ``true``, added stores will also be
+   * enabled, emitting { link Backend.persona_store_added} for each
+   * newly-enabled store. After all addition signals are emitted, a change
+   * notification for { link Backend.persona_stores} will be emitted (but only
+   * if at least one addition signal is emitted).
+   *
+   * Persona stores are identified by their { link PersonaStore.id}; if a store
+   * in ``stores`` has the same ID as a store previously registered through this
+   * method, the duplicate will be ignored (so
+   * { link Backend.persona_store_added} won't be emitted for that store).
+   *
+   * Persona stores must be instances of { link Dummyf.PersonaStore} or
+   * subclasses of it, allowing for different persona store implementations to
+   * be tested.
+   *
+   * @param stores set of persona stores to register
+   * @param enable_stores whether to automatically enable the stores
    * @since UNRELEASED
    */
-  public void register_persona_stores (Set<PersonaStore> stores)
+  public void register_persona_stores (Set<PersonaStore> stores,
+      bool enable_stores = true)
     {
+      this.freeze_notify ();
+
       foreach (var store in stores)
         {
           if (this._all_persona_stores.has_key (store.id))
@@ -293,17 +337,36 @@ public class Dummyf.Backend : Folks.Backend
             }
 
           this._all_persona_stores.set (store.id, store);
-          this._enable_persona_store (store);
+
+          if (enable_stores == true)
+            {
+              this._enable_persona_store (store);
+            }
         }
+
+      this.thaw_notify ();
     }
 
   /**
-   * TODO
+   * Disable and unregister some { link Dummyf.PersonaStores}.
+   *
+   * For each of the persona stores in ``stores``, disable it (if it was
+   * enabled) and unregister it from the backend so that it cannot be re-enabled
+   * using { link Backend.enable_persona_store} or
+   * { link Backend.set_persona_stores}.
+   *
+   * { link Backend.persona_store_removed} will be emitted for all persona
+   * stores in ``stores`` which were previously enabled. After all removal
+   * signals are emitted, a change notification for
+   * { link Backend.persona_stores} will be emitted (but only if at least one
+   * removal signal is emitted).
    *
    * @since UNRELEASED
    */
   public void unregister_persona_stores (Set<PersonaStore> stores)
     {
+      this.freeze_notify ();
+
       foreach (var store in stores)
         {
           if (!this._all_persona_stores.has_key (store.id))
@@ -314,5 +377,7 @@ public class Dummyf.Backend : Folks.Backend
           this._disable_persona_store (store);
           this._all_persona_stores.unset (store.id);
         }
+
+      this.thaw_notify ();
     }
 }
diff --git a/backends/dummy/lib/dummy-fat-persona.vala b/backends/dummy/lib/dummy-fat-persona.vala
index 643e5c8..c157734 100644
--- a/backends/dummy/lib/dummy-fat-persona.vala
+++ b/backends/dummy/lib/dummy-fat-persona.vala
@@ -16,7 +16,9 @@
  *
  * Authors:
  *       Philip Withnall <philip tecnocode co uk>
- * TODO
+ *       Travis Reitter <travis reitter collabora co uk>
+ *       Marco Barisione <marco barisione collabora co uk>
+ *       Raul Gutierrez Segales <raul gutierrez segales collabora co uk>
  */
 
 using Folks;
@@ -668,10 +670,14 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         });
     }
 
-  /**
-   * TODO All the functions below here are...
+
+  /*
+   * All the functions below here are to be used by testing code rather than by
+   * libfolks clients. They form the interface which would normally be between
+   * the Persona and a web service or backing store of some kind.
    */
 
+
   private HashSet<T> _dup_to_hash_set<T> (Set<T> input_set)
     {
       var output_set = new HashSet<T> ();
@@ -696,6 +702,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
       return output_multi_map;
     }
 
+  /**
+   * Update persona's gender.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link GenderDetails.gender} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param gender persona's new gender
+   * @since UNRELEASED
+   */
   public void update_gender (Gender gender)
     {
       if (this._gender != gender)
@@ -705,6 +722,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's birthday calendar event ID.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link BirthdayDetails.calendar_event_id} property. It is intended to be
+   * used for testing code which consumes this property. If the property value
+   * changes, this results in a property change notification on the persona.
+   *
+   * @param calendar_event_id persona's new birthday calendar event ID
+   * @since UNRELEASED
+   */
   public void update_calendar_event_id (string? calendar_event_id)
     {
       if (calendar_event_id != this._calendar_event_id)
@@ -714,6 +742,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's birthday.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link BirthdayDetails.birthday} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param birthday persona's new birthday
+   * @since UNRELEASED
+   */
   public void update_birthday (DateTime? birthday)
     {
       if ((this._birthday == null) != (birthday == null) ||
@@ -725,6 +764,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's roles.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link RoleDetails.roles} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param roles persona's new roles
+   * @since UNRELEASED
+   */
   public void update_roles (Set<RoleFieldDetails> roles)
     {
       if (!Folks.Internal.equal_sets<RoleFieldDetails> (roles, this._roles))
@@ -735,6 +785,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's groups.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link GroupDetails.groups} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param groups persona's new groups
+   * @since UNRELEASED
+   */
   public void update_groups (Set<string> groups)
     {
       if (!Folks.Internal.equal_sets<string> (groups, this._groups))
@@ -745,6 +806,18 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's web service addresses.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link WebServiceDetails.web_service_addresses} property. It is intended to
+   * be used for testing code which consumes this property. If the property
+   * value changes, this results in a property change notification on the
+   * persona.
+   *
+   * @param web_service_addresses persona's new web service addresses
+   * @since UNRELEASED
+   */
   public void update_web_service_addresses (
       MultiMap<string, WebServiceFieldDetails> web_service_addresses)
     {
@@ -758,6 +831,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's e-mail addresses.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link EmailDetails.email_addresses} property. It is intended to be used
+   * for testing code which consumes this property. If the property value
+   * changes, this results in a property change notification on the persona.
+   *
+   * @param email_addresses persona's new e-mail addresses
+   * @since UNRELEASED
+   */
   public void update_email_addresses (Set<EmailFieldDetails> email_addresses)
     {
       if (!Folks.Internal.equal_sets<EmailFieldDetails> (email_addresses,
@@ -770,6 +854,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
        }
     }
 
+  /**
+   * Update persona's notes.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link NoteDetails.notes} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param notes persona's new notes
+   * @since UNRELEASED
+   */
   public void update_notes (Set<NoteFieldDetails> notes)
     {
       if (!Folks.Internal.equal_sets<NoteFieldDetails> (notes, this._notes))
@@ -780,6 +875,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's full name.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link NameDetails.full_name} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param full_name persona's new full name
+   * @since UNRELEASED
+   */
   public void update_full_name (string full_name)
     {
       if (this._full_name != full_name)
@@ -789,6 +895,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's nickname.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link NameDetails.nickname} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param nickname persona's new nickname
+   * @since UNRELEASED
+   */
   public void update_nickname (string nickname)
     {
       if (this._nickname != nickname)
@@ -798,6 +915,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's structured name.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link NameDetails.structured_name} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param structured_name persona's new structured name
+   * @since UNRELEASED
+   */
   public void update_structured_name (StructuredName? structured_name)
     {
       if (structured_name != null && !((!) structured_name).is_empty ())
@@ -812,6 +940,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's avatar.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link AvatarDetails.avatar} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param avatar persona's new avatar
+   * @since UNRELEASED
+   */
   public void update_avatar (LoadableIcon? avatar)
     {
       if ((this._avatar == null) != (avatar == null) ||
@@ -823,6 +962,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's URIs.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link UrlDetails.urls} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param urls persona's new URIs
+   * @since UNRELEASED
+   */
   public void update_urls (Set<UrlFieldDetails> urls)
     {
       if (!Utils.set_afd_equal (urls, this._urls))
@@ -833,6 +983,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's IM addresses.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link ImDetails.im_addresses} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param im_addresses persona's new IM addresses
+   * @since UNRELEASED
+   */
   public void update_im_addresses (
       MultiMap<string, ImFieldDetails> im_addresses)
     {
@@ -846,16 +1007,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
-  public void _update_groups (Set<string> groups)
-    {
-      if (!Folks.Internal.equal_sets<string> (groups, this._groups))
-        {
-          this._groups = this._dup_to_hash_set<string> (groups);
-          this._groups_ro = this._groups.read_only_view;
-          this.notify_property ("groups");
-        }
-   }
-
+  /**
+   * Update persona's phone numbers.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link PhoneDetails.phone_numbers} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param phone_numbers persona's new phone numbers
+   * @since UNRELEASED
+   */
   public void update_phone_numbers (Set<PhoneFieldDetails> phone_numbers)
     {
       if (!Folks.Internal.equal_sets<PhoneFieldDetails> (phone_numbers,
@@ -868,6 +1030,18 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
    }
 
+  /**
+   * Update persona's postal addresses.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link PostalAddressDetails.postal_addresses} property. It is intended to
+   * be used for testing code which consumes this property. If the property
+   * value changes, this results in a property change notification on the
+   * persona.
+   *
+   * @param postal_addresses persona's new postal addresses
+   * @since UNRELEASED
+   */
   public void update_postal_addresses (
       Set<PostalAddressFieldDetails> postal_addresses)
     {
@@ -882,6 +1056,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's local IDs.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link LocalIdDetails.local_ids} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param local_ids persona's new local IDs
+   * @since UNRELEASED
+   */
   public void update_local_ids (Set<string> local_ids)
     {
       /* Make sure it includes our local ID. */
@@ -895,6 +1080,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's status as a favourite.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link FavouriteDetails.is_favourite} property. It is intended to be used
+   * for testing code which consumes this property. If the property value
+   * changes, this results in a property change notification on the persona.
+   *
+   * @param is_favourite persona's new status as a favourite
+   * @since UNRELEASED
+   */
   public void update_is_favourite (bool is_favourite)
     {
       if (is_favourite != this._is_favourite)
@@ -904,6 +1100,17 @@ public class Dummyf.FatPersona : Dummyf.Persona,
         }
     }
 
+  /**
+   * Update persona's anti-links.
+   *
+   * This simulates a backing-store-side update of the persona's
+   * { link AntiLinkable.anti_links} property. It is intended to be used for
+   * testing code which consumes this property. If the property value changes,
+   * this results in a property change notification on the persona.
+   *
+   * @param anti_links persona's new anti-links
+   * @since UNRELEASED
+   */
   public void update_anti_links (Set<string> anti_links)
     {
       if (!Folks.Internal.equal_sets<string> (anti_links, this._anti_links))
diff --git a/backends/dummy/lib/dummy-persona-store.vala b/backends/dummy/lib/dummy-persona-store.vala
index e2b6f2d..f9e9c6e 100644
--- a/backends/dummy/lib/dummy-persona-store.vala
+++ b/backends/dummy/lib/dummy-persona-store.vala
@@ -23,10 +23,14 @@ using Gee;
 using GLib;
 
 /**
- * A persona store representing a single EDS address book. TODO
+ * A persona store which allows { link Dummyf.Persona}s to be programmatically
+ * created and manipulated, for the purposes of testing the core of libfolks
+ * itself.
  *
- * The persona store will contain { link Dummy.Persona}s for each contact in the
- * address book it represents.
+ * TODO: Mock functions
+ * TODO: Unstable API
+ *
+ * TODO
  *
  * TODO: trust_level and is_user_set_default can be set as normal properties
  *
@@ -192,12 +196,12 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
     }
 
   /**
-   * Create a new PersonaStore.
+   * Create a new persona store.
    *
-   * Create a new persona store to store the { link Persona}s for the contacts
-   * in ``s``. Passing a re-used source registry to the constructor (compared to
-   * the old { link Dummy.PersonaStore} constructor) saves a lot of time and
-   * D-Bus round trips. TODO
+   * This store will have no personas to begin with; use
+   * { link Dummyf.PersonaStore.register_personas} to add some, then call
+   * { link Dummyf.PersonaStore.reach_quiescence} to signal the store reaching
+   * quiescence.
    *
    * @param id The new store's ID.
    * @param display_name The new store's display name.
@@ -222,6 +226,43 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
     }
 
   /**
+   * Type of a mock function for { link PersonaStore.add_persona_from_details}.
+   *
+   * See { link Dummyf.PersonaStore.add_persona_from_details_mock}.
+   *
+   * @param persona the persona being added to the store, as constructed from
+   * the details passed to { link PersonaStore.add_persona_from_details}.
+   * @throws PersonaStoreError to be thrown from
+   * { link PersonaStore.add_persona_from_details}
+   * @since UNRELEASED
+   */
+  public delegate void AddPersonaFromDetailsMock (Persona persona)
+      throws PersonaStoreError;
+
+  /**
+   * Mock function for { link PersonaStore.add_persona_from_details}.
+   *
+   * This function is called whenever this store's
+   * { link PersonaStore.add_persona_from_details} method is called. It allows
+   * the caller to determine whether adding the given persona should fail, by
+   * throwing an error from this mock function. If no error is thrown from this
+   * function, adding the given persona will succeed. This is useful for testing
+   * error handling of calls to { link PersoneStore.add_persona_from_details}.
+   *
+   * If this is ``null``, all calls to
+   * { link PersonaStore.add_persona_from_details} will succeed.
+   *
+   * This mock function may be changed at any time; changes will take effect for
+   * the next call to { link PersonaStore.add_persona_from_details}.
+   *
+   * @since UNRELEASED
+   */
+  public unowned AddPersonaFromDetailsMock? add_persona_from_details_mock
+    {
+      get; set; default = null;
+    }
+
+  /**
    * Add a new { link Persona} to the PersonaStore.
    *
    * Accepted keys for ``details`` are:
@@ -251,7 +292,7 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
    * @since UNRELEASED
    */
   public override async Folks.Persona? add_persona_from_details (
-      HashTable<string, Value?> details) throws Folks.PersonaStoreError
+      HashTable<string, Value?> details) throws PersonaStoreError
     {
       // We have to have called prepare() beforehand.
       if (!this._is_prepared)
@@ -260,8 +301,20 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
               "Persona store has not yet been prepared.");
         }
 
-      /* TODO: Allow overriding the class used. */
-      var persona = new Dummyf.Persona (this, "TODO", false, {});
+      /* Allow overriding the class used. */
+      var contact_id = "TODO";
+      var uid = Folks.Persona.build_uid (BACKEND_NAME, this.id, contact_id);
+      var iid = this.id + ":" + contact_id;
+
+      var persona = Object.new (this._persona_type,
+          "display-id", contact_id,
+          "uid", uid,
+          "iid", iid,
+          "store", this,
+          "is-user", false,
+          null) as Dummyf.Persona;
+      assert (persona != null);
+      persona.update_writeable_properties (this.always_writeable_properties);
 
       var iter = HashTableIter<string, Value?> (details);
       unowned string k;
@@ -388,7 +441,14 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
             }*/
         }
 
-      /* TODO: How to simulate failure? */
+      /* Allow the caller to inject failures into add_persona_from_details()
+       * by providing a mock function which can throw errors as appropriate. */
+      if (this.add_persona_from_details_mock != null)
+        {
+          this.add_persona_from_details_mock (persona);
+        }
+
+      /* No simulated failure: continue adding the persona. */
       this._personas.set (persona.iid, persona);
 
       /* Notify of the new persona. */
@@ -400,6 +460,42 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
     }
 
   /**
+   * Type of a mock function for { link PersonaStore.remove_persona}.
+   *
+   * See { link Dummyf.PersonaStore.remove_persona_mock}.
+   *
+   * @param persona the persona being removed from the store
+   * @throws PersonaStoreError to be thrown from
+   * { link PersonaStore.remove_persona}
+   * @since UNRELEASED
+   */
+  public delegate void RemovePersonaMock (Persona persona)
+      throws PersonaStoreError;
+
+  /**
+   * Mock function for { link PersonaStore.remove_persona}.
+   *
+   * This function is called whenever this store's
+   * { link PersonaStore.remove_persona} method is called. It allows
+   * the caller to determine whether removing the given persona should fail, by
+   * throwing an error from this mock function. If no error is thrown from this
+   * function, removing the given persona will succeed. This is useful for
+   * testing error handling of calls to { link PersoneStore.remove_persona}.
+   *
+   * If this is ``null``, all calls to { link PersonaStore.remove_persona} will
+   * succeed.
+   *
+   * This mock function may be changed at any time; changes will take effect for
+   * the next call to { link PersonaStore.remove_persona}.
+   *
+   * @since UNRELEASED
+   */
+  public unowned RemovePersonaMock? remove_persona_mock
+    {
+      get; set; default = null;
+    }
+
+  /**
    * Remove a { link Persona} from the PersonaStore.
    *
    * See { link Folks.PersonaStore.remove_persona}.
@@ -416,7 +512,8 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
    * @since UNRELEASED
    */
   public override async void remove_persona (Folks.Persona persona)
-      throws Folks.PersonaStoreError
+      throws PersonaStoreError
+      requires (persona is Dummyf.Persona)
     {
       // We have to have called prepare() beforehand.
       if (!this._is_prepared)
@@ -425,9 +522,15 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
               "Persona store has not yet been prepared.");
         }
 
-      /* TODO: How to simulate failure? */
+      /* Allow the caller to inject failures into remove_persona()
+       * by providing a mock function which can throw errors as appropriate. */
+      if (this.remove_persona_mock != null)
+        {
+          this.remove_persona_mock ((Dummyf.Persona) persona);
+        }
+
       Persona? _persona = this._personas.get (persona.iid);
-      if (persona != null)
+      if (_persona != null)
         {
           this._personas.unset (persona.iid);
 
@@ -446,15 +549,50 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
     }
 
   /**
+   * Type of a mock function for { link PersonaStore.prepare}.
+   *
+   * See { link Dummyf.PersonaStore.prepare_mock}.
+   *
+   * @throws PersonaStoreError to be thrown from { link PersonaStore.prepare}
+   * @since UNRELEASED
+   */
+  public delegate void PrepareMock () throws PersonaStoreError;
+
+  /**
+   * Mock function for { link PersonaStore.prepare}.
+   *
+   * This function is called whenever this store's
+   * { link PersonaStore.prepare} method is called on an unprepared store. It
+   * allows the caller to determine whether preparing the store should fail, by
+   * throwing an error from this mock function. If no error is thrown from this
+   * function, preparing the store will succeed (and all future calls to
+   * { link PersonaStore.prepare} will return immediately without calling this
+   * mock function). This is useful for testing error handling of calls to
+   * { link PersoneStore.prepare}.
+   *
+   * If this is ``null``, all calls to { link PersonaStore.prepare} will
+   * succeed.
+   *
+   * This mock function may be changed at any time; changes will take effect for
+   * the next call to { link PersonaStore.prepare}.
+   *
+   * @since UNRELEASED
+   */
+  public unowned PrepareMock? prepare_mock
+    {
+      get; set; default = null;
+    }
+
+  /**
    * Prepare the PersonaStore for use.
    *
    * See { link Folks.PersonaStore.prepare}.
    *
-   * @throws Folks.PersonaStoreError.STORE_OFFLINE if the EDS store is offline
+   * @throws Folks.PersonaStoreError.STORE_OFFLINE if the store is offline
    * @throws Folks.PersonaStoreError.PERMISSION_DENIED if permission was denied
-   * to open the EDS store
+   * to open the store
    * @throws Folks.PersonaStoreError.INVALID_ARGUMENT if any other error
-   * occurred in the EDS store
+   * occurred in the store
    *
    * @since UNRELEASED
    */
@@ -472,6 +610,13 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
         {
           this._prepare_pending = true;
 
+          /* Allow the caller to inject failures into prepare() by providing a
+           * mock function which can throw errors as appropriate. */
+          if (this.prepare_mock != null)
+            {
+              this.prepare_mock ();
+            }
+
           this._is_prepared = true;
           this.notify_property ("is-prepared");
 
@@ -490,12 +635,55 @@ public class Dummyf.PersonaStore : Folks.PersonaStore
       Internal.profiling_end ("preparing Dummy.PersonaStore");
     }
 
+
+  /*
+   * All the functions below here are to be used by testing code rather than by
+   * libfolks clients. They form the interface which would normally be between
+   * the PersonaStore and a web service or backing store of some kind.
+   */
+
+
+  private Type _persona_type = typeof (Dummyf.Persona);
+
   /**
-   * TODO
+   * Type of programmatically created personas.
+   *
+   * This is the type used to create new personas when
+   * { link PersonaStore.add_persona_from_details} is called. It must be a
+   * subtype of { link Dummyf.Persona}.
    *
-   * @param can_add_personas TODO
-   * @param can_alias_personas TODO
-   * @param can_remove_personas TODO
+   * This may be modified at any time, with modifications taking effect for the
+   * next call to { link PersonaStore.add_persona_from_details}.
+   *
+   * @since UNRELEASED
+   */
+  public Type persona_type
+    {
+      get { return this._persona_type; }
+      set
+        {
+          assert (value.is_a (typeof (Dummyf.Persona)));
+          if (this._persona_type != value)
+            {
+              this._persona_type = value;
+              this.notify_property ("persona-type");
+            }
+        }
+    }
+
+  /**
+   * Set capabilities of the persona store.
+   *
+   * This sets the capabilities of the store, as if they were changed on a
+   * backing store somewhere. This is intended to be used for testing code which
+   * depends on the values of { link PersonaStore.can_add_personas},
+   * { link PersonaStore.can_alias_personas} and
+   * { link PersonaStore.can_remove_personas}.
+   *
+   * @param can_add_personas whether the store can handle adding personas
+   * @param can_alias_personas whether the store can handle and update
+   * user-specified persona aliases
+   * @param can_remove_personas whether the store can handle removing personas
    * @since UNRELEASED
    */
   public void set_capabilities (MaybeBool can_add_personas,
diff --git a/backends/dummy/lib/dummy-persona.vala b/backends/dummy/lib/dummy-persona.vala
index 8b5f467..9634347 100644
--- a/backends/dummy/lib/dummy-persona.vala
+++ b/backends/dummy/lib/dummy-persona.vala
@@ -153,6 +153,14 @@ public class Dummyf.Persona : Folks.Persona
         }
     }
 
+
+  /*
+   * All the functions below here are to be used by testing code rather than by
+   * libfolks clients. They form the interface which would normally be between
+   * the Persona and a web service or backing store of some kind.
+   */
+
+
   /**
    * TODO
    *
diff --git a/configure.ac b/configure.ac
index 3ec081f..a3522bb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -663,6 +663,7 @@ AC_CONFIG_FILES([
     tests/tracker/Makefile
     tests/lib/Makefile
     tests/lib/folks-test-uninstalled.pc
+    tests/lib/dummy/Makefile
     tests/lib/eds/Makefile
     tests/lib/key-file/Makefile
     tests/lib/libsocialweb/Makefile
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index c8884b4..017b570 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -1,6 +1,7 @@
 EXTRA_DIST = \
        avatar-01.jpg \
        backend-store-all.ini \
+       backend-store-dummy-only.ini \
        $(NULL)
 
 -include $(top_srcdir)/git.mk
diff --git a/tests/data/backend-store-all.ini b/tests/data/backend-store-all.ini
index a24f714..bc019c7 100644
--- a/tests/data/backend-store-all.ini
+++ b/tests/data/backend-store-all.ini
@@ -5,5 +5,8 @@ enabled=true
 [telepathy]
 enabled=true
 
+[dummy]
+enabled=true
+
 [all-others]
 enabled=false
diff --git a/tests/folks/Makefile.am b/tests/folks/Makefile.am
index bfc362c..5b79a84 100644
--- a/tests/folks/Makefile.am
+++ b/tests/folks/Makefile.am
@@ -3,7 +3,9 @@ AM_CPPFLAGS = \
        $(GEE_CFLAGS) \
        $(TP_GLIB_CFLAGS) \
        -I$(top_srcdir)/folks \
+       -I$(top_srcdir)/backends/dummy/lib \
        -I$(top_srcdir)/tests/lib \
+       -I$(top_srcdir)/tests/lib/dummy \
        -I$(top_srcdir)/tests/lib/key-file \
        -I$(top_srcdir)/tests/lib/telepathy/contactlist \
        -include $(CONFIG_HEADER) \
@@ -11,8 +13,10 @@ AM_CPPFLAGS = \
 
 LDADD = \
        $(top_builddir)/tests/lib/libfolks-test.la \
+       $(top_builddir)/tests/lib/dummy/libdummy-test.la \
        $(top_builddir)/tests/lib/key-file/libkf-test.la \
        $(top_builddir)/tests/lib/telepathy/contactlist/libtp-test-contactlist.la \
+       $(top_builddir)/backends/dummy/lib/libfolks-dummy.la \
        $(top_builddir)/folks/libfolks.la \
        $(GLIB_LIBS) \
        $(GEE_LIBS) \
@@ -29,7 +33,9 @@ AM_VALAFLAGS = \
        --disable-warnings \
        --vapidir=. \
        --vapidir=$(top_srcdir)/folks \
+       --vapidir=$(top_srcdir)/backends/dummy/lib \
        --vapidir=$(top_srcdir)/tests/lib \
+       --vapidir=$(top_srcdir)/tests/lib/dummy \
        --vapidir=$(top_srcdir)/tests/lib/key-file \
        --vapidir=$(top_builddir)/tests/lib/telepathy/contactlist/ \
        --pkg gobject-2.0 \
@@ -37,8 +43,10 @@ AM_VALAFLAGS = \
        --pkg gee-1.0 \
        --pkg folks \
        --pkg folks-test \
+       --pkg dummy-test \
        --pkg kf-test \
        --pkg tp-test-contactlist \
+       --pkg folks-dummy \
        -g \
        $(NULL)
 
@@ -55,7 +63,7 @@ noinst_PROGRAMS = \
        $(NULL)
 
 SESSION_CONF = $(top_builddir)/tests/lib/telepathy/contactlist/session.conf
-backend_store_key_file=$(top_srcdir)/tests/data/backend-store-all.ini
+backend_store_key_file=$(top_srcdir)/tests/data/backend-store-dummy-only.ini # TODO
 avatar_file= abs_top_srcdir@/tests/data/avatar-01.jpg
 TESTS_ENVIRONMENT = \
        FOLKS_BACKEND_PATH=$(BACKEND_UNINST_PATH) \
diff --git a/tests/folks/aggregation.vala b/tests/folks/aggregation.vala
index e555998..006e98d 100644
--- a/tests/folks/aggregation.vala
+++ b/tests/folks/aggregation.vala
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2013 Philip Withnall
  *
  * 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
@@ -15,16 +16,18 @@
  * along with this library.  If not, see <http://www.gnu.org/licenses/>.
  *
  * Authors: Travis Reitter <travis reitter collabora co uk>
+ *          Philip Withnall <philip tecnocode co uk>
  */
 
 using Gee;
 using Folks;
 using TpTests;
 
-public class AggregationTests : Folks.TestCase
+public class AggregationTests : Folks.NormalTestCase
 {
   private KfTest.Backend _kf_backend;
   private TpTests.Backend _tp_backend;
+  private DummyTest.Backend _dummy_backend;
   private HashSet<string> _default_personas;
   private int _test_timeout = 3;
 
@@ -34,6 +37,7 @@ public class AggregationTests : Folks.TestCase
 
       this._kf_backend = new KfTest.Backend ();
       this._tp_backend = new TpTests.Backend ();
+      this._dummy_backend = new DummyTest.Backend ();
 
       /* Create a set of the individuals we expect to see */
       this._default_personas = new HashSet<string> (str_hash, str_equal);
@@ -67,14 +71,18 @@ public class AggregationTests : Folks.TestCase
           this._test_timeout = 10;
     }
 
-  public override void set_up ()
+  public override async void set_up ()
     {
+      yield base.set_up ();
+      this._dummy_backend.set_up (this._backend_store);
       this._tp_backend.set_up ();
     }
 
-  public override void tear_down ()
+  public override async void tear_down ()
     {
       this._tp_backend.tear_down ();
+      this._dummy_backend.tear_down ();
+      yield base.tear_down ();
     }
 
   /* Test that personas are aggregated if their IIDs match (e.g. with the
@@ -88,108 +96,59 @@ public class AggregationTests : Folks.TestCase
     {
       var main_loop = new GLib.MainLoop (null, false);
 
-      this._kf_backend.set_up ("");
-
-      void* account1_handle = this._tp_backend.add_account ("protocol",
-          "me example com", "cm", "account");
-      void* account2_handle = this._tp_backend.add_account ("protocol",
-          "me2 example com", "cm", "account2");
-
-      /* IDs of the individuals we expect to see.
-       * These are externally opaque, but internally are SHA-1 hashes of the
-       * concatenated UIDs of the Personas in the Individual. In these cases,
-       * each default_individual contains two Personas with the same IID.
-       * e.g.
-       *  telepathy:/org/freedesktop/Telepathy/Account/cm/protocol/account2:sjoerd example com
-       * and
-       *  telepathy:/org/freedesktop/Telepathy/Account/cm/protocol/account:sjoerd example com
-       * in a single Individual. */
-      var default_individuals = new HashSet<string> ();
-
-      /* guillaume example com */
-      default_individuals.add ("6380b17dc511b21a1defd4811f1add97b278f92c");
-      /* sjoerd example com */
-      default_individuals.add ("6b08188cb2ef8cbaca140b277780069b5af8add6");
-      /* travis example com */
-      default_individuals.add ("60c91326018f6a60604f8d260fc24a60a5b8512c");
-      /* olivier example com */
-      default_individuals.add ("0e46c5e74f61908f49550d241f2a1651892a1695");
-      /* christian example com */
-      default_individuals.add ("07b913b8977c04d2f2011e26a46ea3e3dcfe3e3d");
-      /* geraldine example com */
-      default_individuals.add ("f948d4d2af79085ab860f0ef67bf0c201c4602d4");
-      /* helen example com */
-      default_individuals.add ("f34529a442577b840a75271b464e90666c38c464");
-      /* wim example com */
-      default_individuals.add ("467d13f955e62bf30ebf9620fa052aaee2160260");
-
-      /* Work on a copy of the set of individuals so we can mangle it. We keep
-       * one copy of the set for the individuals_changed signal, and one for
-       * the individuals_changed_detailed signal so that we can compare their
-       * behaviour. */
-      HashSet<string> expected_individuals = new HashSet<string> ();
-      var expected_individuals_detailed = new HashSet<string> ();
-      foreach (var id in default_individuals)
-        {
-          expected_individuals.add (id);
-          expected_individuals_detailed.add (id);
-        }
-
-      /* Set up the aggregator */
-      var aggregator = new IndividualAggregator ();
-      aggregator.individuals_changed_detailed.connect ((changes) =>
-        {
-          var removed = changes.get_keys ();
-          var added = changes.get_values ();
-
-          this._test_iid_individuals_changed (true, added, removed,
-              default_individuals, expected_individuals_detailed);
-        });
-      aggregator.individuals_changed.connect ((added, removed, m, a, r) =>
-        {
-          this._test_iid_individuals_changed (false, added, removed,
-              default_individuals, expected_individuals);
-        });
-
       /* Kill the main loop after a few seconds. If there are still individuals
        * in the set of expected individuals, the aggregator has either failed or
        * been too slow (which we can consider to be failure). */
       Timeout.add_seconds (this._test_timeout, () =>
         {
+          warning ("Killing main loop after %u seconds.", this._test_timeout);
           main_loop.quit ();
           return false;
         });
 
       Idle.add (() =>
         {
-          aggregator.prepare.begin ((s,r) =>
+          this.test_iid_async.begin ((s, r) =>
             {
-              try
-                {
-                  aggregator.prepare.end (r);
-                }
-              catch (GLib.Error e1)
-                {
-                  GLib.critical ("Failed to prepare aggregator: %s",
-                    e1.message);
-                  assert_not_reached ();
-                }
+              this.test_iid_async.end (r);
+              main_loop.quit ();
             });
 
           return false;
         });
 
       main_loop.run ();
+    }
+
+  public async void test_iid_async ()
+    {
+      var store1 = this._dummy_backend.add_persona_store ("store1", {},
+          (store) =>
+        {
+          return { new Dummyf.Persona (store, "iid1") };
+        });
+
+      var store2 = this._dummy_backend.add_persona_store ("store2", {},
+          (store) =>
+        {
+          return { new Dummyf.Persona (store, "iid1") };
+        });
+
+      this._kf_backend.set_up ("");
 
-      /* We should have enumerated exactly the individuals in the set */
-      assert (expected_individuals.size == 0);
-      assert (expected_individuals_detailed.size == 0);
+      /* Prepare the aggregator. */
+      var individuals = yield this.individual_aggregator_prepare ();
+
+      /* Check the individuals. */
+      TestUtils.individuals_map_equals (individuals,
+        {
+          "store1:iid1,store2:iid1"
+        });
 
       /* Clean up for the next test */
-      this._tp_backend.remove_account (account2_handle);
-      this._tp_backend.remove_account (account1_handle);
+      this._dummy_backend.remove_persona_store (store2);
+      this._dummy_backend.remove_persona_store (store1);
       this._kf_backend.tear_down ();
-      aggregator = null;
     }
 
   private void _test_iid_individuals_changed (bool detailed,
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index bcaf81d..a0a53f8 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -1,4 +1,5 @@
 SUBDIRS = \
+       dummy \
        key-file \
        $(NULL)
 
@@ -19,6 +20,7 @@ SUBDIRS += eds
 endif
 
 DIST_SUBDIRS = \
+       dummy \
        key-file \
        telepathy \
        eds \
@@ -28,7 +30,11 @@ DIST_SUBDIRS = \
 
 noinst_LTLIBRARIES = libfolks-test.la
 
-libfolks_test_la_SOURCES = test-case.vala test-utils.vala
+libfolks_test_la_SOURCES = \
+       normal-test-case.vala \
+       test-case.vala \
+       test-utils.vala \
+       $(NULL)
 
 libfolks_test_la_CFLAGS = \
        $(AM_CFLAGS) \
diff --git a/tests/lib/test-case.vala b/tests/lib/test-case.vala
index a0715a1..05ca1bb 100644
--- a/tests/lib/test-case.vala
+++ b/tests/lib/test-case.vala
@@ -43,11 +43,11 @@ public abstract class Folks.TestCase : Object
             adaptor.name, adaptor.set_up, adaptor.run, adaptor.tear_down));
     }
 
-  public virtual void set_up ()
+  public virtual async void set_up ()
     {
     }
 
-  public virtual void tear_down ()
+  public virtual async void tear_down ()
     {
     }
 
@@ -56,7 +56,7 @@ public abstract class Folks.TestCase : Object
       return this._suite;
     }
 
-       private class Adaptor
+  private class Adaptor
     {
       public string name { get; private set; }
       private unowned TestMethod _test;
@@ -74,7 +74,20 @@ public abstract class Folks.TestCase : Object
           GLib.set_printerr_handler (Adaptor._printerr_func_stack_trace);
           Log.set_default_handler (this._log_func_stack_trace);
 
-          this._test_case.set_up ();
+          var main_loop = new MainLoop ();
+
+          Idle.add (() =>
+            {
+              this._test_case.set_up.begin ((s, r) =>
+                {
+                  this._test_case.set_up.end (r);
+                  main_loop.quit ();
+                });
+
+              return false;
+            });
+
+          main_loop.run ();
         }
 
       private static void _printerr_func_stack_trace (string? text)
@@ -112,7 +125,20 @@ public abstract class Folks.TestCase : Object
 
       public void tear_down (void* fixture)
         {
-          this._test_case.tear_down ();
+          var main_loop = new MainLoop ();
+
+          Idle.add (() =>
+            {
+              this._test_case.tear_down.begin ((s, r) =>
+                {
+                  this._test_case.tear_down.end (r);
+                  main_loop.quit ();
+                });
+
+              return false;
+            });
+
+          main_loop.run ();
         }
     }
 }
diff --git a/tests/lib/test-utils.vala b/tests/lib/test-utils.vala
index f6e97cd..8c3cfc9 100644
--- a/tests/lib/test-utils.vala
+++ b/tests/lib/test-utils.vala
@@ -22,6 +22,7 @@
 
 using Folks;
 using GLib;
+using Gee;
 
 public class Folks.TestUtils
 {
@@ -135,4 +136,73 @@ public class Folks.TestUtils
           assert (aggregator.is_quiescent == true);
         }
     }
+
+  /**
+   * TODO
+   *
+   * @since UNRELEASED
+   */
+  public static bool personas_set_equals (Set<Persona> actual_personas,
+      string[] expected_personas)
+    {
+      if (actual_personas.size != expected_personas.length)
+        {
+          return false;
+        }
+
+      foreach (var p in actual_personas)
+        {
+          if (!(p.uid in expected_personas))
+            {
+              return false;
+            }
+        }
+
+      return true;
+    }
+
+  /**
+   * TODO
+   *
+   * @since UNRELEASED
+   */
+  public static bool individuals_map_equals (
+      Map<string, Individual> actual_individuals, string[] expected_individuals)
+    {
+      if (actual_individuals.size != expected_individuals.length)
+        {
+          return false;
+        }
+
+      var _actual_individuals = new HashSet<Individual> ();
+      _actual_individuals.add_all (actual_individuals.values);
+      assert (_actual_individuals.size == actual_individuals.size);
+
+      var _expected_individuals = new HashSet<string> ();
+      foreach (var i in expected_individuals)
+        {
+          _expected_individuals.add (i);
+        }
+
+      foreach (var i in _actual_individuals)
+        {
+          var actual_personas = i.personas;
+
+          foreach (var _expected_personas in _expected_individuals)
+            {
+              var expected_personas = _expected_personas.split (",");
+
+              if (TestUtils.personas_set_equals (actual_personas,
+                  expected_personas) == true)
+                {
+                  _expected_individuals.remove (_expected_personas);
+                  break;
+                }
+            }
+
+          return false;
+        }
+
+      return true;
+    }
 }



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