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