1 #include <gio/gio.h>
2 #include <gio/gunixsocketaddress.h>
3 #include <glib/gstdio.h>
4 #include <string.h>
5 
6 #include "gdbus-sessionbus.h"
test_multi_mass_spring_damper_inputs()7 
8 #include "glib/glib-private.h"
9 
10 static gboolean
11 time_out (gpointer unused G_GNUC_UNUSED)
12 {
13   g_error ("Timed out");
14   /* not reached */
15   return FALSE;
16 }
17 
18 static guint
19 add_timeout (guint seconds)
20 {
21 #ifdef G_OS_UNIX
22   /* Safety-catch against the main loop having blocked */
23   alarm (seconds + 5);
24 #endif
25   return g_timeout_add_seconds (seconds, time_out, NULL);
26 }
27 
28 static void
29 cancel_timeout (guint timeout_id)
30 {
31 #ifdef G_OS_UNIX
32   alarm (0);
33 #endif
34   g_source_remove (timeout_id);
35 }
36 
37 /* Markup printing {{{1 */
test_multi_mass_spring_damper_higher_order()38 
39 /* This used to be part of GLib, but it was removed before the stable
40  * release because it wasn't generally useful.  We want it here, though.
41  */
42 static void
43 indent_string (GString *string,
44                gint     indent)
45 {
46   while (indent--)
47     g_string_append_c (string, ' ');
48 }
49 
50 static GString *
51 g_menu_markup_print_string (GString    *string,
52                             GMenuModel *model,
53                             gint        indent,
54                             gint        tabstop)
55 {
56   gboolean need_nl = FALSE;
test_n_link_pendulum_on_cart_inputs()57   gint i, n;
58 
59   if G_UNLIKELY (string == NULL)
60     string = g_string_new (NULL);
61 
62   n = g_menu_model_get_n_items (model);
63 
64   for (i = 0; i < n; i++)
65     {
66       GMenuAttributeIter *attr_iter;
67       GMenuLinkIter *link_iter;
68       GString *contents;
69       GString *attrs;
70 
71       attr_iter = g_menu_model_iterate_item_attributes (model, i);
72       link_iter = g_menu_model_iterate_item_links (model, i);
73       contents = g_string_new (NULL);
74       attrs = g_string_new (NULL);
75 
76       while (g_menu_attribute_iter_next (attr_iter))
77         {
78           const char *name = g_menu_attribute_iter_get_name (attr_iter);
79           GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
80 
81           if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
82             {
83               gchar *str;
84               str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
85               g_string_append (attrs, str);
86               g_free (str);
87             }
88 
89           else
90             {
91               gchar *printed;
92               gchar *str;
test_n_link_pendulum_on_cart_higher_order()93               const gchar *type;
94 
95               printed = g_variant_print (value, TRUE);
96               type = g_variant_type_peek_string (g_variant_get_type (value));
97               str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
98               indent_string (contents, indent + tabstop);
99               g_string_append (contents, str);
100               g_free (printed);
101               g_free (str);
102             }
103 
104           g_variant_unref (value);
105         }
106       g_object_unref (attr_iter);
107 
108       while (g_menu_link_iter_next (link_iter))
109         {
110           const gchar *name = g_menu_link_iter_get_name (link_iter);
111           GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
112           gchar *str;
113 
114           if (contents->str[0])
115             g_string_append_c (contents, '\n');
116 
117           str = g_markup_printf_escaped ("<link name='%s'>\n", name);
118           indent_string (contents, indent + tabstop);
119           g_string_append (contents, str);
120           g_free (str);
121 
122           g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
123 
124           indent_string (contents, indent + tabstop);
125           g_string_append (contents, "</link>\n");
126           g_object_unref (menu);
127         }
128       g_object_unref (link_iter);
129 
130       if (contents->str[0])
131         {
132           indent_string (string, indent);
133           g_string_append_printf (string, "<item%s>\n", attrs->str);
134           g_string_append (string, contents->str);
135           indent_string (string, indent);
136           g_string_append (string, "</item>\n");
137           need_nl = TRUE;
138         }
139 
140       else
141         {
142           if (need_nl)
143             g_string_append_c (string, '\n');
144 
145           indent_string (string, indent);
146           g_string_append_printf (string, "<item%s/>\n", attrs->str);
147           need_nl = FALSE;
148         }
149 
150       g_string_free (contents, TRUE);
151       g_string_free (attrs, TRUE);
152     }
153 
154   return string;
155 }
156 
157 /* TestItem {{{1 */
158 
159 /* This utility struct is used by both the RandomMenu and MirrorMenu
160  * class implementations below.
161  */
162 typedef struct {
163   GHashTable *attributes;
164   GHashTable *links;
165 } TestItem;
166 
167 static TestItem *
168 test_item_new (GHashTable *attributes,
169                GHashTable *links)
170 {
171   TestItem *item;
172 
173   item = g_slice_new (TestItem);
174   item->attributes = g_hash_table_ref (attributes);
175   item->links = g_hash_table_ref (links);
176 
177   return item;
178 }
179 
180 static void
181 test_item_free (gpointer data)
182 {
183   TestItem *item = data;
184 
185   g_hash_table_unref (item->attributes);
186   g_hash_table_unref (item->links);
187 
188   g_slice_free (TestItem, item);
189 }
190 
191 /* RandomMenu {{{1 */
192 #define MAX_ITEMS 5
193 #define TOP_ORDER 4
194 
195 typedef struct {
196   GMenuModel parent_instance;
197 
198   GSequence *items;
199   gint order;
200 } RandomMenu;
201 
202 typedef GMenuModelClass RandomMenuClass;
203 
204 static GType random_menu_get_type (void);
205 G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL)
206 
207 static gboolean
208 random_menu_is_mutable (GMenuModel *model)
209 {
210   return TRUE;
211 }
212 
213 static gint
214 random_menu_get_n_items (GMenuModel *model)
215 {
216   RandomMenu *menu = (RandomMenu *) model;
217 
218   return g_sequence_get_length (menu->items);
219 }
220 
221 static void
222 random_menu_get_item_attributes (GMenuModel  *model,
223                                  gint         position,
224                                  GHashTable **table)
225 {
226   RandomMenu *menu = (RandomMenu *) model;
227   TestItem *item;
228 
229   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
230   *table = g_hash_table_ref (item->attributes);
231 }
232 
233 static void
234 random_menu_get_item_links (GMenuModel  *model,
235                             gint         position,
236                             GHashTable **table)
237 {
238   RandomMenu *menu = (RandomMenu *) model;
239   TestItem *item;
240 
241   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
242   *table = g_hash_table_ref (item->links);
243 }
244 
245 static void
246 random_menu_finalize (GObject *object)
247 {
248   RandomMenu *menu = (RandomMenu *) object;
249 
250   g_sequence_free (menu->items);
251 
252   G_OBJECT_CLASS (random_menu_parent_class)
253     ->finalize (object);
254 }
255 
256 static void
257 random_menu_init (RandomMenu *menu)
258 {
259 }
260 
261 static void
262 random_menu_class_init (GMenuModelClass *class)
263 {
264   GObjectClass *object_class = G_OBJECT_CLASS (class);
265 
266   class->is_mutable = random_menu_is_mutable;
267   class->get_n_items = random_menu_get_n_items;
268   class->get_item_attributes = random_menu_get_item_attributes;
269   class->get_item_links = random_menu_get_item_links;
270 
271   object_class->finalize = random_menu_finalize;
272 }
273 
274 static RandomMenu * random_menu_new (GRand *rand, gint order);
275 
276 static void
277 random_menu_change (RandomMenu *menu,
278                     GRand      *rand)
279 {
280   gint position, removes, adds;
281   GSequenceIter *point;
282   gint n_items;
283   gint i;
284 
285   n_items = g_sequence_get_length (menu->items);
286 
287   do
288     {
289       position = g_rand_int_range (rand, 0, n_items + 1);
290       removes = g_rand_int_range (rand, 0, n_items - position + 1);
291       adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
292     }
293   while (removes == 0 && adds == 0);
294 
295   point = g_sequence_get_iter_at_pos (menu->items, position + removes);
296 
297   if (removes)
298     {
299       GSequenceIter *start;
300 
301       start = g_sequence_get_iter_at_pos (menu->items, position);
302       g_sequence_remove_range (start, point);
303     }
304 
305   for (i = 0; i < adds; i++)
306     {
307       const gchar *label;
308       GHashTable *links;
309       GHashTable *attributes;
310 
311       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
312       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
313 
314       if (menu->order > 0 && g_rand_boolean (rand))
315         {
316           RandomMenu *child;
317 	  const gchar *subtype;
318 
319           child = random_menu_new (rand, menu->order - 1);
320 
321           if (g_rand_boolean (rand))
322             {
323               subtype = G_MENU_LINK_SECTION;
324               /* label some section headers */
325               if (g_rand_boolean (rand))
326                 label = "Section";
327               else
328                 label = NULL;
329             }
330           else
331             {
332               /* label all submenus */
333               subtype = G_MENU_LINK_SUBMENU;
334               label = "Submenu";
335             }
336 
337           g_hash_table_insert (links, g_strdup (subtype), child);
338         }
339       else
340         /* label all terminals */
341         label = "Menu Item";
342 
343       if (label)
344         g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
345 
346       g_sequence_insert_before (point, test_item_new (attributes, links));
347       g_hash_table_unref (links);
348       g_hash_table_unref (attributes);
349     }
350 
351   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
352 }
353 
354 static RandomMenu *
355 random_menu_new (GRand *rand,
356                  gint   order)
357 {
358   RandomMenu *menu;
359 
360   menu = g_object_new (random_menu_get_type (), NULL);
361   menu->items = g_sequence_new (test_item_free);
362   menu->order = order;
363 
364   random_menu_change (menu, rand);
365 
366   return menu;
367 }
368 
369 /* MirrorMenu {{{1 */
370 typedef struct {
371   GMenuModel parent_instance;
372 
373   GMenuModel *clone_of;
374   GSequence *items;
375   gulong handler_id;
376 } MirrorMenu;
377 
378 typedef GMenuModelClass MirrorMenuClass;
379 
380 static GType mirror_menu_get_type (void);
381 G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL)
382 
383 static gboolean
384 mirror_menu_is_mutable (GMenuModel *model)
385 {
386   MirrorMenu *menu = (MirrorMenu *) model;
387 
388   return menu->handler_id != 0;
389 }
390 
391 static gint
392 mirror_menu_get_n_items (GMenuModel *model)
393 {
394   MirrorMenu *menu = (MirrorMenu *) model;
395 
396   return g_sequence_get_length (menu->items);
397 }
398 
399 static void
400 mirror_menu_get_item_attributes (GMenuModel  *model,
401                                  gint         position,
402                                  GHashTable **table)
403 {
404   MirrorMenu *menu = (MirrorMenu *) model;
405   TestItem *item;
406 
407   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
408   *table = g_hash_table_ref (item->attributes);
409 }
410 
411 static void
412 mirror_menu_get_item_links (GMenuModel  *model,
413                             gint         position,
414                             GHashTable **table)
415 {
416   MirrorMenu *menu = (MirrorMenu *) model;
417   TestItem *item;
418 
419   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
420   *table = g_hash_table_ref (item->links);
421 }
422 
423 static void
424 mirror_menu_finalize (GObject *object)
425 {
426   MirrorMenu *menu = (MirrorMenu *) object;
427 
428   if (menu->handler_id)
429     g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
430 
431   g_sequence_free (menu->items);
432   g_object_unref (menu->clone_of);
433 
434   G_OBJECT_CLASS (mirror_menu_parent_class)
435     ->finalize (object);
436 }
437 
438 static void
439 mirror_menu_init (MirrorMenu *menu)
440 {
441 }
442 
443 static void
444 mirror_menu_class_init (GMenuModelClass *class)
445 {
446   GObjectClass *object_class = G_OBJECT_CLASS (class);
447 
448   class->is_mutable = mirror_menu_is_mutable;
449   class->get_n_items = mirror_menu_get_n_items;
450   class->get_item_attributes = mirror_menu_get_item_attributes;
451   class->get_item_links = mirror_menu_get_item_links;
452 
453   object_class->finalize = mirror_menu_finalize;
454 }
455 
456 static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
457 
458 static void
459 mirror_menu_changed (GMenuModel *model,
460                      gint        position,
461                      gint        removed,
462                      gint        added,
463                      gpointer    user_data)
464 {
465   MirrorMenu *menu = user_data;
466   GSequenceIter *point;
467   gint i;
468 
469   g_assert (model == menu->clone_of);
470 
471   point = g_sequence_get_iter_at_pos (menu->items, position + removed);
472 
473   if (removed)
474     {
475       GSequenceIter *start;
476 
477       start = g_sequence_get_iter_at_pos (menu->items, position);
478       g_sequence_remove_range (start, point);
479     }
480 
481   for (i = position; i < position + added; i++)
482     {
483       GMenuAttributeIter *attr_iter;
484       GMenuLinkIter *link_iter;
485       GHashTable *links;
486       GHashTable *attributes;
487       const gchar *name;
488       GMenuModel *child;
489       GVariant *value;
490 
491       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
492       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
493 
494       attr_iter = g_menu_model_iterate_item_attributes (model, i);
495       while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
496         {
497           g_hash_table_insert (attributes, g_strdup (name), value);
498         }
499       g_object_unref (attr_iter);
500 
501       link_iter = g_menu_model_iterate_item_links (model, i);
502       while (g_menu_link_iter_get_next (link_iter, &name, &child))
503         {
504           g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
505           g_object_unref (child);
506         }
507       g_object_unref (link_iter);
508 
509       g_sequence_insert_before (point, test_item_new (attributes, links));
510       g_hash_table_unref (attributes);
511       g_hash_table_unref (links);
512     }
513 
514   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
515 }
516 
517 static MirrorMenu *
518 mirror_menu_new (GMenuModel *clone_of)
519 {
520   MirrorMenu *menu;
521 
522   menu = g_object_new (mirror_menu_get_type (), NULL);
523   menu->items = g_sequence_new (test_item_free);
524   menu->clone_of = g_object_ref (clone_of);
525 
526   if (g_menu_model_is_mutable (clone_of))
527     menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
528   mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
529 
530   return menu;
531 }
532 
533 /* check_menus_equal(), assert_menus_equal() {{{1 */
534 static gboolean
535 check_menus_equal (GMenuModel *a,
536                    GMenuModel *b)
537 {
538   gboolean equal = TRUE;
539   gint a_n, b_n;
540   gint i;
541 
542   a_n = g_menu_model_get_n_items (a);
543   b_n = g_menu_model_get_n_items (b);
544 
545   if (a_n != b_n)
546     return FALSE;
547 
548   for (i = 0; i < a_n; i++)
549     {
550       GMenuAttributeIter *attr_iter;
551       GVariant *a_value, *b_value;
552       GMenuLinkIter *link_iter;
553       GMenuModel *a_menu, *b_menu;
554       const gchar *name;
555 
556       attr_iter = g_menu_model_iterate_item_attributes (a, i);
557       while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
558         {
559           b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
560           equal &= b_value && g_variant_equal (a_value, b_value);
561           if (b_value)
562             g_variant_unref (b_value);
563           g_variant_unref (a_value);
564         }
565       g_object_unref (attr_iter);
566 
567       attr_iter = g_menu_model_iterate_item_attributes (b, i);
568       while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
569         {
570           a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
571           equal &= a_value && g_variant_equal (a_value, b_value);
572           if (a_value)
573             g_variant_unref (a_value);
574           g_variant_unref (b_value);
575         }
576       g_object_unref (attr_iter);
577 
578       link_iter = g_menu_model_iterate_item_links (a, i);
579       while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
580         {
581           b_menu = g_menu_model_get_item_link (b, i, name);
582           equal &= b_menu && check_menus_equal (a_menu, b_menu);
583           if (b_menu)
584             g_object_unref (b_menu);
585           g_object_unref (a_menu);
586         }
587       g_object_unref (link_iter);
588 
589       link_iter = g_menu_model_iterate_item_links (b, i);
590       while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
591         {
592           a_menu = g_menu_model_get_item_link (a, i, name);
593           equal &= a_menu && check_menus_equal (a_menu, b_menu);
594           if (a_menu)
595             g_object_unref (a_menu);
596           g_object_unref (b_menu);
597         }
598       g_object_unref (link_iter);
599     }
600 
601   return equal;
602 }
603 
604 static void
605 assert_menus_equal (GMenuModel *a,
606                     GMenuModel *b)
607 {
608   if (!check_menus_equal (a, b))
609     {
610       GString *string;
611 
612       string = g_string_new ("\n  <a>\n");
613       g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
614       g_string_append (string, "  </a>\n\n-------------\n  <b>\n");
615       g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
616       g_string_append (string, "  </b>\n");
617       g_error ("%s", string->str);
618     }
619 }
620 
621 static void
622 assert_menuitem_equal (GMenuItem  *item,
623                        GMenuModel *model,
624                        gint        index)
625 {
626   GMenuAttributeIter *attr_iter;
627   GMenuLinkIter *link_iter;
628   const gchar *name;
629   GVariant *value;
630   GMenuModel *linked_model;
631 
632   /* NOTE we can't yet test whether item has attributes or links that
633    * are not in the model, because there's no iterator API for menu
634    * items */
635 
636   attr_iter = g_menu_model_iterate_item_attributes (model, index);
637   while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
638     {
639       GVariant *item_value;
640 
641       item_value = g_menu_item_get_attribute_value (item, name, g_variant_get_type (value));
642       g_assert (item_value && g_variant_equal (item_value, value));
643 
644       g_variant_unref (item_value);
645       g_variant_unref (value);
646     }
647 
648   link_iter = g_menu_model_iterate_item_links (model, index);
649   while (g_menu_link_iter_get_next (link_iter, &name, &linked_model))
650     {
651       GMenuModel *item_linked_model;
652 
653       item_linked_model = g_menu_item_get_link (item, name);
654       g_assert (linked_model == item_linked_model);
655 
656       g_object_unref (item_linked_model);
657       g_object_unref (linked_model);
658     }
659 
660   g_object_unref (attr_iter);
661   g_object_unref (link_iter);
662 }
663 
664 /* Test cases {{{1 */
665 static void
666 test_equality (void)
667 {
668   GRand *randa, *randb;
669   guint32 seed;
670   gint i;
671 
672   seed = g_test_rand_int ();
673 
674   randa = g_rand_new_with_seed (seed);
675   randb = g_rand_new_with_seed (seed);
676 
677   for (i = 0; i < 500; i++)
678     {
679       RandomMenu *a, *b;
680 
681       a = random_menu_new (randa, TOP_ORDER);
682       b = random_menu_new (randb, TOP_ORDER);
683       assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
684       g_object_unref (b);
685       g_object_unref (a);
686     }
687 
688   g_rand_int (randa);
689 
690   for (i = 0; i < 500;)
691     {
692       RandomMenu *a, *b;
693 
694       a = random_menu_new (randa, TOP_ORDER);
695       b = random_menu_new (randb, TOP_ORDER);
696       if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
697         {
698           /* by chance, they may really be equal.  double check. */
699           GString *as, *bs;
700 
701           as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
702           bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
703           g_assert_cmpstr (as->str, ==, bs->str);
704           g_string_free (bs, TRUE);
705           g_string_free (as, TRUE);
706 
707           /* we're here because randa and randb just generated equal
708            * menus.  they may do it again, so throw away randb and make
709            * a fresh one.
710            */
711           g_rand_free (randb);
712           randb = g_rand_new_with_seed (g_rand_int (randa));
713         }
714       else
715         /* make sure we get enough unequals (ie: no GRand failure) */
716         i++;
717 
718       g_object_unref (b);
719       g_object_unref (a);
720     }
721 
722   g_rand_free (randb);
723   g_rand_free (randa);
724 }
725 
726 static void
727 test_random (void)
728 {
729   RandomMenu *random;
730   MirrorMenu *mirror;
731   GRand *rand;
732   gint i;
733 
734   rand = g_rand_new_with_seed (g_test_rand_int ());
735   random = random_menu_new (rand, TOP_ORDER);
736   mirror = mirror_menu_new (G_MENU_MODEL (random));
737 
738   for (i = 0; i < 500; i++)
739     {
740       assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
741       random_menu_change (random, rand);
742     }
743 
744   g_object_unref (mirror);
745   g_object_unref (random);
746 
747   g_rand_free (rand);
748 }
749 
750 typedef struct
751 {
752   GDBusConnection *client_connection;
753   GDBusConnection *server_connection;
754   GDBusServer *server;
755 
756   GThread *service_thread;
757   /* Protects server_connection and service_loop. */
758   GMutex service_loop_lock;
759   GCond service_loop_cond;
760 
761   GMainLoop *service_loop;
762 } PeerConnection;
763 
764 static gboolean
765 on_new_connection (GDBusServer *server,
766                    GDBusConnection *connection,
767                    gpointer user_data)
768 {
769   PeerConnection *data = user_data;
770 
771   g_mutex_lock (&data->service_loop_lock);
772   data->server_connection = g_object_ref (connection);
773   g_cond_broadcast (&data->service_loop_cond);
774   g_mutex_unlock (&data->service_loop_lock);
775 
776   return TRUE;
777 }
778 
779 static void
780 create_service_loop (GMainContext   *service_context,
781                      PeerConnection *data)
782 {
783   g_assert (data->service_loop == NULL);
784   g_mutex_lock (&data->service_loop_lock);
785   data->service_loop = g_main_loop_new (service_context, FALSE);
786   g_cond_broadcast (&data->service_loop_cond);
787   g_mutex_unlock (&data->service_loop_lock);
788 }
789 
790 static void
791 teardown_service_loop (PeerConnection *data)
792 {
793   g_mutex_lock (&data->service_loop_lock);
794   g_clear_pointer (&data->service_loop, g_main_loop_unref);
795   g_mutex_unlock (&data->service_loop_lock);
796 }
797 
798 static void
799 await_service_loop (PeerConnection *data)
800 {
801   g_mutex_lock (&data->service_loop_lock);
802   while (data->service_loop == NULL)
803     g_cond_wait (&data->service_loop_cond, &data->service_loop_lock);
804   g_mutex_unlock (&data->service_loop_lock);
805 }
806 
807 static void
808 await_server_connection (PeerConnection *data)
809 {
810   g_mutex_lock (&data->service_loop_lock);
811   while (data->server_connection == NULL)
812     g_cond_wait (&data->service_loop_cond, &data->service_loop_lock);
813   g_mutex_unlock (&data->service_loop_lock);
814 }
815 
816 static gpointer
817 service_thread_func (gpointer user_data)
818 {
819   PeerConnection *data = user_data;
820   GMainContext *service_context;
821   GError *error;
822   gchar *address;
823   gchar *tmpdir;
824   GDBusServerFlags flags;
825   gchar *guid;
826 
827   service_context = g_main_context_new ();
828   g_main_context_push_thread_default (service_context);
829 
830   tmpdir = NULL;
831   flags = G_DBUS_SERVER_FLAGS_NONE;
832 
833 #ifdef G_OS_UNIX
834   if (g_unix_socket_address_abstract_names_supported ())
835     address = g_strdup ("unix:tmpdir=/tmp/test-dbus-peer");
836   else
837     {
838       tmpdir = g_dir_make_tmp ("test-dbus-peer-XXXXXX", NULL);
839       address = g_strdup_printf ("unix:tmpdir=%s", tmpdir);
840     }
841 #else
842   address = g_strdup ("nonce-tcp:");
843   flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
844 #endif
845 
846   guid = g_dbus_generate_guid ();
847 
848   error = NULL;
849   data->server = g_dbus_server_new_sync (address,
850                                          flags,
851                                          guid,
852                                          NULL,
853                                          NULL,
854                                          &error);
855   g_assert_no_error (error);
856   g_free (address);
857   g_free (guid);
858 
859   g_signal_connect (data->server,
860                     "new-connection",
861                     G_CALLBACK (on_new_connection),
862                     data);
863 
864   g_dbus_server_start (data->server);
865 
866   create_service_loop (service_context, data);
867   g_main_loop_run (data->service_loop);
868 
869   g_main_context_pop_thread_default (service_context);
870 
871   teardown_service_loop (data);
872   g_main_context_unref (service_context);
873 
874   if (tmpdir)
875     {
876       g_rmdir (tmpdir);
877       g_free (tmpdir);
878     }
879 
880   return NULL;
881 }
882 
883 static void
884 peer_connection_up (PeerConnection *data)
885 {
886   GError *error;
887 
888   memset (data, '\0', sizeof (PeerConnection));
889 
890   g_mutex_init (&data->service_loop_lock);
891   g_cond_init (&data->service_loop_cond);
892 
893   /* bring up a server - we run the server in a different thread to
894      avoid deadlocks */
895   data->service_thread = g_thread_new ("test_dbus_peer",
896                                        service_thread_func,
897                                        data);
898   await_service_loop (data);
899   g_assert (data->server != NULL);
900 
901   /* bring up a connection and accept it */
902   error = NULL;
903   data->client_connection =
904     g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (data->server),
905                                             G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
906                                             NULL, /* GDBusAuthObserver */
907                                             NULL, /* cancellable */
908                                             &error);
909   g_assert_no_error (error);
910   g_assert (data->client_connection != NULL);
911   await_server_connection (data);
912 }
913 
914 static void
915 peer_connection_down (PeerConnection *data)
916 {
917   g_object_unref (data->client_connection);
918   g_object_unref (data->server_connection);
919 
920   g_dbus_server_stop (data->server);
921   g_object_unref (data->server);
922 
923   g_main_loop_quit (data->service_loop);
924   g_thread_join (data->service_thread);
925 
926   g_mutex_clear (&data->service_loop_lock);
927   g_cond_clear (&data->service_loop_cond);
928 }
929 
930 struct roundtrip_state
931 {
932   RandomMenu *random;
933   MirrorMenu *proxy_mirror;
934   GDBusMenuModel *proxy;
935   GMainLoop *loop;
936   GRand *rand;
937   gint success;
938   gint count;
939 };
940 
941 static gboolean
942 roundtrip_step (gpointer data)
943 {
944   struct roundtrip_state *state = data;
945 
946   if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)) &&
947       check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy_mirror)))
948     {
949       state->success++;
950       state->count = 0;
951 
952       if (state->success < 100)
953         random_menu_change (state->random, state->rand);
954       else
955         g_main_loop_quit (state->loop);
956     }
957   else if (state->count == 100)
958     {
959       assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
960       g_assert_not_reached ();
961     }
962   else
963     state->count++;
964 
965   return G_SOURCE_CONTINUE;
966 }
967 
968 static void
969 do_roundtrip (GDBusConnection *exporter_connection,
970               GDBusConnection *proxy_connection)
971 {
972   struct roundtrip_state state;
973   guint export_id;
974   guint id;
975 
976   state.rand = g_rand_new_with_seed (g_test_rand_int ());
977 
978   state.random = random_menu_new (state.rand, 2);
979   export_id = g_dbus_connection_export_menu_model (exporter_connection,
980                                                    "/",
981                                                    G_MENU_MODEL (state.random),
982                                                    NULL);
983   state.proxy = g_dbus_menu_model_get (proxy_connection,
984                                        g_dbus_connection_get_unique_name (proxy_connection),
985                                        "/");
986   state.proxy_mirror = mirror_menu_new (G_MENU_MODEL (state.proxy));
987   state.count = 0;
988   state.success = 0;
989 
990   id = g_timeout_add (10, roundtrip_step, &state);
991 
992   state.loop = g_main_loop_new (NULL, FALSE);
993   g_main_loop_run (state.loop);
994 
995   g_main_loop_unref (state.loop);
996   g_source_remove (id);
997   g_object_unref (state.proxy);
998   g_dbus_connection_unexport_menu_model (exporter_connection, export_id);
999   g_object_unref (state.random);
1000   g_object_unref (state.proxy_mirror);
1001   g_rand_free (state.rand);
1002 }
1003 
1004 static void
1005 test_dbus_roundtrip (void)
1006 {
1007   GDBusConnection *bus;
1008 
1009   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1010   do_roundtrip (bus, bus);
1011   g_object_unref (bus);
1012 }
1013 
1014 static void
1015 test_dbus_peer_roundtrip (void)
1016 {
1017 #ifdef _GLIB_ADDRESS_SANITIZER
1018   g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313");
1019   (void) peer_connection_up;
1020   (void) peer_connection_down;
1021 #else
1022   PeerConnection peer;
1023 
1024   peer_connection_up (&peer);
1025   do_roundtrip (peer.server_connection, peer.client_connection);
1026   peer_connection_down (&peer);
1027 #endif
1028 }
1029 
1030 static gint items_changed_count;
1031 
1032 static void
1033 items_changed (GMenuModel *model,
1034                gint        position,
1035                gint        removed,
1036                gint        added,
1037                gpointer    data)
1038 {
1039   items_changed_count++;
1040 }
1041 
1042 static gboolean
1043 stop_loop (gpointer data)
1044 {
1045   GMainLoop *loop = data;
1046 
1047   g_main_loop_quit (loop);
1048 
1049   return G_SOURCE_REMOVE;
1050 }
1051 
1052 static void
1053 do_subscriptions (GDBusConnection *exporter_connection,
1054                   GDBusConnection *proxy_connection)
1055 {
1056   GMenu *menu;
1057   GDBusMenuModel *proxy;
1058   GMainLoop *loop;
1059   GError *error = NULL;
1060   guint export_id;
1061   guint timeout_id;
1062 
1063   timeout_id = add_timeout (60);
1064   loop = g_main_loop_new (NULL, FALSE);
1065 
1066   menu = g_menu_new ();
1067 
1068   export_id = g_dbus_connection_export_menu_model (exporter_connection,
1069                                                    "/",
1070                                                    G_MENU_MODEL (menu),
1071                                                    &error);
1072   g_assert_no_error (error);
1073 
1074   proxy = g_dbus_menu_model_get (proxy_connection,
1075                                  g_dbus_connection_get_unique_name (proxy_connection),
1076                                  "/");
1077   items_changed_count = 0;
1078   g_signal_connect (proxy, "items-changed",
1079                     G_CALLBACK (items_changed), NULL);
1080 
1081   g_menu_append (menu, "item1", NULL);
1082   g_menu_append (menu, "item2", NULL);
1083   g_menu_append (menu, "item3", NULL);
1084 
1085   g_assert_cmpint (items_changed_count, ==, 0);
1086 
1087   /* We don't subscribe to change-notification until we look at the items */
1088   g_timeout_add (100, stop_loop, loop);
1089   g_main_loop_run (loop);
1090 
1091   /* Looking at the items triggers subscription */
1092   g_menu_model_get_n_items (G_MENU_MODEL (proxy));
1093 
1094   while (items_changed_count < 1)
1095     g_main_context_iteration (NULL, TRUE);
1096 
1097   /* We get all three items in one batch */
1098   g_assert_cmpint (items_changed_count, ==, 1);
1099   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
1100 
1101   /* If we wait, we don't get any more */
1102   g_timeout_add (100, stop_loop, loop);
1103   g_main_loop_run (loop);
1104   g_assert_cmpint (items_changed_count, ==, 1);
1105   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
1106 
1107   /* Now we're subscribed, we get changes individually */
1108   g_menu_append (menu, "item4", NULL);
1109   g_menu_append (menu, "item5", NULL);
1110   g_menu_append (menu, "item6", NULL);
1111   g_menu_remove (menu, 0);
1112   g_menu_remove (menu, 0);
1113 
1114   while (items_changed_count < 6)
1115     g_main_context_iteration (NULL, TRUE);
1116 
1117   g_assert_cmpint (items_changed_count, ==, 6);
1118 
1119   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4);
1120 
1121   /* After destroying the proxy and waiting a bit, we don't get any more
1122    * items-changed signals */
1123   g_object_unref (proxy);
1124 
1125   g_timeout_add (100, stop_loop, loop);
1126   g_main_loop_run (loop);
1127 
1128   g_menu_remove (menu, 0);
1129   g_menu_remove (menu, 0);
1130 
1131   g_timeout_add (100, stop_loop, loop);
1132   g_main_loop_run (loop);
1133 
1134   g_assert_cmpint (items_changed_count, ==, 6);
1135 
1136   g_dbus_connection_unexport_menu_model (exporter_connection, export_id);
1137   g_object_unref (menu);
1138 
1139   g_main_loop_unref (loop);
1140   cancel_timeout (timeout_id);
1141 }
1142 
1143 static void
1144 test_dbus_subscriptions (void)
1145 {
1146   GDBusConnection *bus;
1147 
1148   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1149   do_subscriptions (bus, bus);
1150   g_object_unref (bus);
1151 }
1152 
1153 static void
1154 test_dbus_peer_subscriptions (void)
1155 {
1156 #ifdef _GLIB_ADDRESS_SANITIZER
1157   g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313");
1158   (void) peer_connection_up;
1159   (void) peer_connection_down;
1160 #else
1161   PeerConnection peer;
1162 
1163   peer_connection_up (&peer);
1164   do_subscriptions (peer.server_connection, peer.client_connection);
1165   peer_connection_down (&peer);
1166 #endif
1167 }
1168 
1169 static gpointer
1170 do_modify (gpointer data)
1171 {
1172   RandomMenu *menu = data;
1173   GRand *rand;
1174   gint i;
1175 
1176   rand = g_rand_new_with_seed (g_test_rand_int ());
1177 
1178   for (i = 0; i < 10000; i++)
1179     {
1180       random_menu_change (menu, rand);
1181     }
1182 
1183   g_rand_free (rand);
1184 
1185   return NULL;
1186 }
1187 
1188 static gpointer
1189 do_export (gpointer data)
1190 {
1191   GMenuModel *menu = data;
1192   gint i;
1193   GDBusConnection *bus;
1194   gchar *path;
1195   GError *error = NULL;
1196   guint id;
1197 
1198   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1199   path = g_strdup_printf ("/%p", data);
1200 
1201   for (i = 0; i < 10000; i++)
1202     {
1203       id = g_dbus_connection_export_menu_model (bus, path, menu, &error);
1204       g_assert_no_error (error);
1205       g_dbus_connection_unexport_menu_model (bus, id);
1206       while (g_main_context_iteration (NULL, FALSE));
1207     }
1208 
1209   g_free (path);
1210 
1211   g_object_unref (bus);
1212 
1213   return NULL;
1214 }
1215 
1216 static void
1217 test_dbus_threaded (void)
1218 {
1219   RandomMenu *menu[10];
1220   GThread *call[10];
1221   GThread *export[10];
1222   gint i;
1223 
1224   for (i = 0; i < 10; i++)
1225     {
1226       GRand *rand = g_rand_new_with_seed (g_test_rand_int ());
1227       menu[i] = random_menu_new (rand, 2);
1228       call[i] = g_thread_new ("call", do_modify, menu[i]);
1229       export[i] = g_thread_new ("export", do_export, menu[i]);
1230       g_rand_free (rand);
1231     }
1232 
1233   for (i = 0; i < 10; i++)
1234     {
1235       g_thread_join (call[i]);
1236       g_thread_join (export[i]);
1237     }
1238 
1239   for (i = 0; i < 10; i++)
1240     g_object_unref (menu[i]);
1241 }
1242 
1243 static void
1244 test_attributes (void)
1245 {
1246   GMenu *menu;
1247   GMenuItem *item;
1248   GVariant *v;
1249 
1250   menu = g_menu_new ();
1251 
1252   item = g_menu_item_new ("test", NULL);
1253   g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
1254   g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
1255 
1256   g_menu_item_set_attribute (item, "double", "d", 1.5);
1257   v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
1258   g_menu_item_set_attribute_value (item, "complex", v);
1259   g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
1260 
1261   g_menu_append_item (menu, item);
1262 
1263   g_menu_item_set_attribute (item, "double", "d", G_PI);
1264 
1265   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
1266 
1267   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
1268   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
1269   g_variant_unref (v);
1270 
1271   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
1272   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
1273   g_variant_unref (v);
1274 
1275   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
1276   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
1277   g_variant_unref (v);
1278 
1279   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
1280   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
1281   g_variant_unref (v);
1282 
1283   g_menu_remove_all (menu);
1284 
1285   g_object_unref (menu);
1286   g_object_unref (item);
1287 }
1288 
1289 static void
1290 test_attribute_iter (void)
1291 {
1292   GMenu *menu;
1293   GMenuItem *item;
1294   const gchar *name;
1295   GVariant *v;
1296   GMenuAttributeIter *iter;
1297   GHashTable *found;
1298 
1299   menu = g_menu_new ();
1300 
1301   item = g_menu_item_new ("test", NULL);
1302   g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
1303   g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
1304 
1305   g_menu_item_set_attribute (item, "double", "d", 1.5);
1306   v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
1307   g_menu_item_set_attribute_value (item, "complex", v);
1308   g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
1309 
1310   g_menu_append_item (menu, item);
1311 
1312   g_menu_item_set_attribute (item, "double", "d", G_PI);
1313 
1314   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
1315 
1316   found = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
1317 
1318   iter = g_menu_model_iterate_item_attributes (G_MENU_MODEL (menu), 0);
1319   while (g_menu_attribute_iter_get_next (iter, &name, &v))
1320     g_hash_table_insert (found, g_strdup (name), v);
1321 
1322   g_assert_cmpint (g_hash_table_size (found), ==, 6);
1323 
1324   v = g_hash_table_lookup (found, "label");
1325   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
1326 
1327   v = g_hash_table_lookup (found, "boolean");
1328   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
1329 
1330   v = g_hash_table_lookup (found, "string");
1331   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
1332 
1333   v = g_hash_table_lookup (found, "double");
1334   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
1335 
1336   v = g_hash_table_lookup (found, "complex");
1337   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
1338 
1339   v = g_hash_table_lookup (found, "test-123");
1340   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
1341 
1342   g_hash_table_unref (found);
1343 
1344   g_menu_remove_all (menu);
1345 
1346   g_object_unref (menu);
1347   g_object_unref (item);
1348 }
1349 
1350 static void
1351 test_links (void)
1352 {
1353   GMenu *menu;
1354   GMenuModel *m;
1355   GMenuModel *x;
1356   GMenuItem *item;
1357 
1358   m = G_MENU_MODEL (g_menu_new ());
1359   g_menu_append (G_MENU (m), "test", NULL);
1360 
1361   menu = g_menu_new ();
1362 
1363   item = g_menu_item_new ("test2", NULL);
1364   g_menu_item_set_link (item, "submenu", m);
1365   g_menu_prepend_item (menu, item);
1366 
1367   item = g_menu_item_new ("test1", NULL);
1368   g_menu_item_set_link (item, "section", m);
1369   g_menu_insert_item (menu, 0, item);
1370 
1371   item = g_menu_item_new ("test3", NULL);
1372   g_menu_item_set_link (item, "wallet", m);
1373   g_menu_insert_item (menu, 1000, item);
1374 
1375   item = g_menu_item_new ("test4", NULL);
1376   g_menu_item_set_link (item, "purse", m);
1377   g_menu_item_set_link (item, "purse", NULL);
1378   g_menu_append_item (menu, item);
1379 
1380   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
1381 
1382   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
1383   g_assert (x == m);
1384   g_object_unref (x);
1385 
1386   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
1387   g_assert (x == m);
1388   g_object_unref (x);
1389 
1390   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
1391   g_assert (x == m);
1392   g_object_unref (x);
1393 
1394   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
1395   g_assert (x == NULL);
1396 
1397   g_object_unref (m);
1398   g_object_unref (menu);
1399 }
1400 
1401 static void
1402 test_mutable (void)
1403 {
1404   GMenu *menu;
1405 
1406   menu = g_menu_new ();
1407   g_menu_append (menu, "test", "test");
1408 
1409   g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
1410   g_menu_freeze (menu);
1411   g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
1412 
1413   g_object_unref (menu);
1414 }
1415 
1416 static void
1417 test_convenience (void)
1418 {
1419   GMenu *m1, *m2;
1420   GMenu *sub;
1421   GMenuItem *item;
1422 
1423   m1 = g_menu_new ();
1424   m2 = g_menu_new ();
1425   sub = g_menu_new ();
1426 
1427   g_menu_prepend (m1, "label1", "do::something");
1428   g_menu_insert (m2, 0, "label1", "do::something");
1429 
1430   g_menu_append (m1, "label2", "do::somethingelse");
1431   g_menu_insert (m2, -1, "label2", "do::somethingelse");
1432 
1433   g_menu_insert_section (m1, 10, "label3", G_MENU_MODEL (sub));
1434   item = g_menu_item_new_section ("label3", G_MENU_MODEL (sub));
1435   g_menu_insert_item (m2, 10, item);
1436   g_object_unref (item);
1437 
1438   g_menu_prepend_section (m1, "label4", G_MENU_MODEL (sub));
1439   g_menu_insert_section (m2, 0, "label4", G_MENU_MODEL (sub));
1440 
1441   g_menu_append_section (m1, "label5", G_MENU_MODEL (sub));
1442   g_menu_insert_section (m2, -1, "label5", G_MENU_MODEL (sub));
1443 
1444   g_menu_insert_submenu (m1, 5, "label6", G_MENU_MODEL (sub));
1445   item = g_menu_item_new_submenu ("label6", G_MENU_MODEL (sub));
1446   g_menu_insert_item (m2, 5, item);
1447   g_object_unref (item);
1448 
1449   g_menu_prepend_submenu (m1, "label7", G_MENU_MODEL (sub));
1450   g_menu_insert_submenu (m2, 0, "label7", G_MENU_MODEL (sub));
1451 
1452   g_menu_append_submenu (m1, "label8", G_MENU_MODEL (sub));
1453   g_menu_insert_submenu (m2, -1, "label8", G_MENU_MODEL (sub));
1454 
1455   assert_menus_equal (G_MENU_MODEL (m1), G_MENU_MODEL (m2));
1456 
1457   g_object_unref (m1);
1458   g_object_unref (m2);
1459 }
1460 
1461 static void
1462 test_menuitem (void)
1463 {
1464   GMenu *menu;
1465   GMenu *submenu;
1466   GMenuItem *item;
1467   GIcon *icon;
1468   gboolean b;
1469   gchar *s;
1470 
1471   menu = g_menu_new ();
1472   submenu = g_menu_new ();
1473 
1474   item = g_menu_item_new ("label", "action");
1475   g_menu_item_set_attribute (item, "attribute", "b", TRUE);
1476   g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, G_MENU_MODEL (submenu));
1477   g_menu_append_item (menu, item);
1478 
1479   icon = g_themed_icon_new ("bla");
1480   g_menu_item_set_icon (item, icon);
1481   g_object_unref (icon);
1482 
1483   g_assert (g_menu_item_get_attribute (item, "attribute", "b", &b));
1484   g_assert (b);
1485 
1486   g_menu_item_set_action_and_target (item, "action", "(bs)", TRUE, "string");
1487   g_assert (g_menu_item_get_attribute (item, "target", "(bs)", &b, &s));
1488   g_assert (b);
1489   g_assert_cmpstr (s, ==, "string");
1490   g_free (s);
1491 
1492   g_object_unref (item);
1493 
1494   item = g_menu_item_new_from_model (G_MENU_MODEL (menu), 0);
1495   assert_menuitem_equal (item, G_MENU_MODEL (menu), 0);
1496   g_object_unref (item);
1497 
1498   g_object_unref (menu);
1499   g_object_unref (submenu);
1500 }
1501 
1502 /* Epilogue {{{1 */
1503 int
1504 main (int argc, char **argv)
1505 {
1506   gboolean ret;
1507 
1508   g_test_init (&argc, &argv, NULL);
1509 
1510   session_bus_up ();
1511 
1512   g_test_add_func ("/gmenu/equality", test_equality);
1513   g_test_add_func ("/gmenu/random", test_random);
1514   g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip);
1515   g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions);
1516   g_test_add_func ("/gmenu/dbus/threaded", test_dbus_threaded);
1517   g_test_add_func ("/gmenu/dbus/peer/roundtrip", test_dbus_peer_roundtrip);
1518   g_test_add_func ("/gmenu/dbus/peer/subscriptions", test_dbus_peer_subscriptions);
1519   g_test_add_func ("/gmenu/attributes", test_attributes);
1520   g_test_add_func ("/gmenu/attributes/iterate", test_attribute_iter);
1521   g_test_add_func ("/gmenu/links", test_links);
1522   g_test_add_func ("/gmenu/mutable", test_mutable);
1523   g_test_add_func ("/gmenu/convenience", test_convenience);
1524   g_test_add_func ("/gmenu/menuitem", test_menuitem);
1525 
1526   ret = g_test_run ();
1527 
1528   session_bus_down ();
1529 
1530   return ret;
1531 }
1532 /* vim:set foldmethod=marker: */
1533