• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GLib testing framework examples and tests
2  *
3  * Copyright (C) 2008-2010 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: David Zeuthen <davidz@redhat.com>
19  */
20 
21 #include <gio/gio.h>
22 #include <unistd.h>
23 #include <string.h>
24 
25 #include "gdbus-tests.h"
26 
27 /* all tests rely on a global connection */
28 static GDBusConnection *c = NULL;
29 
30 typedef struct
31 {
32   GMainContext *context;
33   gboolean timed_out;
34 } TimeoutData;
35 
36 static gboolean
timeout_cb(gpointer user_data)37 timeout_cb (gpointer user_data)
38 {
39   TimeoutData *data = user_data;
40 
41   data->timed_out = TRUE;
42   g_main_context_wakeup (data->context);
43 
44   return G_SOURCE_REMOVE;
45 }
46 
47 /* Check that the given @connection has only one ref, waiting to let any pending
48  * unrefs complete first. This is typically used on the shared connection, to
49  * ensure it’s in a correct state before beginning the next test. */
50 static void
assert_connection_has_one_ref(GDBusConnection * connection,GMainContext * context)51 assert_connection_has_one_ref (GDBusConnection *connection,
52                                GMainContext    *context)
53 {
54   GSource *timeout_source = NULL;
55   TimeoutData data = { context, FALSE };
56 
57   if (g_atomic_int_get (&G_OBJECT (connection)->ref_count) == 1)
58     return;
59 
60   timeout_source = g_timeout_source_new_seconds (3);
61   g_source_set_callback (timeout_source, timeout_cb, &data, NULL);
62   g_source_attach (timeout_source, context);
63 
64   while (g_atomic_int_get (&G_OBJECT (connection)->ref_count) != 1 && !data.timed_out)
65     {
66       g_debug ("refcount of %p is not right, sleeping", connection);
67       g_main_context_iteration (NULL, TRUE);
68     }
69 
70   g_source_destroy (timeout_source);
71   g_source_unref (timeout_source);
72 
73   if (g_atomic_int_get (&G_OBJECT (connection)->ref_count) != 1)
74     g_error ("connection %p had too many refs", connection);
75 }
76 
77 /* ---------------------------------------------------------------------------------------------------- */
78 /* Ensure that signal and method replies are delivered in the right thread */
79 /* ---------------------------------------------------------------------------------------------------- */
80 
81 typedef struct {
82   GThread *thread;
83   GMainContext *context;
84   guint signal_count;
85   gboolean unsubscribe_complete;
86   GAsyncResult *async_result;
87 } DeliveryData;
88 
89 static void
async_result_cb(GDBusConnection * connection,GAsyncResult * res,gpointer user_data)90 async_result_cb (GDBusConnection *connection,
91                  GAsyncResult    *res,
92                  gpointer         user_data)
93 {
94   DeliveryData *data = user_data;
95 
96   data->async_result = g_object_ref (res);
97 
98   g_assert_true (g_thread_self () == data->thread);
99 
100   g_main_context_wakeup (data->context);
101 }
102 
103 static void
signal_handler(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)104 signal_handler (GDBusConnection *connection,
105                 const gchar      *sender_name,
106                 const gchar      *object_path,
107                 const gchar      *interface_name,
108                 const gchar      *signal_name,
109                 GVariant         *parameters,
110                 gpointer         user_data)
111 {
112   DeliveryData *data = user_data;
113 
114   g_assert_true (g_thread_self () == data->thread);
115 
116   data->signal_count++;
117 
118   g_main_context_wakeup (data->context);
119 }
120 
121 static void
signal_data_free_cb(gpointer user_data)122 signal_data_free_cb (gpointer user_data)
123 {
124   DeliveryData *data = user_data;
125 
126   g_assert_true (g_thread_self () == data->thread);
127 
128   data->unsubscribe_complete = TRUE;
129 
130   g_main_context_wakeup (data->context);
131 }
132 
133 static gpointer
test_delivery_in_thread_func(gpointer _data)134 test_delivery_in_thread_func (gpointer _data)
135 {
136   GMainContext *thread_context;
137   DeliveryData data;
138   GCancellable *ca;
139   guint subscription_id;
140   GError *error = NULL;
141   GVariant *result_variant = NULL;
142 
143   thread_context = g_main_context_new ();
144   g_main_context_push_thread_default (thread_context);
145 
146   data.thread = g_thread_self ();
147   data.context = thread_context;
148   data.signal_count = 0;
149   data.unsubscribe_complete = FALSE;
150   data.async_result = NULL;
151 
152   /* ---------------------------------------------------------------------------------------------------- */
153 
154   /*
155    * Check that we get a reply to the GetId() method call.
156    */
157   g_dbus_connection_call (c,
158                           "org.freedesktop.DBus",  /* bus_name */
159                           "/org/freedesktop/DBus", /* object path */
160                           "org.freedesktop.DBus",  /* interface name */
161                           "GetId",                 /* method name */
162                           NULL, NULL,
163                           G_DBUS_CALL_FLAGS_NONE,
164                           -1,
165                           NULL,
166                           (GAsyncReadyCallback) async_result_cb,
167                           &data);
168   while (data.async_result == NULL)
169     g_main_context_iteration (thread_context, TRUE);
170 
171   result_variant = g_dbus_connection_call_finish (c, data.async_result, &error);
172   g_assert_no_error (error);
173   g_assert_nonnull (result_variant);
174   g_clear_pointer (&result_variant, g_variant_unref);
175   g_clear_object (&data.async_result);
176 
177   /*
178    * Check that we never actually send a message if the GCancellable
179    * is already cancelled - i.e.  we should get #G_IO_ERROR_CANCELLED
180    * when the actual connection is not up.
181    */
182   ca = g_cancellable_new ();
183   g_cancellable_cancel (ca);
184   g_dbus_connection_call (c,
185                           "org.freedesktop.DBus",  /* bus_name */
186                           "/org/freedesktop/DBus", /* object path */
187                           "org.freedesktop.DBus",  /* interface name */
188                           "GetId",                 /* method name */
189                           NULL, NULL,
190                           G_DBUS_CALL_FLAGS_NONE,
191                           -1,
192                           ca,
193                           (GAsyncReadyCallback) async_result_cb,
194                           &data);
195   while (data.async_result == NULL)
196     g_main_context_iteration (thread_context, TRUE);
197 
198   result_variant = g_dbus_connection_call_finish (c, data.async_result, &error);
199   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
200   g_assert_false (g_dbus_error_is_remote_error (error));
201   g_clear_error (&error);
202   g_assert_null (result_variant);
203   g_clear_object (&data.async_result);
204 
205   g_object_unref (ca);
206 
207   /*
208    * Check that cancellation works when the message is already in flight.
209    */
210   ca = g_cancellable_new ();
211   g_dbus_connection_call (c,
212                           "org.freedesktop.DBus",  /* bus_name */
213                           "/org/freedesktop/DBus", /* object path */
214                           "org.freedesktop.DBus",  /* interface name */
215                           "GetId",                 /* method name */
216                           NULL, NULL,
217                           G_DBUS_CALL_FLAGS_NONE,
218                           -1,
219                           ca,
220                           (GAsyncReadyCallback) async_result_cb,
221                           &data);
222   g_cancellable_cancel (ca);
223 
224   while (data.async_result == NULL)
225     g_main_context_iteration (thread_context, TRUE);
226 
227   result_variant = g_dbus_connection_call_finish (c, data.async_result, &error);
228   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
229   g_assert_false (g_dbus_error_is_remote_error (error));
230   g_clear_error (&error);
231   g_assert_null (result_variant);
232   g_clear_object (&data.async_result);
233 
234   g_object_unref (ca);
235 
236   /*
237    * Check that signals are delivered to the correct thread.
238    *
239    * First we subscribe to the signal, then we call EmitSignal(). This should
240    * cause a TestSignal emission from the testserver.
241    */
242   subscription_id = g_dbus_connection_signal_subscribe (c,
243                                                         "com.example.TestService", /* sender */
244                                                         "com.example.Frob",        /* interface */
245                                                         "TestSignal",              /* member */
246                                                         "/com/example/TestObject", /* path */
247                                                         NULL,
248                                                         G_DBUS_SIGNAL_FLAGS_NONE,
249                                                         signal_handler,
250                                                         &data,
251                                                         signal_data_free_cb);
252   g_assert_cmpuint (subscription_id, !=, 0);
253   g_assert_cmpuint (data.signal_count, ==, 0);
254 
255   g_dbus_connection_call (c,
256                           "com.example.TestService", /* bus_name */
257                           "/com/example/TestObject", /* object path */
258                           "com.example.Frob",        /* interface name */
259                           "EmitSignal",              /* method name */
260                           g_variant_new_parsed ("('hello', @o '/com/example/TestObject')"),
261                           NULL,
262                           G_DBUS_CALL_FLAGS_NONE,
263                           -1,
264                           NULL,
265                           (GAsyncReadyCallback) async_result_cb,
266                           &data);
267   while (data.async_result == NULL || data.signal_count < 1)
268     g_main_context_iteration (thread_context, TRUE);
269 
270   result_variant = g_dbus_connection_call_finish (c, data.async_result, &error);
271   g_assert_no_error (error);
272   g_assert_nonnull (result_variant);
273   g_clear_pointer (&result_variant, g_variant_unref);
274   g_clear_object (&data.async_result);
275 
276   g_assert_cmpuint (data.signal_count, ==, 1);
277 
278   g_dbus_connection_signal_unsubscribe (c, subscription_id);
279   subscription_id = 0;
280 
281   while (!data.unsubscribe_complete)
282     g_main_context_iteration (thread_context, TRUE);
283   g_assert_true (data.unsubscribe_complete);
284 
285   /* ---------------------------------------------------------------------------------------------------- */
286 
287   g_main_context_pop_thread_default (thread_context);
288   g_main_context_unref (thread_context);
289 
290   return NULL;
291 }
292 
293 static void
test_delivery_in_thread(void)294 test_delivery_in_thread (void)
295 {
296   GThread *thread;
297 
298   thread = g_thread_new ("deliver",
299                          test_delivery_in_thread_func,
300                          NULL);
301 
302   g_thread_join (thread);
303 
304   assert_connection_has_one_ref (c, NULL);
305 }
306 
307 /* ---------------------------------------------------------------------------------------------------- */
308 
309 typedef struct {
310   GDBusProxy *proxy;
311   gint msec;
312   guint num;
313   gboolean async;
314 
315   GMainLoop *thread_loop;
316   GThread *thread;
317 } SyncThreadData;
318 
319 static void
sleep_cb(GDBusProxy * proxy,GAsyncResult * res,gpointer user_data)320 sleep_cb (GDBusProxy   *proxy,
321           GAsyncResult *res,
322           gpointer      user_data)
323 {
324   SyncThreadData *data = user_data;
325   GError *error;
326   GVariant *result;
327 
328   error = NULL;
329   result = g_dbus_proxy_call_finish (proxy,
330                                      res,
331                                      &error);
332   g_assert_no_error (error);
333   g_assert_nonnull (result);
334   g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
335   g_variant_unref (result);
336 
337   g_assert_true (data->thread == g_thread_self ());
338 
339   g_main_loop_quit (data->thread_loop);
340 
341   //g_debug ("async cb (%p)", g_thread_self ());
342 }
343 
344 static gpointer
test_sleep_in_thread_func(gpointer _data)345 test_sleep_in_thread_func (gpointer _data)
346 {
347   SyncThreadData *data = _data;
348   GMainContext *thread_context;
349   guint n;
350 
351   thread_context = g_main_context_new ();
352   data->thread_loop = g_main_loop_new (thread_context, FALSE);
353   g_main_context_push_thread_default (thread_context);
354 
355   data->thread = g_thread_self ();
356 
357   for (n = 0; n < data->num; n++)
358     {
359       if (data->async)
360         {
361           //g_debug ("invoking async (%p)", g_thread_self ());
362           g_dbus_proxy_call (data->proxy,
363                              "Sleep",
364                              g_variant_new ("(i)", data->msec),
365                              G_DBUS_CALL_FLAGS_NONE,
366                              -1,
367                              NULL,
368                              (GAsyncReadyCallback) sleep_cb,
369                              data);
370           g_main_loop_run (data->thread_loop);
371           if (g_test_verbose ())
372             g_printerr ("A");
373           //g_debug ("done invoking async (%p)", g_thread_self ());
374         }
375       else
376         {
377           GError *error;
378           GVariant *result;
379 
380           error = NULL;
381           //g_debug ("invoking sync (%p)", g_thread_self ());
382           result = g_dbus_proxy_call_sync (data->proxy,
383                                            "Sleep",
384                                            g_variant_new ("(i)", data->msec),
385                                            G_DBUS_CALL_FLAGS_NONE,
386                                            -1,
387                                            NULL,
388                                            &error);
389           if (g_test_verbose ())
390             g_printerr ("S");
391           //g_debug ("done invoking sync (%p)", g_thread_self ());
392           g_assert_no_error (error);
393           g_assert_nonnull (result);
394           g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
395           g_variant_unref (result);
396         }
397     }
398 
399   g_main_context_pop_thread_default (thread_context);
400   g_main_loop_unref (data->thread_loop);
401   g_main_context_unref (thread_context);
402 
403   return NULL;
404 }
405 
406 static void
test_method_calls_on_proxy(GDBusProxy * proxy)407 test_method_calls_on_proxy (GDBusProxy *proxy)
408 {
409   guint n, divisor;
410 
411   /*
412    * Check that multiple threads can do calls without interfering with
413    * each other. We do this by creating three threads that call the
414    * Sleep() method on the server (which handles it asynchronously, e.g.
415    * it won't block other requests) with different sleep durations and
416    * a number of times. We do this so each set of calls add up to 4000
417    * milliseconds.
418    *
419    * The dbus test server that this code calls into uses glib timeouts
420    * to do the sleeping which have only a granularity of 1ms.  It is
421    * therefore possible to lose as much as 40ms; the test could finish
422    * in slightly less than 4 seconds.
423    *
424    * We run this test twice - first with async calls in each thread, then
425    * again with sync calls
426    */
427 
428   if (g_test_thorough ())
429     divisor = 1;
430   else
431     divisor = 10;
432 
433   for (n = 0; n < 2; n++)
434     {
435       gboolean do_async;
436       GThread *thread1;
437       GThread *thread2;
438       GThread *thread3;
439       SyncThreadData data1;
440       SyncThreadData data2;
441       SyncThreadData data3;
442       gint64 start_time, end_time;
443       guint elapsed_msec;
444 
445       do_async = (n == 0);
446 
447       start_time = g_get_real_time ();
448 
449       data1.proxy = proxy;
450       data1.msec = 40;
451       data1.num = 100 / divisor;
452       data1.async = do_async;
453       thread1 = g_thread_new ("sleep",
454                               test_sleep_in_thread_func,
455                               &data1);
456 
457       data2.proxy = proxy;
458       data2.msec = 20;
459       data2.num = 200 / divisor;
460       data2.async = do_async;
461       thread2 = g_thread_new ("sleep2",
462                               test_sleep_in_thread_func,
463                               &data2);
464 
465       data3.proxy = proxy;
466       data3.msec = 100;
467       data3.num = 40 / divisor;
468       data3.async = do_async;
469       thread3 = g_thread_new ("sleep3",
470                               test_sleep_in_thread_func,
471                               &data3);
472 
473       g_thread_join (thread1);
474       g_thread_join (thread2);
475       g_thread_join (thread3);
476 
477       end_time = g_get_real_time ();
478 
479       elapsed_msec = (end_time - start_time) / 1000;
480 
481       //g_debug ("Elapsed time for %s = %d msec", n == 0 ? "async" : "sync", elapsed_msec);
482 
483       /* elapsed_msec should be 4000 msec +/- change for overhead/inaccuracy */
484       g_assert_cmpint (elapsed_msec, >=, 3950 / divisor);
485       g_assert_cmpint (elapsed_msec,  <, 30000 / divisor);
486 
487       if (g_test_verbose ())
488         g_printerr (" ");
489     }
490 }
491 
492 static void
test_method_calls_in_thread(void)493 test_method_calls_in_thread (void)
494 {
495   GDBusProxy *proxy;
496   GDBusConnection *connection;
497   GError *error;
498 
499   error = NULL;
500   connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
501                                NULL,
502                                &error);
503   g_assert_no_error (error);
504   error = NULL;
505   proxy = g_dbus_proxy_new_sync (connection,
506                                  G_DBUS_PROXY_FLAGS_NONE,
507                                  NULL,                      /* GDBusInterfaceInfo */
508                                  "com.example.TestService", /* name */
509                                  "/com/example/TestObject", /* object path */
510                                  "com.example.Frob",        /* interface */
511                                  NULL, /* GCancellable */
512                                  &error);
513   g_assert_no_error (error);
514 
515   test_method_calls_on_proxy (proxy);
516 
517   g_object_unref (proxy);
518   g_object_unref (connection);
519 
520   if (g_test_verbose ())
521     g_printerr ("\n");
522 
523   assert_connection_has_one_ref (c, NULL);
524 }
525 
526 #define SLEEP_MIN_USEC 1
527 #define SLEEP_MAX_USEC 10
528 
529 /* Can run in any thread */
530 static void
ensure_connection_works(GDBusConnection * conn)531 ensure_connection_works (GDBusConnection *conn)
532 {
533   GVariant *v;
534   GError *error = NULL;
535 
536   v = g_dbus_connection_call_sync (conn, "org.freedesktop.DBus",
537       "/org/freedesktop/DBus", "org.freedesktop.DBus", "GetId", NULL, NULL, 0, -1,
538       NULL, &error);
539   g_assert_no_error (error);
540   g_assert_nonnull (v);
541   g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE ("(s)")));
542   g_variant_unref (v);
543 }
544 
545 /**
546  * get_sync_in_thread:
547  * @data: (type guint): delay in microseconds
548  *
549  * Sleep for a short time, then get a session bus connection and call
550  * a method on it.
551  *
552  * Runs in a non-main thread.
553  *
554  * Returns: (transfer full): the connection
555  */
556 static gpointer
get_sync_in_thread(gpointer data)557 get_sync_in_thread (gpointer data)
558 {
559   guint delay = GPOINTER_TO_UINT (data);
560   GError *error = NULL;
561   GDBusConnection *conn;
562 
563   g_usleep (delay);
564 
565   conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
566   g_assert_no_error (error);
567 
568   ensure_connection_works (conn);
569 
570   return conn;
571 }
572 
573 static void
test_threaded_singleton(void)574 test_threaded_singleton (void)
575 {
576   guint i, n;
577   guint unref_wins = 0;
578   guint get_wins = 0;
579 
580   if (g_test_thorough ())
581     n = 100000;
582   else
583     n = 1000;
584 
585   for (i = 0; i < n; i++)
586     {
587       GThread *thread;
588       guint unref_delay, get_delay;
589       GDBusConnection *new_conn;
590 
591       /* We want to be the last ref, so let it finish setting up */
592       assert_connection_has_one_ref (c, NULL);
593 
594       if (g_test_verbose () && (i % (n/50)) == 0)
595         g_printerr ("%u%%\n", ((i * 100) / n));
596 
597       /* Delay for a random time on each side of the race, to perturb the
598        * timing. Ideally, we want each side to win half the races; these
599        * timings are about right on smcv's laptop.
600        */
601       unref_delay = g_random_int_range (SLEEP_MIN_USEC, SLEEP_MAX_USEC);
602       get_delay = g_random_int_range (SLEEP_MIN_USEC / 2, SLEEP_MAX_USEC / 2);
603 
604       /* One half of the race is to call g_bus_get_sync... */
605       thread = g_thread_new ("get_sync_in_thread", get_sync_in_thread,
606           GUINT_TO_POINTER (get_delay));
607 
608       /* ... and the other half is to unref the shared connection, which must
609        * have exactly one ref at this point
610        */
611       g_usleep (unref_delay);
612       g_object_unref (c);
613 
614       /* Wait for the thread to run; see what it got */
615       new_conn = g_thread_join (thread);
616 
617       /* If the thread won the race, it will have kept the same connection,
618        * and it'll have one ref
619        */
620       if (new_conn == c)
621         {
622           get_wins++;
623         }
624       else
625         {
626           unref_wins++;
627           /* c is invalid now, but new_conn is suitable for the
628            * next round
629            */
630           c = new_conn;
631         }
632 
633       ensure_connection_works (c);
634     }
635 
636   if (g_test_verbose ())
637     g_printerr ("Unref won %u races; Get won %u races\n", unref_wins, get_wins);
638 }
639 
640 /* ---------------------------------------------------------------------------------------------------- */
641 
642 int
main(int argc,char * argv[])643 main (int   argc,
644       char *argv[])
645 {
646   GError *error;
647   gint ret;
648   gchar *path;
649 
650   g_test_init (&argc, &argv, NULL);
651 
652   session_bus_up ();
653 
654   /* this is safe; testserver will exit once the bus goes away */
655   path = g_test_build_filename (G_TEST_BUILT, "gdbus-testserver", NULL);
656   g_assert_true (g_spawn_command_line_async (path, NULL));
657   g_free (path);
658 
659   /* Create the connection in the main thread */
660   error = NULL;
661   c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
662   g_assert_no_error (error);
663   g_assert_nonnull (c);
664 
665   ensure_gdbus_testserver_up (c, NULL);
666 
667   g_test_add_func ("/gdbus/delivery-in-thread", test_delivery_in_thread);
668   g_test_add_func ("/gdbus/method-calls-in-thread", test_method_calls_in_thread);
669   g_test_add_func ("/gdbus/threaded-singleton", test_threaded_singleton);
670 
671   ret = g_test_run();
672 
673   g_object_unref (c);
674 
675   /* tear down bus */
676   session_bus_down ();
677 
678   return ret;
679 }
680