summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip.withnall@collabora.co.uk>2013-11-11 13:44:14 +0000
committerPhilip Withnall <philip@tecnocode.co.uk>2014-02-16 23:52:31 +0000
commit6e85b2bec3ee5511d596e71438e228ce343c663a (patch)
tree3d8b04c9a82e7a4fe3092ea13eed6e65967eea0a
parent02bf00de034ba639a5a81faf555001c084975da6 (diff)
bluez: Add a test suite
This adds a test suite for the BlueZ backend, using a python-dbusmock mock up of the BlueZ and OBEX D-Bus services. This requires the latest version of python-dbusmock, plus up-to-date versions of GLib and Vala for binding updates. The use of the second and third arguments to AM_PROG_VALAC in configure.ac also necessitates use of automake 1.12 or newer. Only a few test cases have been added so far, covering vCard parsing and general set up of PersonaStores. Using python-dbusmock it should be easy to add more tests covering advanced Bluetooth device appearance/disappearance situations in future. https://bugzilla.gnome.org/show_bug.cgi?id=712274
-rwxr-xr-xautogen.sh13
-rw-r--r--configure.ac25
-rw-r--r--tests/Makefile.am7
-rw-r--r--tests/bluez/Makefile.am48
-rw-r--r--tests/bluez/device-properties.vala305
-rw-r--r--tests/bluez/individual-retrieval.vala232
-rw-r--r--tests/bluez/vcard-parsing.vala255
-rw-r--r--tests/lib/Makefile.am7
-rw-r--r--tests/lib/bluez/Makefile.am51
-rw-r--r--tests/lib/bluez/backend.vala306
-rw-r--r--tests/lib/bluez/test-case.vala127
-rw-r--r--tests/lib/test-case.vala3
12 files changed, 1365 insertions, 14 deletions
diff --git a/autogen.sh b/autogen.sh
index b3b63b11..a38c8cb8 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,19 +1,6 @@
#!/bin/sh
set -e
-if test -n "$AUTOMAKE"; then
- : # don't override an explicit user request
-elif automake-1.11 --version >/dev/null 2>/dev/null && \
- aclocal-1.11 --version >/dev/null 2>/dev/null; then
- # If we have automake-1.11, use it. This is the oldest version (=> least
- # likely to introduce undeclared dependencies) that will give us
- # --enable-silent-rules support.
- AUTOMAKE=automake-1.11
- export AUTOMAKE
- ACLOCAL=aclocal-1.11
- export ACLOCAL
-fi
-
autoreconf -i -f
intltoolize --force --copy --automake
diff --git a/configure.ac b/configure.ac
index 4ea9298c..729e9719 100644
--- a/configure.ac
+++ b/configure.ac
@@ -79,7 +79,7 @@ AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_SRCDIR([Makefile.am])
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_SRCDIR([configure.ac])
-AM_INIT_AUTOMAKE([1.11 dist-xz no-define
+AM_INIT_AUTOMAKE([1.12 dist-xz no-define
no-dist-gzip tar-ustar -Wno-portability color-tests
parallel-tests])
AM_MAINTAINER_MODE([enable])
@@ -336,8 +336,29 @@ AS_IF([test x$enable_ofono_backend = xyes], [
AS_IF([test x$enable_bluez_backend = xyes], [
PKG_CHECK_MODULES([EBOOK], [libebook-1.2 >= $EBOOK_REQUIRED])
+
+ # Dependencies for the BlueZ tests
+ PKG_CHECK_MODULES([GLIB_2_39_2], [glib-2.0 >= 2.39.2],
+ [have_glib_2_39_2=yes], [have_glib_2_39_2=no])
+ AM_PATH_PYTHON([3.0], [have_python=yes], [have_python=no])
+
+ AC_MSG_CHECKING([for python-dbusmock])
+ AS_IF([! $PYTHON -c 'import dbusmock' > /dev/null 2>&1],
+ [have_dbusmock=no], [have_dbusmock=yes])
+ AC_MSG_RESULT([$have_dbusmock])
+
+ AM_PROG_VALAC([0.22.0.45-383d-dirty],
+ [have_valac_0_22_2=yes], [have_valac_0_22_2=no])
])
+# The BlueZ tests are conditional on several bleeding-edge dependencies.
+# FIXME: Remove this once things have stabilised a bit.
+AM_CONDITIONAL([HAVE_BLUEZ_TESTS],
+ [test "x$have_glib_2_39_2" = "xyes" -a \
+ "x$have_python" = "xyes" -a \
+ "x$have_dbusmock" = "xyes" -a \
+ "x$have_valac_0_22_2" = "xyes"])
+
#
# Vala building options -- allows tarball builds without installing Vala
#
@@ -751,6 +772,7 @@ AC_CONFIG_FILES([
docs/Makefile
po/Makefile.in
tests/Makefile
+ tests/bluez/Makefile
tests/data/Makefile
tests/eds/Makefile
tests/folks/Makefile
@@ -761,6 +783,7 @@ AC_CONFIG_FILES([
tests/tracker/Makefile
tests/lib/Makefile
tests/lib/folks-test-uninstalled.pc
+ tests/lib/bluez/Makefile
tests/lib/eds/Makefile
tests/lib/dummy/Makefile
tests/lib/key-file/Makefile
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2fad8ce4..38561942 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -8,6 +8,12 @@ SUBDIRS = \
tools \
$(NULL)
+if ENABLE_BLUEZ
+if HAVE_BLUEZ_TESTS
+SUBDIRS += bluez
+endif
+endif
+
if ENABLE_TELEPATHY
SUBDIRS += folks telepathy
endif
@@ -34,6 +40,7 @@ DIST_SUBDIRS = \
dummy \
eds \
key-file \
+ bluez \
telepathy \
libsocialweb \
tracker \
diff --git a/tests/bluez/Makefile.am b/tests/bluez/Makefile.am
new file mode 100644
index 00000000..46661fbf
--- /dev/null
+++ b/tests/bluez/Makefile.am
@@ -0,0 +1,48 @@
+include $(top_srcdir)/tests/test.mk
+
+AM_VALAFLAGS = \
+ $(test_valaflags) \
+ --vapidir=$(top_srcdir)/tests/lib/bluez \
+ --pkg bluez-test \
+ --pkg folks-generics \
+ $(NULL)
+
+AM_CPPFLAGS = \
+ $(test_cppflags) \
+ -I$(top_srcdir)/tests/lib/bluez \
+ $(NULL)
+
+AM_CFLAGS = \
+ $(test_cflags) \
+ $(NULL)
+
+LDADD = \
+ $(AM_LDADD) \
+ $(test_ldadd) \
+ $(top_builddir)/tests/lib/bluez/libbluez-test.la \
+ $(NULL)
+
+# in order from least to most complex
+noinst_PROGRAMS = \
+ device-properties \
+ individual-retrieval \
+ vcard-parsing \
+ $(NULL)
+
+TESTS = $(noinst_PROGRAMS)
+
+device_properties_SOURCES = \
+ device-properties.vala \
+ $(NULL)
+
+individual_retrieval_SOURCES = \
+ individual-retrieval.vala \
+ $(NULL)
+
+vcard_parsing_SOURCES = \
+ vcard-parsing.vala \
+ $(NULL)
+
+-include $(top_srcdir)/git.mk
+-include $(top_srcdir)/valgrind.mk
+-include $(top_srcdir)/check.mk
diff --git a/tests/bluez/device-properties.vala b/tests/bluez/device-properties.vala
new file mode 100644
index 00000000..19d806a5
--- /dev/null
+++ b/tests/bluez/device-properties.vala
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2013 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 Gee;
+using Folks;
+using BluezTest;
+
+public class DevicePropertiesTests : BluezTest.TestCase
+{
+ public DevicePropertiesTests ()
+ {
+ base ("DeviceProperties");
+
+ this.add_test ("device pairing", this.test_device_pairing);
+ this.add_test ("blocked device", this.test_blocked_device);
+ this.add_test ("device alias", this.test_device_alias);
+ }
+
+ /* Start with an unpaired Bluetooth device, and check that it’s not turned
+ * into a PersonaStore. Then pair the device, and check that it is added as
+ * a store. */
+ public void test_device_pairing ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+
+ /* Set up a simple unpaired device. */
+ try
+ {
+ this.bluez_backend.mock_bluez.add_adapter ("hci0", "Test System");
+ this.bluez_backend.mock_bluez.add_device ("hci0",
+ this.bluez_backend.primary_device_address, "My Phone");
+ }
+ catch (IOError e1)
+ {
+ error ("Error setting up mock BlueZ device: %s", e1.message);
+ }
+
+ /* Set up its vCard in preparation. */
+ this.bluez_backend.set_simple_device_vcard (
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "N:Jones;Pam;Mrs.\n" +
+ "FN:Pam Jones\n" +
+ "TEL:0123456789\n" +
+ "END:VCARD\n");
+
+ /* Set up the aggregator and wait until either quiescence, or the test
+ * times out and fails. Unset the primary store to prevent a warning. */
+ Environment.set_variable ("FOLKS_PRIMARY_STORE", "", true);
+
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_quiescence.begin (aggregator,
+ (o, r) =>
+ {
+ try
+ {
+ TestUtils.aggregator_prepare_and_wait_for_quiescence.end (r);
+ }
+ catch (GLib.Error e2)
+ {
+ error ("Error preparing aggregator: %s", e2.message);
+ }
+
+ main_loop.quit ();
+ });
+
+ TestUtils.loop_run_with_timeout (main_loop);
+
+ var real_backend =
+ aggregator.backend_store.dup_backend_by_name ("bluez");
+
+ /* Check there are no individuals and no persona stores. */
+ assert (aggregator.individuals.size == 0);
+ assert (real_backend.persona_stores.size == 0);
+
+ /* Wait for a signal about an added persona store. */
+ TestUtils.aggregator_wait_for_individuals.begin (aggregator,
+ {"Pam Jones"}, {}, (o, r) =>
+ {
+ TestUtils.aggregator_wait_for_individuals.end (r);
+ main_loop.quit ();
+ });
+
+ /* Pair the device. */
+ try
+ {
+ this.bluez_backend.mock_bluez.pair_device ("hci0",
+ this.bluez_backend.primary_device_address);
+ }
+ catch (IOError e4)
+ {
+ error ("Error pairing mock BlueZ device: %s", e4.message);
+ }
+
+ TestUtils.loop_run_with_timeout (main_loop);
+ }
+
+ /* Start with a blocked Bluetooth device, and check it’s not made into a
+ * PersonaStore. Then unblock the device and check a PersonaStore is created.
+ * Then block the device again and check the PersonaStore is removed. */
+ public void test_blocked_device ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+
+ /* Set up a simple paired but blocked device. */
+ this.bluez_backend.create_simple_device_with_vcard (
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "N:Jones;Pam;Mrs.\n" +
+ "FN:Pam Jones\n" +
+ "TEL:0123456789\n" +
+ "END:VCARD\n");
+
+ try
+ {
+ this.bluez_backend.mock_bluez.block_device ("hci0",
+ this.bluez_backend.primary_device_address);
+ }
+ catch (IOError e1)
+ {
+ error ("Error blocking device: %s", e1.message);
+ }
+
+ /* Set up the aggregator and wait until either quiescence, or the test
+ * times out and fails. Unset the primary store to prevent a warning. */
+ Environment.set_variable ("FOLKS_PRIMARY_STORE", "", true);
+
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_quiescence.begin (aggregator,
+ (o, r) =>
+ {
+ try
+ {
+ TestUtils.aggregator_prepare_and_wait_for_quiescence.end (r);
+ }
+ catch (GLib.Error e2)
+ {
+ error ("Error preparing aggregator: %s", e2.message);
+ }
+
+ main_loop.quit ();
+ });
+
+ TestUtils.loop_run_with_timeout (main_loop);
+
+ var real_backend =
+ aggregator.backend_store.dup_backend_by_name ("bluez");
+
+ /* Check there are no individuals and no persona stores. */
+ assert (aggregator.individuals.size == 0);
+ assert (real_backend.persona_stores.size == 0);
+
+ /* Wait for a signal about an added persona store. */
+ TestUtils.aggregator_wait_for_individuals.begin (aggregator,
+ {"Pam Jones"}, {}, (o, r) =>
+ {
+ TestUtils.aggregator_wait_for_individuals.end (r);
+ main_loop.quit ();
+ });
+
+ /* Unblock the device. */
+ try
+ {
+ this.bluez_backend.mock_bluez.pair_device ("hci0",
+ this.bluez_backend.primary_device_address);
+ }
+ catch (IOError e4)
+ {
+ error ("Error blocking device: %s", e4.message);
+ }
+
+ TestUtils.loop_run_with_timeout (main_loop);
+
+ /* Wait for a signal about a removed persona store. */
+ TestUtils.aggregator_wait_for_individuals.begin (aggregator,
+ {}, {"Pam Jones"}, (o, r) =>
+ {
+ TestUtils.aggregator_wait_for_individuals.end (r);
+ main_loop.quit ();
+ });
+
+ /* Block the device again. */
+ try
+ {
+ this.bluez_backend.mock_bluez.block_device ("hci0",
+ this.bluez_backend.primary_device_address);
+ }
+ catch (IOError e5)
+ {
+ error ("Error blocking device again: %s", e5.message);
+ }
+
+ TestUtils.loop_run_with_timeout (main_loop);
+
+ /* Check there are no individuals and no persona stores. */
+ assert (aggregator.individuals.size == 0);
+ assert (real_backend.persona_stores.size == 0);
+ }
+
+ /* Test that changes of a device’s Alias property result in the PersonaStore’s
+ * display-name being updated. */
+ public void test_device_alias ()
+ {
+ /* Set up the backend. */
+ string device_path = "";
+ this.bluez_backend.create_simple_device_with_vcard (
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "N:Jones;Pam;Mrs.\n" +
+ "FN:Pam Jones\n" +
+ "TEL:0123456789\n" +
+ "END:VCARD\n",
+ null, out device_path);
+
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_individuals_sync_with_timeout (
+ aggregator, {"Pam Jones"});
+
+ /* Check the PersonaStore’s alias. */
+ var real_backend =
+ aggregator.backend_store.dup_backend_by_name ("bluez");
+ assert (real_backend.persona_stores.size == 1);
+
+ var real_store =
+ real_backend.persona_stores.get (
+ this.bluez_backend.primary_device_address);
+
+ /* FIXME: Have to get the display-name this way because
+ * Folks.PersonaStore.display_name is not declared as abstract. */
+ string display_name = "";
+ real_store.get ("display-name", out display_name);
+ assert (display_name == "My Phone");
+
+ /* Change the device’s Alias and see if the display-name changes. */
+ var main_loop = new GLib.MainLoop (null, false);
+ real_store.notify["display-name"].connect ((p) =>
+ {
+ real_store.get ("display-name", out display_name);
+ assert (display_name == "New Alias!");
+ main_loop.quit ();
+ });
+
+ try
+ {
+ Device mock_device =
+ Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", device_path);
+ org.freedesktop.DBus.Mock mock =
+ Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", device_path);
+
+ var props = new HashTable<string, Variant> (str_hash, str_equal);
+ props.insert ("Alias", "New Alias!");
+
+ mock_device.alias = "New Alias!";
+ mock.emit_signal ("org.freedesktop.DBus.Properties",
+ "PropertiesChanged", "sa{sv}as",
+ {
+ "org.bluez.Device1",
+ props,
+ new Variant.array (VariantType.STRING, {})
+ });
+ }
+ catch (IOError e1)
+ {
+ error ("Error setting device alias: %s", e1.message);
+ }
+
+ TestUtils.loop_run_with_timeout (main_loop);
+ }
+}
+
+/* Mini-copy of the org-bluez.vala file in the BlueZ backend. */
+[DBus (name = "org.bluez.Device1")]
+public interface Device : Object
+ {
+ [DBus (name = "Alias")]
+ public abstract string alias { owned get; set; }
+ }
+
+public int main (string[] args)
+{
+ Test.init (ref args);
+
+ var tests = new DevicePropertiesTests ();
+ tests.register ();
+ Test.run ();
+ tests.final_tear_down ();
+
+ return 0;
+}
diff --git a/tests/bluez/individual-retrieval.vala b/tests/bluez/individual-retrieval.vala
new file mode 100644
index 00000000..033821ed
--- /dev/null
+++ b/tests/bluez/individual-retrieval.vala
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2013 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 Gee;
+using Folks;
+using BluezTest;
+
+public class IndividualRetrievalTests : BluezTest.TestCase
+{
+ public IndividualRetrievalTests ()
+ {
+ base ("IndividualRetrieval");
+
+ this.add_test ("singleton individuals", this.test_singleton_individuals);
+ this.add_test ("empty address book", this.test_empty_address_book);
+ this.add_test ("photos downloaded later",
+ this.test_photos_downloaded_later);
+ }
+
+ /* Test that personas on a pre-existing Bluetooth device are successfully
+ * downloaded and presented as singleton individuals by the aggregator. */
+ public void test_singleton_individuals ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+
+ /* Set up the backend. */
+ this.bluez_backend.create_simple_device_with_vcard (
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "N:Gump;Forrest;Mr.\n" +
+ "FN:Forrest Gump\n" +
+ "NICKNAME:Fir\n" +
+ "TEL;TYPE=WORK,VOICE:(111) 555-1212\n" +
+ "TEL;TYPE=HOME,VOICE:(404) 555-1212\n" +
+ "EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com\n" +
+ "URL;TYPE=HOME:http://example.com/\n" +
+ "END:VCARD\n" +
+ "\n" +
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "N:Jones;Pam;Mrs.\n" +
+ "FN:Pam Jones\n" +
+ "TEL:0123456789\n" +
+ "END:VCARD\n");
+
+ /* Set up the aggregator and wait until either the expected persona are
+ * seen, or the test times out and fails. */
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_individuals.begin (aggregator,
+ {"Forrest Gump", "Pam Jones"}, (o, r) =>
+ {
+ try
+ {
+ TestUtils.aggregator_prepare_and_wait_for_individuals.end (r);
+ }
+ catch (GLib.Error e1)
+ {
+ error ("Error preparing aggregator: %s", e1.message);
+ }
+
+ main_loop.quit ();
+ });
+
+ TestUtils.loop_run_with_timeout (main_loop);
+ }
+
+ /* Test that an empty address book is handled correctly. */
+ public void test_empty_address_book ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+
+ /* Set up the backend with *no* contacts. */
+ this.bluez_backend.create_simple_device_with_vcard ("");
+
+ /* Set up the aggregator and wait until either quiescence, or the test
+ * times out and fails. */
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_quiescence.begin (aggregator,
+ (o, r) =>
+ {
+ try
+ {
+ TestUtils.aggregator_prepare_and_wait_for_quiescence.end (r);
+ }
+ catch (GLib.Error e1)
+ {
+ error ("Error preparing aggregator: %s", e1.message);
+ }
+
+ main_loop.quit ();
+ });
+
+ TestUtils.loop_run_with_timeout (main_loop);
+
+ /* Check there are no individuals. */
+ assert (aggregator.individuals.size == 0);
+ }
+
+ /* Test that photos are downloaded in a second sweep of the address book. */
+ public void test_photos_downloaded_later ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+
+ /* Set up the backend, at first with a vCard without a photo. */
+ var vcard_signal_id = this.bluez_backend.create_simple_device_with_vcard (
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "N:Gump;Forrest;Mr.\n" +
+ "FN:Forrest Gump\n" +
+ "NICKNAME:Fir\n" +
+ "TEL;TYPE=WORK,VOICE:(111) 555-1212\n" +
+ "TEL;TYPE=HOME,VOICE:(404) 555-1212\n" +
+ "EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com\n" +
+ "URL;TYPE=HOME:http://example.com/\n" +
+ "END:VCARD\n");
+
+ /* Set up the aggregator and wait until either the expected persona are
+ * seen, or the test times out and fails. */
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_individuals.begin (aggregator,
+ {"Forrest Gump"}, (o, r) =>
+ {
+ try
+ {
+ TestUtils.aggregator_prepare_and_wait_for_individuals.end (r);
+ }
+ catch (GLib.Error e1)
+ {
+ error ("Error preparing aggregator: %s", e1.message);
+ }
+
+ main_loop.quit ();
+ });
+
+ TestUtils.loop_run_with_timeout (main_loop);
+
+ /* Re-set the backend to now return a vCard with a photo (and nothing
+ * else). */
+ this.bluez_backend.mock_obex.disconnect (vcard_signal_id);
+ this.bluez_backend.set_simple_device_vcard (
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "N:Gump;Forrest;Mr.\n" +
+ "FN:Forrest Gump\n" +
+ "NICKNAME:Fir\n" +
+ "TEL;TYPE=WORK,VOICE:(111) 555-1212\n" +
+ "TEL;TYPE=HOME,VOICE:(404) 555-1212\n" +
+ "EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com\n" +
+ "URL;TYPE=HOME:http://example.com/\n" +
+ "PHOTO;TYPE=jpeg;ENCODING=b:" +
+ "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\n" +
+ "END:VCARD\n");
+
+ /* The individual should not have a photo to begin with; wait until one
+ * appears. */
+ assert (aggregator.individuals.size == 1);
+ var iter = aggregator.individuals.map_iterator ();
+ while (iter.next () == true)
+ {
+ var individual = iter.get_value ();
+ assert (individual.avatar == null);
+
+ /* Wait for it to change. Assert that only the avatar changes. */
+ individual.notify.connect ((pspec) =>
+ {
+ assert (pspec.name == "avatar");
+ assert (individual.avatar != null);
+ main_loop.quit ();
+ });
+ }
+
+ /* There’s normally a 5s wait between poll attempts in the backend, but
+ * we set the FOLKS_BLUEZ_TIMEOUT_DIVISOR in the TestCase to reduce
+ * this. */
+ TestUtils.loop_run_with_timeout (main_loop);
+ }
+}
+
+public int main (string[] args)
+{
+ Test.init (ref args);
+
+ var tests = new IndividualRetrievalTests ();
+ tests.register ();
+ Test.run ();
+ tests.final_tear_down ();
+
+ return 0;
+}
diff --git a/tests/bluez/vcard-parsing.vala b/tests/bluez/vcard-parsing.vala
new file mode 100644
index 00000000..f9e91002
--- /dev/null
+++ b/tests/bluez/vcard-parsing.vala
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2013 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 Gee;
+using Folks;
+using BluezTest;
+
+public class VcardParsingTests : BluezTest.TestCase
+{
+ public VcardParsingTests ()
+ {
+ base ("VcardParsing");
+
+ this.add_test ("multiple attributes", this.test_multiple_attributes);
+ this.add_test ("name components", this.test_name_components);
+ this.add_test ("encoding", this.test_encoding);
+ }
+
+ /* Test that vCards containing multiple attributes with the same name (e.g.
+ * multiple phone numbers or e-mail addresses) are parsed correctly. */
+ public void test_multiple_attributes ()
+ {
+ /* Set up the backend. */
+ this.bluez_backend.create_simple_device_with_vcard (
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "FN:Forrest Gump\n" +
+ "TEL;TYPE=WORK,VOICE:(111) 555-1212\n" +
+ "TEL;TYPE=HOME,VOICE:(404) 555-1212\n" +
+ "EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com\n" +
+ "EMAIL:test@example.com\n" +
+ "URL;TYPE=HOME:http://example.com/\n" +
+ "URL:http://forest.com/\n" +
+ "URL:https://test.com/\n" +
+ "END:VCARD\n");
+
+ /* Set up the aggregator and wait until either the expected persona are
+ * seen, or the test times out and fails. */
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_individuals_sync_with_timeout (
+ aggregator, {"Forrest Gump"});
+
+ /* Check the properties of our friend Forrest. */
+ var ind = TestUtils.get_individual_by_name (aggregator, "Forrest Gump");
+
+ var expected_phone_numbers = new SmallSet<PhoneFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+
+ var expected_phone_fd = new PhoneFieldDetails ("(111) 555-1212");
+ expected_phone_fd.add_parameter ("type", "work");
+ expected_phone_fd.add_parameter ("type", "voice");
+ expected_phone_numbers.add (expected_phone_fd);
+
+ expected_phone_fd = new PhoneFieldDetails ("(404) 555-1212");
+ expected_phone_fd.add_parameter ("type", "home");
+ expected_phone_fd.add_parameter ("type", "voice");
+ expected_phone_numbers.add (expected_phone_fd);
+
+ var expected_email_addresses = new SmallSet<EmailFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+
+ var expected_email_fd = new EmailFieldDetails ("forrestgump@example.com");
+ expected_email_fd.add_parameter ("type", "pref");
+ expected_email_fd.add_parameter ("type", "internet");
+ expected_email_addresses.add (expected_email_fd);
+
+ expected_email_fd = new EmailFieldDetails ("test@example.com");
+ expected_email_addresses.add (expected_email_fd);
+
+ var expected_uris = new SmallSet<UrlFieldDetails> (
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+
+ var expected_uri_fd = new UrlFieldDetails ("http://example.com/");
+ expected_uri_fd.add_parameter ("type", "home");
+ expected_uris.add (expected_uri_fd);
+
+ expected_uri_fd = new UrlFieldDetails ("http://forest.com/");
+ expected_uris.add (expected_uri_fd);
+
+ expected_uri_fd = new UrlFieldDetails ("https://test.com/");
+ expected_uris.add (expected_uri_fd);
+
+ assert (Utils.set_afd_equal (ind.phone_numbers, expected_phone_numbers));
+ assert (Utils.set_afd_equal (ind.email_addresses,
+ expected_email_addresses));
+ assert (Utils.set_afd_equal (ind.urls, expected_uris));
+ }
+
+ /* Test that vCards with different numbers of values for their N (structured
+ * name) attribute are parsed correctly. */
+ public void test_name_components ()
+ {
+ /* Set up the backend. */
+ this.bluez_backend.create_simple_device_with_vcard (
+ /* Valid N attributes. */
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "FN:John Public\n" +
+ "N:Public;John;Quinlan;Mr.;Esq.\n" +
+ "END:VCARD\n" +
+ "\n" +
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "FN:John Stevenson\n" +
+ "N:Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P.\n" +
+ "END:VCARD\n" +
+ "\n" +
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "FN:Franco Dianno\n" +
+ "N:Dianno;Franco;;;\n" +
+ "END:VCARD\n" +
+ "\n" +
+ /* Invalid N attributes (but we should handle them anyway). */
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "FN:Amelia Smith\n" +
+ "N:Smith;Amelia;David;Dr.\n" +
+ "END:VCARD\n" +
+ "\n" +
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "FN:Sadie Jones\n" +
+ "N:Jones;Sadie;M.\n" +
+ "END:VCARD\n" +
+ "\n" +
+ "BEGIN:VCARD\n" +
+ "VERSION:3.0\n" +
+ "FN:Alex Lawson\n" +
+ "N:Lawson;Alex\n" +
+ "END:VCARD\n");
+
+ /* Set up the aggregator and wait until either the expected persona are
+ * seen, or the test times out and fails. */
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_individuals_sync_with_timeout (
+ aggregator,
+ {
+ "John Public",
+ "John Stevenson",
+ "Franco Dianno",
+ "Amelia Smith",
+ "Sadie Jones",
+ "Alex Lawson"
+ });
+
+ /* Check the properties of our individuals. */
+ var ind = TestUtils.get_individual_by_name (aggregator, "John Public");
+ var expected_name =
+ new StructuredName ("Public", "John", "Quinlan", "Mr.", "Esq.");
+ assert (ind.structured_name.equal (expected_name));
+
+ ind = TestUtils.get_individual_by_name (aggregator, "John Stevenson");
+ expected_name =
+ new StructuredName ("Stevenson", "John", "Philip,Paul", "Dr.",
+ "Jr.,M.D.,A.C.P.");
+ assert (ind.structured_name.equal (expected_name));
+
+ ind = TestUtils.get_individual_by_name (aggregator, "Franco Dianno");
+ expected_name = new StructuredName ("Dianno", "Franco", null, null, null);
+ assert (ind.structured_name.equal (expected_name));
+
+ ind = TestUtils.get_individual_by_name (aggregator, "Amelia Smith");
+ expected_name =
+ new StructuredName ("Smith", "Amelia", "David", "Dr.", null);
+ assert (ind.structured_name.equal (expected_name));
+
+ ind = TestUtils.get_individual_by_name (aggregator, "Sadie Jones");
+ expected_name = new StructuredName ("Jones", "Sadie", "M.", null, null);
+ assert (ind.structured_name.equal (expected_name));
+
+ ind = TestUtils.get_individual_by_name (aggregator, "Alex Lawson");
+ expected_name = new StructuredName ("Lawson", "Alex", null, null, null);
+ assert (ind.structured_name.equal (expected_name));
+ }
+
+ /* Test that vCards with weird encodings are parsed correctly. */
+ public void test_encoding ()
+ {
+ /* Set up the backend. */
+ this.bluez_backend.create_simple_device_with_vcard (
+ /* From https://bugs.kde.org/show_bug.cgi?id=98790 */
+ "BEGIN:VCARD\n" +
+ "VERSION:2.1\n" +
+ "FN:Test 1\n" +
+ "N;CHARSET=UTF-8:溌剌;元気\n" +
+ "END:VCARD\n" +
+ "\n" +
+ /* From https://git.gnome.org/browse/evolution-data-server/tree/tests/
+ * libebook-contacts/test-vcard-parsing.c#n360 */
+ "BEGIN:VCARD\n" +
+ "VERSION:2.1\n" +
+ "FN;ENCODING=quoted-printable:ActualValue=20=C4=9B=C5=A1" +
+ "=C4=8D=C5=99=C5=BE=C3=BD=C3=A1=C3=AD=C3=A9=C3=BA=C5=AF=C3" +
+ "=B3=C3=B6=C4=9A=C5=A0=C4=8C=C5=98=C5=BD=C3=9D=C3=81=C3=8D" +
+ "=C3=89=C3=9A=C5=AE=C3=93=C3=96=C2=A7=201234567890=2012345" +
+ "67890=201234567890=201234567890=201234567890\n" +
+ "END:VCARD\n");
+
+ /* Set up the aggregator and wait until either the expected persona are
+ * seen, or the test times out and fails. */
+ var aggregator = IndividualAggregator.dup ();
+ TestUtils.aggregator_prepare_and_wait_for_individuals_sync_with_timeout (
+ aggregator,
+ {
+ "Test 1",
+ "ActualValue ěščřžýáíéúůóöĚŠČŘŽÝÁÍÉÚŮÓÖ§ " +
+ "1234567890 1234567890 1234567890 1234567890 1234567890"
+ });
+
+ /* Check the properties of our individuals. */
+ var ind = TestUtils.get_individual_by_name (aggregator, "Test 1");
+ var expected_name =
+ new StructuredName ("溌剌", "元気", null, null, null);
+ assert (ind.structured_name.equal (expected_name));
+
+ ind =
+ TestUtils.get_individual_by_name (aggregator,
+ "ActualValue ěščřžýáíéúůóöĚŠČŘŽÝÁÍÉÚŮÓÖ§ " +
+ "1234567890 1234567890 1234567890 1234567890 1234567890");
+ assert (ind.full_name == "ActualValue ěščřžýáíéúůóöĚŠČŘŽÝÁÍÉÚŮÓÖ§ " +
+ "1234567890 1234567890 1234567890 1234567890 1234567890");
+ }
+}
+
+public int main (string[] args)
+{
+ Test.init (ref args);
+
+ var tests = new VcardParsingTests ();
+ tests.register ();
+ Test.run ();
+ tests.final_tear_down ();
+
+ return 0;
+}
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index 4486e433..a6e958f0 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -5,6 +5,12 @@ SUBDIRS = \
key-file \
$(NULL)
+if ENABLE_BLUEZ
+if HAVE_BLUEZ_TESTS
+SUBDIRS += bluez
+endif
+endif
+
if ENABLE_TELEPATHY
# Build the contactlist first because autotools fails to recognize the
# dependencies implicitly. There may be a better way to fix this, but then I'd
@@ -30,6 +36,7 @@ endif
DIST_SUBDIRS = \
dummy \
key-file \
+ bluez \
telepathy \
eds \
libsocialweb \
diff --git a/tests/lib/bluez/Makefile.am b/tests/lib/bluez/Makefile.am
new file mode 100644
index 00000000..a2a382dd
--- /dev/null
+++ b/tests/lib/bluez/Makefile.am
@@ -0,0 +1,51 @@
+noinst_LTLIBRARIES = libbluez-test.la
+
+libbluez_test_la_VALAFLAGS = \
+ $(AM_VALAFLAGS) \
+ $(TARGET_VALAFLAGS) \
+ $(ERROR_VALAFLAGS) \
+ --library bluez-test \
+ --vapi bluez-test.vapi \
+ --header bluez-test.h \
+ --vapidir=$(abs_srcdir) \
+ --vapidir=$(abs_builddir) \
+ --vapidir=$(abs_top_srcdir)/folks \
+ --vapidir=$(abs_top_builddir)/folks \
+ --vapidir=$(abs_top_srcdir)/tests/lib \
+ --vapidir=$(abs_top_builddir)/tests/lib \
+ --pkg folks-test \
+ --pkg folks-test-dbus \
+ -g \
+ $(NULL)
+
+libbluez_test_la_SOURCES = \
+ backend.vala \
+ test-case.vala \
+ $(NULL)
+
+libbluez_test_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -include $(top_srcdir)/folks/warnings.h \
+ $(NULL)
+
+libbluez_test_la_CFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/tests/lib \
+ $(AM_CFLAGS) \
+ $(ERROR_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GEE_CFLAGS) \
+ $(NULL)
+
+libbluez_test_la_LIBADD = \
+ $(top_builddir)/tests/lib/libfolks-test.la \
+ $(GLIB_LIBS) \
+ $(GEE_LIBS) \
+ $(NULL)
+
+EXTRA_DIST = \
+ bluez-test.vapi \
+ bluez-test.h \
+ $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/lib/bluez/backend.vala b/tests/lib/bluez/backend.vala
new file mode 100644
index 00000000..1ba34a2a
--- /dev/null
+++ b/tests/lib/bluez/backend.vala
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2013 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;
+
+/* Specific mock interfaces for the BlueZ and OBEX services. */
+namespace org
+ {
+ namespace bluez
+ {
+ /* Interface for the bluez5 python-dbusmock template. */
+ [DBus (name = "org.bluez.Mock")]
+ public interface Mock : Object
+ {
+ [DBus (name = "AddAdapter")]
+ public abstract string add_adapter (string device_name,
+ string system_name) throws IOError;
+
+ [DBus (name = "AddDevice")]
+ public abstract string add_device (string adapter_device_name,
+ string device_address, string alias) throws IOError;
+
+ [DBus (name = "PairDevice")]
+ public abstract void pair_device (string adapter_device_name,
+ string device_address) throws IOError;
+
+ [DBus (name = "BlockDevice")]
+ public abstract void block_device (string adapter_device_name,
+ string device_address) throws IOError;
+ }
+
+ namespace obex
+ {
+ /* Interface for the bluez5-obex python-dbusmock template. */
+ [DBus (name = "org.bluez.obex.Mock")]
+ public interface Mock : Object
+ {
+ [DBus (name = "TransferCreated")]
+ public abstract signal void transfer_created (string path,
+ HashTable<string, Variant> filters,
+ string transfer_filename);
+ }
+
+ namespace transfer1
+ {
+ [DBus (name = "org.bluez.obex.transfer1.Mock")]
+ public interface Mock : Object
+ {
+ [DBus (name = "UpdateStatus")]
+ public abstract void update_status (bool is_complete)
+ throws IOError;
+ }
+ }
+ }
+ }
+ }
+
+/**
+ * Controller for a mock BlueZ backend.
+ *
+ * This contains control methods to instantiate and manipulate a mock BlueZ
+ * service over D-Bus, for the purposes of testing the folks BlueZ backend.
+ *
+ * The mock service uses python-dbusmock, with control messages being sent to
+ * ``*.Mock`` interfaces on the D-Bus objects. Those control interfaces are
+ * exposed as {@link Backend.mock_bluez}, {@link Backend.mock_bluez_base},
+ * {@link Backend.mock_obex} and {@link Backend.mock_obex_base}.
+ *
+ * @since UNRELEASED
+ */
+public class BluezTest.Backend
+{
+ private org.bluez.Mock? _mock_bluez = null;
+ private org.freedesktop.DBus.Mock? _mock_bluez_base = null;
+ private org.bluez.obex.Mock? _mock_obex = null;
+ private org.freedesktop.DBus.Mock? _mock_obex_base = null;
+
+ /**
+ * D-Bus proxy for the BlueZ-specific mock interface on the org.bluez object.
+ *
+ * @since UNRELEASED
+ */
+ public org.bluez.Mock? mock_bluez
+ {
+ get { return this._mock_bluez; }
+ }
+
+ /**
+ * D-Bus proxy for the dbusmock mock interface on the org.bluez object.
+ *
+ * @since UNRELEASED
+ */
+ public org.freedesktop.DBus.Mock? mock_bluez_base
+ {
+ get { return this._mock_bluez_base; }
+ }
+
+ /**
+ * D-Bus proxy for the BlueZ-specific mock interface on the org.bluez.obex
+ * object.
+ *
+ * @since UNRELEASED
+ */
+ public org.bluez.obex.Mock? mock_obex
+ {
+ get { return this._mock_obex; }
+ }
+
+ /**
+ * D-Bus proxy for the dbusmock mock interface on the org.bluez.obex object.
+ *
+ * @since UNRELEASED
+ */
+ public org.freedesktop.DBus.Mock? mock_obex_base
+ {
+ get { return this._mock_obex_base; }
+ }
+
+ /**
+ * Default Bluetooth address used for the primary adapter.
+ *
+ * This is the address used for the primary Bluetooth adapter (``hci0``)
+ * unless otherwise specified.
+ *
+ * @since UNRELEASED
+ */
+ public string primary_device_address
+ {
+ get { return "00:00:00:00:00:00"; }
+ }
+
+ /**
+ * Set up the mock D-Bus interfaces.
+ *
+ * This must be called before every different unit test. It creates D-Bus
+ * proxies for the dbusmock objects, auto-launching python-dbusmock if
+ * necessary.
+ *
+ * The required D-Bus service files must previously have been set up with the
+ * buses which are in use. This is done in
+ * {@link TestCase.create_transient_dir}.
+ *
+ * @since UNRELEASED
+ */
+ public void set_up ()
+ {
+ /* Create proxies for the client code to use. This auto-starts the
+ * services. Their service files are created in TestCase. */
+ try
+ {
+ this._mock_bluez =
+ Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", "/");
+ this._mock_bluez_base =
+ Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", "/");
+
+ this._mock_obex =
+ Bus.get_proxy_sync (BusType.SESSION, "org.bluez.obex", "/");
+ this._mock_obex_base =
+ Bus.get_proxy_sync (BusType.SESSION, "org.bluez.obex", "/");
+ }
+ catch (GLib.Error e1)
+ {
+ /* Tidy up. */
+ this.tear_down ();
+
+ error ("Error connecting to mock object: %s", e1.message);
+ }
+ }
+
+ /**
+ * Tear down the mock D-Bus interfaces.
+ *
+ * This must be called after every different unit test. It undoes
+ * {@link Backend.set_up}, although the python-dbusmock processes are kept
+ * around and reset, rather than being killed.
+ *
+ * @since UNRELEASED
+ */
+ public void tear_down ()
+ {
+ /* Reset the python-dbusmock state. */
+ try
+ {
+ this._mock_obex_base.reset ();
+ this._mock_bluez_base.reset ();
+ }
+ catch (IOError e1)
+ {
+ error ("Error resetting python-dbusmock state: %s", e1.message);
+ }
+
+ /* Remove the D-Bus proxies. The python-dbusmock instances will close by
+ * themselves when the mock D-Bus buses are destroyed in
+ * final_tear_down(). */
+ this._mock_obex_base = null;
+ this._mock_bluez_base = null;
+ this._mock_obex = null;
+ this._mock_bluez = null;
+ }
+
+ /**
+ * Create a simple Bluetooth device with the given vCard.
+ *
+ * Create a new Bluetooth adapter (``hci0``) and a new Bluetooth device (with
+ * address {@link Backend.primary_device_address}). Pair with the Bluetooth
+ * device and simulate it having the given ``vcard`` (potentially containing
+ * multiple whitespace-separated entries) as its address book.
+ *
+ * On error this function will abort the test.
+ *
+ * @param vcard series of vCards for the device’s address book
+ * @param adapter_path optional return location for the adapter’s D-Bus object
+ * path
+ * @param device_path optional return location for the device’s D-Bus object
+ * path
+ * @return ID of the signal returning the vCard, as per
+ * {@link Backend.set_simple_device_vcard}
+ *
+ * @since UNRELEASED
+ */
+ public ulong create_simple_device_with_vcard (string vcard,
+ out string? adapter_path = null, out string? device_path = null)
+ {
+ try
+ {
+ /* Set up a Bluetooth adapter and a single persona store. */
+ adapter_path = this.mock_bluez.add_adapter ("hci0", "Test System");
+ device_path =
+ this.mock_bluez.add_device ("hci0", this.primary_device_address,
+ "My Phone");
+
+ /* Pair with the phone. */
+ this.mock_bluez.pair_device ("hci0", this.primary_device_address);
+
+ /* Set the vCard to be returned for all transfers. */
+ return this.set_simple_device_vcard (vcard);
+ }
+ catch (IOError e1)
+ {
+ error ("Error setting up mock BlueZ device: %s", e1.message);
+ }
+ }
+
+ /**
+ * Set the vCard to be returned by a simple Bluetooth device.
+ *
+ * This sets the vCard which will be returned indefinitely. It returns a
+ * signal ID which may be disconnected with:
+ * {{{
+ * this.mock_obex.disconnect (signal_id);
+ * }}}
+ * to prevent the vCard being returned in future.
+ *
+ * @param vcard series of vCards for the device’s address book
+ * @return ID of the signal returning the vCard
+ *
+ * @since UNRELEASED
+ */
+ public ulong set_simple_device_vcard (string vcard)
+ {
+ /* Wait for a transfer to be created. Skip activating it and go
+ * straight to completion. */
+ return this.mock_obex.transfer_created.connect ((p, f, v) =>
+ {
+ org.bluez.obex.transfer1.Mock proxy;
+
+ try
+ {
+ FileUtils.set_contents (v, vcard);
+ }
+ catch (FileError e1)
+ {
+ error ("Error writing vCard transfer file ‘%s’: %s",
+ v, e1.message);
+ }
+
+ try
+ {
+ proxy =
+ Bus.get_proxy_sync (BusType.SESSION, "org.bluez.obex", p);
+ proxy.update_status (true);
+ }
+ catch (GLib.Error e1)
+ {
+ error ("Error activating transfer: %s", e1.message);
+ }
+ });
+ }
+}
diff --git a/tests/lib/bluez/test-case.vala b/tests/lib/bluez/test-case.vala
new file mode 100644
index 00000000..accaf0b1
--- /dev/null
+++ b/tests/lib/bluez/test-case.vala
@@ -0,0 +1,127 @@
+/*
+ * Copyright © 2013 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+/**
+ * A test case for the BlueZ backend, whose private D-Bus session contains the
+ * necessary python-dbusmock instance to mock up BlueZ.
+ *
+ * @since UNRELEASED
+ */
+public class BluezTest.TestCase : Folks.TestCase
+{
+ /**
+ * A BlueZ backend, normally non-null between set_up() and tear_down().
+ *
+ * If this is non-null, the subclass is expected to have called
+ * its set_up() method at some point before tear_down() is reached.
+ * This usually happens in create_backend().
+ */
+ public BluezTest.Backend? bluez_backend = null;
+
+ public TestCase (string name)
+ {
+ base (name);
+
+ this.bluez_backend = new BluezTest.Backend ();
+
+ Environment.set_variable ("FOLKS_BACKENDS_ALLOWED", "bluez", true);
+ Environment.set_variable ("FOLKS_PRIMARY_STORE", "bluez", true);
+ Environment.set_variable ("FOLKS_BLUEZ_TIMEOUT_DIVISOR", "100", true);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override void set_up ()
+ {
+ base.set_up ();
+ this.create_backend ();
+ this.configure_primary_store ();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override void private_bus_up ()
+ {
+ /* Set up service files for the python-dbusmock services. */
+ this.create_dbusmock_service (BusType.SYSTEM, "org.bluez", "bluez5");
+ this.create_dbusmock_service (BusType.SESSION, "org.bluez.obex",
+ "bluez5-obex");
+
+ base.private_bus_up ();
+ }
+
+ /**
+ * Virtual method to create and set up the BlueZ backend.
+ *
+ * Called from set_up(); may be overridden to not create the backend,
+ * or to create it but not set it up.
+ *
+ * Subclasses may chain up, but are not required to so.
+ *
+ * @since UNRELEASED
+ */
+ public virtual void create_backend ()
+ {
+ this.bluez_backend = new BluezTest.Backend ();
+ ((!) this.bluez_backend).set_up ();
+ }
+
+ /**
+ * Virtual method to configure ``FOLKS_PRIMARY_STORE`` to point to
+ * our //bluez_backend//.
+ *
+ * Subclasses may chain up, but are not required to so.
+ *
+ * @since UNRELEASED
+ */
+ public virtual void configure_primary_store ()
+ {
+ /* By default, configure BlueZ as the primary store. */
+ assert (this.bluez_backend != null);
+ var config_val =
+ "bluez:" + ((!) this.bluez_backend).primary_device_address;
+ Environment.set_variable ("FOLKS_PRIMARY_STORE", config_val, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public override void tear_down ()
+ {
+ if (this.bluez_backend != null)
+ {
+ ((!) this.bluez_backend).tear_down ();
+ this.bluez_backend = null;
+ }
+
+ Environment.unset_variable ("FOLKS_PRIMARY_STORE");
+
+ base.tear_down ();
+ }
+}
diff --git a/tests/lib/test-case.vala b/tests/lib/test-case.vala
index 11673ba7..02d8c23f 100644
--- a/tests/lib/test-case.vala
+++ b/tests/lib/test-case.vala
@@ -79,6 +79,9 @@ public abstract class Folks.TestCase : Object
if (Folks.BuildConf.HAVE_TRACKER)
locations += Folks.BuildConf.ABS_TOP_BUILDDIR + "/backends/tracker/.libs/tracker.so";
+ if (Folks.BuildConf.HAVE_BLUEZ)
+ locations += Folks.BuildConf.ABS_TOP_BUILDDIR + "/backends/bluez/.libs/bluez.so";
+
Environment.set_variable ("FOLKS_BACKEND_PATH",
string.joinv (":", locations), true);
}