* 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., 59 Temple Place, Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Authors: David Zeuthen <davidz@redhat.com>
* Xavier Claessens <xavier.claessens@collabora.co.uk>
#include <stdlib.h>
#include <stdio.h>
#include <gstdio.h>
-#ifdef HAVE_UNISTD_H
+#ifdef G_OS_UNIX
#include <unistd.h>
#endif
#ifdef G_OS_WIN32
#include "glibintl.h"
#ifdef G_OS_WIN32
-#define _WIN32_WINNT 0x0500
#include <windows.h>
#endif
}
static gboolean
-unref_on_idle (gpointer object)
+dispose_on_idle (gpointer object)
{
+ g_object_run_dispose (object);
g_object_unref (object);
return FALSE;
}
static gboolean
-_g_object_unref_and_wait_weak_notify (gpointer object)
+_g_object_dispose_and_wait_weak_notify (gpointer object)
{
WeakNotifyData data;
guint timeout_id;
/* Drop the ref in an idle callback, this is to make sure the mainloop
* is already running when weak notify happens */
- g_idle_add (unref_on_idle, object);
+ g_idle_add (dispose_on_idle, object);
/* Make sure we don't block forever */
timeout_id = g_timeout_add (30 * 1000, on_weak_notify_timeout, &data);
g_main_loop_run (data.loop);
- g_source_remove (timeout_id);
-
if (data.timed_out)
{
g_warning ("Weak notify timeout, object ref_count=%d\n",
G_OBJECT (object)->ref_count);
}
+ else
+ {
+ g_source_remove (timeout_id);
+ }
+ g_main_loop_unref (data.loop);
return data.timed_out;
}
*
* A helper class for testing code which uses D-Bus without touching the user's
* session bus.
+ *
+ * Note that #GTestDBus modifies the user’s environment, calling setenv().
+ * This is not thread-safe, so all #GTestDBus calls should be completed before
+ * threads are spawned, or should have appropriate locking to ensure no access
+ * conflicts to environment variables shared between #GTestDBus and other
+ * threads.
+ *
+ * ## Creating unit tests using GTestDBus
+ *
+ * Testing of D-Bus services can be tricky because normally we only ever run
+ * D-Bus services over an existing instance of the D-Bus daemon thus we
+ * usually don't activate D-Bus services that are not yet installed into the
+ * target system. The #GTestDBus object makes this easier for us by taking care
+ * of the lower level tasks such as running a private D-Bus daemon and looking
+ * up uninstalled services in customizable locations, typically in your source
+ * code tree.
+ *
+ * The first thing you will need is a separate service description file for the
+ * D-Bus daemon. Typically a `services` subdirectory of your `tests` directory
+ * is a good place to put this file.
+ *
+ * The service file should list your service along with an absolute path to the
+ * uninstalled service executable in your source tree. Using autotools we would
+ * achieve this by adding a file such as `my-server.service.in` in the services
+ * directory and have it processed by configure.
+ * |[
+ * [D-BUS Service]
+ * Name=org.gtk.GDBus.Examples.ObjectManager
+ * Exec=@abs_top_builddir@/gio/tests/gdbus-example-objectmanager-server
+ * ]|
+ * You will also need to indicate this service directory in your test
+ * fixtures, so you will need to pass the path while compiling your
+ * test cases. Typically this is done with autotools with an added
+ * preprocessor flag specified to compile your tests such as:
+ * |[
+ * -DTEST_SERVICES=\""$(abs_top_builddir)/tests/services"\"
+ * ]|
+ * Once you have a service definition file which is local to your source tree,
+ * you can proceed to set up a GTest fixture using the #GTestDBus scaffolding.
+ *
+ * An example of a test fixture for D-Bus services can be found
+ * here:
+ * [gdbus-test-fixture.c](https://git.gnome.org/browse/glib/tree/gio/tests/gdbus-test-fixture.c)
+ *
+ * Note that these examples only deal with isolating the D-Bus aspect of your
+ * service. To successfully run isolated unit tests on your service you may need
+ * some additional modifications to your test case fixture. For example; if your
+ * service uses GSettings and installs a schema then it is important that your test service
+ * not load the schema in the ordinary installed location (chances are that your service
+ * and schema files are not yet installed, or worse; there is an older version of the
+ * schema file sitting in the install location).
+ *
+ * Most of the time we can work around these obstacles using the
+ * environment. Since the environment is inherited by the D-Bus daemon
+ * created by #GTestDBus and then in turn inherited by any services the
+ * D-Bus daemon activates, using the setup routine for your fixture is
+ * a practical place to help sandbox your runtime environment. For the
+ * rather typical GSettings case we can work around this by setting
+ * `GSETTINGS_SCHEMA_DIR` to the in tree directory holding your schemas
+ * in the above fixture_setup() routine.
+ *
+ * The GSettings schemas need to be locally pre-compiled for this to work. This can be achieved
+ * by compiling the schemas locally as a step before running test cases, an autotools setup might
+ * do the following in the directory holding schemas:
+ * |[
+ * all-am:
+ * $(GLIB_COMPILE_SCHEMAS) .
+ *
+ * CLEANFILES += gschemas.compiled
+ * ]|
*/
typedef struct _GTestDBusClass GTestDBusClass;
GTestDBusFlags flags;
GPtrArray *service_dirs;
GPid bus_pid;
+ gint bus_stdout_fd;
gchar *bus_address;
gboolean up;
};
PROP_FLAGS,
};
-G_DEFINE_TYPE (GTestDBus, g_test_dbus, G_TYPE_OBJECT)
+G_DEFINE_TYPE_WITH_PRIVATE (GTestDBus, g_test_dbus, G_TYPE_OBJECT)
static void
g_test_dbus_init (GTestDBus *self)
{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), G_TYPE_TEST_DBUS,
- GTestDBusPrivate);
-
+ self->priv = g_test_dbus_get_instance_private (self);
self->priv->service_dirs = g_ptr_array_new_with_free_func (g_free);
}
object_class->get_property = g_test_dbus_get_property;
object_class->set_property = g_test_dbus_set_property;
- g_type_class_add_private (object_class, sizeof (GTestDBusPrivate));
-
/**
* GTestDBus:flags:
*
- * #GTestDBusFlags specifying the behaviour of the dbus session
+ * #GTestDBusFlags specifying the behaviour of the D-Bus session.
*
* Since: 2.34
*/
g_object_class_install_property (object_class, PROP_FLAGS,
g_param_spec_flags ("flags",
- P_("dbus session flags"),
- P_("Flags specifying the behaviour of the dbus session"),
+ P_("D-Bus session flags"),
+ P_("Flags specifying the behaviour of the D-Bus session"),
G_TYPE_TEST_DBUS_FLAGS, G_TEST_DBUS_NONE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
for (i = 0; i < self->priv->service_dirs->len; i++)
{
- const gchar *path = g_ptr_array_index (self->priv->service_dirs, i);
+ const gchar *dir_path = g_ptr_array_index (self->priv->service_dirs, i);
g_string_append_printf (contents,
- " <servicedir>%s</servicedir>\n", path);
+ " <servicedir>%s</servicedir>\n", dir_path);
}
g_string_append (contents,
" </policy>\n"
"</busconfig>\n");
+ close (fd);
g_file_set_contents (path, contents->str, contents->len, &error);
g_assert_no_error (error);
g_string_free (contents, TRUE);
- close (fd);
-
return path;
}
static void
start_daemon (GTestDBus *self)
{
- gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL};
+ const gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL};
gchar *config_path;
gchar *config_arg;
- gint stdout_fd;
GIOChannel *channel;
+ gint stdout_fd2;
gsize termpos;
GError *error = NULL;
/* Spawn dbus-daemon */
g_spawn_async_with_pipes (NULL,
- argv,
+ (gchar **) argv,
NULL,
#ifdef G_OS_WIN32
/* We Need this to get the pid returned on win32 */
NULL,
&self->priv->bus_pid,
NULL,
- &stdout_fd,
+ &self->priv->bus_stdout_fd,
NULL,
&error);
g_assert_no_error (error);
_g_test_watcher_add_pid (self->priv->bus_pid);
- /* Read bus address from daemon' stdout */
- channel = g_io_channel_unix_new (stdout_fd);
+ /* Read bus address from daemon' stdout. We have to be careful to avoid
+ * closing the FD, as it is passed to any D-Bus service activated processes,
+ * and if we close it, they will get a SIGPIPE and die when they try to write
+ * to their stdout. */
+ stdout_fd2 = dup (self->priv->bus_stdout_fd);
+ g_assert_cmpint (stdout_fd2, >=, 0);
+ channel = g_io_channel_unix_new (stdout_fd2);
+
g_io_channel_read_line (channel, &self->priv->bus_address, NULL,
&termpos, &error);
g_assert_no_error (error);
_g_test_watcher_remove_pid (self->priv->bus_pid);
g_spawn_close_pid (self->priv->bus_pid);
self->priv->bus_pid = 0;
+ close (self->priv->bus_stdout_fd);
+ self->priv->bus_stdout_fd = -1;
g_free (self->priv->bus_address);
self->priv->bus_address = NULL;
* g_test_dbus_get_flags:
* @self: a #GTestDBus
*
- * Gets the flags of the #GTestDBus object.
+ * Get the flags of the #GTestDBus object.
*
* Returns: the value of #GTestDBus:flags property
*/
* g_test_dbus_get_bus_address:
* @self: a #GTestDBus
*
- * Get the address on which dbus-daemon is running. if g_test_dbus_up() has not
+ * Get the address on which dbus-daemon is running. If g_test_dbus_up() has not
* been called yet, %NULL is returned. This can be used with
- * g_dbus_connection_new_for_address()
+ * g_dbus_connection_new_for_address().
*
- * Returns: the address of the bus, or %NULL.
+ * Returns: (allow-none): the address of the bus, or %NULL.
*/
const gchar *
g_test_dbus_get_bus_address (GTestDBus *self)
* @self: a #GTestDBus
* @path: path to a directory containing .service files
*
- * Add a path where dbus-daemon will lookup for .services files. This can't be
+ * Add a path where dbus-daemon will look up .service files. This can't be
* called after g_test_dbus_up().
*/
void
start_daemon (self);
+ g_test_dbus_unset ();
g_setenv ("DBUS_SESSION_BUS_ADDRESS", self->priv->bus_address, TRUE);
self->priv->up = TRUE;
}
stop_daemon (self);
if (connection != NULL)
- _g_object_unref_and_wait_weak_notify (connection);
+ _g_object_dispose_and_wait_weak_notify (connection);
- g_unsetenv ("DBUS_SESSION_BUS_ADDRESS");
+ g_test_dbus_unset ();
self->priv->up = FALSE;
}
{
g_unsetenv ("DISPLAY");
g_unsetenv ("DBUS_SESSION_BUS_ADDRESS");
+ g_unsetenv ("DBUS_STARTER_ADDRESS");
+ g_unsetenv ("DBUS_STARTER_BUS_TYPE");
}