[folks] Add a generic caching object to the core of folks
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] Add a generic caching object to the core of folks
- Date: Wed, 27 Jul 2011 22:53:09 +0000 (UTC)
commit 776adae7b0e61ef5d542d992d5819bcacadce70d
Author: Philip Withnall <philip tecnocode co uk>
Date: Thu Jun 16 18:33:26 2011 +0100
Add a generic caching object to the core of folks
This adds the ObjectCache API.
Helps: bgo#652643
NEWS | 1 +
folks/Makefile.am | 1 +
folks/object-cache.vala | 403 ++++++++++++++++++++++++++++++
tests/folks/Makefile.am | 6 +
tests/folks/object-cache.vala | 544 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 955 insertions(+), 0 deletions(-)
---
diff --git a/NEWS b/NEWS
index 07c095b..9c05d18 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,7 @@ API changes:
* Make Folks.Utils public and add Gee structure equality functions
* AvatarDetails.avatar is now of type LoadableIcon?
* Add AvatarCache class
+* Add ObjectCache class
Overview of changes from libfolks 0.5.1 to libfolks 0.5.2
=========================================================
diff --git a/folks/Makefile.am b/folks/Makefile.am
index c145087..82d9044 100644
--- a/folks/Makefile.am
+++ b/folks/Makefile.am
@@ -41,6 +41,7 @@ libfolks_la_SOURCES = \
utils.vala \
potential-match.vala \
avatar-cache.vala \
+ object-cache.vala \
$(NULL)
libfolks_la_VALAFLAGS = \
diff --git a/folks/object-cache.vala b/folks/object-cache.vala
new file mode 100644
index 0000000..3724f9e
--- /dev/null
+++ b/folks/object-cache.vala
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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:
+ * Philip Withnall <philip withnall collabora co uk>
+ */
+
+using GLib;
+using Gee;
+
+/**
+ * A generic abstract cache for sets of objects. This can be used by subclasses
+ * to implement caching of homogeneous sets of objects. Subclasses simply have
+ * to implement serialisation and deserialisation of the objects to and from
+ * { link Variant}s.
+ *
+ * It's intended that this class be used for providing caching layers for
+ * { link PersonaStore}s, for example.
+ *
+ * @since UNRELEASED
+ */
+public abstract class Folks.ObjectCache<T> : Object
+{
+ /* The version number of the header/wrapper for a cache file. When accompanied
+ * by a version number for the serialised object type, this unambiguously
+ * keys the variant type describing an entire cache file.
+ *
+ * The wrapper and object version numbers are stored as the first two bytes
+ * of a cache file. They can't be stored as part of the Variant which forms
+ * the rest of the file, as to interpret the Variant its entire type has to
+ * be known â which depends on the version numbers. */
+ private static const uint8 _FILE_FORMAT_VERSION = 1;
+
+ /* The length of the version header at the beginning of the file. This has
+ * to be a multiple of 8 to keep Variant's alignment code happy.
+ * As documented above, currently only the first two bytes of this header
+ * are used (for version numbers). */
+ private static const size_t _HEADER_WIDTH = 8; /* bytes */
+
+ private string _type_id;
+ private string _id;
+ private File _cache_directory;
+ private File _cache_file;
+
+ /**
+ * Get the { link VariantType} of the serialised form of an object stored
+ * in this cache.
+ *
+ * If a smooth upgrade path is needed in future due to cache file format
+ * changes, this may be modified to take a version parameter.
+ *
+ * @since UNRELEASED
+ */
+ protected abstract VariantType get_serialised_object_type ();
+
+ /**
+ * Get the version of the variant type returned by
+ * { link ObjectCache.get_serialised_object_type}. This must be incremented
+ * every time the variant type changes so that old cache files aren't
+ * misinterpreted.
+ *
+ * @since UNRELEASED
+ */
+ protected abstract uint8 get_serialised_object_version ();
+
+ /**
+ * Serialise the given `object` to a { link Variant} and return the variant.
+ * The variant must be of the type returned by
+ * { link ObjectCache.get_serialised_object_type()}.
+ *
+ * @since UNRELEASED
+ */
+ protected abstract Variant serialise_object (T object);
+
+ /**
+ * Deserialise the given `variant` to a new instance of an object. The variant
+ * is guaranteed to have the type returned by
+ * { link ObjectCache.get_serialised_object_type()}.
+ *
+ * @since UNRELEASED
+ */
+ protected abstract T deserialise_object (Variant variant);
+
+ /**
+ * Create a new cache instance using the given type ID and ID. This is
+ * protected as the `type_id` will typically be set statically by subclasses.
+ *
+ * @param type_id A string identifying the type of object being cached. This
+ * has to be suitable for use as a directory name; i.e. lower case,
+ * hyphen-separated.
+ * @param id A string identifying the particular cache instance. This will
+ * form the file name of the cache file, but will be escaped beforehand, so
+ * can be an arbitrary non-empty string.
+ * @return A new cache instance
+ *
+ * @since UNRELEASED
+ */
+ protected ObjectCache (string type_id, string id)
+ {
+ assert (id != "");
+
+ this._type_id = type_id;
+ this._id = id;
+
+ debug ("Creating object cache for type ID '%s' with ID '%s'.",
+ type_id, id);
+
+ this._cache_directory =
+ File.new_for_path (Environment.get_user_cache_dir ())
+ .get_child ("folks")
+ .get_child (type_id);
+ this._cache_file =
+ this._cache_directory.get_child (Uri.escape_string (id, "", false));
+ }
+
+ /**
+ * Load a set of objects from the cache and return them as a new set. If the
+ * cache file doesn't exist, `null` will be returned. An empty set will be
+ * returned if the cache file existed but was empty (i.e. was stored with
+ * an empty set originally).
+ *
+ * Loading the objects can be cancelled using `cancellable`. If it is, `null`
+ * will be returned.
+ *
+ * If any errors are encountered while loading the objects, warnings will be
+ * logged as appropriate and `null` will be returned.
+ *
+ * @param cancellable A { link Cancellable} for the operation, or `null`.
+ * @return A set of objects from the cache, or `null`.
+ *
+ * @since UNRELEASED
+ */
+ public async Set<T>? load_objects (Cancellable? cancellable = null)
+ {
+ debug ("Loading cache (type ID '%s', ID '%s') from file '%s'.",
+ this._type_id, this._id, this._cache_file.get_path ());
+
+ // Read in the file
+ uint8[] data;
+
+ try
+ {
+ yield this._cache_file.load_contents_async (cancellable, out data);
+ }
+ catch (Error e)
+ {
+ if (e is IOError.CANCELLED)
+ {
+ /* not a true error */
+ }
+ else if (e is IOError.NOT_FOUND)
+ {
+ debug ("Couldn't load cache file '%s': %s",
+ this._cache_file.get_path (), e.message);
+ }
+ else
+ {
+ warning ("Couldn't load cache file '%s': %s",
+ this._cache_file.get_path (), e.message);
+ }
+
+ return null;
+ }
+
+ // Check the length
+ if (data.length < this._HEADER_WIDTH)
+ {
+ warning ("Cache file '%s' was too small. The file was deleted.",
+ this._cache_file.get_path ());
+ yield this.clear_cache ();
+
+ return null;
+ }
+
+ // Check the version
+ var wrapper_version = data[0];
+ var object_version = data[1];
+
+ if (wrapper_version != this._FILE_FORMAT_VERSION)
+ {
+ warning ("Cache file '%s' was version %u of the file format, " +
+ "but only version %u is supported. The file was deleted.",
+ this._cache_file.get_path (), wrapper_version,
+ this._FILE_FORMAT_VERSION);
+ yield this.clear_cache ();
+
+ return null;
+ }
+
+ unowned uint8[] variant_data = data[this._HEADER_WIDTH:data.length];
+
+ // Deserialise the variant according to the given version numbers
+ var variant_type =
+ this._get_cache_file_variant_type (wrapper_version, object_version);
+ var variant =
+ Variant.new_from_data<uint8[]> (variant_type, variant_data, false,
+ data);
+
+ // Check the variant was deserialised correctly
+ if (variant.is_normal_form () == false)
+ {
+ warning ("Cache file '%s' was corrupt and was deleted.",
+ this._cache_file.get_path ());
+ yield this.clear_cache ();
+
+ return null;
+ }
+
+ // Unpack the stored data
+ var type_id = variant.get_child_value (0).get_string ();
+
+ if (type_id != this._type_id)
+ {
+ warning ("Cache file '%s' had type ID '%s', but '%s' was expected." +
+ "The file was deleted.", this._cache_file.get_path (), type_id,
+ this._type_id);
+ yield this.clear_cache ();
+
+ return null;
+ }
+
+ var id = variant.get_child_value (1).get_string ();
+
+ if (id != this._id)
+ {
+ warning ("Cache file '%s' had ID '%s', but '%s' was expected." +
+ "The file was deleted.", this._cache_file.get_path (), id,
+ this._id);
+ yield this.clear_cache ();
+
+ return null;
+ }
+
+ var objects_variant = variant.get_child_value (2);
+
+ var objects = new HashSet<T> ();
+
+ for (uint i = 0; i < objects_variant.n_children (); i++)
+ {
+ var object_variant = objects_variant.get_child_value (i);
+ var object = this.deserialise_object (object_variant);
+
+ objects.add (object);
+ }
+
+ return objects;
+ }
+
+ /**
+ * Store a set of objects to the cache file, overwriting any existing set of
+ * objects in the cache, or creating the cache file from scratch if it didn't
+ * previously exist.
+ *
+ * Storing the objects can be cancelled using `cancellable`. If it is, the
+ * cache will be left in a consistent state, but may be storing the old set
+ * of objects or the new set.
+ *
+ * @param objects A set of objects to store. This may be empty, but may not
+ * be `null`.
+ * @cancellable A { link Cancellable} for the operation, or `null`.
+ *
+ * @since UNRELEASED
+ */
+ public async void store_objects (Set<T> objects,
+ Cancellable? cancellable = null)
+ {
+ debug ("Storing cache (type ID '%s', ID '%s') to file '%s'.",
+ this._type_id, this._id, this._cache_file.get_path ());
+
+ var child_type = this.get_serialised_object_type ();
+ Variant[] children = new Variant[objects.size];
+
+ // Serialise all the objects in the set
+ uint i = 0;
+ foreach (var object in objects)
+ {
+ children[i++] = this.serialise_object (object);
+ }
+
+ // File format
+ var wrapper_version = this._FILE_FORMAT_VERSION;
+ var object_version = this.get_serialised_object_version ();
+
+ var variant = new Variant.tuple ({
+ new Variant.string (this._type_id), // Type ID
+ new Variant.string (this._id), // ID
+ new Variant.array (child_type, children) // Array of objects
+ });
+
+ assert (variant.get_type ().equal (
+ this._get_cache_file_variant_type (wrapper_version, object_version)));
+
+ // Prepend the version numbers to the data
+ uint8[] data = new uint8[this._HEADER_WIDTH + variant.get_size ()];
+ data[0] = wrapper_version;
+ data[1] = object_version;
+ variant.store (data[this._HEADER_WIDTH:data.length]);
+
+ // Write the data out to the file
+ while (true)
+ {
+ try
+ {
+ yield this._cache_file.replace_contents_async (
+ (string) data, data.length, null, false,
+ FileCreateFlags.PRIVATE, cancellable);
+ break;
+ }
+ catch (Error e)
+ {
+ if (e is IOError.NOT_FOUND)
+ {
+ try
+ {
+ yield this._create_cache_directory ();
+ continue;
+ }
+ catch (Error e2)
+ {
+ warning ("Couldn't create cache directory '%s': %s",
+ this._cache_directory.get_path (), e.message);
+ return;
+ }
+ }
+ else if (e is IOError.CANCELLED)
+ {
+ /* We assume the replace_contents_async() call is atomic,
+ * so cancelling it is atomic as well. */
+ return;
+ }
+
+ /* Print a warning and delete the cache file so we don't leave
+ * stale cached objects lying around. */
+ warning ("Couldn't write to cache file '%s', so deleting it: %s",
+ this._cache_file.get_path (), e.message);
+ yield this.clear_cache ();
+
+ return;
+ }
+ }
+ }
+
+ /**
+ * Clear this cache object, deleting its backing file.
+ *
+ * @since UNRELEASED
+ */
+ public async void clear_cache ()
+ {
+ debug ("Clearing cache (type ID '%s', ID '%s'); deleting file '%s'.",
+ this._type_id, this._id, this._cache_file.get_path ());
+
+ try
+ {
+ this._cache_file.delete ();
+ }
+ catch (Error e)
+ {
+ // Ignore errors
+ }
+ }
+
+ private VariantType _get_cache_file_variant_type (uint8 wrapper_version,
+ uint8 object_version)
+ {
+ return new VariantType.tuple ({
+ VariantType.STRING, // Type ID
+ VariantType.STRING, // ID
+ new VariantType.array (this.get_serialised_object_type ()) // Objects
+ });
+ }
+
+ private async void _create_cache_directory () throws Error
+ {
+ try
+ {
+ this._cache_directory.make_directory_with_parents ();
+ }
+ catch (Error e)
+ {
+ // Ignore errors caused by the directory existing already
+ if (!(e is IOError.EXISTS))
+ {
+ throw e;
+ }
+ }
+ }
+}
+
+/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
diff --git a/tests/folks/Makefile.am b/tests/folks/Makefile.am
index 8723a28..0d24b12 100644
--- a/tests/folks/Makefile.am
+++ b/tests/folks/Makefile.am
@@ -44,6 +44,7 @@ noinst_PROGRAMS = \
backend-loading \
aggregation \
avatar-cache \
+ object-cache \
$(NULL)
SESSION_CONF = $(top_builddir)/tests/lib/telepathy/contactlist/session.conf
@@ -80,6 +81,10 @@ avatar_cache_SOURCES = \
avatar-cache.vala \
$(NULL)
+object_cache_SOURCES = \
+ object-cache.vala \
+ $(NULL)
+
CLEANFILES = \
*.pid \
*.address \
@@ -93,6 +98,7 @@ MAINTAINERCLEANFILES = \
field_details_vala.stamp \
utils_vala.stamp \
avatar_cache_vala.stamp \
+ object_cache_vala.stamp \
$(NULL)
EXTRA_DIST = \
diff --git a/tests/folks/object-cache.vala b/tests/folks/object-cache.vala
new file mode 100644
index 0000000..b7cee02
--- /dev/null
+++ b/tests/folks/object-cache.vala
@@ -0,0 +1,544 @@
+using Gee;
+using Folks;
+
+/* Dummy ObjectCache subclass */
+internal class TestObject
+{
+ public string my_string { get; set; }
+ public uint my_int { get; set; }
+
+ public TestObject (string my_string, uint my_int)
+ {
+ this.my_string = my_string;
+ this.my_int = my_int;
+ }
+}
+
+internal class TestCache : Folks.ObjectCache<TestObject>
+{
+ internal TestCache (string id)
+ {
+ base ("test", id);
+ }
+
+ protected override VariantType get_serialised_object_type ()
+ {
+ return new VariantType.tuple ({
+ VariantType.STRING,
+ VariantType.UINT32
+ });
+ }
+
+ protected override uint8 get_serialised_object_version ()
+ {
+ return 1;
+ }
+
+ protected override Variant serialise_object (TestObject obj)
+ {
+ return new Variant.tuple ({
+ new Variant.string (obj.my_string),
+ new Variant.uint32 (obj.my_int)
+ });
+ }
+
+ protected override TestObject deserialise_object (Variant variant)
+ {
+ // Deserialise the persona
+ var my_string = variant.get_child_value (0).get_string ();
+ var my_int = variant.get_child_value (1).get_uint32 ();
+
+ return new TestObject (my_string, my_int);
+ }
+}
+
+/* Test suite */
+public class ObjectCacheTests : Folks.TestCase
+{
+ private File _cache_dir;
+
+ public ObjectCacheTests ()
+ {
+ base ("ObjectCache");
+
+ /* Use a temporary cache directory */
+ this._cache_dir =
+ File.new_for_path (Environment.get_tmp_dir ()).
+ get_child ("folks-object-cache-tests");
+
+ // Basic functionality tests
+ this.add_test ("create", this.test_create);
+ this.add_test ("store-objects", this.test_store_objects);
+ this.add_test ("store-objects-empty", this.test_store_objects_empty);
+ this.add_test ("load-objects", this.test_load_objects);
+ this.add_test ("load-objects-empty", this.test_load_objects_empty);
+ this.add_test ("load-objects-nonexistent",
+ this.test_load_objects_nonexistent);
+ this.add_test ("clear", this.test_clear);
+ this.add_test ("clear-empty", this.test_clear_empty);
+ this.add_test ("clear-nonexistent", this.test_clear_nonexistent);
+
+ // Cancellation tests
+ this.add_test ("store-objects-cancellation",
+ this.test_store_objects_cancellation);
+ this.add_test ("load-objects-cancellation",
+ this.test_load_objects_cancellation);
+
+ // Stress test
+ this.add_test ("stress", this.test_stress);
+ }
+
+ public override void set_up ()
+ {
+ this._delete_cache_directory ();
+ Environment.set_variable ("XDG_CACHE_HOME", this._cache_dir.get_path (),
+ true);
+ }
+
+ public override void tear_down ()
+ {
+ this._delete_cache_directory ();
+ }
+
+ protected void _delete_directory (File dir) throws GLib.Error
+ {
+ // Delete the files in the directory
+ var enumerator =
+ dir.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME,
+ FileQueryInfoFlags.NONE);
+
+ FileInfo? file_info = enumerator.next_file ();
+ while (file_info != null)
+ {
+ var child_file = dir.get_child (file_info.get_name ());
+
+ if (child_file.query_file_type (FileQueryInfoFlags.NONE) ==
+ FileType.DIRECTORY)
+ {
+ this._delete_directory (child_file);
+ }
+ else
+ {
+ child_file.delete ();
+ }
+
+ file_info = enumerator.next_file ();
+ }
+ enumerator.close ();
+
+ // Delete the directory itself
+ dir.delete ();
+ }
+
+ protected void _delete_cache_directory ()
+ {
+ try
+ {
+ this._delete_directory (this._cache_dir);
+ }
+ catch (Error e)
+ {
+ // Ignore it
+ }
+ }
+
+ public void test_create ()
+ {
+ // Does creating a cache object work?
+ var cache = new TestCache ("test-create");
+
+ assert (cache != null);
+ }
+
+ public void test_store_objects ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-store-objects");
+
+ var obj_set = new HashSet<TestObject> ();
+ obj_set.add (new TestObject ("Foo", 1));
+ obj_set.add (new TestObject ("Bar", 2));
+ obj_set.add (new TestObject ("De", 3));
+ obj_set.add (new TestObject ("Baz", 4));
+
+ cache.store_objects.begin (obj_set, null, (o, r) =>
+ {
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+ }
+
+ public void test_store_objects_empty ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-store-objects-empty");
+
+ cache.store_objects.begin (new HashSet<TestObject> (), null, (o, r) =>
+ {
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+ }
+
+ public void test_load_objects ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-load-objects");
+
+ // Create some objects
+ var obj1 = new TestObject ("Foo", 1);
+ var obj2 = new TestObject ("Bar", 2);
+
+ var obj_set = new HashSet<TestObject> ();
+ obj_set.add (obj1);
+ obj_set.add (obj2);
+
+ // Store the objects
+ cache.store_objects.begin (obj_set, null, (o, r) =>
+ {
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Load the objects
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (null, (o, r) =>
+ {
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Check the objects
+ assert (new_obj_set != null);
+ assert (new_obj_set.size == obj_set.size);
+
+ foreach (var new_obj in new_obj_set)
+ {
+ bool partner_found = false;
+
+ foreach (var original_obj in obj_set)
+ {
+ if (new_obj.my_string == original_obj.my_string &&
+ new_obj.my_int == original_obj.my_int)
+ {
+ obj_set.remove (original_obj);
+ partner_found = true;
+ break;
+ }
+ }
+
+ assert (partner_found);
+ }
+
+ assert (obj_set.size == 0);
+ }
+
+ public void test_load_objects_empty ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-load-objects-empty");
+
+ // Store an empty set of objects
+ cache.store_objects.begin (new HashSet<TestObject> (), null, (o, r) =>
+ {
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Load the set
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (null, (o, r) =>
+ {
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Check the set
+ assert (new_obj_set != null);
+ assert (new_obj_set.size == 0);
+ }
+
+ public void test_load_objects_nonexistent ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-load-objects-nonexistent");
+
+ // Remove the cache directory
+ this._delete_cache_directory ();
+
+ // Load the cache file
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (null, (o, r) =>
+ {
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Check the set is nonexistent
+ assert (new_obj_set == null);
+ }
+
+ public void test_clear ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-clear");
+
+ // Create some objects
+ var obj1 = new TestObject ("Foo", 1);
+ var obj2 = new TestObject ("Bar", 2);
+
+ var obj_set = new HashSet<TestObject> ();
+ obj_set.add (obj1);
+ obj_set.add (obj2);
+
+ // Store the objects
+ cache.store_objects.begin (obj_set, null, (o, r) =>
+ {
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Clear the cache
+ cache.clear_cache.begin ((o, r) =>
+ {
+ cache.clear_cache.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Attempt to load the cache file. This should fail.
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (null, (o, r) =>
+ {
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ assert (new_obj_set == null);
+ }
+
+ public void test_clear_empty ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-clear-empty");
+
+ // Store an empty set
+ cache.store_objects.begin (new HashSet<TestObject> (), null, (o, r) =>
+ {
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Clear the cache
+ cache.clear_cache.begin ((o, r) =>
+ {
+ cache.clear_cache.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Attempt to load the cache file. This should fail.
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (null, (o, r) =>
+ {
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ assert (new_obj_set == null);
+ }
+
+ public void test_clear_nonexistent ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-clear-nonexistent");
+
+ // Remove the cache directory
+ this._delete_cache_directory ();
+
+ // Clear the cache
+ cache.clear_cache.begin ((o, r) =>
+ {
+ cache.clear_cache.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Attempt to load the cache file. This should fail.
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (null, (o, r) =>
+ {
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ assert (new_obj_set == null);
+ }
+
+ public void test_store_objects_cancellation ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-store-objects-cancellation");
+
+ var obj_set = new HashSet<TestObject> ();
+ obj_set.add (new TestObject ("Foo", 1));
+ obj_set.add (new TestObject ("Bar", 2));
+ obj_set.add (new TestObject ("De", 3));
+ obj_set.add (new TestObject ("Baz", 4));
+
+ var cancellable = new Cancellable ();
+
+ cache.store_objects.begin (obj_set, cancellable, (o, r) =>
+ {
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ // Cancel the operation before running the main loop
+ cancellable.cancel ();
+ main_loop.run ();
+
+ // Check that loading the objects fails (i.e. storing them failed)
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (null, (o, r) =>
+ {
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ assert (new_obj_set == null);
+ }
+
+ public void test_load_objects_cancellation ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-load-objects-cancellation");
+
+ // Create some objects
+ var obj1 = new TestObject ("Foo", 1);
+ var obj2 = new TestObject ("Bar", 2);
+
+ var obj_set = new HashSet<TestObject> ();
+ obj_set.add (obj1);
+ obj_set.add (obj2);
+
+ // Store the objects
+ cache.store_objects.begin (obj_set, null, (o, r) =>
+ {
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Load the objects and check that nothing is returned
+ var cancellable = new Cancellable ();
+
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (cancellable, (o, r) =>
+ {
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ // Cancel the operation before running the main loop
+ cancellable.cancel ();
+ main_loop.run ();
+
+ assert (new_obj_set == null);
+ }
+
+ public void test_stress ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ var cache = new TestCache ("test-stress");
+
+ // Create a handful of objects
+ var obj_count = 66666;
+ var obj_set = new HashSet<TestObject> ();
+
+ for (var i = 0; i < obj_count; i++)
+ {
+ obj_set.add (new TestObject ("bizzle", i));
+ }
+
+ // Store the objects
+ Test.timer_start ();
+
+ cache.store_objects.begin (obj_set, null, (o, r) =>
+ {
+ var elapsed_time = Test.timer_elapsed ();
+ message ("Storing %u objects in a cache file took %f seconds.",
+ obj_count, elapsed_time);
+
+ cache.store_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ // Load the objects
+ Test.timer_start ();
+
+ Set<TestObject>? new_obj_set = null;
+ cache.load_objects.begin (null, (o, r) =>
+ {
+ var elapsed_time = Test.timer_elapsed ();
+ message ("Loading %u objects from a cache file took %f seconds.",
+ obj_count, elapsed_time);
+
+ new_obj_set = cache.load_objects.end (r);
+ main_loop.quit ();
+ });
+
+ main_loop.run ();
+
+ /* Check the set is the right size. We don't bother to check that the
+ * objects themselves are OK â the loading tests do that. */
+ assert (new_obj_set != null);
+ assert (new_obj_set.size == obj_set.size);
+ }
+}
+
+public int main (string[] args)
+{
+ Test.init (ref args);
+
+ TestSuite root = TestSuite.get_root ();
+ root.add_suite (new ObjectCacheTests ().get_suite ());
+
+ Test.run ();
+
+ return 0;
+}
+
+/* vim: filetype=vala textwidth=80 tabstop=2 expandtab: */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]