X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gio%2Fgtestdbus.c;h=4af270be0fb32e625047df3e6e60c2d22e18da51;hb=4482977238ae80f64c2fe318d1500f4662c73980;hp=dec92fa1f58d6e6568a7af381e8be0f316a49b38;hpb=95bf3d1194a42aa0933e2d1dba26ba57395613fc;p=platform%2Fupstream%2Fglib.git
diff --git a/gio/gtestdbus.c b/gio/gtestdbus.c
index dec92fa..4af270b 100644
--- a/gio/gtestdbus.c
+++ b/gio/gtestdbus.c
@@ -14,9 +14,7 @@
* 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 .
*
* Authors: David Zeuthen
* Xavier Claessens
@@ -26,7 +24,13 @@
#include
#include
+#include
+#ifdef G_OS_UNIX
#include
+#endif
+#ifdef G_OS_WIN32
+#include
+#endif
#include
@@ -38,6 +42,10 @@
#include "glibintl.h"
+#ifdef G_OS_WIN32
+#include
+#endif
+
/* -------------------------------------------------------------------------- */
/* Utility: Wait until object has a single ref */
@@ -57,14 +65,15 @@ on_weak_notify_timeout (gpointer user_data)
}
static gboolean
-unref_on_idle (gpointer object)
+dispose_on_idle (gpointer object)
{
+ g_object_run_dispose (object);
g_object_unref (object);
return FALSE;
}
-gboolean
-_g_object_unref_and_wait_weak_notify (gpointer object)
+static gboolean
+_g_object_dispose_and_wait_weak_notify (gpointer object)
{
WeakNotifyData data;
guint timeout_id;
@@ -76,27 +85,68 @@ _g_object_unref_and_wait_weak_notify (gpointer object)
/* 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;
}
/* -------------------------------------------------------------------------- */
/* Utilities to cleanup the mess in the case unit test process crash */
+#ifdef G_OS_WIN32
+
+/* This could be interesting to expose in public API */
+static void
+_g_test_watcher_add_pid (GPid pid)
+{
+ static gsize started = 0;
+ HANDLE job;
+
+ if (g_once_init_enter (&started))
+ {
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
+
+ job = CreateJobObjectW (NULL, NULL);
+ memset (&info, 0, sizeof (info));
+ info.BasicLimitInformation.LimitFlags = 0x2000 /* JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE */;
+
+ if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info, sizeof (info)))
+ g_warning ("Can't enable JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: %s", g_win32_error_message (GetLastError()));
+
+ g_once_init_leave (&started,(gsize)job);
+ }
+
+ job = (HANDLE)started;
+
+ if (!AssignProcessToJobObject(job, pid))
+ g_warning ("Can't assign process to job: %s", g_win32_error_message (GetLastError()));
+}
+
+static void
+_g_test_watcher_remove_pid (GPid pid)
+{
+ /* No need to unassign the process from the job object as the process
+ will be killed anyway */
+}
+
+#else
+
#define ADD_PID_FORMAT "add pid %d\n"
#define REMOVE_PID_FORMAT "remove pid %d\n"
@@ -258,6 +308,8 @@ _g_test_watcher_remove_pid (GPid pid)
g_free (command);
}
+#endif
+
/* -------------------------------------------------------------------------- */
/* GTestDBus object implementation */
@@ -266,7 +318,78 @@ _g_test_watcher_remove_pid (GPid pid)
* @short_description: D-Bus testing helper
* @include: gio/gio.h
*
- * Helper to test D-Bus code wihtout messing up with user' session bus.
+ * 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;
@@ -295,6 +418,7 @@ struct _GTestDBusPrivate
GTestDBusFlags flags;
GPtrArray *service_dirs;
GPid bus_pid;
+ gint bus_stdout_fd;
gchar *bus_address;
gboolean up;
};
@@ -305,14 +429,12 @@ enum
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);
}
@@ -386,50 +508,52 @@ g_test_dbus_class_init (GTestDBusClass *klass)
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));
}
-static GFile *
+static gchar *
write_config_file (GTestDBus *self)
{
GString *contents;
- GFile *file;
- GFileIOStream *iostream;
+ gint fd;
guint i;
GError *error = NULL;
+ gchar *path = NULL;
- file = g_file_new_tmp ("g-test-dbus-XXXXXX", &iostream, &error);
+ fd = g_file_open_tmp ("g-test-dbus-XXXXXX", &path, &error);
g_assert_no_error (error);
- g_object_unref (iostream);
contents = g_string_new (NULL);
g_string_append (contents,
"\n"
" session\n"
- " unix:tmpdir=/tmp\n");
+#ifdef G_OS_WIN32
+ " nonce-tcp:\n"
+#else
+ " unix:tmpdir=/tmp\n"
+#endif
+ );
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,
- " %s\n", path);
+ " %s\n", dir_path);
}
g_string_append (contents,
@@ -443,51 +567,62 @@ write_config_file (GTestDBus *self)
" \n"
"\n");
- g_file_replace_contents (file, contents->str, contents->len,
- NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
+ close (fd);
+ g_file_set_contents (path, contents->str, contents->len, &error);
g_assert_no_error (error);
g_string_free (contents, TRUE);
- return file;
+ return path;
}
static void
start_daemon (GTestDBus *self)
{
- gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL};
- GFile *file;
+ 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;
+ if (g_getenv ("G_TEST_DBUS_DAEMON") != NULL)
+ argv[0] = (gchar *)g_getenv ("G_TEST_DBUS_DAEMON");
+
/* Write config file and set its path in argv */
- file = write_config_file (self);
- config_path = g_file_get_path (file);
+ config_path = write_config_file (self);
config_arg = g_strdup_printf ("--config-file=%s", config_path);
argv[2] = config_arg;
/* 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 */
+ G_SPAWN_DO_NOT_REAP_CHILD |
+#endif
G_SPAWN_SEARCH_PATH,
NULL,
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);
@@ -503,7 +638,7 @@ start_daemon (GTestDBus *self)
g_spawn_command_line_async (command, NULL);
g_free (command);
- usleep (500 * 1000);
+ g_usleep (500 * 1000);
}
/* Cleanup */
@@ -511,9 +646,9 @@ start_daemon (GTestDBus *self)
g_assert_no_error (error);
g_io_channel_unref (channel);
- g_file_delete (file, NULL, &error);
- g_assert_no_error (error);
- g_object_unref (file);
+ /* Don't use g_file_delete since it calls into gvfs */
+ if (g_unlink (config_path) != 0)
+ g_assert_not_reached ();
g_free (config_path);
g_free (config_arg);
@@ -522,9 +657,17 @@ start_daemon (GTestDBus *self)
static void
stop_daemon (GTestDBus *self)
{
+#ifdef G_OS_WIN32
+ if (!TerminateProcess (self->priv->bus_pid, 0))
+ g_warning ("Can't terminate process: %s", g_win32_error_message (GetLastError()));
+#else
kill (self->priv->bus_pid, SIGTERM);
+#endif
_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;
@@ -550,6 +693,8 @@ g_test_dbus_new (GTestDBusFlags flags)
* g_test_dbus_get_flags:
* @self: a #GTestDBus
*
+ * Get the flags of the #GTestDBus object.
+ *
* Returns: the value of #GTestDBus:flags property
*/
GTestDBusFlags
@@ -564,11 +709,11 @@ g_test_dbus_get_flags (GTestDBus *self)
* 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)
@@ -583,7 +728,7 @@ 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
@@ -601,7 +746,7 @@ g_test_dbus_add_service_dir (GTestDBus *self,
* @self: a #GTestDBus
*
* Start a dbus-daemon instance and set DBUS_SESSION_BUS_ADDRESS. After this
- * call, it is safe for unit tests to start sending messages on the session bug.
+ * call, it is safe for unit tests to start sending messages on the session bus.
*
* If this function is called from setup callback of g_test_add(),
* g_test_dbus_down() must be called in its teardown callback.
@@ -618,6 +763,7 @@ g_test_dbus_up (GTestDBus *self)
start_daemon (self);
+ g_test_dbus_unset ();
g_setenv ("DBUS_SESSION_BUS_ADDRESS", self->priv->bus_address, TRUE);
self->priv->up = TRUE;
}
@@ -669,9 +815,9 @@ g_test_dbus_down (GTestDBus *self)
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;
}
@@ -690,4 +836,6 @@ g_test_dbus_unset (void)
{
g_unsetenv ("DISPLAY");
g_unsetenv ("DBUS_SESSION_BUS_ADDRESS");
+ g_unsetenv ("DBUS_STARTER_ADDRESS");
+ g_unsetenv ("DBUS_STARTER_BUS_TYPE");
}