mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 00:12:19 +01:00 
			
		
		
		
	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
		
			
				
	
	
		
			1711 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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: */
 |