Add GMenuModel testcases
authorRyan Lortie <desrt@desrt.ca>
Sat, 22 Oct 2011 03:51:48 +0000 (23:51 -0400)
committerRyan Lortie <desrt@desrt.ca>
Thu, 8 Dec 2011 23:05:12 +0000 (18:05 -0500)
gio/tests/.gitignore
gio/tests/Makefile.am
gio/tests/gmenumodel.c [new file with mode: 0644]

index e3961af..f229d74 100644 (file)
@@ -57,6 +57,7 @@ gdbus-serialization
 gdbus-test-codegen
 gdbus-test-codegen-generated*
 gdbus-threading
+gmenumodel
 g-file
 g-file-info
 g-icon
index b449a38..7c7bded 100644 (file)
@@ -58,6 +58,7 @@ TEST_PROGS +=                 \
        vfs                     \
        network-monitor         \
        fileattributematcher    \
+       gmenumodel              \
        $(NULL)
 
 if OS_UNIX
@@ -426,6 +427,8 @@ gapplication_example_cmdline3_LDADD   = $(progs_ldadd)
 gapplication_example_actions_SOURCES = gapplication-example-actions.c
 gapplication_example_actions_LDADD   = $(progs_ldadd)
 
+gmenumodel_LDADD = $(progs_ldadd)
+
 schema_tests = \
        schema-tests/array-default-not-in-choices.gschema.xml           \
        schema-tests/bad-choice.gschema.xml                             \
diff --git a/gio/tests/gmenumodel.c b/gio/tests/gmenumodel.c
new file mode 100644 (file)
index 0000000..b5825f2
--- /dev/null
@@ -0,0 +1,625 @@
+#include <gio/gio.h>
+
+/* TestItem {{{1 */
+
+/* This utility struct is used by both the RandomMenu and MirrorMenu
+ * class implementations below.
+ */
+typedef struct {
+  GHashTable *attributes;
+  GHashTable *links;
+} TestItem;
+
+static TestItem *
+test_item_new (GHashTable *attributes,
+               GHashTable *links)
+{
+  TestItem *item;
+
+  item = g_slice_new (TestItem);
+  item->attributes = g_hash_table_ref (attributes);
+  item->links = g_hash_table_ref (links);
+
+  return item;
+}
+
+static void
+test_item_free (gpointer data)
+{
+  TestItem *item = data;
+
+  g_hash_table_unref (item->attributes);
+  g_hash_table_unref (item->links);
+
+  g_slice_free (TestItem, item);
+}
+
+/* RandomMenu {{{1 */
+#define MAX_ITEMS 5
+#define TOP_ORDER 4
+
+typedef struct {
+  GMenuModel parent_instance;
+
+  GSequence *items;
+  gint order;
+} RandomMenu;
+
+typedef GMenuModelClass RandomMenuClass;
+
+static GType random_menu_get_type (void);
+G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL);
+
+static gboolean
+random_menu_is_mutable (GMenuModel *model)
+{
+  return TRUE;
+}
+
+static gint
+random_menu_get_n_items (GMenuModel *model)
+{
+  RandomMenu *menu = (RandomMenu *) model;
+
+  return g_sequence_get_length (menu->items);
+}
+
+static void
+random_menu_get_item_attributes (GMenuModel  *model,
+                                 gint         position,
+                                 GHashTable **table)
+{
+  RandomMenu *menu = (RandomMenu *) model;
+  TestItem *item;
+
+  item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
+  *table = g_hash_table_ref (item->attributes);
+}
+
+static void
+random_menu_get_item_links (GMenuModel  *model,
+                            gint         position,
+                            GHashTable **table)
+{
+  RandomMenu *menu = (RandomMenu *) model;
+  TestItem *item;
+
+  item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
+  *table = g_hash_table_ref (item->links);
+}
+
+static void
+random_menu_finalize (GObject *object)
+{
+  RandomMenu *menu = (RandomMenu *) object;
+
+  g_sequence_free (menu->items);
+
+  G_OBJECT_CLASS (random_menu_parent_class)
+    ->finalize (object);
+}
+
+static void
+random_menu_init (RandomMenu *menu)
+{
+}
+
+static void
+random_menu_class_init (GMenuModelClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  class->is_mutable = random_menu_is_mutable;
+  class->get_n_items = random_menu_get_n_items;
+  class->get_item_attributes = random_menu_get_item_attributes;
+  class->get_item_links = random_menu_get_item_links;
+
+  object_class->finalize = random_menu_finalize;
+}
+
+static RandomMenu * random_menu_new (GRand *rand, gint order);
+
+static void
+random_menu_change (RandomMenu *menu,
+                    GRand      *rand)
+{
+  gint position, removes, adds;
+  GSequenceIter *point;
+  gint n_items;
+  gint i;
+
+  n_items = g_sequence_get_length (menu->items);
+
+  do
+    {
+      position = g_rand_int_range (rand, 0, n_items + 1);
+      removes = g_rand_int_range (rand, 0, n_items - position + 1);
+      adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
+    }
+  while (removes == 0 && adds == 0);
+
+  point = g_sequence_get_iter_at_pos (menu->items, position + removes);
+
+  if (removes)
+    {
+      GSequenceIter *start;
+
+      start = g_sequence_get_iter_at_pos (menu->items, position);
+      g_sequence_remove_range (start, point);
+    }
+
+  for (i = 0; i < adds; i++)
+    {
+      const gchar *label;
+      GHashTable *links;
+      GHashTable *attributes;
+
+      attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
+      links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
+
+      if (menu->order > 0 && g_rand_boolean (rand))
+        {
+          RandomMenu *child;
+         const gchar *subtype;
+
+          child = random_menu_new (rand, menu->order - 1);
+
+          if (g_rand_boolean (rand))
+            {
+              subtype = G_MENU_LINK_SECTION;
+              /* label some section headers */
+              if (g_rand_boolean (rand))
+                label = "Section";
+              else
+                label = NULL;
+            }
+          else
+            {
+              /* label all submenus */
+              subtype = G_MENU_LINK_SUBMENU;
+              label = "Submenu";
+            }
+
+          g_hash_table_insert (links, g_strdup (subtype), child);
+        }
+      else
+        /* label all terminals */
+        label = "Menu Item";
+
+      if (label)
+        g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
+
+      g_sequence_insert_before (point, test_item_new (attributes, links));
+      g_hash_table_unref (links);
+      g_hash_table_unref (attributes);
+    }
+
+  g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
+}
+
+static RandomMenu *
+random_menu_new (GRand *rand,
+                 gint   order)
+{
+  RandomMenu *menu;
+
+  menu = g_object_new (random_menu_get_type (), NULL);
+  menu->items = g_sequence_new (test_item_free);
+  menu->order = order;
+
+  random_menu_change (menu, rand);
+
+  return menu;
+}
+
+/* MirrorMenu {{{1 */
+typedef struct {
+  GMenuModel parent_instance;
+
+  GMenuModel *clone_of;
+  GSequence *items;
+  gulong handler_id;
+} MirrorMenu;
+
+typedef GMenuModelClass MirrorMenuClass;
+
+static GType mirror_menu_get_type (void);
+G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL);
+
+static gboolean
+mirror_menu_is_mutable (GMenuModel *model)
+{
+  MirrorMenu *menu = (MirrorMenu *) model;
+
+  return menu->handler_id != 0;
+}
+
+static gint
+mirror_menu_get_n_items (GMenuModel *model)
+{
+  MirrorMenu *menu = (MirrorMenu *) model;
+
+  return g_sequence_get_length (menu->items);
+}
+
+static void
+mirror_menu_get_item_attributes (GMenuModel  *model,
+                                 gint         position,
+                                 GHashTable **table)
+{
+  MirrorMenu *menu = (MirrorMenu *) model;
+  TestItem *item;
+
+  item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
+  *table = g_hash_table_ref (item->attributes);
+}
+
+static void
+mirror_menu_get_item_links (GMenuModel  *model,
+                            gint         position,
+                            GHashTable **table)
+{
+  MirrorMenu *menu = (MirrorMenu *) model;
+  TestItem *item;
+
+  item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
+  *table = g_hash_table_ref (item->links);
+}
+
+static void
+mirror_menu_finalize (GObject *object)
+{
+  MirrorMenu *menu = (MirrorMenu *) object;
+
+  if (menu->handler_id)
+    g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
+
+  g_sequence_free (menu->items);
+  g_object_unref (menu->clone_of);
+
+  G_OBJECT_CLASS (mirror_menu_parent_class)
+    ->finalize (object);
+}
+
+static void
+mirror_menu_init (MirrorMenu *menu)
+{
+}
+
+static void
+mirror_menu_class_init (GMenuModelClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  class->is_mutable = mirror_menu_is_mutable;
+  class->get_n_items = mirror_menu_get_n_items;
+  class->get_item_attributes = mirror_menu_get_item_attributes;
+  class->get_item_links = mirror_menu_get_item_links;
+
+  object_class->finalize = mirror_menu_finalize;
+}
+
+static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
+
+static void
+mirror_menu_changed (GMenuModel *model,
+                     gint        position,
+                     gint        removed,
+                     gint        added,
+                     gpointer    user_data)
+{
+  MirrorMenu *menu = user_data;
+  GSequenceIter *point;
+  gint i;
+
+  g_assert (model == menu->clone_of);
+
+  point = g_sequence_get_iter_at_pos (menu->items, position + removed);
+
+  if (removed)
+    {
+      GSequenceIter *start;
+
+      start = g_sequence_get_iter_at_pos (menu->items, position);
+      g_sequence_remove_range (start, point);
+    }
+
+  for (i = position; i < position + added; i++)
+    {
+      GMenuAttributeIter *attr_iter;
+      GMenuLinkIter *link_iter;
+      GHashTable *links;
+      GHashTable *attributes;
+      const gchar *name;
+      GMenuModel *child;
+      GVariant *value;
+
+      attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
+      links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
+
+      attr_iter = g_menu_model_iterate_item_attributes (model, i);
+      while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
+        {
+          g_hash_table_insert (attributes, g_strdup (name), value);
+        }
+      g_object_unref (attr_iter);
+
+      link_iter = g_menu_model_iterate_item_links (model, i);
+      while (g_menu_link_iter_get_next (link_iter, &name, &child))
+        {
+          g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
+          g_object_unref (child);
+        }
+      g_object_unref (link_iter);
+
+      g_sequence_insert_before (point, test_item_new (attributes, links));
+      g_hash_table_unref (attributes);
+      g_hash_table_unref (links);
+    }
+
+  g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
+}
+
+static MirrorMenu *
+mirror_menu_new (GMenuModel *clone_of)
+{
+  MirrorMenu *menu;
+
+  menu = g_object_new (mirror_menu_get_type (), NULL);
+  menu->items = g_sequence_new (test_item_free);
+  menu->clone_of = g_object_ref (clone_of);
+
+  if (g_menu_model_is_mutable (clone_of))
+    menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
+  mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
+
+  return menu;
+}
+
+/* check_menus_equal(), assert_menus_equal() {{{1 */
+static gboolean
+check_menus_equal (GMenuModel *a,
+                   GMenuModel *b)
+{
+  gboolean equal = TRUE;
+  gint a_n, b_n;
+  gint i;
+
+  a_n = g_menu_model_get_n_items (a);
+  b_n = g_menu_model_get_n_items (b);
+
+  if (a_n != b_n)
+    return FALSE;
+
+  for (i = 0; i < a_n; i++)
+    {
+      GMenuAttributeIter *attr_iter;
+      GVariant *a_value, *b_value;
+      GMenuLinkIter *link_iter;
+      GMenuModel *a_menu, *b_menu;
+      const gchar *name;
+
+      attr_iter = g_menu_model_iterate_item_attributes (a, i);
+      while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
+        {
+          b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
+          equal &= b_value && g_variant_equal (a_value, b_value);
+          if (b_value)
+            g_variant_unref (b_value);
+          g_variant_unref (a_value);
+        }
+      g_object_unref (attr_iter);
+
+      attr_iter = g_menu_model_iterate_item_attributes (b, i);
+      while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
+        {
+          a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
+          equal &= a_value && g_variant_equal (a_value, b_value);
+          if (a_value)
+            g_variant_unref (a_value);
+          g_variant_unref (b_value);
+        }
+      g_object_unref (attr_iter);
+
+      link_iter = g_menu_model_iterate_item_links (a, i);
+      while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
+        {
+          b_menu = g_menu_model_get_item_link (b, i, name);
+          equal &= b_menu && check_menus_equal (a_menu, b_menu);
+          if (b_menu)
+            g_object_unref (b_menu);
+          g_object_unref (a_menu);
+        }
+      g_object_unref (link_iter);
+
+      link_iter = g_menu_model_iterate_item_links (b, i);
+      while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
+        {
+          a_menu = g_menu_model_get_item_link (a, i, name);
+          equal &= a_menu && check_menus_equal (a_menu, b_menu);
+          if (a_menu)
+            g_object_unref (a_menu);
+          g_object_unref (b_menu);
+        }
+      g_object_unref (link_iter);
+    }
+
+  return equal;
+}
+
+static void
+assert_menus_equal (GMenuModel *a,
+                    GMenuModel *b)
+{
+  if (!check_menus_equal (a, b))
+    {
+      GString *string;
+
+      string = g_string_new ("\n  <a>\n");
+      g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
+      g_string_append (string, "  </a>\n\n-------------\n  <b>\n");
+      g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
+      g_string_append (string, "  </b>\n");
+      g_error ("%s", string->str);
+    }
+}
+
+/* Test cases {{{1 */
+static void
+test_equality (void)
+{
+  GRand *randa, *randb;
+  guint32 seed;
+  gint i;
+
+  seed = g_test_rand_int ();
+
+  randa = g_rand_new_with_seed (seed);
+  randb = g_rand_new_with_seed (seed);
+
+  for (i = 0; i < 500; i++)
+    {
+      RandomMenu *a, *b;
+
+      a = random_menu_new (randa, TOP_ORDER);
+      b = random_menu_new (randb, TOP_ORDER);
+      assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
+      g_object_unref (b);
+      g_object_unref (a);
+    }
+
+  g_rand_int (randa);
+
+  for (i = 0; i < 500;)
+    {
+      RandomMenu *a, *b;
+
+      a = random_menu_new (randa, TOP_ORDER);
+      b = random_menu_new (randb, TOP_ORDER);
+      if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
+        {
+          /* by chance, they may really be equal.  double check. */
+          GString *as, *bs;
+
+          as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
+          bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
+          g_assert_cmpstr (as->str, ==, bs->str);
+          g_string_free (bs, TRUE);
+          g_string_free (as, TRUE);
+        }
+      else
+        /* make sure we get enough unequals (ie: no GRand failure) */
+        i++;
+
+      g_object_unref (b);
+      g_object_unref (a);
+    }
+
+  g_rand_free (randb);
+  g_rand_free (randa);
+}
+
+static void
+test_random (void)
+{
+  RandomMenu *random;
+  MirrorMenu *mirror;
+  GRand *rand;
+  gint i;
+
+  rand = g_rand_new_with_seed (g_test_rand_int ());
+  random = random_menu_new (rand, TOP_ORDER);
+  mirror = mirror_menu_new (G_MENU_MODEL (random));
+
+  for (i = 0; i < 500; i++)
+    {
+      assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
+      random_menu_change (random, rand);
+    }
+
+  g_object_unref (mirror);
+  g_object_unref (random);
+
+  g_rand_free (rand);
+}
+
+struct roundtrip_state
+{
+  RandomMenu *random;
+  GMenuProxy *proxy;
+  GMainLoop *loop;
+  GRand *rand;
+  gint success;
+  gint count;
+};
+
+static gboolean
+roundtrip_step (gpointer data)
+{
+  struct roundtrip_state *state = data;
+
+  if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)))
+    {
+      state->success++;
+      state->count = 0;
+
+      if (state->success < 100)
+        random_menu_change (state->random, state->rand);
+      else
+        g_main_loop_quit (state->loop);
+    }
+  else if (state->count == 100)
+    {
+      assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
+      g_assert_not_reached ();
+    }
+  else
+    state->count++;
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+test_roundtrip (void)
+{
+  struct roundtrip_state state;
+  GDBusConnection *bus;
+
+  bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+
+  state.rand = g_rand_new_with_seed (g_test_rand_int ());
+
+  state.random = random_menu_new (state.rand, TOP_ORDER);
+  g_menu_exporter_export (bus, "/", G_MENU_MODEL (state.random), NULL);
+  state.proxy = g_menu_proxy_get (bus, g_dbus_connection_get_unique_name (bus), "/");
+  state.count = 0;
+  state.success = 0;
+
+  g_timeout_add (10, roundtrip_step, &state);
+
+  state.loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (state.loop);
+
+  g_main_loop_unref (state.loop);
+  g_object_unref (state.proxy);
+  g_menu_exporter_stop (G_MENU_MODEL (state.random));
+  g_object_unref (state.random);
+  g_rand_free (state.rand);
+  g_object_unref (bus);
+}
+
+/* Epilogue {{{1 */
+int
+main (int argc, char **argv)
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_type_init ();
+
+  g_test_add_func ("/gmenu/equality", test_equality);
+  g_test_add_func ("/gmenu/random", test_random);
+  g_test_add_func ("/gmenu/roundtrip", test_roundtrip);
+
+  return g_test_run ();
+}
+/* vim:set foldmethod=marker: */