Rename exporter APIs
[platform/upstream/glib.git] / gio / tests / gmenumodel.c
1 #include <gio/gio.h>
2
3 /* TestItem {{{1 */
4
5 /* This utility struct is used by both the RandomMenu and MirrorMenu
6  * class implementations below.
7  */
8 typedef struct {
9   GHashTable *attributes;
10   GHashTable *links;
11 } TestItem;
12
13 static TestItem *
14 test_item_new (GHashTable *attributes,
15                GHashTable *links)
16 {
17   TestItem *item;
18
19   item = g_slice_new (TestItem);
20   item->attributes = g_hash_table_ref (attributes);
21   item->links = g_hash_table_ref (links);
22
23   return item;
24 }
25
26 static void
27 test_item_free (gpointer data)
28 {
29   TestItem *item = data;
30
31   g_hash_table_unref (item->attributes);
32   g_hash_table_unref (item->links);
33
34   g_slice_free (TestItem, item);
35 }
36
37 /* RandomMenu {{{1 */
38 #define MAX_ITEMS 5
39 #define TOP_ORDER 4
40
41 typedef struct {
42   GMenuModel parent_instance;
43
44   GSequence *items;
45   gint order;
46 } RandomMenu;
47
48 typedef GMenuModelClass RandomMenuClass;
49
50 static GType random_menu_get_type (void);
51 G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL);
52
53 static gboolean
54 random_menu_is_mutable (GMenuModel *model)
55 {
56   return TRUE;
57 }
58
59 static gint
60 random_menu_get_n_items (GMenuModel *model)
61 {
62   RandomMenu *menu = (RandomMenu *) model;
63
64   return g_sequence_get_length (menu->items);
65 }
66
67 static void
68 random_menu_get_item_attributes (GMenuModel  *model,
69                                  gint         position,
70                                  GHashTable **table)
71 {
72   RandomMenu *menu = (RandomMenu *) model;
73   TestItem *item;
74
75   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
76   *table = g_hash_table_ref (item->attributes);
77 }
78
79 static void
80 random_menu_get_item_links (GMenuModel  *model,
81                             gint         position,
82                             GHashTable **table)
83 {
84   RandomMenu *menu = (RandomMenu *) model;
85   TestItem *item;
86
87   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
88   *table = g_hash_table_ref (item->links);
89 }
90
91 static void
92 random_menu_finalize (GObject *object)
93 {
94   RandomMenu *menu = (RandomMenu *) object;
95
96   g_sequence_free (menu->items);
97
98   G_OBJECT_CLASS (random_menu_parent_class)
99     ->finalize (object);
100 }
101
102 static void
103 random_menu_init (RandomMenu *menu)
104 {
105 }
106
107 static void
108 random_menu_class_init (GMenuModelClass *class)
109 {
110   GObjectClass *object_class = G_OBJECT_CLASS (class);
111
112   class->is_mutable = random_menu_is_mutable;
113   class->get_n_items = random_menu_get_n_items;
114   class->get_item_attributes = random_menu_get_item_attributes;
115   class->get_item_links = random_menu_get_item_links;
116
117   object_class->finalize = random_menu_finalize;
118 }
119
120 static RandomMenu * random_menu_new (GRand *rand, gint order);
121
122 static void
123 random_menu_change (RandomMenu *menu,
124                     GRand      *rand)
125 {
126   gint position, removes, adds;
127   GSequenceIter *point;
128   gint n_items;
129   gint i;
130
131   n_items = g_sequence_get_length (menu->items);
132
133   do
134     {
135       position = g_rand_int_range (rand, 0, n_items + 1);
136       removes = g_rand_int_range (rand, 0, n_items - position + 1);
137       adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
138     }
139   while (removes == 0 && adds == 0);
140
141   point = g_sequence_get_iter_at_pos (menu->items, position + removes);
142
143   if (removes)
144     {
145       GSequenceIter *start;
146
147       start = g_sequence_get_iter_at_pos (menu->items, position);
148       g_sequence_remove_range (start, point);
149     }
150
151   for (i = 0; i < adds; i++)
152     {
153       const gchar *label;
154       GHashTable *links;
155       GHashTable *attributes;
156
157       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
158       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
159
160       if (menu->order > 0 && g_rand_boolean (rand))
161         {
162           RandomMenu *child;
163           const gchar *subtype;
164
165           child = random_menu_new (rand, menu->order - 1);
166
167           if (g_rand_boolean (rand))
168             {
169               subtype = G_MENU_LINK_SECTION;
170               /* label some section headers */
171               if (g_rand_boolean (rand))
172                 label = "Section";
173               else
174                 label = NULL;
175             }
176           else
177             {
178               /* label all submenus */
179               subtype = G_MENU_LINK_SUBMENU;
180               label = "Submenu";
181             }
182
183           g_hash_table_insert (links, g_strdup (subtype), child);
184         }
185       else
186         /* label all terminals */
187         label = "Menu Item";
188
189       if (label)
190         g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
191
192       g_sequence_insert_before (point, test_item_new (attributes, links));
193       g_hash_table_unref (links);
194       g_hash_table_unref (attributes);
195     }
196
197   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
198 }
199
200 static RandomMenu *
201 random_menu_new (GRand *rand,
202                  gint   order)
203 {
204   RandomMenu *menu;
205
206   menu = g_object_new (random_menu_get_type (), NULL);
207   menu->items = g_sequence_new (test_item_free);
208   menu->order = order;
209
210   random_menu_change (menu, rand);
211
212   return menu;
213 }
214
215 /* MirrorMenu {{{1 */
216 typedef struct {
217   GMenuModel parent_instance;
218
219   GMenuModel *clone_of;
220   GSequence *items;
221   gulong handler_id;
222 } MirrorMenu;
223
224 typedef GMenuModelClass MirrorMenuClass;
225
226 static GType mirror_menu_get_type (void);
227 G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL);
228
229 static gboolean
230 mirror_menu_is_mutable (GMenuModel *model)
231 {
232   MirrorMenu *menu = (MirrorMenu *) model;
233
234   return menu->handler_id != 0;
235 }
236
237 static gint
238 mirror_menu_get_n_items (GMenuModel *model)
239 {
240   MirrorMenu *menu = (MirrorMenu *) model;
241
242   return g_sequence_get_length (menu->items);
243 }
244
245 static void
246 mirror_menu_get_item_attributes (GMenuModel  *model,
247                                  gint         position,
248                                  GHashTable **table)
249 {
250   MirrorMenu *menu = (MirrorMenu *) model;
251   TestItem *item;
252
253   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
254   *table = g_hash_table_ref (item->attributes);
255 }
256
257 static void
258 mirror_menu_get_item_links (GMenuModel  *model,
259                             gint         position,
260                             GHashTable **table)
261 {
262   MirrorMenu *menu = (MirrorMenu *) model;
263   TestItem *item;
264
265   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
266   *table = g_hash_table_ref (item->links);
267 }
268
269 static void
270 mirror_menu_finalize (GObject *object)
271 {
272   MirrorMenu *menu = (MirrorMenu *) object;
273
274   if (menu->handler_id)
275     g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
276
277   g_sequence_free (menu->items);
278   g_object_unref (menu->clone_of);
279
280   G_OBJECT_CLASS (mirror_menu_parent_class)
281     ->finalize (object);
282 }
283
284 static void
285 mirror_menu_init (MirrorMenu *menu)
286 {
287 }
288
289 static void
290 mirror_menu_class_init (GMenuModelClass *class)
291 {
292   GObjectClass *object_class = G_OBJECT_CLASS (class);
293
294   class->is_mutable = mirror_menu_is_mutable;
295   class->get_n_items = mirror_menu_get_n_items;
296   class->get_item_attributes = mirror_menu_get_item_attributes;
297   class->get_item_links = mirror_menu_get_item_links;
298
299   object_class->finalize = mirror_menu_finalize;
300 }
301
302 static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
303
304 static void
305 mirror_menu_changed (GMenuModel *model,
306                      gint        position,
307                      gint        removed,
308                      gint        added,
309                      gpointer    user_data)
310 {
311   MirrorMenu *menu = user_data;
312   GSequenceIter *point;
313   gint i;
314
315   g_assert (model == menu->clone_of);
316
317   point = g_sequence_get_iter_at_pos (menu->items, position + removed);
318
319   if (removed)
320     {
321       GSequenceIter *start;
322
323       start = g_sequence_get_iter_at_pos (menu->items, position);
324       g_sequence_remove_range (start, point);
325     }
326
327   for (i = position; i < position + added; i++)
328     {
329       GMenuAttributeIter *attr_iter;
330       GMenuLinkIter *link_iter;
331       GHashTable *links;
332       GHashTable *attributes;
333       const gchar *name;
334       GMenuModel *child;
335       GVariant *value;
336
337       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
338       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
339
340       attr_iter = g_menu_model_iterate_item_attributes (model, i);
341       while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
342         {
343           g_hash_table_insert (attributes, g_strdup (name), value);
344         }
345       g_object_unref (attr_iter);
346
347       link_iter = g_menu_model_iterate_item_links (model, i);
348       while (g_menu_link_iter_get_next (link_iter, &name, &child))
349         {
350           g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
351           g_object_unref (child);
352         }
353       g_object_unref (link_iter);
354
355       g_sequence_insert_before (point, test_item_new (attributes, links));
356       g_hash_table_unref (attributes);
357       g_hash_table_unref (links);
358     }
359
360   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
361 }
362
363 static MirrorMenu *
364 mirror_menu_new (GMenuModel *clone_of)
365 {
366   MirrorMenu *menu;
367
368   menu = g_object_new (mirror_menu_get_type (), NULL);
369   menu->items = g_sequence_new (test_item_free);
370   menu->clone_of = g_object_ref (clone_of);
371
372   if (g_menu_model_is_mutable (clone_of))
373     menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
374   mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
375
376   return menu;
377 }
378
379 /* check_menus_equal(), assert_menus_equal() {{{1 */
380 static gboolean
381 check_menus_equal (GMenuModel *a,
382                    GMenuModel *b)
383 {
384   gboolean equal = TRUE;
385   gint a_n, b_n;
386   gint i;
387
388   a_n = g_menu_model_get_n_items (a);
389   b_n = g_menu_model_get_n_items (b);
390
391   if (a_n != b_n)
392     return FALSE;
393
394   for (i = 0; i < a_n; i++)
395     {
396       GMenuAttributeIter *attr_iter;
397       GVariant *a_value, *b_value;
398       GMenuLinkIter *link_iter;
399       GMenuModel *a_menu, *b_menu;
400       const gchar *name;
401
402       attr_iter = g_menu_model_iterate_item_attributes (a, i);
403       while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
404         {
405           b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
406           equal &= b_value && g_variant_equal (a_value, b_value);
407           if (b_value)
408             g_variant_unref (b_value);
409           g_variant_unref (a_value);
410         }
411       g_object_unref (attr_iter);
412
413       attr_iter = g_menu_model_iterate_item_attributes (b, i);
414       while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
415         {
416           a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
417           equal &= a_value && g_variant_equal (a_value, b_value);
418           if (a_value)
419             g_variant_unref (a_value);
420           g_variant_unref (b_value);
421         }
422       g_object_unref (attr_iter);
423
424       link_iter = g_menu_model_iterate_item_links (a, i);
425       while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
426         {
427           b_menu = g_menu_model_get_item_link (b, i, name);
428           equal &= b_menu && check_menus_equal (a_menu, b_menu);
429           if (b_menu)
430             g_object_unref (b_menu);
431           g_object_unref (a_menu);
432         }
433       g_object_unref (link_iter);
434
435       link_iter = g_menu_model_iterate_item_links (b, i);
436       while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
437         {
438           a_menu = g_menu_model_get_item_link (a, i, name);
439           equal &= a_menu && check_menus_equal (a_menu, b_menu);
440           if (a_menu)
441             g_object_unref (a_menu);
442           g_object_unref (b_menu);
443         }
444       g_object_unref (link_iter);
445     }
446
447   return equal;
448 }
449
450 static void
451 assert_menus_equal (GMenuModel *a,
452                     GMenuModel *b)
453 {
454   if (!check_menus_equal (a, b))
455     {
456       GString *string;
457
458       string = g_string_new ("\n  <a>\n");
459       g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
460       g_string_append (string, "  </a>\n\n-------------\n  <b>\n");
461       g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
462       g_string_append (string, "  </b>\n");
463       g_error ("%s", string->str);
464     }
465 }
466
467 /* Test cases {{{1 */
468 static void
469 test_equality (void)
470 {
471   GRand *randa, *randb;
472   guint32 seed;
473   gint i;
474
475   seed = g_test_rand_int ();
476
477   randa = g_rand_new_with_seed (seed);
478   randb = g_rand_new_with_seed (seed);
479
480   for (i = 0; i < 500; i++)
481     {
482       RandomMenu *a, *b;
483
484       a = random_menu_new (randa, TOP_ORDER);
485       b = random_menu_new (randb, TOP_ORDER);
486       assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
487       g_object_unref (b);
488       g_object_unref (a);
489     }
490
491   g_rand_int (randa);
492
493   for (i = 0; i < 500;)
494     {
495       RandomMenu *a, *b;
496
497       a = random_menu_new (randa, TOP_ORDER);
498       b = random_menu_new (randb, TOP_ORDER);
499       if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
500         {
501           /* by chance, they may really be equal.  double check. */
502           GString *as, *bs;
503
504           as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
505           bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
506           g_assert_cmpstr (as->str, ==, bs->str);
507           g_string_free (bs, TRUE);
508           g_string_free (as, TRUE);
509         }
510       else
511         /* make sure we get enough unequals (ie: no GRand failure) */
512         i++;
513
514       g_object_unref (b);
515       g_object_unref (a);
516     }
517
518   g_rand_free (randb);
519   g_rand_free (randa);
520 }
521
522 static void
523 test_random (void)
524 {
525   RandomMenu *random;
526   MirrorMenu *mirror;
527   GRand *rand;
528   gint i;
529
530   rand = g_rand_new_with_seed (g_test_rand_int ());
531   random = random_menu_new (rand, TOP_ORDER);
532   mirror = mirror_menu_new (G_MENU_MODEL (random));
533
534   for (i = 0; i < 500; i++)
535     {
536       assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
537       random_menu_change (random, rand);
538     }
539
540   g_object_unref (mirror);
541   g_object_unref (random);
542
543   g_rand_free (rand);
544 }
545
546 struct roundtrip_state
547 {
548   RandomMenu *random;
549   GMenuProxy *proxy;
550   GMainLoop *loop;
551   GRand *rand;
552   gint success;
553   gint count;
554 };
555
556 static gboolean
557 roundtrip_step (gpointer data)
558 {
559   struct roundtrip_state *state = data;
560
561   if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)))
562     {
563       state->success++;
564       state->count = 0;
565
566       if (state->success < 100)
567         random_menu_change (state->random, state->rand);
568       else
569         g_main_loop_quit (state->loop);
570     }
571   else if (state->count == 100)
572     {
573       assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
574       g_assert_not_reached ();
575     }
576   else
577     state->count++;
578
579   return G_SOURCE_CONTINUE;
580 }
581
582 static void
583 test_roundtrip (void)
584 {
585   struct roundtrip_state state;
586   GDBusConnection *bus;
587
588   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
589
590   state.rand = g_rand_new_with_seed (g_test_rand_int ());
591
592   state.random = random_menu_new (state.rand, TOP_ORDER);
593   g_menu_model_dbus_export_start (bus, "/", G_MENU_MODEL (state.random), NULL);
594   state.proxy = g_menu_proxy_get (bus, g_dbus_connection_get_unique_name (bus), "/");
595   state.count = 0;
596   state.success = 0;
597
598   g_timeout_add (10, roundtrip_step, &state);
599
600   state.loop = g_main_loop_new (NULL, FALSE);
601   g_main_loop_run (state.loop);
602
603   g_main_loop_unref (state.loop);
604   g_object_unref (state.proxy);
605   g_menu_model_dbus_export_stop (G_MENU_MODEL (state.random));
606   g_object_unref (state.random);
607   g_rand_free (state.rand);
608   g_object_unref (bus);
609 }
610
611 /* Epilogue {{{1 */
612 int
613 main (int argc, char **argv)
614 {
615   g_test_init (&argc, &argv, NULL);
616
617   g_type_init ();
618
619   g_test_add_func ("/gmenu/equality", test_equality);
620   g_test_add_func ("/gmenu/random", test_random);
621   g_test_add_func ("/gmenu/roundtrip", test_roundtrip);
622
623   return g_test_run ();
624 }
625 /* vim:set foldmethod=marker: */