1 | /* GIO testing utilities |
2 | * |
3 | * Copyright (C) 2008-2010 Red Hat, Inc. |
4 | * Copyright (C) 2012 Collabora Ltd. <http://www.collabora.co.uk/> |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2.1 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General |
17 | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | * |
19 | * Authors: David Zeuthen <davidz@redhat.com> |
20 | * Xavier Claessens <xavier.claessens@collabora.co.uk> |
21 | */ |
22 | |
23 | #include "config.h" |
24 | |
25 | #include <stdlib.h> |
26 | #include <stdio.h> |
27 | #include <errno.h> |
28 | #include <string.h> |
29 | #include <gstdio.h> |
30 | #ifdef G_OS_UNIX |
31 | #include <unistd.h> |
32 | #endif |
33 | #ifdef G_OS_WIN32 |
34 | #include <io.h> |
35 | #endif |
36 | |
37 | #include <glib.h> |
38 | |
39 | #include "gdbusconnection.h" |
40 | #include "gdbusprivate.h" |
41 | #include "gfile.h" |
42 | #include "gioenumtypes.h" |
43 | #include "gtestdbus.h" |
44 | |
45 | #include "glibintl.h" |
46 | |
47 | #ifdef G_OS_WIN32 |
48 | #include <windows.h> |
49 | #endif |
50 | |
51 | /* -------------------------------------------------------------------------- */ |
52 | /* Utility: Wait until object has a single ref */ |
53 | |
54 | typedef struct |
55 | { |
56 | GMainLoop *loop; |
57 | gboolean timed_out; |
58 | } WeakNotifyData; |
59 | |
60 | static gboolean |
61 | on_weak_notify_timeout (gpointer user_data) |
62 | { |
63 | WeakNotifyData *data = user_data; |
64 | data->timed_out = TRUE; |
65 | g_main_loop_quit (loop: data->loop); |
66 | return FALSE; |
67 | } |
68 | |
69 | static gboolean |
70 | unref_on_idle (gpointer object) |
71 | { |
72 | g_object_unref (object); |
73 | return FALSE; |
74 | } |
75 | |
76 | static gboolean |
77 | _g_object_unref_and_wait_weak_notify (gpointer object) |
78 | { |
79 | WeakNotifyData data; |
80 | guint timeout_id; |
81 | |
82 | data.loop = g_main_loop_new (NULL, FALSE); |
83 | data.timed_out = FALSE; |
84 | |
85 | g_object_weak_ref (object, notify: (GWeakNotify) g_main_loop_quit, data: data.loop); |
86 | |
87 | /* Drop the strong ref held by the caller in an idle callback. This is to |
88 | * make sure the mainloop is already running when weak notify happens (when |
89 | * all other strong ref holders have dropped theirs). */ |
90 | g_idle_add (function: unref_on_idle, data: object); |
91 | |
92 | /* Make sure we don't block forever */ |
93 | timeout_id = g_timeout_add (interval: 30 * 1000, function: on_weak_notify_timeout, data: &data); |
94 | |
95 | g_main_loop_run (loop: data.loop); |
96 | |
97 | if (data.timed_out) |
98 | { |
99 | g_warning ("Weak notify timeout, object ref_count=%d" , |
100 | G_OBJECT (object)->ref_count); |
101 | } |
102 | else |
103 | { |
104 | g_source_remove (tag: timeout_id); |
105 | } |
106 | |
107 | g_main_loop_unref (loop: data.loop); |
108 | return data.timed_out; |
109 | } |
110 | |
111 | /* -------------------------------------------------------------------------- */ |
112 | /* Utilities to cleanup the mess in the case unit test process crash */ |
113 | |
114 | #ifdef G_OS_WIN32 |
115 | |
116 | /* This could be interesting to expose in public API */ |
117 | static void |
118 | _g_test_watcher_add_pid (GPid pid) |
119 | { |
120 | static gsize started = 0; |
121 | HANDLE job; |
122 | |
123 | if (g_once_init_enter (&started)) |
124 | { |
125 | JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; |
126 | |
127 | job = CreateJobObjectW (NULL, NULL); |
128 | memset (&info, 0, sizeof (info)); |
129 | info.BasicLimitInformation.LimitFlags = 0x2000 /* JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE */; |
130 | |
131 | if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info, sizeof (info))) |
132 | g_warning ("Can't enable JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: %s" , g_win32_error_message (GetLastError())); |
133 | |
134 | g_once_init_leave (&started,(gsize)job); |
135 | } |
136 | |
137 | job = (HANDLE)started; |
138 | |
139 | if (!AssignProcessToJobObject(job, pid)) |
140 | g_warning ("Can't assign process to job: %s" , g_win32_error_message (GetLastError())); |
141 | } |
142 | |
143 | static void |
144 | _g_test_watcher_remove_pid (GPid pid) |
145 | { |
146 | /* No need to unassign the process from the job object as the process |
147 | will be killed anyway */ |
148 | } |
149 | |
150 | #else |
151 | |
152 | #define ADD_PID_FORMAT "add pid %d\n" |
153 | #define REMOVE_PID_FORMAT "remove pid %d\n" |
154 | |
155 | static void |
156 | watch_parent (gint fd) |
157 | { |
158 | GIOChannel *channel; |
159 | GPollFD fds[1]; |
160 | GArray *pids_to_kill; |
161 | |
162 | channel = g_io_channel_unix_new (fd); |
163 | |
164 | fds[0].fd = fd; |
165 | fds[0].events = G_IO_HUP | G_IO_IN; |
166 | fds[0].revents = 0; |
167 | |
168 | pids_to_kill = g_array_new (FALSE, FALSE, element_size: sizeof (guint)); |
169 | |
170 | do |
171 | { |
172 | gint num_events; |
173 | gchar *command = NULL; |
174 | guint pid; |
175 | guint n; |
176 | GError *error = NULL; |
177 | |
178 | num_events = g_poll (fds, nfds: 1, timeout: -1); |
179 | if (num_events == 0) |
180 | continue; |
181 | |
182 | if (fds[0].revents & G_IO_HUP) |
183 | { |
184 | /* Parent quit, cleanup the mess and exit */ |
185 | for (n = 0; n < pids_to_kill->len; n++) |
186 | { |
187 | pid = g_array_index (pids_to_kill, guint, n); |
188 | g_printerr (format: "cleaning up pid %d\n" , pid); |
189 | kill (pid: pid, SIGTERM); |
190 | } |
191 | |
192 | g_array_unref (array: pids_to_kill); |
193 | g_io_channel_shutdown (channel, FALSE, err: &error); |
194 | g_assert_no_error (error); |
195 | g_io_channel_unref (channel); |
196 | |
197 | exit (status: 0); |
198 | } |
199 | |
200 | /* Read the command from the input */ |
201 | g_io_channel_read_line (channel, str_return: &command, NULL, NULL, error: &error); |
202 | g_assert_no_error (error); |
203 | |
204 | /* Check for known commands */ |
205 | if (sscanf (s: command, ADD_PID_FORMAT, &pid) == 1) |
206 | { |
207 | g_array_append_val (pids_to_kill, pid); |
208 | } |
209 | else if (sscanf (s: command, REMOVE_PID_FORMAT, &pid) == 1) |
210 | { |
211 | for (n = 0; n < pids_to_kill->len; n++) |
212 | { |
213 | if (g_array_index (pids_to_kill, guint, n) == pid) |
214 | { |
215 | g_array_remove_index (array: pids_to_kill, index_: n); |
216 | pid = 0; |
217 | break; |
218 | } |
219 | } |
220 | if (pid != 0) |
221 | { |
222 | g_warning ("unknown pid %d to remove" , pid); |
223 | } |
224 | } |
225 | else |
226 | { |
227 | g_warning ("unknown command from parent '%s'" , command); |
228 | } |
229 | |
230 | g_free (mem: command); |
231 | } |
232 | while (TRUE); |
233 | } |
234 | |
235 | static GIOChannel * |
236 | watcher_init (void) |
237 | { |
238 | static gsize started = 0; |
239 | static GIOChannel *channel = NULL; |
240 | int errsv; |
241 | |
242 | if (g_once_init_enter (&started)) |
243 | { |
244 | gint pipe_fds[2]; |
245 | |
246 | /* fork a child to clean up when we are killed */ |
247 | if (pipe (pipedes: pipe_fds) != 0) |
248 | { |
249 | errsv = errno; |
250 | g_warning ("pipe() failed: %s" , g_strerror (errsv)); |
251 | g_assert_not_reached (); |
252 | } |
253 | |
254 | /* flush streams to avoid buffers being duplicated in the child and |
255 | * flushed by both the child and parent later |
256 | * |
257 | * FIXME: This is a workaround for the fact that watch_parent() uses |
258 | * non-async-signal-safe API. See |
259 | * https://gitlab.gnome.org/GNOME/glib/-/issues/2322#note_1034330 |
260 | */ |
261 | fflush (stdout); |
262 | fflush (stderr); |
263 | |
264 | switch (fork ()) |
265 | { |
266 | case -1: |
267 | errsv = errno; |
268 | g_warning ("fork() failed: %s" , g_strerror (errsv)); |
269 | g_assert_not_reached (); |
270 | break; |
271 | |
272 | case 0: |
273 | /* child */ |
274 | close (fd: pipe_fds[1]); |
275 | watch_parent (fd: pipe_fds[0]); |
276 | break; |
277 | |
278 | default: |
279 | /* parent */ |
280 | close (fd: pipe_fds[0]); |
281 | channel = g_io_channel_unix_new (fd: pipe_fds[1]); |
282 | } |
283 | |
284 | g_once_init_leave (&started, 1); |
285 | } |
286 | |
287 | return channel; |
288 | } |
289 | |
290 | static void |
291 | watcher_send_command (const gchar *command) |
292 | { |
293 | GIOChannel *channel; |
294 | GError *error = NULL; |
295 | GIOStatus status; |
296 | |
297 | channel = watcher_init (); |
298 | |
299 | do |
300 | status = g_io_channel_write_chars (channel, buf: command, count: -1, NULL, error: &error); |
301 | while (status == G_IO_STATUS_AGAIN); |
302 | g_assert_no_error (error); |
303 | |
304 | g_io_channel_flush (channel, error: &error); |
305 | g_assert_no_error (error); |
306 | } |
307 | |
308 | /* This could be interesting to expose in public API */ |
309 | static void |
310 | _g_test_watcher_add_pid (GPid pid) |
311 | { |
312 | gchar *command; |
313 | |
314 | command = g_strdup_printf (ADD_PID_FORMAT, (guint) pid); |
315 | watcher_send_command (command); |
316 | g_free (mem: command); |
317 | } |
318 | |
319 | static void |
320 | _g_test_watcher_remove_pid (GPid pid) |
321 | { |
322 | gchar *command; |
323 | |
324 | command = g_strdup_printf (REMOVE_PID_FORMAT, (guint) pid); |
325 | watcher_send_command (command); |
326 | g_free (mem: command); |
327 | } |
328 | |
329 | #endif |
330 | |
331 | /* -------------------------------------------------------------------------- */ |
332 | /* GTestDBus object implementation */ |
333 | |
334 | /** |
335 | * SECTION:gtestdbus |
336 | * @short_description: D-Bus testing helper |
337 | * @include: gio/gio.h |
338 | * |
339 | * A helper class for testing code which uses D-Bus without touching the user's |
340 | * session bus. |
341 | * |
342 | * Note that #GTestDBus modifies the user’s environment, calling setenv(). |
343 | * This is not thread-safe, so all #GTestDBus calls should be completed before |
344 | * threads are spawned, or should have appropriate locking to ensure no access |
345 | * conflicts to environment variables shared between #GTestDBus and other |
346 | * threads. |
347 | * |
348 | * ## Creating unit tests using GTestDBus |
349 | * |
350 | * Testing of D-Bus services can be tricky because normally we only ever run |
351 | * D-Bus services over an existing instance of the D-Bus daemon thus we |
352 | * usually don't activate D-Bus services that are not yet installed into the |
353 | * target system. The #GTestDBus object makes this easier for us by taking care |
354 | * of the lower level tasks such as running a private D-Bus daemon and looking |
355 | * up uninstalled services in customizable locations, typically in your source |
356 | * code tree. |
357 | * |
358 | * The first thing you will need is a separate service description file for the |
359 | * D-Bus daemon. Typically a `services` subdirectory of your `tests` directory |
360 | * is a good place to put this file. |
361 | * |
362 | * The service file should list your service along with an absolute path to the |
363 | * uninstalled service executable in your source tree. Using autotools we would |
364 | * achieve this by adding a file such as `my-server.service.in` in the services |
365 | * directory and have it processed by configure. |
366 | * |[ |
367 | * [D-BUS Service] |
368 | * Name=org.gtk.GDBus.Examples.ObjectManager |
369 | * Exec=@abs_top_builddir@/gio/tests/gdbus-example-objectmanager-server |
370 | * ]| |
371 | * You will also need to indicate this service directory in your test |
372 | * fixtures, so you will need to pass the path while compiling your |
373 | * test cases. Typically this is done with autotools with an added |
374 | * preprocessor flag specified to compile your tests such as: |
375 | * |[ |
376 | * -DTEST_SERVICES=\""$(abs_top_builddir)/tests/services"\" |
377 | * ]| |
378 | * Once you have a service definition file which is local to your source tree, |
379 | * you can proceed to set up a GTest fixture using the #GTestDBus scaffolding. |
380 | * |
381 | * An example of a test fixture for D-Bus services can be found |
382 | * here: |
383 | * [gdbus-test-fixture.c](https://git.gnome.org/browse/glib/tree/gio/tests/gdbus-test-fixture.c) |
384 | * |
385 | * Note that these examples only deal with isolating the D-Bus aspect of your |
386 | * service. To successfully run isolated unit tests on your service you may need |
387 | * some additional modifications to your test case fixture. For example; if your |
388 | * service uses GSettings and installs a schema then it is important that your test service |
389 | * not load the schema in the ordinary installed location (chances are that your service |
390 | * and schema files are not yet installed, or worse; there is an older version of the |
391 | * schema file sitting in the install location). |
392 | * |
393 | * Most of the time we can work around these obstacles using the |
394 | * environment. Since the environment is inherited by the D-Bus daemon |
395 | * created by #GTestDBus and then in turn inherited by any services the |
396 | * D-Bus daemon activates, using the setup routine for your fixture is |
397 | * a practical place to help sandbox your runtime environment. For the |
398 | * rather typical GSettings case we can work around this by setting |
399 | * `GSETTINGS_SCHEMA_DIR` to the in tree directory holding your schemas |
400 | * in the above fixture_setup() routine. |
401 | * |
402 | * The GSettings schemas need to be locally pre-compiled for this to work. This can be achieved |
403 | * by compiling the schemas locally as a step before running test cases, an autotools setup might |
404 | * do the following in the directory holding schemas: |
405 | * |[ |
406 | * all-am: |
407 | * $(GLIB_COMPILE_SCHEMAS) . |
408 | * |
409 | * CLEANFILES += gschemas.compiled |
410 | * ]| |
411 | */ |
412 | |
413 | typedef struct _GTestDBusClass GTestDBusClass; |
414 | typedef struct _GTestDBusPrivate GTestDBusPrivate; |
415 | |
416 | /** |
417 | * GTestDBus: |
418 | * |
419 | * The #GTestDBus structure contains only private data and |
420 | * should only be accessed using the provided API. |
421 | * |
422 | * Since: 2.34 |
423 | */ |
424 | struct _GTestDBus { |
425 | GObject parent; |
426 | |
427 | GTestDBusPrivate *priv; |
428 | }; |
429 | |
430 | struct _GTestDBusClass { |
431 | GObjectClass parent_class; |
432 | }; |
433 | |
434 | struct _GTestDBusPrivate |
435 | { |
436 | GTestDBusFlags flags; |
437 | GPtrArray *service_dirs; |
438 | GPid bus_pid; |
439 | gint bus_stdout_fd; |
440 | gchar *bus_address; |
441 | gboolean up; |
442 | }; |
443 | |
444 | enum |
445 | { |
446 | PROP_0, |
447 | PROP_FLAGS, |
448 | }; |
449 | |
450 | G_DEFINE_TYPE_WITH_PRIVATE (GTestDBus, g_test_dbus, G_TYPE_OBJECT) |
451 | |
452 | static void |
453 | g_test_dbus_init (GTestDBus *self) |
454 | { |
455 | self->priv = g_test_dbus_get_instance_private (self); |
456 | self->priv->service_dirs = g_ptr_array_new_with_free_func (element_free_func: g_free); |
457 | } |
458 | |
459 | static void |
460 | g_test_dbus_dispose (GObject *object) |
461 | { |
462 | GTestDBus *self = (GTestDBus *) object; |
463 | |
464 | if (self->priv->up) |
465 | g_test_dbus_down (self); |
466 | |
467 | G_OBJECT_CLASS (g_test_dbus_parent_class)->dispose (object); |
468 | } |
469 | |
470 | static void |
471 | g_test_dbus_finalize (GObject *object) |
472 | { |
473 | GTestDBus *self = (GTestDBus *) object; |
474 | |
475 | g_ptr_array_unref (array: self->priv->service_dirs); |
476 | g_free (mem: self->priv->bus_address); |
477 | |
478 | G_OBJECT_CLASS (g_test_dbus_parent_class)->finalize (object); |
479 | } |
480 | |
481 | static void |
482 | g_test_dbus_get_property (GObject *object, |
483 | guint property_id, |
484 | GValue *value, |
485 | GParamSpec *pspec) |
486 | { |
487 | GTestDBus *self = (GTestDBus *) object; |
488 | |
489 | switch (property_id) |
490 | { |
491 | case PROP_FLAGS: |
492 | g_value_set_flags (value, v_flags: g_test_dbus_get_flags (self)); |
493 | break; |
494 | default: |
495 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
496 | break; |
497 | } |
498 | } |
499 | |
500 | static void |
501 | g_test_dbus_set_property (GObject *object, |
502 | guint property_id, |
503 | const GValue *value, |
504 | GParamSpec *pspec) |
505 | { |
506 | GTestDBus *self = (GTestDBus *) object; |
507 | |
508 | switch (property_id) |
509 | { |
510 | case PROP_FLAGS: |
511 | self->priv->flags = g_value_get_flags (value); |
512 | break; |
513 | default: |
514 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
515 | break; |
516 | } |
517 | } |
518 | |
519 | static void |
520 | g_test_dbus_class_init (GTestDBusClass *klass) |
521 | { |
522 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
523 | |
524 | object_class->dispose = g_test_dbus_dispose; |
525 | object_class->finalize = g_test_dbus_finalize; |
526 | object_class->get_property = g_test_dbus_get_property; |
527 | object_class->set_property = g_test_dbus_set_property; |
528 | |
529 | /** |
530 | * GTestDBus:flags: |
531 | * |
532 | * #GTestDBusFlags specifying the behaviour of the D-Bus session. |
533 | * |
534 | * Since: 2.34 |
535 | */ |
536 | g_object_class_install_property (oclass: object_class, property_id: PROP_FLAGS, |
537 | pspec: g_param_spec_flags (name: "flags" , |
538 | P_("D-Bus session flags" ), |
539 | P_("Flags specifying the behaviour of the D-Bus session" ), |
540 | flags_type: G_TYPE_TEST_DBUS_FLAGS, default_value: G_TEST_DBUS_NONE, |
541 | flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
542 | G_PARAM_STATIC_STRINGS)); |
543 | |
544 | } |
545 | |
546 | static gchar * |
547 | write_config_file (GTestDBus *self) |
548 | { |
549 | GString *contents; |
550 | gint fd; |
551 | guint i; |
552 | GError *error = NULL; |
553 | gchar *path = NULL; |
554 | |
555 | fd = g_file_open_tmp (tmpl: "g-test-dbus-XXXXXX" , name_used: &path, error: &error); |
556 | g_assert_no_error (error); |
557 | |
558 | contents = g_string_new (NULL); |
559 | g_string_append (string: contents, |
560 | val: "<busconfig>\n" |
561 | " <type>session</type>\n" |
562 | #ifdef G_OS_WIN32 |
563 | " <listen>nonce-tcp:</listen>\n" |
564 | #else |
565 | " <listen>unix:tmpdir=/tmp</listen>\n" |
566 | #endif |
567 | ); |
568 | |
569 | for (i = 0; i < self->priv->service_dirs->len; i++) |
570 | { |
571 | const gchar *dir_path = g_ptr_array_index (self->priv->service_dirs, i); |
572 | |
573 | g_string_append_printf (string: contents, |
574 | format: " <servicedir>%s</servicedir>\n" , dir_path); |
575 | } |
576 | |
577 | g_string_append (string: contents, |
578 | val: " <policy context=\"default\">\n" |
579 | " <!-- Allow everything to be sent -->\n" |
580 | " <allow send_destination=\"*\" eavesdrop=\"true\"/>\n" |
581 | " <!-- Allow everything to be received -->\n" |
582 | " <allow eavesdrop=\"true\"/>\n" |
583 | " <!-- Allow anyone to own anything -->\n" |
584 | " <allow own=\"*\"/>\n" |
585 | " </policy>\n" |
586 | "</busconfig>\n" ); |
587 | |
588 | close (fd: fd); |
589 | g_file_set_contents_full (filename: path, contents: contents->str, length: contents->len, |
590 | flags: G_FILE_SET_CONTENTS_NONE, |
591 | mode: 0600, error: &error); |
592 | g_assert_no_error (error); |
593 | |
594 | g_string_free (string: contents, TRUE); |
595 | |
596 | return path; |
597 | } |
598 | |
599 | static void |
600 | start_daemon (GTestDBus *self) |
601 | { |
602 | const gchar *argv[] = {"dbus-daemon" , "--print-address" , "--config-file=foo" , NULL}; |
603 | gchar *config_path; |
604 | gchar *config_arg; |
605 | GIOChannel *channel; |
606 | gint stdout_fd2; |
607 | gsize termpos; |
608 | GError *error = NULL; |
609 | |
610 | if (g_getenv (variable: "G_TEST_DBUS_DAEMON" ) != NULL) |
611 | argv[0] = (gchar *)g_getenv (variable: "G_TEST_DBUS_DAEMON" ); |
612 | |
613 | /* Write config file and set its path in argv */ |
614 | config_path = write_config_file (self); |
615 | config_arg = g_strdup_printf (format: "--config-file=%s" , config_path); |
616 | argv[2] = config_arg; |
617 | |
618 | /* Spawn dbus-daemon */ |
619 | g_spawn_async_with_pipes (NULL, |
620 | argv: (gchar **) argv, |
621 | NULL, |
622 | /* We Need this to get the pid returned on win32 */ |
623 | flags: G_SPAWN_DO_NOT_REAP_CHILD | |
624 | G_SPAWN_SEARCH_PATH | |
625 | /* dbus-daemon will not abuse our descriptors, and |
626 | * passing this means we can use posix_spawn() for speed */ |
627 | G_SPAWN_LEAVE_DESCRIPTORS_OPEN, |
628 | NULL, |
629 | NULL, |
630 | child_pid: &self->priv->bus_pid, |
631 | NULL, |
632 | standard_output: &self->priv->bus_stdout_fd, |
633 | NULL, |
634 | error: &error); |
635 | g_assert_no_error (error); |
636 | |
637 | _g_test_watcher_add_pid (pid: self->priv->bus_pid); |
638 | |
639 | /* Read bus address from daemon' stdout. We have to be careful to avoid |
640 | * closing the FD, as it is passed to any D-Bus service activated processes, |
641 | * and if we close it, they will get a SIGPIPE and die when they try to write |
642 | * to their stdout. */ |
643 | stdout_fd2 = dup (fd: self->priv->bus_stdout_fd); |
644 | g_assert_cmpint (stdout_fd2, >=, 0); |
645 | channel = g_io_channel_unix_new (fd: stdout_fd2); |
646 | |
647 | g_io_channel_read_line (channel, str_return: &self->priv->bus_address, NULL, |
648 | terminator_pos: &termpos, error: &error); |
649 | g_assert_no_error (error); |
650 | self->priv->bus_address[termpos] = '\0'; |
651 | |
652 | /* start dbus-monitor */ |
653 | if (g_getenv (variable: "G_DBUS_MONITOR" ) != NULL) |
654 | { |
655 | gchar *command; |
656 | |
657 | command = g_strdup_printf (format: "dbus-monitor --address %s" , |
658 | self->priv->bus_address); |
659 | g_spawn_command_line_async (command_line: command, NULL); |
660 | g_free (mem: command); |
661 | |
662 | g_usleep (microseconds: 500 * 1000); |
663 | } |
664 | |
665 | /* Cleanup */ |
666 | g_io_channel_shutdown (channel, FALSE, err: &error); |
667 | g_assert_no_error (error); |
668 | g_io_channel_unref (channel); |
669 | |
670 | /* Don't use g_file_delete since it calls into gvfs */ |
671 | if (g_unlink (filename: config_path) != 0) |
672 | g_assert_not_reached (); |
673 | |
674 | g_free (mem: config_path); |
675 | g_free (mem: config_arg); |
676 | } |
677 | |
678 | static void |
679 | stop_daemon (GTestDBus *self) |
680 | { |
681 | #ifdef G_OS_WIN32 |
682 | if (!TerminateProcess (self->priv->bus_pid, 0)) |
683 | g_warning ("Can't terminate process: %s" , g_win32_error_message (GetLastError())); |
684 | #else |
685 | kill (pid: self->priv->bus_pid, SIGTERM); |
686 | #endif |
687 | _g_test_watcher_remove_pid (pid: self->priv->bus_pid); |
688 | g_spawn_close_pid (pid: self->priv->bus_pid); |
689 | self->priv->bus_pid = 0; |
690 | close (fd: self->priv->bus_stdout_fd); |
691 | self->priv->bus_stdout_fd = -1; |
692 | |
693 | g_free (mem: self->priv->bus_address); |
694 | self->priv->bus_address = NULL; |
695 | } |
696 | |
697 | /** |
698 | * g_test_dbus_new: |
699 | * @flags: a #GTestDBusFlags |
700 | * |
701 | * Create a new #GTestDBus object. |
702 | * |
703 | * Returns: (transfer full): a new #GTestDBus. |
704 | */ |
705 | GTestDBus * |
706 | g_test_dbus_new (GTestDBusFlags flags) |
707 | { |
708 | return g_object_new (G_TYPE_TEST_DBUS, |
709 | first_property_name: "flags" , flags, |
710 | NULL); |
711 | } |
712 | |
713 | /** |
714 | * g_test_dbus_get_flags: |
715 | * @self: a #GTestDBus |
716 | * |
717 | * Get the flags of the #GTestDBus object. |
718 | * |
719 | * Returns: the value of #GTestDBus:flags property |
720 | */ |
721 | GTestDBusFlags |
722 | g_test_dbus_get_flags (GTestDBus *self) |
723 | { |
724 | g_return_val_if_fail (G_IS_TEST_DBUS (self), G_TEST_DBUS_NONE); |
725 | |
726 | return self->priv->flags; |
727 | } |
728 | |
729 | /** |
730 | * g_test_dbus_get_bus_address: |
731 | * @self: a #GTestDBus |
732 | * |
733 | * Get the address on which dbus-daemon is running. If g_test_dbus_up() has not |
734 | * been called yet, %NULL is returned. This can be used with |
735 | * g_dbus_connection_new_for_address(). |
736 | * |
737 | * Returns: (nullable): the address of the bus, or %NULL. |
738 | */ |
739 | const gchar * |
740 | g_test_dbus_get_bus_address (GTestDBus *self) |
741 | { |
742 | g_return_val_if_fail (G_IS_TEST_DBUS (self), NULL); |
743 | |
744 | return self->priv->bus_address; |
745 | } |
746 | |
747 | /** |
748 | * g_test_dbus_add_service_dir: |
749 | * @self: a #GTestDBus |
750 | * @path: path to a directory containing .service files |
751 | * |
752 | * Add a path where dbus-daemon will look up .service files. This can't be |
753 | * called after g_test_dbus_up(). |
754 | */ |
755 | void |
756 | g_test_dbus_add_service_dir (GTestDBus *self, |
757 | const gchar *path) |
758 | { |
759 | g_return_if_fail (G_IS_TEST_DBUS (self)); |
760 | g_return_if_fail (self->priv->bus_address == NULL); |
761 | |
762 | g_ptr_array_add (array: self->priv->service_dirs, data: g_strdup (str: path)); |
763 | } |
764 | |
765 | /** |
766 | * g_test_dbus_up: |
767 | * @self: a #GTestDBus |
768 | * |
769 | * Start a dbus-daemon instance and set DBUS_SESSION_BUS_ADDRESS. After this |
770 | * call, it is safe for unit tests to start sending messages on the session bus. |
771 | * |
772 | * If this function is called from setup callback of g_test_add(), |
773 | * g_test_dbus_down() must be called in its teardown callback. |
774 | * |
775 | * If this function is called from unit test's main(), then g_test_dbus_down() |
776 | * must be called after g_test_run(). |
777 | */ |
778 | void |
779 | g_test_dbus_up (GTestDBus *self) |
780 | { |
781 | g_return_if_fail (G_IS_TEST_DBUS (self)); |
782 | g_return_if_fail (self->priv->bus_address == NULL); |
783 | g_return_if_fail (!self->priv->up); |
784 | |
785 | start_daemon (self); |
786 | |
787 | g_test_dbus_unset (); |
788 | g_setenv (variable: "DBUS_SESSION_BUS_ADDRESS" , value: self->priv->bus_address, TRUE); |
789 | self->priv->up = TRUE; |
790 | } |
791 | |
792 | |
793 | /** |
794 | * g_test_dbus_stop: |
795 | * @self: a #GTestDBus |
796 | * |
797 | * Stop the session bus started by g_test_dbus_up(). |
798 | * |
799 | * Unlike g_test_dbus_down(), this won't verify the #GDBusConnection |
800 | * singleton returned by g_bus_get() or g_bus_get_sync() is destroyed. Unit |
801 | * tests wanting to verify behaviour after the session bus has been stopped |
802 | * can use this function but should still call g_test_dbus_down() when done. |
803 | */ |
804 | void |
805 | g_test_dbus_stop (GTestDBus *self) |
806 | { |
807 | g_return_if_fail (G_IS_TEST_DBUS (self)); |
808 | g_return_if_fail (self->priv->bus_address != NULL); |
809 | |
810 | stop_daemon (self); |
811 | } |
812 | |
813 | /** |
814 | * g_test_dbus_down: |
815 | * @self: a #GTestDBus |
816 | * |
817 | * Stop the session bus started by g_test_dbus_up(). |
818 | * |
819 | * This will wait for the singleton returned by g_bus_get() or g_bus_get_sync() |
820 | * to be destroyed. This is done to ensure that the next unit test won't get a |
821 | * leaked singleton from this test. |
822 | */ |
823 | void |
824 | g_test_dbus_down (GTestDBus *self) |
825 | { |
826 | GDBusConnection *connection; |
827 | |
828 | g_return_if_fail (G_IS_TEST_DBUS (self)); |
829 | g_return_if_fail (self->priv->up); |
830 | |
831 | connection = _g_bus_get_singleton_if_exists (bus_type: G_BUS_TYPE_SESSION); |
832 | if (connection != NULL) |
833 | g_dbus_connection_set_exit_on_close (connection, FALSE); |
834 | |
835 | if (self->priv->bus_address != NULL) |
836 | stop_daemon (self); |
837 | |
838 | if (connection != NULL) |
839 | _g_object_unref_and_wait_weak_notify (object: connection); |
840 | |
841 | g_test_dbus_unset (); |
842 | _g_bus_forget_singleton (bus_type: G_BUS_TYPE_SESSION); |
843 | self->priv->up = FALSE; |
844 | } |
845 | |
846 | /** |
847 | * g_test_dbus_unset: |
848 | * |
849 | * Unset DISPLAY and DBUS_SESSION_BUS_ADDRESS env variables to ensure the test |
850 | * won't use user's session bus. |
851 | * |
852 | * This is useful for unit tests that want to verify behaviour when no session |
853 | * bus is running. It is not necessary to call this if unit test already calls |
854 | * g_test_dbus_up() before acquiring the session bus. |
855 | */ |
856 | void |
857 | g_test_dbus_unset (void) |
858 | { |
859 | g_unsetenv (variable: "DISPLAY" ); |
860 | g_unsetenv (variable: "DBUS_SESSION_BUS_ADDRESS" ); |
861 | g_unsetenv (variable: "DBUS_STARTER_ADDRESS" ); |
862 | g_unsetenv (variable: "DBUS_STARTER_BUS_TYPE" ); |
863 | /* avoid using XDG_RUNTIME_DIR/bus */ |
864 | g_unsetenv (variable: "XDG_RUNTIME_DIR" ); |
865 | } |
866 | |