#include #include #include #include #include "gdbus-sessionbus.h" #include "glib/glib-private.h" static void time_out (gpointer unused G_GNUC_UNUSED) { g_error ("Timed out"); } static guint add_timeout (guint seconds) { #ifdef G_OS_UNIX /* Safety-catch against the main loop having blocked */ alarm (seconds + 5); #endif return g_timeout_add_seconds_once (seconds, time_out, NULL); } static void cancel_timeout (guint timeout_id) { #ifdef G_OS_UNIX alarm (0); #endif g_source_remove (timeout_id); } /* Markup printing {{{1 */ /* This used to be part of GLib, but it was removed before the stable * release because it wasn't generally useful. We want it here, though. */ static void indent_string (GString *string, gint indent) { while (indent--) g_string_append_c (string, ' '); } static GString * g_menu_markup_print_string (GString *string, GMenuModel *model, gint indent, gint tabstop) { gboolean need_nl = FALSE; gint i, n; if G_UNLIKELY (string == NULL) string = g_string_new (NULL); n = g_menu_model_get_n_items (model); for (i = 0; i < n; i++) { GMenuAttributeIter *attr_iter; GMenuLinkIter *link_iter; GString *contents; GString *attrs; attr_iter = g_menu_model_iterate_item_attributes (model, i); link_iter = g_menu_model_iterate_item_links (model, i); contents = g_string_new (NULL); attrs = g_string_new (NULL); while (g_menu_attribute_iter_next (attr_iter)) { const char *name = g_menu_attribute_iter_get_name (attr_iter); GVariant *value = g_menu_attribute_iter_get_value (attr_iter); if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { gchar *str; str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL)); g_string_append (attrs, str); g_free (str); } else { gchar *printed; gchar *str; const gchar *type; printed = g_variant_print (value, TRUE); type = g_variant_type_peek_string (g_variant_get_type (value)); str = g_markup_printf_escaped ("%s\n", name, type, printed); indent_string (contents, indent + tabstop); g_string_append (contents, str); g_free (printed); g_free (str); } g_variant_unref (value); } g_object_unref (attr_iter); while (g_menu_link_iter_next (link_iter)) { const gchar *name = g_menu_link_iter_get_name (link_iter); GMenuModel *menu = g_menu_link_iter_get_value (link_iter); gchar *str; if (contents->str[0]) g_string_append_c (contents, '\n'); str = g_markup_printf_escaped ("\n", name); indent_string (contents, indent + tabstop); g_string_append (contents, str); g_free (str); g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop); indent_string (contents, indent + tabstop); g_string_append (contents, "\n"); g_object_unref (menu); } g_object_unref (link_iter); if (contents->str[0]) { indent_string (string, indent); g_string_append_printf (string, "\n", attrs->str); g_string_append (string, contents->str); indent_string (string, indent); g_string_append (string, "\n"); need_nl = TRUE; } else { if (need_nl) g_string_append_c (string, '\n'); indent_string (string, indent); g_string_append_printf (string, "\n", attrs->str); need_nl = FALSE; } g_string_free (contents, TRUE); g_string_free (attrs, TRUE); } return string; } /* 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); } } static void assert_menuitem_equal (GMenuItem *item, GMenuModel *model, gint index) { GMenuAttributeIter *attr_iter; GMenuLinkIter *link_iter; const gchar *name; GVariant *value; GMenuModel *linked_model; /* NOTE we can't yet test whether item has attributes or links that * are not in the model, because there's no iterator API for menu * items */ attr_iter = g_menu_model_iterate_item_attributes (model, index); while (g_menu_attribute_iter_get_next (attr_iter, &name, &value)) { GVariant *item_value; item_value = g_menu_item_get_attribute_value (item, name, g_variant_get_type (value)); g_assert (item_value && g_variant_equal (item_value, value)); g_variant_unref (item_value); g_variant_unref (value); } link_iter = g_menu_model_iterate_item_links (model, index); while (g_menu_link_iter_get_next (link_iter, &name, &linked_model)) { GMenuModel *item_linked_model; item_linked_model = g_menu_item_get_link (item, name); g_assert (linked_model == item_linked_model); g_object_unref (item_linked_model); g_object_unref (linked_model); } g_object_unref (attr_iter); g_object_unref (link_iter); } /* 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); /* we're here because randa and randb just generated equal * menus. they may do it again, so throw away randb and make * a fresh one. */ g_rand_free (randb); randb = g_rand_new_with_seed (g_rand_int (randa)); } 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); } typedef struct { GDBusConnection *client_connection; GDBusConnection *server_connection; GDBusServer *server; GThread *service_thread; /* Protects server_connection and service_loop. */ GMutex service_loop_lock; GCond service_loop_cond; GMainLoop *service_loop; } PeerConnection; static gboolean on_new_connection (GDBusServer *server, GDBusConnection *connection, gpointer user_data) { PeerConnection *data = user_data; g_mutex_lock (&data->service_loop_lock); data->server_connection = g_object_ref (connection); g_cond_broadcast (&data->service_loop_cond); g_mutex_unlock (&data->service_loop_lock); return TRUE; } static void create_service_loop (GMainContext *service_context, PeerConnection *data) { g_assert (data->service_loop == NULL); g_mutex_lock (&data->service_loop_lock); data->service_loop = g_main_loop_new (service_context, FALSE); g_cond_broadcast (&data->service_loop_cond); g_mutex_unlock (&data->service_loop_lock); } static void teardown_service_loop (PeerConnection *data) { g_mutex_lock (&data->service_loop_lock); g_clear_pointer (&data->service_loop, g_main_loop_unref); g_mutex_unlock (&data->service_loop_lock); } static void await_service_loop (PeerConnection *data) { g_mutex_lock (&data->service_loop_lock); while (data->service_loop == NULL) g_cond_wait (&data->service_loop_cond, &data->service_loop_lock); g_mutex_unlock (&data->service_loop_lock); } static void await_server_connection (PeerConnection *data) { g_mutex_lock (&data->service_loop_lock); while (data->server_connection == NULL) g_cond_wait (&data->service_loop_cond, &data->service_loop_lock); g_mutex_unlock (&data->service_loop_lock); } static gpointer service_thread_func (gpointer user_data) { PeerConnection *data = user_data; GMainContext *service_context; GError *error; gchar *address; gchar *tmpdir; GDBusServerFlags flags; gchar *guid; service_context = g_main_context_new (); g_main_context_push_thread_default (service_context); tmpdir = NULL; flags = G_DBUS_SERVER_FLAGS_NONE; #ifdef G_OS_UNIX tmpdir = g_dir_make_tmp ("test-dbus-peer-XXXXXX", NULL); address = g_strdup_printf ("unix:tmpdir=%s", tmpdir); #else address = g_strdup ("nonce-tcp:"); flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS; #endif guid = g_dbus_generate_guid (); error = NULL; data->server = g_dbus_server_new_sync (address, flags, guid, NULL, NULL, &error); g_assert_no_error (error); g_free (address); g_free (guid); g_signal_connect (data->server, "new-connection", G_CALLBACK (on_new_connection), data); g_dbus_server_start (data->server); create_service_loop (service_context, data); g_main_loop_run (data->service_loop); g_main_context_pop_thread_default (service_context); teardown_service_loop (data); g_main_context_unref (service_context); if (tmpdir) { g_rmdir (tmpdir); g_free (tmpdir); } return NULL; } static void peer_connection_up (PeerConnection *data) { GError *error; memset (data, '\0', sizeof (PeerConnection)); g_mutex_init (&data->service_loop_lock); g_cond_init (&data->service_loop_cond); /* bring up a server - we run the server in a different thread to avoid deadlocks */ data->service_thread = g_thread_new ("test_dbus_peer", service_thread_func, data); await_service_loop (data); g_assert (data->server != NULL); /* bring up a connection and accept it */ error = NULL; data->client_connection = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (data->server), G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, NULL, /* GDBusAuthObserver */ NULL, /* cancellable */ &error); g_assert_no_error (error); g_assert (data->client_connection != NULL); await_server_connection (data); } static void peer_connection_down (PeerConnection *data) { g_object_unref (data->client_connection); g_object_unref (data->server_connection); g_dbus_server_stop (data->server); g_object_unref (data->server); g_main_loop_quit (data->service_loop); g_thread_join (data->service_thread); g_mutex_clear (&data->service_loop_lock); g_cond_clear (&data->service_loop_cond); } struct roundtrip_state { RandomMenu *random; MirrorMenu *proxy_mirror; GDBusMenuModel *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)) && check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy_mirror))) { 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 do_roundtrip (GDBusConnection *exporter_connection, GDBusConnection *proxy_connection) { struct roundtrip_state state; guint export_id; guint id; state.rand = g_rand_new_with_seed (g_test_rand_int ()); state.random = random_menu_new (state.rand, 2); export_id = g_dbus_connection_export_menu_model (exporter_connection, "/", G_MENU_MODEL (state.random), NULL); state.proxy = g_dbus_menu_model_get (proxy_connection, g_dbus_connection_get_unique_name (proxy_connection), "/"); state.proxy_mirror = mirror_menu_new (G_MENU_MODEL (state.proxy)); state.count = 0; state.success = 0; id = 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_source_remove (id); g_object_unref (state.proxy); g_dbus_connection_unexport_menu_model (exporter_connection, export_id); g_object_unref (state.random); g_object_unref (state.proxy_mirror); g_rand_free (state.rand); } static void test_dbus_roundtrip (void) { GDBusConnection *bus; bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); do_roundtrip (bus, bus); g_object_unref (bus); } static void test_dbus_peer_roundtrip (void) { #ifdef _GLIB_ADDRESS_SANITIZER g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313"); (void) peer_connection_up; (void) peer_connection_down; #else PeerConnection peer; peer_connection_up (&peer); do_roundtrip (peer.server_connection, peer.client_connection); peer_connection_down (&peer); #endif } static gint items_changed_count; static void items_changed (GMenuModel *model, gint position, gint removed, gint added, gpointer data) { items_changed_count++; } static gboolean stop_loop (gpointer data) { GMainLoop *loop = data; g_main_loop_quit (loop); return G_SOURCE_REMOVE; } static void do_subscriptions (GDBusConnection *exporter_connection, GDBusConnection *proxy_connection) { GMenu *menu; GDBusMenuModel *proxy; GMainLoop *loop; GError *error = NULL; guint export_id; guint timeout_id; timeout_id = add_timeout (60); loop = g_main_loop_new (NULL, FALSE); menu = g_menu_new (); export_id = g_dbus_connection_export_menu_model (exporter_connection, "/", G_MENU_MODEL (menu), &error); g_assert_no_error (error); proxy = g_dbus_menu_model_get (proxy_connection, g_dbus_connection_get_unique_name (proxy_connection), "/"); items_changed_count = 0; g_signal_connect (proxy, "items-changed", G_CALLBACK (items_changed), NULL); g_menu_append (menu, "item1", NULL); g_menu_append (menu, "item2", NULL); g_menu_append (menu, "item3", NULL); g_assert_cmpint (items_changed_count, ==, 0); /* We don't subscribe to change-notification until we look at the items */ g_timeout_add (100, stop_loop, loop); g_main_loop_run (loop); /* Looking at the items triggers subscription */ g_menu_model_get_n_items (G_MENU_MODEL (proxy)); while (items_changed_count < 1) g_main_context_iteration (NULL, TRUE); /* We get all three items in one batch */ g_assert_cmpint (items_changed_count, ==, 1); g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3); /* If we wait, we don't get any more */ g_timeout_add (100, stop_loop, loop); g_main_loop_run (loop); g_assert_cmpint (items_changed_count, ==, 1); g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3); /* Now we're subscribed, we get changes individually */ g_menu_append (menu, "item4", NULL); g_menu_append (menu, "item5", NULL); g_menu_append (menu, "item6", NULL); g_menu_remove (menu, 0); g_menu_remove (menu, 0); while (items_changed_count < 6) g_main_context_iteration (NULL, TRUE); g_assert_cmpint (items_changed_count, ==, 6); g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4); /* After destroying the proxy and waiting a bit, we don't get any more * items-changed signals */ g_object_unref (proxy); g_timeout_add (100, stop_loop, loop); g_main_loop_run (loop); g_menu_remove (menu, 0); g_menu_remove (menu, 0); g_timeout_add (100, stop_loop, loop); g_main_loop_run (loop); g_assert_cmpint (items_changed_count, ==, 6); g_dbus_connection_unexport_menu_model (exporter_connection, export_id); g_object_unref (menu); g_main_loop_unref (loop); cancel_timeout (timeout_id); } static void test_dbus_subscriptions (void) { GDBusConnection *bus; bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); do_subscriptions (bus, bus); g_object_unref (bus); } static void test_dbus_peer_subscriptions (void) { #ifdef _GLIB_ADDRESS_SANITIZER g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313"); (void) peer_connection_up; (void) peer_connection_down; #else PeerConnection peer; peer_connection_up (&peer); do_subscriptions (peer.server_connection, peer.client_connection); peer_connection_down (&peer); #endif } static void test_dbus_export_error_handling (void) { GRand *rand = NULL; RandomMenu *menu = NULL; GDBusConnection *bus; GError *local_error = NULL; guint id1, id2; g_test_summary ("Test that error handling of menu model export failure works"); g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3366"); bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); rand = g_rand_new_with_seed (g_test_rand_int ()); menu = random_menu_new (rand, 2); id1 = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (menu), &local_error); g_assert_no_error (local_error); g_assert_cmpuint (id1, !=, 0); /* Trigger a failure by trying to export on a path which is already in use */ id2 = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (menu), &local_error); g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS); g_assert_cmpuint (id2, ==, 0); g_clear_error (&local_error); g_dbus_connection_unexport_menu_model (bus, id1); while (g_main_context_iteration (NULL, FALSE)); g_clear_object (&menu); g_rand_free (rand); g_clear_object (&bus); } static gpointer do_modify (gpointer data) { RandomMenu *menu = data; GRand *rand; gint i; rand = g_rand_new_with_seed (g_test_rand_int ()); for (i = 0; i < 10000; i++) { random_menu_change (menu, rand); } g_rand_free (rand); return NULL; } static gpointer do_export (gpointer data) { GMenuModel *menu = data; gint i; GDBusConnection *bus; gchar *path; GError *error = NULL; guint id; bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); path = g_strdup_printf ("/%p", data); for (i = 0; i < 10000; i++) { id = g_dbus_connection_export_menu_model (bus, path, menu, &error); g_assert_no_error (error); g_dbus_connection_unexport_menu_model (bus, id); while (g_main_context_iteration (NULL, FALSE)); } g_free (path); g_object_unref (bus); return NULL; } static void test_dbus_threaded (void) { RandomMenu *menu[10]; GThread *call[10]; GThread *export[10]; gint i; for (i = 0; i < 10; i++) { GRand *rand = g_rand_new_with_seed (g_test_rand_int ()); menu[i] = random_menu_new (rand, 2); call[i] = g_thread_new ("call", do_modify, menu[i]); export[i] = g_thread_new ("export", do_export, menu[i]); g_rand_free (rand); } for (i = 0; i < 10; i++) { g_thread_join (call[i]); g_thread_join (export[i]); } for (i = 0; i < 10; i++) g_object_unref (menu[i]); } static void test_attributes (void) { GMenu *menu; GMenuItem *item; GVariant *v; menu = g_menu_new (); item = g_menu_item_new ("test", NULL); g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE)); g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla")); g_menu_item_set_attribute (item, "double", "d", 1.5); v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three"); g_menu_item_set_attribute_value (item, "complex", v); g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123")); g_menu_append_item (menu, item); g_menu_item_set_attribute (item, "double", "d", G_PI); g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1); v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)); g_variant_unref (v); v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)); g_variant_unref (v); v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE)); g_variant_unref (v); v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)"))); g_variant_unref (v); g_menu_remove_all (menu); g_object_unref (menu); g_object_unref (item); } static void test_attribute_iter (void) { GMenu *menu; GMenuItem *item; const gchar *name; GVariant *v; GMenuAttributeIter *iter; GHashTable *found; menu = g_menu_new (); item = g_menu_item_new ("test", NULL); g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE)); g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla")); g_menu_item_set_attribute (item, "double", "d", 1.5); v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three"); g_menu_item_set_attribute_value (item, "complex", v); g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123")); g_menu_append_item (menu, item); g_menu_item_set_attribute (item, "double", "d", G_PI); g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1); found = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); iter = g_menu_model_iterate_item_attributes (G_MENU_MODEL (menu), 0); while (g_menu_attribute_iter_get_next (iter, &name, &v)) g_hash_table_insert (found, g_strdup (name), v); g_object_unref (iter); g_assert_cmpint (g_hash_table_size (found), ==, 6); v = g_hash_table_lookup (found, "label"); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)); v = g_hash_table_lookup (found, "boolean"); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)); v = g_hash_table_lookup (found, "string"); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)); v = g_hash_table_lookup (found, "double"); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE)); v = g_hash_table_lookup (found, "complex"); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)"))); v = g_hash_table_lookup (found, "test-123"); g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)); g_hash_table_unref (found); g_menu_remove_all (menu); g_object_unref (menu); g_object_unref (item); } static void test_links (void) { GMenu *menu; GMenuModel *m; GMenuModel *x; GMenuItem *item; m = G_MENU_MODEL (g_menu_new ()); g_menu_append (G_MENU (m), "test", NULL); menu = g_menu_new (); item = g_menu_item_new ("test2", NULL); g_menu_item_set_link (item, "submenu", m); g_menu_prepend_item (menu, item); g_object_unref (item); item = g_menu_item_new ("test1", NULL); g_menu_item_set_link (item, "section", m); g_menu_insert_item (menu, 0, item); g_object_unref (item); item = g_menu_item_new ("test3", NULL); g_menu_item_set_link (item, "wallet", m); g_menu_insert_item (menu, 1000, item); g_object_unref (item); item = g_menu_item_new ("test4", NULL); g_menu_item_set_link (item, "purse", m); g_menu_item_set_link (item, "purse", NULL); g_menu_append_item (menu, item); g_object_unref (item); g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4); x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section"); g_assert (x == m); g_object_unref (x); x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu"); g_assert (x == m); g_object_unref (x); x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet"); g_assert (x == m); g_object_unref (x); x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse"); g_assert (x == NULL); g_object_unref (m); g_object_unref (menu); } static void test_mutable (void) { GMenu *menu; menu = g_menu_new (); g_menu_append (menu, "test", "test"); g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu))); g_menu_freeze (menu); g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu))); g_object_unref (menu); } static void test_convenience (void) { GMenu *m1, *m2; GMenu *sub; GMenuItem *item; m1 = g_menu_new (); m2 = g_menu_new (); sub = g_menu_new (); g_menu_prepend (m1, "label1", "do::something"); g_menu_insert (m2, 0, "label1", "do::something"); g_menu_append (m1, "label2", "do::somethingelse"); g_menu_insert (m2, -1, "label2", "do::somethingelse"); g_menu_insert_section (m1, 10, "label3", G_MENU_MODEL (sub)); item = g_menu_item_new_section ("label3", G_MENU_MODEL (sub)); g_menu_insert_item (m2, 10, item); g_object_unref (item); g_menu_prepend_section (m1, "label4", G_MENU_MODEL (sub)); g_menu_insert_section (m2, 0, "label4", G_MENU_MODEL (sub)); g_menu_append_section (m1, "label5", G_MENU_MODEL (sub)); g_menu_insert_section (m2, -1, "label5", G_MENU_MODEL (sub)); g_menu_insert_submenu (m1, 5, "label6", G_MENU_MODEL (sub)); item = g_menu_item_new_submenu ("label6", G_MENU_MODEL (sub)); g_menu_insert_item (m2, 5, item); g_object_unref (item); g_menu_prepend_submenu (m1, "label7", G_MENU_MODEL (sub)); g_menu_insert_submenu (m2, 0, "label7", G_MENU_MODEL (sub)); g_menu_append_submenu (m1, "label8", G_MENU_MODEL (sub)); g_menu_insert_submenu (m2, -1, "label8", G_MENU_MODEL (sub)); assert_menus_equal (G_MENU_MODEL (m1), G_MENU_MODEL (m2)); g_object_unref (m1); g_object_unref (m2); g_object_unref (sub); } static void test_menuitem (void) { GMenu *menu; GMenu *submenu; GMenuItem *item; GIcon *icon; gboolean b; gchar *s; menu = g_menu_new (); submenu = g_menu_new (); item = g_menu_item_new ("label", "action"); g_menu_item_set_attribute (item, "attribute", "b", TRUE); g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, G_MENU_MODEL (submenu)); g_menu_append_item (menu, item); icon = g_themed_icon_new ("bla"); g_menu_item_set_icon (item, icon); g_object_unref (icon); g_assert (g_menu_item_get_attribute (item, "attribute", "b", &b)); g_assert (b); g_menu_item_set_action_and_target (item, "action", "(bs)", TRUE, "string"); g_assert (g_menu_item_get_attribute (item, "target", "(bs)", &b, &s)); g_assert (b); g_assert_cmpstr (s, ==, "string"); g_free (s); g_object_unref (item); item = g_menu_item_new_from_model (G_MENU_MODEL (menu), 0); assert_menuitem_equal (item, G_MENU_MODEL (menu), 0); g_object_unref (item); g_object_unref (menu); g_object_unref (submenu); } static GDBusInterfaceInfo * org_gtk_Menus_get_interface (void) { static GDBusInterfaceInfo *interface_info; if (interface_info == NULL) { GError *error = NULL; GDBusNodeInfo *info; info = g_dbus_node_info_new_for_xml ("" " " " " " " " " " " " " " " " " " " " arg type='a(uuuuaa{sv})' name='changes'/>" " " " " "", &error); if (info == NULL) g_error ("%s\n", error->message); interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus"); g_assert (interface_info != NULL); g_dbus_interface_info_ref (interface_info); g_dbus_node_info_unref (info); } return interface_info; } static void g_menu_exporter_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { const struct { guint position; guint removed; } data[] = { { -2, 4 }, { 0, 3 }, { 4, 1 } }; gsize i; GError *error = NULL; g_dbus_method_invocation_return_value (invocation, g_variant_new_parsed ("@(a(uuaa{sv})) ([(0, 0, [{ 'label': <'test'> }])],)")); /* invalid signatures */ g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed", g_variant_new_parsed ("([(1, 2, 3)],)"), &error); g_assert_no_error (error); /* add an item at an invalid position */ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*"); g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed", g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [{ 'label': <'test'> }])],)", 0, 0, 2, 0), &error); g_assert_no_error (error); for (i = 0; i < G_N_ELEMENTS (data); i++) { GVariant *params; g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*"); params = g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [])],)", 0, 0, data[i].position, data[i].removed); g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed", params, &error); g_assert_no_error (error); } } static void menu_changed (GMenuModel *menu, gint position, gint removed, gint added, gpointer user_data) { unsigned int *counter = user_data; *counter += 1; } static void test_input_validation (void) { const GDBusInterfaceVTable vtable = { g_menu_exporter_method_call, NULL, NULL, { NULL, } }; GError *error = NULL; GDBusConnection *bus; GDBusMenuModel *proxy; guint id; const gchar *bus_name; GMainLoop *loop; unsigned int n_signal_emissions = 0; gulong signal_id; g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/861"); bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); g_assert_no_error (error); id = g_dbus_connection_register_object (bus, "/", org_gtk_Menus_get_interface (), &vtable, NULL, NULL, &error); g_assert_no_error (error); bus_name = g_dbus_connection_get_unique_name (bus); proxy = g_dbus_menu_model_get (bus, bus_name, "/"); signal_id = g_signal_connect (proxy, "items-changed", G_CALLBACK (menu_changed), &n_signal_emissions); /* get over laziness */ g_menu_model_get_n_items (G_MENU_MODEL (proxy)); loop = g_main_loop_new (NULL, FALSE); g_timeout_add (100, stop_loop, loop); g_main_loop_run (loop); /* "items-changed" should only be emitted for the initial contents of * the menu. Subsequent calls are all invalid. */ g_assert_cmpuint (n_signal_emissions, ==, 1); g_test_assert_expected_messages (); g_main_loop_unref (loop); g_dbus_connection_unregister_object (bus, id); g_signal_handler_disconnect (proxy, signal_id); g_object_unref (proxy); g_object_unref (bus); } /* Epilogue {{{1 */ int main (int argc, char **argv) { gboolean ret; g_test_init (&argc, &argv, NULL); session_bus_up (); g_test_add_func ("/gmenu/equality", test_equality); g_test_add_func ("/gmenu/random", test_random); g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip); g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions); g_test_add_func ("/gmenu/dbus/threaded", test_dbus_threaded); g_test_add_func ("/gmenu/dbus/peer/roundtrip", test_dbus_peer_roundtrip); g_test_add_func ("/gmenu/dbus/peer/subscriptions", test_dbus_peer_subscriptions); g_test_add_func ("/gmenu/dbus/export/error-handling", test_dbus_export_error_handling); g_test_add_func ("/gmenu/attributes", test_attributes); g_test_add_func ("/gmenu/attributes/iterate", test_attribute_iter); g_test_add_func ("/gmenu/links", test_links); g_test_add_func ("/gmenu/mutable", test_mutable); g_test_add_func ("/gmenu/convenience", test_convenience); g_test_add_func ("/gmenu/menuitem", test_menuitem); g_test_add_func ("/gmenu/input-validation", test_input_validation); ret = g_test_run (); session_bus_down (); return ret; } /* vim:set foldmethod=marker: */