glib/gio/tests/gmenumodel.c
Marco Trevisan (Treviño) 3a07b2abd4 GCancellable: Use per-instance mutex logic instead of global critical sections
GCancellable is meant to be used in multi-thread operations but all the
cancellable instances were sharing a single mutex to synchronize them
which can be less optimal when many instances are in place.
Especially when we're doing a lock/unlock dances that may leave another
thread to take the control of a critical section in an unexpected way.

This in fact was leading to some races in GCancellableSources causing
leaks because we were assuming that the "cancelled" callback was always
called before our dispose implementation.

As per this, use per-instance mutexes.

The lock is also now used only to protect the calls that may interact
with cancelled state or that depends on that, as per this we can just
reduce it to the cancel and reset case, other than to the connect one to
prevent the race that we could have when connecting to a cancellable
that is reset from another thread.

We don't really need to release the locks during callbacks now as they
are per instance, and there's really no function that we allowed to call
during a ::cancelled signal callback that may require an unlocked state.
This could been done in case with a recursive lock, that is easy enough
to implement but not really needed for this case.

Fixes: #2309, #2313
2024-07-24 00:21:35 +02:00

1711 lines
46 KiB
C

#include <gio/gio.h>
#include <gio/gunixsocketaddress.h>
#include <glib/gstdio.h>
#include <string.h>
#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 ("<attribute name='%s' type='%s'>%s</attribute>\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 ("<link name='%s'>\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, "</link>\n");
g_object_unref (menu);
}
g_object_unref (link_iter);
if (contents->str[0])
{
indent_string (string, indent);
g_string_append_printf (string, "<item%s>\n", attrs->str);
g_string_append (string, contents->str);
indent_string (string, indent);
g_string_append (string, "</item>\n");
need_nl = TRUE;
}
else
{
if (need_nl)
g_string_append_c (string, '\n');
indent_string (string, indent);
g_string_append_printf (string, "<item%s/>\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 <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);
}
}
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)
{
PeerConnection peer;
#ifdef _GLIB_ADDRESS_SANITIZER
g_test_message ("Ensure that no GCancellableSource are leaked");
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2313");
#endif
peer_connection_up (&peer);
do_roundtrip (peer.server_connection, peer.client_connection);
peer_connection_down (&peer);
}
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)
{
PeerConnection peer;
#ifdef _GLIB_ADDRESS_SANITIZER
g_test_message ("Ensure that no GCancellableSource are leaked");
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2313");
#endif
peer_connection_up (&peer);
do_subscriptions (peer.server_connection, peer.client_connection);
peer_connection_down (&peer);
}
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 ("<node>"
" <interface name='org.gtk.Menus'>"
" <method name='Start'>"
" <arg type='au' name='groups' direction='in'/>"
" <arg type='a(uuaa{sv})' name='content' direction='out'/>"
" </method>"
" <method name='End'>"
" <arg type='au' name='groups' direction='in'/>"
" </method>"
" <signal name='Changed'>"
" arg type='a(uuuuaa{sv})' name='changes'/>"
" </signal>"
" </interface>"
"</node>", &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: */