Improve test coverage for GPropertyAction
[platform/upstream/glib.git] / gio / tests / gmenumodel.c
1 #include <gio/gio.h>
2
3 #include "gdbus-sessionbus.h"
4
5 /* Markup printing {{{1 */
6
7 /* This used to be part of GLib, but it was removed before the stable
8  * release because it wasn't generally useful.  We want it here, though.
9  */
10 static void
11 indent_string (GString *string,
12                gint     indent)
13 {
14   while (indent--)
15     g_string_append_c (string, ' ');
16 }
17
18 static GString *
19 g_menu_markup_print_string (GString    *string,
20                             GMenuModel *model,
21                             gint        indent,
22                             gint        tabstop)
23 {
24   gboolean need_nl = FALSE;
25   gint i, n;
26
27   if G_UNLIKELY (string == NULL)
28     string = g_string_new (NULL);
29
30   n = g_menu_model_get_n_items (model);
31
32   for (i = 0; i < n; i++)
33     {
34       GMenuAttributeIter *attr_iter;
35       GMenuLinkIter *link_iter;
36       GString *contents;
37       GString *attrs;
38
39       attr_iter = g_menu_model_iterate_item_attributes (model, i);
40       link_iter = g_menu_model_iterate_item_links (model, i);
41       contents = g_string_new (NULL);
42       attrs = g_string_new (NULL);
43
44       while (g_menu_attribute_iter_next (attr_iter))
45         {
46           const char *name = g_menu_attribute_iter_get_name (attr_iter);
47           GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
48
49           if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
50             {
51               gchar *str;
52               str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
53               g_string_append (attrs, str);
54               g_free (str);
55             }
56
57           else
58             {
59               gchar *printed;
60               gchar *str;
61               const gchar *type;
62
63               printed = g_variant_print (value, TRUE);
64               type = g_variant_type_peek_string (g_variant_get_type (value));
65               str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
66               indent_string (contents, indent + tabstop);
67               g_string_append (contents, str);
68               g_free (printed);
69               g_free (str);
70             }
71
72           g_variant_unref (value);
73         }
74       g_object_unref (attr_iter);
75
76       while (g_menu_link_iter_next (link_iter))
77         {
78           const gchar *name = g_menu_link_iter_get_name (link_iter);
79           GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
80           gchar *str;
81
82           if (contents->str[0])
83             g_string_append_c (contents, '\n');
84
85           str = g_markup_printf_escaped ("<link name='%s'>\n", name);
86           indent_string (contents, indent + tabstop);
87           g_string_append (contents, str);
88           g_free (str);
89
90           g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
91
92           indent_string (contents, indent + tabstop);
93           g_string_append (contents, "</link>\n");
94           g_object_unref (menu);
95         }
96       g_object_unref (link_iter);
97
98       if (contents->str[0])
99         {
100           indent_string (string, indent);
101           g_string_append_printf (string, "<item%s>\n", attrs->str);
102           g_string_append (string, contents->str);
103           indent_string (string, indent);
104           g_string_append (string, "</item>\n");
105           need_nl = TRUE;
106         }
107
108       else
109         {
110           if (need_nl)
111             g_string_append_c (string, '\n');
112
113           indent_string (string, indent);
114           g_string_append_printf (string, "<item%s/>\n", attrs->str);
115           need_nl = FALSE;
116         }
117
118       g_string_free (contents, TRUE);
119       g_string_free (attrs, TRUE);
120     }
121
122   return string;
123 }
124
125 /* TestItem {{{1 */
126
127 /* This utility struct is used by both the RandomMenu and MirrorMenu
128  * class implementations below.
129  */
130 typedef struct {
131   GHashTable *attributes;
132   GHashTable *links;
133 } TestItem;
134
135 static TestItem *
136 test_item_new (GHashTable *attributes,
137                GHashTable *links)
138 {
139   TestItem *item;
140
141   item = g_slice_new (TestItem);
142   item->attributes = g_hash_table_ref (attributes);
143   item->links = g_hash_table_ref (links);
144
145   return item;
146 }
147
148 static void
149 test_item_free (gpointer data)
150 {
151   TestItem *item = data;
152
153   g_hash_table_unref (item->attributes);
154   g_hash_table_unref (item->links);
155
156   g_slice_free (TestItem, item);
157 }
158
159 /* RandomMenu {{{1 */
160 #define MAX_ITEMS 5
161 #define TOP_ORDER 4
162
163 typedef struct {
164   GMenuModel parent_instance;
165
166   GSequence *items;
167   gint order;
168 } RandomMenu;
169
170 typedef GMenuModelClass RandomMenuClass;
171
172 static GType random_menu_get_type (void);
173 G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL);
174
175 static gboolean
176 random_menu_is_mutable (GMenuModel *model)
177 {
178   return TRUE;
179 }
180
181 static gint
182 random_menu_get_n_items (GMenuModel *model)
183 {
184   RandomMenu *menu = (RandomMenu *) model;
185
186   return g_sequence_get_length (menu->items);
187 }
188
189 static void
190 random_menu_get_item_attributes (GMenuModel  *model,
191                                  gint         position,
192                                  GHashTable **table)
193 {
194   RandomMenu *menu = (RandomMenu *) model;
195   TestItem *item;
196
197   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
198   *table = g_hash_table_ref (item->attributes);
199 }
200
201 static void
202 random_menu_get_item_links (GMenuModel  *model,
203                             gint         position,
204                             GHashTable **table)
205 {
206   RandomMenu *menu = (RandomMenu *) model;
207   TestItem *item;
208
209   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
210   *table = g_hash_table_ref (item->links);
211 }
212
213 static void
214 random_menu_finalize (GObject *object)
215 {
216   RandomMenu *menu = (RandomMenu *) object;
217
218   g_sequence_free (menu->items);
219
220   G_OBJECT_CLASS (random_menu_parent_class)
221     ->finalize (object);
222 }
223
224 static void
225 random_menu_init (RandomMenu *menu)
226 {
227 }
228
229 static void
230 random_menu_class_init (GMenuModelClass *class)
231 {
232   GObjectClass *object_class = G_OBJECT_CLASS (class);
233
234   class->is_mutable = random_menu_is_mutable;
235   class->get_n_items = random_menu_get_n_items;
236   class->get_item_attributes = random_menu_get_item_attributes;
237   class->get_item_links = random_menu_get_item_links;
238
239   object_class->finalize = random_menu_finalize;
240 }
241
242 static RandomMenu * random_menu_new (GRand *rand, gint order);
243
244 static void
245 random_menu_change (RandomMenu *menu,
246                     GRand      *rand)
247 {
248   gint position, removes, adds;
249   GSequenceIter *point;
250   gint n_items;
251   gint i;
252
253   n_items = g_sequence_get_length (menu->items);
254
255   do
256     {
257       position = g_rand_int_range (rand, 0, n_items + 1);
258       removes = g_rand_int_range (rand, 0, n_items - position + 1);
259       adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
260     }
261   while (removes == 0 && adds == 0);
262
263   point = g_sequence_get_iter_at_pos (menu->items, position + removes);
264
265   if (removes)
266     {
267       GSequenceIter *start;
268
269       start = g_sequence_get_iter_at_pos (menu->items, position);
270       g_sequence_remove_range (start, point);
271     }
272
273   for (i = 0; i < adds; i++)
274     {
275       const gchar *label;
276       GHashTable *links;
277       GHashTable *attributes;
278
279       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
280       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
281
282       if (menu->order > 0 && g_rand_boolean (rand))
283         {
284           RandomMenu *child;
285           const gchar *subtype;
286
287           child = random_menu_new (rand, menu->order - 1);
288
289           if (g_rand_boolean (rand))
290             {
291               subtype = G_MENU_LINK_SECTION;
292               /* label some section headers */
293               if (g_rand_boolean (rand))
294                 label = "Section";
295               else
296                 label = NULL;
297             }
298           else
299             {
300               /* label all submenus */
301               subtype = G_MENU_LINK_SUBMENU;
302               label = "Submenu";
303             }
304
305           g_hash_table_insert (links, g_strdup (subtype), child);
306         }
307       else
308         /* label all terminals */
309         label = "Menu Item";
310
311       if (label)
312         g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
313
314       g_sequence_insert_before (point, test_item_new (attributes, links));
315       g_hash_table_unref (links);
316       g_hash_table_unref (attributes);
317     }
318
319   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
320 }
321
322 static RandomMenu *
323 random_menu_new (GRand *rand,
324                  gint   order)
325 {
326   RandomMenu *menu;
327
328   menu = g_object_new (random_menu_get_type (), NULL);
329   menu->items = g_sequence_new (test_item_free);
330   menu->order = order;
331
332   random_menu_change (menu, rand);
333
334   return menu;
335 }
336
337 /* MirrorMenu {{{1 */
338 typedef struct {
339   GMenuModel parent_instance;
340
341   GMenuModel *clone_of;
342   GSequence *items;
343   gulong handler_id;
344 } MirrorMenu;
345
346 typedef GMenuModelClass MirrorMenuClass;
347
348 static GType mirror_menu_get_type (void);
349 G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL);
350
351 static gboolean
352 mirror_menu_is_mutable (GMenuModel *model)
353 {
354   MirrorMenu *menu = (MirrorMenu *) model;
355
356   return menu->handler_id != 0;
357 }
358
359 static gint
360 mirror_menu_get_n_items (GMenuModel *model)
361 {
362   MirrorMenu *menu = (MirrorMenu *) model;
363
364   return g_sequence_get_length (menu->items);
365 }
366
367 static void
368 mirror_menu_get_item_attributes (GMenuModel  *model,
369                                  gint         position,
370                                  GHashTable **table)
371 {
372   MirrorMenu *menu = (MirrorMenu *) model;
373   TestItem *item;
374
375   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
376   *table = g_hash_table_ref (item->attributes);
377 }
378
379 static void
380 mirror_menu_get_item_links (GMenuModel  *model,
381                             gint         position,
382                             GHashTable **table)
383 {
384   MirrorMenu *menu = (MirrorMenu *) model;
385   TestItem *item;
386
387   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
388   *table = g_hash_table_ref (item->links);
389 }
390
391 static void
392 mirror_menu_finalize (GObject *object)
393 {
394   MirrorMenu *menu = (MirrorMenu *) object;
395
396   if (menu->handler_id)
397     g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
398
399   g_sequence_free (menu->items);
400   g_object_unref (menu->clone_of);
401
402   G_OBJECT_CLASS (mirror_menu_parent_class)
403     ->finalize (object);
404 }
405
406 static void
407 mirror_menu_init (MirrorMenu *menu)
408 {
409 }
410
411 static void
412 mirror_menu_class_init (GMenuModelClass *class)
413 {
414   GObjectClass *object_class = G_OBJECT_CLASS (class);
415
416   class->is_mutable = mirror_menu_is_mutable;
417   class->get_n_items = mirror_menu_get_n_items;
418   class->get_item_attributes = mirror_menu_get_item_attributes;
419   class->get_item_links = mirror_menu_get_item_links;
420
421   object_class->finalize = mirror_menu_finalize;
422 }
423
424 static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
425
426 static void
427 mirror_menu_changed (GMenuModel *model,
428                      gint        position,
429                      gint        removed,
430                      gint        added,
431                      gpointer    user_data)
432 {
433   MirrorMenu *menu = user_data;
434   GSequenceIter *point;
435   gint i;
436
437   g_assert (model == menu->clone_of);
438
439   point = g_sequence_get_iter_at_pos (menu->items, position + removed);
440
441   if (removed)
442     {
443       GSequenceIter *start;
444
445       start = g_sequence_get_iter_at_pos (menu->items, position);
446       g_sequence_remove_range (start, point);
447     }
448
449   for (i = position; i < position + added; i++)
450     {
451       GMenuAttributeIter *attr_iter;
452       GMenuLinkIter *link_iter;
453       GHashTable *links;
454       GHashTable *attributes;
455       const gchar *name;
456       GMenuModel *child;
457       GVariant *value;
458
459       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
460       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
461
462       attr_iter = g_menu_model_iterate_item_attributes (model, i);
463       while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
464         {
465           g_hash_table_insert (attributes, g_strdup (name), value);
466         }
467       g_object_unref (attr_iter);
468
469       link_iter = g_menu_model_iterate_item_links (model, i);
470       while (g_menu_link_iter_get_next (link_iter, &name, &child))
471         {
472           g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
473           g_object_unref (child);
474         }
475       g_object_unref (link_iter);
476
477       g_sequence_insert_before (point, test_item_new (attributes, links));
478       g_hash_table_unref (attributes);
479       g_hash_table_unref (links);
480     }
481
482   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
483 }
484
485 static MirrorMenu *
486 mirror_menu_new (GMenuModel *clone_of)
487 {
488   MirrorMenu *menu;
489
490   menu = g_object_new (mirror_menu_get_type (), NULL);
491   menu->items = g_sequence_new (test_item_free);
492   menu->clone_of = g_object_ref (clone_of);
493
494   if (g_menu_model_is_mutable (clone_of))
495     menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
496   mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
497
498   return menu;
499 }
500
501 /* check_menus_equal(), assert_menus_equal() {{{1 */
502 static gboolean
503 check_menus_equal (GMenuModel *a,
504                    GMenuModel *b)
505 {
506   gboolean equal = TRUE;
507   gint a_n, b_n;
508   gint i;
509
510   a_n = g_menu_model_get_n_items (a);
511   b_n = g_menu_model_get_n_items (b);
512
513   if (a_n != b_n)
514     return FALSE;
515
516   for (i = 0; i < a_n; i++)
517     {
518       GMenuAttributeIter *attr_iter;
519       GVariant *a_value, *b_value;
520       GMenuLinkIter *link_iter;
521       GMenuModel *a_menu, *b_menu;
522       const gchar *name;
523
524       attr_iter = g_menu_model_iterate_item_attributes (a, i);
525       while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
526         {
527           b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
528           equal &= b_value && g_variant_equal (a_value, b_value);
529           if (b_value)
530             g_variant_unref (b_value);
531           g_variant_unref (a_value);
532         }
533       g_object_unref (attr_iter);
534
535       attr_iter = g_menu_model_iterate_item_attributes (b, i);
536       while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
537         {
538           a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
539           equal &= a_value && g_variant_equal (a_value, b_value);
540           if (a_value)
541             g_variant_unref (a_value);
542           g_variant_unref (b_value);
543         }
544       g_object_unref (attr_iter);
545
546       link_iter = g_menu_model_iterate_item_links (a, i);
547       while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
548         {
549           b_menu = g_menu_model_get_item_link (b, i, name);
550           equal &= b_menu && check_menus_equal (a_menu, b_menu);
551           if (b_menu)
552             g_object_unref (b_menu);
553           g_object_unref (a_menu);
554         }
555       g_object_unref (link_iter);
556
557       link_iter = g_menu_model_iterate_item_links (b, i);
558       while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
559         {
560           a_menu = g_menu_model_get_item_link (a, i, name);
561           equal &= a_menu && check_menus_equal (a_menu, b_menu);
562           if (a_menu)
563             g_object_unref (a_menu);
564           g_object_unref (b_menu);
565         }
566       g_object_unref (link_iter);
567     }
568
569   return equal;
570 }
571
572 static void
573 assert_menus_equal (GMenuModel *a,
574                     GMenuModel *b)
575 {
576   if (!check_menus_equal (a, b))
577     {
578       GString *string;
579
580       string = g_string_new ("\n  <a>\n");
581       g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
582       g_string_append (string, "  </a>\n\n-------------\n  <b>\n");
583       g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
584       g_string_append (string, "  </b>\n");
585       g_error ("%s", string->str);
586     }
587 }
588
589 static void
590 assert_menuitem_equal (GMenuItem  *item,
591                        GMenuModel *model,
592                        gint        index)
593 {
594   GMenuAttributeIter *attr_iter;
595   GMenuLinkIter *link_iter;
596   const gchar *name;
597   GVariant *value;
598   GMenuModel *linked_model;
599
600   /* NOTE we can't yet test whether item has attributes or links that
601    * are not in the model, because there's no iterator API for menu
602    * items */
603
604   attr_iter = g_menu_model_iterate_item_attributes (model, index);
605   while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
606     {
607       GVariant *item_value;
608
609       item_value = g_menu_item_get_attribute_value (item, name, g_variant_get_type (value));
610       g_assert (item_value && g_variant_equal (item_value, value));
611
612       g_variant_unref (item_value);
613       g_variant_unref (value);
614     }
615
616   link_iter = g_menu_model_iterate_item_links (model, index);
617   while (g_menu_link_iter_get_next (link_iter, &name, &linked_model))
618     {
619       GMenuModel *item_linked_model;
620
621       item_linked_model = g_menu_item_get_link (item, name);
622       g_assert (linked_model == item_linked_model);
623
624       g_object_unref (item_linked_model);
625       g_object_unref (linked_model);
626     }
627
628   g_object_unref (attr_iter);
629   g_object_unref (link_iter);
630 }
631
632 /* Test cases {{{1 */
633 static void
634 test_equality (void)
635 {
636   GRand *randa, *randb;
637   guint32 seed;
638   gint i;
639
640   seed = g_test_rand_int ();
641
642   randa = g_rand_new_with_seed (seed);
643   randb = g_rand_new_with_seed (seed);
644
645   for (i = 0; i < 500; i++)
646     {
647       RandomMenu *a, *b;
648
649       a = random_menu_new (randa, TOP_ORDER);
650       b = random_menu_new (randb, TOP_ORDER);
651       assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
652       g_object_unref (b);
653       g_object_unref (a);
654     }
655
656   g_rand_int (randa);
657
658   for (i = 0; i < 500;)
659     {
660       RandomMenu *a, *b;
661
662       a = random_menu_new (randa, TOP_ORDER);
663       b = random_menu_new (randb, TOP_ORDER);
664       if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
665         {
666           /* by chance, they may really be equal.  double check. */
667           GString *as, *bs;
668
669           as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
670           bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
671           g_assert_cmpstr (as->str, ==, bs->str);
672           g_string_free (bs, TRUE);
673           g_string_free (as, TRUE);
674
675           /* we're here because randa and randb just generated equal
676            * menus.  they may do it again, so throw away randb and make
677            * a fresh one.
678            */
679           g_rand_free (randb);
680           randb = g_rand_new_with_seed (g_rand_int (randa));
681         }
682       else
683         /* make sure we get enough unequals (ie: no GRand failure) */
684         i++;
685
686       g_object_unref (b);
687       g_object_unref (a);
688     }
689
690   g_rand_free (randb);
691   g_rand_free (randa);
692 }
693
694 static void
695 test_random (void)
696 {
697   RandomMenu *random;
698   MirrorMenu *mirror;
699   GRand *rand;
700   gint i;
701
702   rand = g_rand_new_with_seed (g_test_rand_int ());
703   random = random_menu_new (rand, TOP_ORDER);
704   mirror = mirror_menu_new (G_MENU_MODEL (random));
705
706   for (i = 0; i < 500; i++)
707     {
708       assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
709       random_menu_change (random, rand);
710     }
711
712   g_object_unref (mirror);
713   g_object_unref (random);
714
715   g_rand_free (rand);
716 }
717
718 struct roundtrip_state
719 {
720   RandomMenu *random;
721   MirrorMenu *proxy_mirror;
722   GDBusMenuModel *proxy;
723   GMainLoop *loop;
724   GRand *rand;
725   gint success;
726   gint count;
727 };
728
729 static gboolean
730 roundtrip_step (gpointer data)
731 {
732   struct roundtrip_state *state = data;
733
734   if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)) &&
735       check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy_mirror)))
736     {
737       state->success++;
738       state->count = 0;
739
740       if (state->success < 100)
741         random_menu_change (state->random, state->rand);
742       else
743         g_main_loop_quit (state->loop);
744     }
745   else if (state->count == 100)
746     {
747       assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
748       g_assert_not_reached ();
749     }
750   else
751     state->count++;
752
753   return G_SOURCE_CONTINUE;
754 }
755
756 static void
757 test_dbus_roundtrip (void)
758 {
759   struct roundtrip_state state;
760   GDBusConnection *bus;
761   guint export_id;
762   guint id;
763
764   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
765
766   state.rand = g_rand_new_with_seed (g_test_rand_int ());
767
768   state.random = random_menu_new (state.rand, 2);
769   export_id = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (state.random), NULL);
770   state.proxy = g_dbus_menu_model_get (bus, g_dbus_connection_get_unique_name (bus), "/");
771   state.proxy_mirror = mirror_menu_new (G_MENU_MODEL (state.proxy));
772   state.count = 0;
773   state.success = 0;
774
775   id = g_timeout_add (10, roundtrip_step, &state);
776
777   state.loop = g_main_loop_new (NULL, FALSE);
778   g_main_loop_run (state.loop);
779
780   g_main_loop_unref (state.loop);
781   g_source_remove (id);
782   g_object_unref (state.proxy);
783   g_dbus_connection_unexport_menu_model (bus, export_id);
784   g_object_unref (state.random);
785   g_object_unref (state.proxy_mirror);
786   g_rand_free (state.rand);
787   g_object_unref (bus);
788 }
789
790 static gint items_changed_count;
791
792 static void
793 items_changed (GMenuModel *model,
794                gint        position,
795                gint        removed,
796                gint        added,
797                gpointer    data)
798 {
799   items_changed_count++;
800 }
801
802 static gboolean
803 stop_loop (gpointer data)
804 {
805   GMainLoop *loop = data;
806
807   g_main_loop_quit (loop);
808
809   return G_SOURCE_REMOVE;
810 }
811
812 static void
813 test_dbus_subscriptions (void)
814 {
815   GDBusConnection *bus;
816   GMenu *menu;
817   GDBusMenuModel *proxy;
818   GMainLoop *loop;
819   GError *error = NULL;
820   guint export_id;
821
822   loop = g_main_loop_new (NULL, FALSE);
823
824   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
825
826   menu = g_menu_new ();
827
828   export_id = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (menu), &error);
829   g_assert_no_error (error);
830
831   proxy = g_dbus_menu_model_get (bus, g_dbus_connection_get_unique_name (bus), "/");
832   items_changed_count = 0;
833   g_signal_connect (proxy, "items-changed",
834                     G_CALLBACK (items_changed), NULL);
835
836   g_menu_append (menu, "item1", NULL);
837   g_menu_append (menu, "item2", NULL);
838   g_menu_append (menu, "item3", NULL);
839
840   g_assert_cmpint (items_changed_count, ==, 0);
841
842   g_timeout_add (100, stop_loop, loop);
843   g_main_loop_run (loop);
844
845   g_menu_model_get_n_items (G_MENU_MODEL (proxy));
846
847   g_timeout_add (100, stop_loop, loop);
848   g_main_loop_run (loop);
849
850   g_assert_cmpint (items_changed_count, ==, 1);
851   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
852
853   g_timeout_add (100, stop_loop, loop);
854   g_main_loop_run (loop);
855
856   g_menu_append (menu, "item4", NULL);
857   g_menu_append (menu, "item5", NULL);
858   g_menu_append (menu, "item6", NULL);
859   g_menu_remove (menu, 0);
860   g_menu_remove (menu, 0);
861
862   g_timeout_add (200, stop_loop, loop);
863   g_main_loop_run (loop);
864
865   g_assert_cmpint (items_changed_count, ==, 6);
866
867   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4);
868   g_object_unref (proxy);
869
870   g_timeout_add (100, stop_loop, loop);
871   g_main_loop_run (loop);
872
873   g_menu_remove (menu, 0);
874   g_menu_remove (menu, 0);
875
876   g_timeout_add (100, stop_loop, loop);
877   g_main_loop_run (loop);
878
879   g_assert_cmpint (items_changed_count, ==, 6);
880
881   g_dbus_connection_unexport_menu_model (bus, export_id);
882   g_object_unref (menu);
883
884   g_main_loop_unref (loop);
885   g_object_unref (bus);
886 }
887
888 static gpointer
889 do_modify (gpointer data)
890 {
891   RandomMenu *menu = data;
892   GRand *rand;
893   gint i;
894
895   rand = g_rand_new_with_seed (g_test_rand_int ());
896
897   for (i = 0; i < 10000; i++)
898     {
899       random_menu_change (menu, rand);
900     }
901
902   return NULL;
903 }
904
905 static gpointer
906 do_export (gpointer data)
907 {
908   GMenuModel *menu = data;
909   gint i;
910   GDBusConnection *bus;
911   gchar *path;
912   GError *error = NULL;
913   guint id;
914
915   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
916   path = g_strdup_printf ("/%p", data);
917
918   for (i = 0; i < 10000; i++)
919     {
920       id = g_dbus_connection_export_menu_model (bus, path, menu, &error);
921       g_assert_no_error (error);
922       g_dbus_connection_unexport_menu_model (bus, id);
923       while (g_main_context_iteration (NULL, FALSE));
924     }
925
926   g_free (path);
927
928   g_object_unref (bus);
929
930   return NULL;
931 }
932
933 static void
934 test_dbus_threaded (void)
935 {
936   RandomMenu *menu[10];
937   GThread *call[10];
938   GThread *export[10];
939   gint i;
940
941   for (i = 0; i < 10; i++)
942     {
943       menu[i] = random_menu_new (g_rand_new_with_seed (g_test_rand_int ()), 2);
944       call[i] = g_thread_new ("call", do_modify, menu[i]);
945       export[i] = g_thread_new ("export", do_export, menu[i]);
946     }
947
948   for (i = 0; i < 10; i++)
949     {
950       g_thread_join (call[i]);
951       g_thread_join (export[i]);
952     }
953
954   for (i = 0; i < 10; i++)
955     g_object_unref (menu[i]);
956 }
957
958 static void
959 test_attributes (void)
960 {
961   GMenu *menu;
962   GMenuItem *item;
963   GVariant *v;
964
965   menu = g_menu_new ();
966
967   item = g_menu_item_new ("test", NULL);
968   g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
969   g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
970
971   g_menu_item_set_attribute (item, "double", "d", 1.5);
972   v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
973   g_menu_item_set_attribute_value (item, "complex", v);
974   g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
975
976   g_menu_append_item (menu, item);
977
978   g_menu_item_set_attribute (item, "double", "d", G_PI);
979
980   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
981
982   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
983   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
984   g_variant_unref (v);
985
986   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
987   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
988   g_variant_unref (v);
989
990   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
991   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
992   g_variant_unref (v);
993
994   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
995   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
996   g_variant_unref (v);
997
998   g_menu_remove_all (menu);
999
1000   g_object_unref (menu);
1001   g_object_unref (item);
1002 }
1003
1004 static void
1005 test_links (void)
1006 {
1007   GMenu *menu;
1008   GMenuModel *m;
1009   GMenuModel *x;
1010   GMenuItem *item;
1011
1012   m = G_MENU_MODEL (g_menu_new ());
1013   g_menu_append (G_MENU (m), "test", NULL);
1014
1015   menu = g_menu_new ();
1016
1017   item = g_menu_item_new ("test2", NULL);
1018   g_menu_item_set_link (item, "submenu", m);
1019   g_menu_prepend_item (menu, item);
1020
1021   item = g_menu_item_new ("test1", NULL);
1022   g_menu_item_set_link (item, "section", m);
1023   g_menu_insert_item (menu, 0, item);
1024
1025   item = g_menu_item_new ("test3", NULL);
1026   g_menu_item_set_link (item, "wallet", m);
1027   g_menu_insert_item (menu, 1000, item);
1028
1029   item = g_menu_item_new ("test4", NULL);
1030   g_menu_item_set_link (item, "purse", m);
1031   g_menu_item_set_link (item, "purse", NULL);
1032   g_menu_append_item (menu, item);
1033
1034   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
1035
1036   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
1037   g_assert (x == m);
1038   g_object_unref (x);
1039
1040   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
1041   g_assert (x == m);
1042   g_object_unref (x);
1043
1044   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
1045   g_assert (x == m);
1046   g_object_unref (x);
1047
1048   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
1049   g_assert (x == NULL);
1050
1051   g_object_unref (m);
1052   g_object_unref (menu);
1053 }
1054
1055 static void
1056 test_mutable (void)
1057 {
1058   GMenu *menu;
1059
1060   menu = g_menu_new ();
1061   g_menu_append (menu, "test", "test");
1062
1063   g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
1064   g_menu_freeze (menu);
1065   g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
1066
1067   g_object_unref (menu);
1068 }
1069
1070 static void
1071 test_convenience (void)
1072 {
1073   GMenu *m1, *m2;
1074   GMenu *sub;
1075   GMenuItem *item;
1076
1077   m1 = g_menu_new ();
1078   m2 = g_menu_new ();
1079   sub = g_menu_new ();
1080
1081   g_menu_prepend (m1, "label1", "do::something");
1082   g_menu_insert (m2, 0, "label1", "do::something");
1083
1084   g_menu_append (m1, "label2", "do::somethingelse");
1085   g_menu_insert (m2, -1, "label2", "do::somethingelse");
1086
1087   g_menu_insert_section (m1, 10, "label3", G_MENU_MODEL (sub));
1088   item = g_menu_item_new_section ("label3", G_MENU_MODEL (sub));
1089   g_menu_insert_item (m2, 10, item);
1090   g_object_unref (item);
1091
1092   g_menu_prepend_section (m1, "label4", G_MENU_MODEL (sub));
1093   g_menu_insert_section (m2, 0, "label4", G_MENU_MODEL (sub));
1094
1095   g_menu_append_section (m1, "label5", G_MENU_MODEL (sub));
1096   g_menu_insert_section (m2, -1, "label5", G_MENU_MODEL (sub));
1097
1098   g_menu_insert_submenu (m1, 5, "label6", G_MENU_MODEL (sub));
1099   item = g_menu_item_new_submenu ("label6", G_MENU_MODEL (sub));
1100   g_menu_insert_item (m2, 5, item);
1101   g_object_unref (item);
1102
1103   g_menu_prepend_submenu (m1, "label7", G_MENU_MODEL (sub));
1104   g_menu_insert_submenu (m2, 0, "label7", G_MENU_MODEL (sub));
1105
1106   g_menu_append_submenu (m1, "label8", G_MENU_MODEL (sub));
1107   g_menu_insert_submenu (m2, -1, "label8", G_MENU_MODEL (sub));
1108
1109   assert_menus_equal (G_MENU_MODEL (m1), G_MENU_MODEL (m2));
1110
1111   g_object_unref (m1);
1112   g_object_unref (m2);
1113 }
1114
1115 static void
1116 test_menuitem (void)
1117 {
1118   GMenu *menu;
1119   GMenu *submenu;
1120   GMenuItem *item;
1121   GIcon *icon;
1122   gboolean b;
1123   gchar *s;
1124
1125   menu = g_menu_new ();
1126   submenu = g_menu_new ();
1127
1128   item = g_menu_item_new ("label", "action");
1129   g_menu_item_set_attribute (item, "attribute", "b", TRUE);
1130   g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, G_MENU_MODEL (submenu));
1131   g_menu_append_item (menu, item);
1132
1133   icon = g_themed_icon_new ("bla");
1134   g_menu_item_set_icon (item, icon);
1135   g_object_unref (icon);
1136
1137   g_assert (g_menu_item_get_attribute (item, "attribute", "b", &b));
1138   g_assert (b);
1139
1140   g_menu_item_set_action_and_target (item, "action", "(bs)", TRUE, "string");
1141   g_assert (g_menu_item_get_attribute (item, "target", "(bs)", &b, &s));
1142   g_assert (b);
1143   g_assert_cmpstr (s, ==, "string");
1144   g_free (s);
1145
1146   g_object_unref (item);
1147
1148   item = g_menu_item_new_from_model (G_MENU_MODEL (menu), 0);
1149   assert_menuitem_equal (item, G_MENU_MODEL (menu), 0);
1150   g_object_unref (item);
1151
1152   g_object_unref (menu);
1153   g_object_unref (submenu);
1154 }
1155
1156 /* Epilogue {{{1 */
1157 int
1158 main (int argc, char **argv)
1159 {
1160   gboolean ret;
1161
1162   g_test_init (&argc, &argv, NULL);
1163
1164   session_bus_up ();
1165
1166   g_test_add_func ("/gmenu/equality", test_equality);
1167   g_test_add_func ("/gmenu/random", test_random);
1168   g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip);
1169   g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions);
1170   g_test_add_func ("/gmenu/dbus/threaded", test_dbus_threaded);
1171   g_test_add_func ("/gmenu/attributes", test_attributes);
1172   g_test_add_func ("/gmenu/links", test_links);
1173   g_test_add_func ("/gmenu/mutable", test_mutable);
1174   g_test_add_func ("/gmenu/convenience", test_convenience);
1175   g_test_add_func ("/gmenu/menuitem", test_menuitem);
1176
1177   ret = g_test_run ();
1178
1179   session_bus_down ();
1180
1181   return ret;
1182 }
1183 /* vim:set foldmethod=marker: */