[folks/648811-dummy-backend-rebase1: 2/41] dummy: UNFINISHED work to document backend and port tests to it



commit 57b62ba98c9bde60d46d606c6078ccc9d7010495
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/folks/Makefile.am                     |   10 ++
 tests/folks/aggregation.vala                |   27 ++--
 tests/lib/Makefile.am                       |    3 +
 tests/lib/test-case.vala                    |   58 +++++++
 tests/lib/test-utils.vala                   |   70 ++++++++
 11 files changed, 665 insertions(+), 56 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 04cdb22..406730e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -695,6 +695,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/folks/Makefile.am b/tests/folks/Makefile.am
index 0856529..a605ec3 100644
--- a/tests/folks/Makefile.am
+++ b/tests/folks/Makefile.am
@@ -8,7 +8,9 @@ AM_CPPFLAGS = \
        $(TP_GLIB_CFLAGS) \
        -I$(top_srcdir) \
        -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 \
        -I$(top_srcdir)/tests/lib/telepathy/contactlist \
@@ -18,9 +20,11 @@ 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/libtpf-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) \
@@ -37,8 +41,12 @@ AM_VALAFLAGS += \
        --vapidir=$(abs_builddir) \
        --vapidir=$(abs_top_srcdir)/folks \
        --vapidir=$(abs_top_builddir)/folks \
+       --vapidir=$(abs_top_srcdir)/backends/dummy/lib \
+       --vapidir=$(abs_top_builddir)/backends/dummy/lib \
        --vapidir=$(abs_top_srcdir)/tests/lib \
        --vapidir=$(abs_top_builddir)/tests/lib \
+       --vapidir=$(abs_top_srcdir)/tests/lib/dummy \
+       --vapidir=$(abs_top_builddir)/tests/lib/dummy \
        --vapidir=$(abs_top_srcdir)/tests/lib/key-file \
        --vapidir=$(abs_top_builddir)/tests/lib/key-file \
        --vapidir=$(abs_top_srcdir)/tests/lib/telepathy \
@@ -51,9 +59,11 @@ AM_VALAFLAGS += \
        --pkg folks \
        --pkg folks-generics \
        --pkg folks-test \
+       --pkg dummy-test \
        --pkg kf-test \
        --pkg tpf-test \
        --pkg tp-test-contactlist \
+       --pkg folks-dummy \
        -g \
        $(NULL)
 
diff --git a/tests/folks/aggregation.vala b/tests/folks/aggregation.vala
index 22946bb..4b361ce 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,6 +16,7 @@
  * 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;
@@ -135,18 +137,10 @@ public class AggregationTests : TpfTest.MixedTestCase
 
       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;
@@ -154,9 +148,14 @@ public class AggregationTests : TpfTest.MixedTestCase
 
       TestUtils.loop_run_with_non_fatal_timeout (main_loop, 3);
 
-      /* 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 */
       tp_backend.remove_account (account2_handle);
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index e080949..d2721b9 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -1,6 +1,7 @@
 # Build . first so that backends' test libraries can link to libfolks-test.la
 SUBDIRS = \
        . \
+       dummy \
        key-file \
        $(NULL)
 
@@ -27,6 +28,7 @@ SUBDIRS += eds
 endif
 
 DIST_SUBDIRS = \
+       dummy \
        key-file \
        telepathy \
        eds \
@@ -38,6 +40,7 @@ noinst_LTLIBRARIES = libfolks-test.la
 
 libfolks_test_la_SOURCES = \
        haze-remove-directory.c \
+       normal-test-case.vala \
        test-case.vala \
        test-case-helper.c \
        test-utils.vala \
diff --git a/tests/lib/test-case.vala b/tests/lib/test-case.vala
index 82b0700..0180a79 100644
--- a/tests/lib/test-case.vala
+++ b/tests/lib/test-case.vala
@@ -240,6 +240,7 @@ public abstract class Folks.TestCase : Object
       this._suite.add (add_test_helper (name, test));
     }
 
+<<<<<<< HEAD
   /* implemented in test-case-helper.c */
   internal extern GLib.TestCase add_test_helper (string name, TestMethod test);
 
@@ -262,6 +263,13 @@ public abstract class Folks.TestCase : Object
    * as the last thing in their implementation.
    */
   public virtual void tear_down ()
+=======
+  public virtual async void set_up ()
+    {
+    }
+
+  public virtual async void tear_down ()
+>>>>>>> dummy: UNFINISHED work to document backend and port tests to it
     {
     }
 
@@ -296,7 +304,11 @@ public abstract class Folks.TestCase : Object
         }
     }
 
+<<<<<<< HEAD
   ~TestCase ()
+=======
+  private class Adaptor
+>>>>>>> dummy: UNFINISHED work to document backend and port tests to it
     {
       this.final_tear_down ();
     }
@@ -309,8 +321,28 @@ public abstract class Folks.TestCase : Object
 
       public static void set_up ()
         {
+<<<<<<< HEAD
           GLib.set_printerr_handler (LogAdaptor._printerr_func_stack_trace);
           Log.set_default_handler (LogAdaptor._log_func_stack_trace);
+=======
+          GLib.set_printerr_handler (Adaptor._printerr_func_stack_trace);
+          Log.set_default_handler (this._log_func_stack_trace);
+
+          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 ();
+>>>>>>> dummy: UNFINISHED work to document backend and port tests to it
         }
 
       private static void _printerr_func_stack_trace (string? text)
@@ -340,5 +372,31 @@ public abstract class Folks.TestCase : Object
               GLib.on_error_stack_trace ("libtool --mode=execute gdb");
             }
         }
+<<<<<<< HEAD
+=======
+
+      public void run (void* fixture)
+        {
+          this._test ();
+        }
+
+      public void tear_down (void* fixture)
+        {
+          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 ();
+        }
+>>>>>>> dummy: UNFINISHED work to document backend and port tests to it
     }
 }
diff --git a/tests/lib/test-utils.vala b/tests/lib/test-utils.vala
index a6b855c..129bc0e 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
 {
@@ -335,4 +336,73 @@ public class Folks.TestUtils
           return BuildConf.ABS_TOP_BUILDDIR + "/tests/" + filename;
         }
     }
+
+  /**
+   * 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]