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