From 6dfb9d583f264da11430acbc7c141f3955fe78ed Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Fri, 21 Oct 2011 23:51:48 -0400 Subject: [PATCH] Add GMenuModel testcases --- gio/tests/.gitignore | 1 + gio/tests/Makefile.am | 3 + gio/tests/gmenumodel.c | 625 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 629 insertions(+) create mode 100644 gio/tests/gmenumodel.c diff --git a/gio/tests/.gitignore b/gio/tests/.gitignore index e3961aff0..f229d747a 100644 --- a/gio/tests/.gitignore +++ b/gio/tests/.gitignore @@ -57,6 +57,7 @@ gdbus-serialization gdbus-test-codegen gdbus-test-codegen-generated* gdbus-threading +gmenumodel g-file g-file-info g-icon diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index b449a380a..7c7bded70 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -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 index 000000000..b5825f2a1 --- /dev/null +++ b/gio/tests/gmenumodel.c @@ -0,0 +1,625 @@ +#include + +/* 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 \n"); + g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2); + g_string_append (string, " \n\n-------------\n \n"); + g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2); + g_string_append (string, " \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: */