(root)/
glib-2.79.0/
gio/
tests/
gmenumodel.c
       1  #include <gio/gio.h>
       2  #include <gio/gunixsocketaddress.h>
       3  #include <glib/gstdio.h>
       4  #include <string.h>
       5  
       6  #include "gdbus-sessionbus.h"
       7  
       8  #include "glib/glib-private.h"
       9  
      10  static void
      11  time_out (gpointer unused G_GNUC_UNUSED)
      12  {
      13    g_error ("Timed out");
      14  }
      15  
      16  static guint
      17  add_timeout (guint seconds)
      18  {
      19  #ifdef G_OS_UNIX
      20    /* Safety-catch against the main loop having blocked */
      21    alarm (seconds + 5);
      22  #endif
      23    return g_timeout_add_seconds_once (seconds, time_out, NULL);
      24  }
      25  
      26  static void
      27  cancel_timeout (guint timeout_id)
      28  {
      29  #ifdef G_OS_UNIX
      30    alarm (0);
      31  #endif
      32    g_source_remove (timeout_id);
      33  }
      34  
      35  /* Markup printing {{{1 */
      36  
      37  /* This used to be part of GLib, but it was removed before the stable
      38   * release because it wasn't generally useful.  We want it here, though.
      39   */
      40  static void
      41  indent_string (GString *string,
      42                 gint     indent)
      43  {
      44    while (indent--)
      45      g_string_append_c (string, ' ');
      46  }
      47  
      48  static GString *
      49  g_menu_markup_print_string (GString    *string,
      50                              GMenuModel *model,
      51                              gint        indent,
      52                              gint        tabstop)
      53  {
      54    gboolean need_nl = FALSE;
      55    gint i, n;
      56  
      57    if G_UNLIKELY (string == NULL)
      58      string = g_string_new (NULL);
      59  
      60    n = g_menu_model_get_n_items (model);
      61  
      62    for (i = 0; i < n; i++)
      63      {
      64        GMenuAttributeIter *attr_iter;
      65        GMenuLinkIter *link_iter;
      66        GString *contents;
      67        GString *attrs;
      68  
      69        attr_iter = g_menu_model_iterate_item_attributes (model, i);
      70        link_iter = g_menu_model_iterate_item_links (model, i);
      71        contents = g_string_new (NULL);
      72        attrs = g_string_new (NULL);
      73  
      74        while (g_menu_attribute_iter_next (attr_iter))
      75          {
      76            const char *name = g_menu_attribute_iter_get_name (attr_iter);
      77            GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
      78  
      79            if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
      80              {
      81                gchar *str;
      82                str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
      83                g_string_append (attrs, str);
      84                g_free (str);
      85              }
      86  
      87            else
      88              {
      89                gchar *printed;
      90                gchar *str;
      91                const gchar *type;
      92  
      93                printed = g_variant_print (value, TRUE);
      94                type = g_variant_type_peek_string (g_variant_get_type (value));
      95                str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
      96                indent_string (contents, indent + tabstop);
      97                g_string_append (contents, str);
      98                g_free (printed);
      99                g_free (str);
     100              }
     101  
     102            g_variant_unref (value);
     103          }
     104        g_object_unref (attr_iter);
     105  
     106        while (g_menu_link_iter_next (link_iter))
     107          {
     108            const gchar *name = g_menu_link_iter_get_name (link_iter);
     109            GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
     110            gchar *str;
     111  
     112            if (contents->str[0])
     113              g_string_append_c (contents, '\n');
     114  
     115            str = g_markup_printf_escaped ("<link name='%s'>\n", name);
     116            indent_string (contents, indent + tabstop);
     117            g_string_append (contents, str);
     118            g_free (str);
     119  
     120            g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
     121  
     122            indent_string (contents, indent + tabstop);
     123            g_string_append (contents, "</link>\n");
     124            g_object_unref (menu);
     125          }
     126        g_object_unref (link_iter);
     127  
     128        if (contents->str[0])
     129          {
     130            indent_string (string, indent);
     131            g_string_append_printf (string, "<item%s>\n", attrs->str);
     132            g_string_append (string, contents->str);
     133            indent_string (string, indent);
     134            g_string_append (string, "</item>\n");
     135            need_nl = TRUE;
     136          }
     137  
     138        else
     139          {
     140            if (need_nl)
     141              g_string_append_c (string, '\n');
     142  
     143            indent_string (string, indent);
     144            g_string_append_printf (string, "<item%s/>\n", attrs->str);
     145            need_nl = FALSE;
     146          }
     147  
     148        g_string_free (contents, TRUE);
     149        g_string_free (attrs, TRUE);
     150      }
     151  
     152    return string;
     153  }
     154  
     155  /* TestItem {{{1 */
     156  
     157  /* This utility struct is used by both the RandomMenu and MirrorMenu
     158   * class implementations below.
     159   */
     160  typedef struct {
     161    GHashTable *attributes;
     162    GHashTable *links;
     163  } TestItem;
     164  
     165  static TestItem *
     166  test_item_new (GHashTable *attributes,
     167                 GHashTable *links)
     168  {
     169    TestItem *item;
     170  
     171    item = g_slice_new (TestItem);
     172    item->attributes = g_hash_table_ref (attributes);
     173    item->links = g_hash_table_ref (links);
     174  
     175    return item;
     176  }
     177  
     178  static void
     179  test_item_free (gpointer data)
     180  {
     181    TestItem *item = data;
     182  
     183    g_hash_table_unref (item->attributes);
     184    g_hash_table_unref (item->links);
     185  
     186    g_slice_free (TestItem, item);
     187  }
     188  
     189  /* RandomMenu {{{1 */
     190  #define MAX_ITEMS 5
     191  #define TOP_ORDER 4
     192  
     193  typedef struct {
     194    GMenuModel parent_instance;
     195  
     196    GSequence *items;
     197    gint order;
     198  } RandomMenu;
     199  
     200  typedef GMenuModelClass RandomMenuClass;
     201  
     202  static GType random_menu_get_type (void);
     203  G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL)
     204  
     205  static gboolean
     206  random_menu_is_mutable (GMenuModel *model)
     207  {
     208    return TRUE;
     209  }
     210  
     211  static gint
     212  random_menu_get_n_items (GMenuModel *model)
     213  {
     214    RandomMenu *menu = (RandomMenu *) model;
     215  
     216    return g_sequence_get_length (menu->items);
     217  }
     218  
     219  static void
     220  random_menu_get_item_attributes (GMenuModel  *model,
     221                                   gint         position,
     222                                   GHashTable **table)
     223  {
     224    RandomMenu *menu = (RandomMenu *) model;
     225    TestItem *item;
     226  
     227    item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
     228    *table = g_hash_table_ref (item->attributes);
     229  }
     230  
     231  static void
     232  random_menu_get_item_links (GMenuModel  *model,
     233                              gint         position,
     234                              GHashTable **table)
     235  {
     236    RandomMenu *menu = (RandomMenu *) model;
     237    TestItem *item;
     238  
     239    item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
     240    *table = g_hash_table_ref (item->links);
     241  }
     242  
     243  static void
     244  random_menu_finalize (GObject *object)
     245  {
     246    RandomMenu *menu = (RandomMenu *) object;
     247  
     248    g_sequence_free (menu->items);
     249  
     250    G_OBJECT_CLASS (random_menu_parent_class)
     251      ->finalize (object);
     252  }
     253  
     254  static void
     255  random_menu_init (RandomMenu *menu)
     256  {
     257  }
     258  
     259  static void
     260  random_menu_class_init (GMenuModelClass *class)
     261  {
     262    GObjectClass *object_class = G_OBJECT_CLASS (class);
     263  
     264    class->is_mutable = random_menu_is_mutable;
     265    class->get_n_items = random_menu_get_n_items;
     266    class->get_item_attributes = random_menu_get_item_attributes;
     267    class->get_item_links = random_menu_get_item_links;
     268  
     269    object_class->finalize = random_menu_finalize;
     270  }
     271  
     272  static RandomMenu * random_menu_new (GRand *rand, gint order);
     273  
     274  static void
     275  random_menu_change (RandomMenu *menu,
     276                      GRand      *rand)
     277  {
     278    gint position, removes, adds;
     279    GSequenceIter *point;
     280    gint n_items;
     281    gint i;
     282  
     283    n_items = g_sequence_get_length (menu->items);
     284  
     285    do
     286      {
     287        position = g_rand_int_range (rand, 0, n_items + 1);
     288        removes = g_rand_int_range (rand, 0, n_items - position + 1);
     289        adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
     290      }
     291    while (removes == 0 && adds == 0);
     292  
     293    point = g_sequence_get_iter_at_pos (menu->items, position + removes);
     294  
     295    if (removes)
     296      {
     297        GSequenceIter *start;
     298  
     299        start = g_sequence_get_iter_at_pos (menu->items, position);
     300        g_sequence_remove_range (start, point);
     301      }
     302  
     303    for (i = 0; i < adds; i++)
     304      {
     305        const gchar *label;
     306        GHashTable *links;
     307        GHashTable *attributes;
     308  
     309        attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
     310        links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
     311  
     312        if (menu->order > 0 && g_rand_boolean (rand))
     313          {
     314            RandomMenu *child;
     315  	  const gchar *subtype;
     316  
     317            child = random_menu_new (rand, menu->order - 1);
     318  
     319            if (g_rand_boolean (rand))
     320              {
     321                subtype = G_MENU_LINK_SECTION;
     322                /* label some section headers */
     323                if (g_rand_boolean (rand))
     324                  label = "Section";
     325                else
     326                  label = NULL;
     327              }
     328            else
     329              {
     330                /* label all submenus */
     331                subtype = G_MENU_LINK_SUBMENU;
     332                label = "Submenu";
     333              }
     334  
     335            g_hash_table_insert (links, g_strdup (subtype), child);
     336          }
     337        else
     338          /* label all terminals */
     339          label = "Menu Item";
     340  
     341        if (label)
     342          g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
     343  
     344        g_sequence_insert_before (point, test_item_new (attributes, links));
     345        g_hash_table_unref (links);
     346        g_hash_table_unref (attributes);
     347      }
     348  
     349    g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
     350  }
     351  
     352  static RandomMenu *
     353  random_menu_new (GRand *rand,
     354                   gint   order)
     355  {
     356    RandomMenu *menu;
     357  
     358    menu = g_object_new (random_menu_get_type (), NULL);
     359    menu->items = g_sequence_new (test_item_free);
     360    menu->order = order;
     361  
     362    random_menu_change (menu, rand);
     363  
     364    return menu;
     365  }
     366  
     367  /* MirrorMenu {{{1 */
     368  typedef struct {
     369    GMenuModel parent_instance;
     370  
     371    GMenuModel *clone_of;
     372    GSequence *items;
     373    gulong handler_id;
     374  } MirrorMenu;
     375  
     376  typedef GMenuModelClass MirrorMenuClass;
     377  
     378  static GType mirror_menu_get_type (void);
     379  G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL)
     380  
     381  static gboolean
     382  mirror_menu_is_mutable (GMenuModel *model)
     383  {
     384    MirrorMenu *menu = (MirrorMenu *) model;
     385  
     386    return menu->handler_id != 0;
     387  }
     388  
     389  static gint
     390  mirror_menu_get_n_items (GMenuModel *model)
     391  {
     392    MirrorMenu *menu = (MirrorMenu *) model;
     393  
     394    return g_sequence_get_length (menu->items);
     395  }
     396  
     397  static void
     398  mirror_menu_get_item_attributes (GMenuModel  *model,
     399                                   gint         position,
     400                                   GHashTable **table)
     401  {
     402    MirrorMenu *menu = (MirrorMenu *) model;
     403    TestItem *item;
     404  
     405    item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
     406    *table = g_hash_table_ref (item->attributes);
     407  }
     408  
     409  static void
     410  mirror_menu_get_item_links (GMenuModel  *model,
     411                              gint         position,
     412                              GHashTable **table)
     413  {
     414    MirrorMenu *menu = (MirrorMenu *) model;
     415    TestItem *item;
     416  
     417    item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
     418    *table = g_hash_table_ref (item->links);
     419  }
     420  
     421  static void
     422  mirror_menu_finalize (GObject *object)
     423  {
     424    MirrorMenu *menu = (MirrorMenu *) object;
     425  
     426    if (menu->handler_id)
     427      g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
     428  
     429    g_sequence_free (menu->items);
     430    g_object_unref (menu->clone_of);
     431  
     432    G_OBJECT_CLASS (mirror_menu_parent_class)
     433      ->finalize (object);
     434  }
     435  
     436  static void
     437  mirror_menu_init (MirrorMenu *menu)
     438  {
     439  }
     440  
     441  static void
     442  mirror_menu_class_init (GMenuModelClass *class)
     443  {
     444    GObjectClass *object_class = G_OBJECT_CLASS (class);
     445  
     446    class->is_mutable = mirror_menu_is_mutable;
     447    class->get_n_items = mirror_menu_get_n_items;
     448    class->get_item_attributes = mirror_menu_get_item_attributes;
     449    class->get_item_links = mirror_menu_get_item_links;
     450  
     451    object_class->finalize = mirror_menu_finalize;
     452  }
     453  
     454  static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
     455  
     456  static void
     457  mirror_menu_changed (GMenuModel *model,
     458                       gint        position,
     459                       gint        removed,
     460                       gint        added,
     461                       gpointer    user_data)
     462  {
     463    MirrorMenu *menu = user_data;
     464    GSequenceIter *point;
     465    gint i;
     466  
     467    g_assert (model == menu->clone_of);
     468  
     469    point = g_sequence_get_iter_at_pos (menu->items, position + removed);
     470  
     471    if (removed)
     472      {
     473        GSequenceIter *start;
     474  
     475        start = g_sequence_get_iter_at_pos (menu->items, position);
     476        g_sequence_remove_range (start, point);
     477      }
     478  
     479    for (i = position; i < position + added; i++)
     480      {
     481        GMenuAttributeIter *attr_iter;
     482        GMenuLinkIter *link_iter;
     483        GHashTable *links;
     484        GHashTable *attributes;
     485        const gchar *name;
     486        GMenuModel *child;
     487        GVariant *value;
     488  
     489        attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
     490        links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
     491  
     492        attr_iter = g_menu_model_iterate_item_attributes (model, i);
     493        while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
     494          {
     495            g_hash_table_insert (attributes, g_strdup (name), value);
     496          }
     497        g_object_unref (attr_iter);
     498  
     499        link_iter = g_menu_model_iterate_item_links (model, i);
     500        while (g_menu_link_iter_get_next (link_iter, &name, &child))
     501          {
     502            g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
     503            g_object_unref (child);
     504          }
     505        g_object_unref (link_iter);
     506  
     507        g_sequence_insert_before (point, test_item_new (attributes, links));
     508        g_hash_table_unref (attributes);
     509        g_hash_table_unref (links);
     510      }
     511  
     512    g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
     513  }
     514  
     515  static MirrorMenu *
     516  mirror_menu_new (GMenuModel *clone_of)
     517  {
     518    MirrorMenu *menu;
     519  
     520    menu = g_object_new (mirror_menu_get_type (), NULL);
     521    menu->items = g_sequence_new (test_item_free);
     522    menu->clone_of = g_object_ref (clone_of);
     523  
     524    if (g_menu_model_is_mutable (clone_of))
     525      menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
     526    mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
     527  
     528    return menu;
     529  }
     530  
     531  /* check_menus_equal(), assert_menus_equal() {{{1 */
     532  static gboolean
     533  check_menus_equal (GMenuModel *a,
     534                     GMenuModel *b)
     535  {
     536    gboolean equal = TRUE;
     537    gint a_n, b_n;
     538    gint i;
     539  
     540    a_n = g_menu_model_get_n_items (a);
     541    b_n = g_menu_model_get_n_items (b);
     542  
     543    if (a_n != b_n)
     544      return FALSE;
     545  
     546    for (i = 0; i < a_n; i++)
     547      {
     548        GMenuAttributeIter *attr_iter;
     549        GVariant *a_value, *b_value;
     550        GMenuLinkIter *link_iter;
     551        GMenuModel *a_menu, *b_menu;
     552        const gchar *name;
     553  
     554        attr_iter = g_menu_model_iterate_item_attributes (a, i);
     555        while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
     556          {
     557            b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
     558            equal &= b_value && g_variant_equal (a_value, b_value);
     559            if (b_value)
     560              g_variant_unref (b_value);
     561            g_variant_unref (a_value);
     562          }
     563        g_object_unref (attr_iter);
     564  
     565        attr_iter = g_menu_model_iterate_item_attributes (b, i);
     566        while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
     567          {
     568            a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
     569            equal &= a_value && g_variant_equal (a_value, b_value);
     570            if (a_value)
     571              g_variant_unref (a_value);
     572            g_variant_unref (b_value);
     573          }
     574        g_object_unref (attr_iter);
     575  
     576        link_iter = g_menu_model_iterate_item_links (a, i);
     577        while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
     578          {
     579            b_menu = g_menu_model_get_item_link (b, i, name);
     580            equal &= b_menu && check_menus_equal (a_menu, b_menu);
     581            if (b_menu)
     582              g_object_unref (b_menu);
     583            g_object_unref (a_menu);
     584          }
     585        g_object_unref (link_iter);
     586  
     587        link_iter = g_menu_model_iterate_item_links (b, i);
     588        while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
     589          {
     590            a_menu = g_menu_model_get_item_link (a, i, name);
     591            equal &= a_menu && check_menus_equal (a_menu, b_menu);
     592            if (a_menu)
     593              g_object_unref (a_menu);
     594            g_object_unref (b_menu);
     595          }
     596        g_object_unref (link_iter);
     597      }
     598  
     599    return equal;
     600  }
     601  
     602  static void
     603  assert_menus_equal (GMenuModel *a,
     604                      GMenuModel *b)
     605  {
     606    if (!check_menus_equal (a, b))
     607      {
     608        GString *string;
     609  
     610        string = g_string_new ("\n  <a>\n");
     611        g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
     612        g_string_append (string, "  </a>\n\n-------------\n  <b>\n");
     613        g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
     614        g_string_append (string, "  </b>\n");
     615        g_error ("%s", string->str);
     616      }
     617  }
     618  
     619  static void
     620  assert_menuitem_equal (GMenuItem  *item,
     621                         GMenuModel *model,
     622                         gint        index)
     623  {
     624    GMenuAttributeIter *attr_iter;
     625    GMenuLinkIter *link_iter;
     626    const gchar *name;
     627    GVariant *value;
     628    GMenuModel *linked_model;
     629  
     630    /* NOTE we can't yet test whether item has attributes or links that
     631     * are not in the model, because there's no iterator API for menu
     632     * items */
     633  
     634    attr_iter = g_menu_model_iterate_item_attributes (model, index);
     635    while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
     636      {
     637        GVariant *item_value;
     638  
     639        item_value = g_menu_item_get_attribute_value (item, name, g_variant_get_type (value));
     640        g_assert (item_value && g_variant_equal (item_value, value));
     641  
     642        g_variant_unref (item_value);
     643        g_variant_unref (value);
     644      }
     645  
     646    link_iter = g_menu_model_iterate_item_links (model, index);
     647    while (g_menu_link_iter_get_next (link_iter, &name, &linked_model))
     648      {
     649        GMenuModel *item_linked_model;
     650  
     651        item_linked_model = g_menu_item_get_link (item, name);
     652        g_assert (linked_model == item_linked_model);
     653  
     654        g_object_unref (item_linked_model);
     655        g_object_unref (linked_model);
     656      }
     657  
     658    g_object_unref (attr_iter);
     659    g_object_unref (link_iter);
     660  }
     661  
     662  /* Test cases {{{1 */
     663  static void
     664  test_equality (void)
     665  {
     666    GRand *randa, *randb;
     667    guint32 seed;
     668    gint i;
     669  
     670    seed = g_test_rand_int ();
     671  
     672    randa = g_rand_new_with_seed (seed);
     673    randb = g_rand_new_with_seed (seed);
     674  
     675    for (i = 0; i < 500; i++)
     676      {
     677        RandomMenu *a, *b;
     678  
     679        a = random_menu_new (randa, TOP_ORDER);
     680        b = random_menu_new (randb, TOP_ORDER);
     681        assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
     682        g_object_unref (b);
     683        g_object_unref (a);
     684      }
     685  
     686    g_rand_int (randa);
     687  
     688    for (i = 0; i < 500;)
     689      {
     690        RandomMenu *a, *b;
     691  
     692        a = random_menu_new (randa, TOP_ORDER);
     693        b = random_menu_new (randb, TOP_ORDER);
     694        if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
     695          {
     696            /* by chance, they may really be equal.  double check. */
     697            GString *as, *bs;
     698  
     699            as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
     700            bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
     701            g_assert_cmpstr (as->str, ==, bs->str);
     702            g_string_free (bs, TRUE);
     703            g_string_free (as, TRUE);
     704  
     705            /* we're here because randa and randb just generated equal
     706             * menus.  they may do it again, so throw away randb and make
     707             * a fresh one.
     708             */
     709            g_rand_free (randb);
     710            randb = g_rand_new_with_seed (g_rand_int (randa));
     711          }
     712        else
     713          /* make sure we get enough unequals (ie: no GRand failure) */
     714          i++;
     715  
     716        g_object_unref (b);
     717        g_object_unref (a);
     718      }
     719  
     720    g_rand_free (randb);
     721    g_rand_free (randa);
     722  }
     723  
     724  static void
     725  test_random (void)
     726  {
     727    RandomMenu *random;
     728    MirrorMenu *mirror;
     729    GRand *rand;
     730    gint i;
     731  
     732    rand = g_rand_new_with_seed (g_test_rand_int ());
     733    random = random_menu_new (rand, TOP_ORDER);
     734    mirror = mirror_menu_new (G_MENU_MODEL (random));
     735  
     736    for (i = 0; i < 500; i++)
     737      {
     738        assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
     739        random_menu_change (random, rand);
     740      }
     741  
     742    g_object_unref (mirror);
     743    g_object_unref (random);
     744  
     745    g_rand_free (rand);
     746  }
     747  
     748  typedef struct
     749  {
     750    GDBusConnection *client_connection;
     751    GDBusConnection *server_connection;
     752    GDBusServer *server;
     753  
     754    GThread *service_thread;
     755    /* Protects server_connection and service_loop. */
     756    GMutex service_loop_lock;
     757    GCond service_loop_cond;
     758  
     759    GMainLoop *service_loop;
     760  } PeerConnection;
     761  
     762  static gboolean
     763  on_new_connection (GDBusServer *server,
     764                     GDBusConnection *connection,
     765                     gpointer user_data)
     766  {
     767    PeerConnection *data = user_data;
     768  
     769    g_mutex_lock (&data->service_loop_lock);
     770    data->server_connection = g_object_ref (connection);
     771    g_cond_broadcast (&data->service_loop_cond);
     772    g_mutex_unlock (&data->service_loop_lock);
     773  
     774    return TRUE;
     775  }
     776  
     777  static void
     778  create_service_loop (GMainContext   *service_context,
     779                       PeerConnection *data)
     780  {
     781    g_assert (data->service_loop == NULL);
     782    g_mutex_lock (&data->service_loop_lock);
     783    data->service_loop = g_main_loop_new (service_context, FALSE);
     784    g_cond_broadcast (&data->service_loop_cond);
     785    g_mutex_unlock (&data->service_loop_lock);
     786  }
     787  
     788  static void
     789  teardown_service_loop (PeerConnection *data)
     790  {
     791    g_mutex_lock (&data->service_loop_lock);
     792    g_clear_pointer (&data->service_loop, g_main_loop_unref);
     793    g_mutex_unlock (&data->service_loop_lock);
     794  }
     795  
     796  static void
     797  await_service_loop (PeerConnection *data)
     798  {
     799    g_mutex_lock (&data->service_loop_lock);
     800    while (data->service_loop == NULL)
     801      g_cond_wait (&data->service_loop_cond, &data->service_loop_lock);
     802    g_mutex_unlock (&data->service_loop_lock);
     803  }
     804  
     805  static void
     806  await_server_connection (PeerConnection *data)
     807  {
     808    g_mutex_lock (&data->service_loop_lock);
     809    while (data->server_connection == NULL)
     810      g_cond_wait (&data->service_loop_cond, &data->service_loop_lock);
     811    g_mutex_unlock (&data->service_loop_lock);
     812  }
     813  
     814  static gpointer
     815  service_thread_func (gpointer user_data)
     816  {
     817    PeerConnection *data = user_data;
     818    GMainContext *service_context;
     819    GError *error;
     820    gchar *address;
     821    gchar *tmpdir;
     822    GDBusServerFlags flags;
     823    gchar *guid;
     824  
     825    service_context = g_main_context_new ();
     826    g_main_context_push_thread_default (service_context);
     827  
     828    tmpdir = NULL;
     829    flags = G_DBUS_SERVER_FLAGS_NONE;
     830  
     831  #ifdef G_OS_UNIX
     832    tmpdir = g_dir_make_tmp ("test-dbus-peer-XXXXXX", NULL);
     833    address = g_strdup_printf ("unix:tmpdir=%s", tmpdir);
     834  #else
     835    address = g_strdup ("nonce-tcp:");
     836    flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
     837  #endif
     838  
     839    guid = g_dbus_generate_guid ();
     840  
     841    error = NULL;
     842    data->server = g_dbus_server_new_sync (address,
     843                                           flags,
     844                                           guid,
     845                                           NULL,
     846                                           NULL,
     847                                           &error);
     848    g_assert_no_error (error);
     849    g_free (address);
     850    g_free (guid);
     851  
     852    g_signal_connect (data->server,
     853                      "new-connection",
     854                      G_CALLBACK (on_new_connection),
     855                      data);
     856  
     857    g_dbus_server_start (data->server);
     858  
     859    create_service_loop (service_context, data);
     860    g_main_loop_run (data->service_loop);
     861  
     862    g_main_context_pop_thread_default (service_context);
     863  
     864    teardown_service_loop (data);
     865    g_main_context_unref (service_context);
     866  
     867    if (tmpdir)
     868      {
     869        g_rmdir (tmpdir);
     870        g_free (tmpdir);
     871      }
     872  
     873    return NULL;
     874  }
     875  
     876  static void
     877  peer_connection_up (PeerConnection *data)
     878  {
     879    GError *error;
     880  
     881    memset (data, '\0', sizeof (PeerConnection));
     882  
     883    g_mutex_init (&data->service_loop_lock);
     884    g_cond_init (&data->service_loop_cond);
     885  
     886    /* bring up a server - we run the server in a different thread to
     887       avoid deadlocks */
     888    data->service_thread = g_thread_new ("test_dbus_peer",
     889                                         service_thread_func,
     890                                         data);
     891    await_service_loop (data);
     892    g_assert (data->server != NULL);
     893  
     894    /* bring up a connection and accept it */
     895    error = NULL;
     896    data->client_connection =
     897      g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (data->server),
     898                                              G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
     899                                              NULL, /* GDBusAuthObserver */
     900                                              NULL, /* cancellable */
     901                                              &error);
     902    g_assert_no_error (error);
     903    g_assert (data->client_connection != NULL);
     904    await_server_connection (data);
     905  }
     906  
     907  static void
     908  peer_connection_down (PeerConnection *data)
     909  {
     910    g_object_unref (data->client_connection);
     911    g_object_unref (data->server_connection);
     912  
     913    g_dbus_server_stop (data->server);
     914    g_object_unref (data->server);
     915  
     916    g_main_loop_quit (data->service_loop);
     917    g_thread_join (data->service_thread);
     918  
     919    g_mutex_clear (&data->service_loop_lock);
     920    g_cond_clear (&data->service_loop_cond);
     921  }
     922  
     923  struct roundtrip_state
     924  {
     925    RandomMenu *random;
     926    MirrorMenu *proxy_mirror;
     927    GDBusMenuModel *proxy;
     928    GMainLoop *loop;
     929    GRand *rand;
     930    gint success;
     931    gint count;
     932  };
     933  
     934  static gboolean
     935  roundtrip_step (gpointer data)
     936  {
     937    struct roundtrip_state *state = data;
     938  
     939    if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)) &&
     940        check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy_mirror)))
     941      {
     942        state->success++;
     943        state->count = 0;
     944  
     945        if (state->success < 100)
     946          random_menu_change (state->random, state->rand);
     947        else
     948          g_main_loop_quit (state->loop);
     949      }
     950    else if (state->count == 100)
     951      {
     952        assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
     953        g_assert_not_reached ();
     954      }
     955    else
     956      state->count++;
     957  
     958    return G_SOURCE_CONTINUE;
     959  }
     960  
     961  static void
     962  do_roundtrip (GDBusConnection *exporter_connection,
     963                GDBusConnection *proxy_connection)
     964  {
     965    struct roundtrip_state state;
     966    guint export_id;
     967    guint id;
     968  
     969    state.rand = g_rand_new_with_seed (g_test_rand_int ());
     970  
     971    state.random = random_menu_new (state.rand, 2);
     972    export_id = g_dbus_connection_export_menu_model (exporter_connection,
     973                                                     "/",
     974                                                     G_MENU_MODEL (state.random),
     975                                                     NULL);
     976    state.proxy = g_dbus_menu_model_get (proxy_connection,
     977                                         g_dbus_connection_get_unique_name (proxy_connection),
     978                                         "/");
     979    state.proxy_mirror = mirror_menu_new (G_MENU_MODEL (state.proxy));
     980    state.count = 0;
     981    state.success = 0;
     982  
     983    id = g_timeout_add (10, roundtrip_step, &state);
     984  
     985    state.loop = g_main_loop_new (NULL, FALSE);
     986    g_main_loop_run (state.loop);
     987  
     988    g_main_loop_unref (state.loop);
     989    g_source_remove (id);
     990    g_object_unref (state.proxy);
     991    g_dbus_connection_unexport_menu_model (exporter_connection, export_id);
     992    g_object_unref (state.random);
     993    g_object_unref (state.proxy_mirror);
     994    g_rand_free (state.rand);
     995  }
     996  
     997  static void
     998  test_dbus_roundtrip (void)
     999  {
    1000    GDBusConnection *bus;
    1001  
    1002    bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
    1003    do_roundtrip (bus, bus);
    1004    g_object_unref (bus);
    1005  }
    1006  
    1007  static void
    1008  test_dbus_peer_roundtrip (void)
    1009  {
    1010  #ifdef _GLIB_ADDRESS_SANITIZER
    1011    g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313");
    1012    (void) peer_connection_up;
    1013    (void) peer_connection_down;
    1014  #else
    1015    PeerConnection peer;
    1016  
    1017    peer_connection_up (&peer);
    1018    do_roundtrip (peer.server_connection, peer.client_connection);
    1019    peer_connection_down (&peer);
    1020  #endif
    1021  }
    1022  
    1023  static gint items_changed_count;
    1024  
    1025  static void
    1026  items_changed (GMenuModel *model,
    1027                 gint        position,
    1028                 gint        removed,
    1029                 gint        added,
    1030                 gpointer    data)
    1031  {
    1032    items_changed_count++;
    1033  }
    1034  
    1035  static gboolean
    1036  stop_loop (gpointer data)
    1037  {
    1038    GMainLoop *loop = data;
    1039  
    1040    g_main_loop_quit (loop);
    1041  
    1042    return G_SOURCE_REMOVE;
    1043  }
    1044  
    1045  static void
    1046  do_subscriptions (GDBusConnection *exporter_connection,
    1047                    GDBusConnection *proxy_connection)
    1048  {
    1049    GMenu *menu;
    1050    GDBusMenuModel *proxy;
    1051    GMainLoop *loop;
    1052    GError *error = NULL;
    1053    guint export_id;
    1054    guint timeout_id;
    1055  
    1056    timeout_id = add_timeout (60);
    1057    loop = g_main_loop_new (NULL, FALSE);
    1058  
    1059    menu = g_menu_new ();
    1060  
    1061    export_id = g_dbus_connection_export_menu_model (exporter_connection,
    1062                                                     "/",
    1063                                                     G_MENU_MODEL (menu),
    1064                                                     &error);
    1065    g_assert_no_error (error);
    1066  
    1067    proxy = g_dbus_menu_model_get (proxy_connection,
    1068                                   g_dbus_connection_get_unique_name (proxy_connection),
    1069                                   "/");
    1070    items_changed_count = 0;
    1071    g_signal_connect (proxy, "items-changed",
    1072                      G_CALLBACK (items_changed), NULL);
    1073  
    1074    g_menu_append (menu, "item1", NULL);
    1075    g_menu_append (menu, "item2", NULL);
    1076    g_menu_append (menu, "item3", NULL);
    1077  
    1078    g_assert_cmpint (items_changed_count, ==, 0);
    1079  
    1080    /* We don't subscribe to change-notification until we look at the items */
    1081    g_timeout_add (100, stop_loop, loop);
    1082    g_main_loop_run (loop);
    1083  
    1084    /* Looking at the items triggers subscription */
    1085    g_menu_model_get_n_items (G_MENU_MODEL (proxy));
    1086  
    1087    while (items_changed_count < 1)
    1088      g_main_context_iteration (NULL, TRUE);
    1089  
    1090    /* We get all three items in one batch */
    1091    g_assert_cmpint (items_changed_count, ==, 1);
    1092    g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
    1093  
    1094    /* If we wait, we don't get any more */
    1095    g_timeout_add (100, stop_loop, loop);
    1096    g_main_loop_run (loop);
    1097    g_assert_cmpint (items_changed_count, ==, 1);
    1098    g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
    1099  
    1100    /* Now we're subscribed, we get changes individually */
    1101    g_menu_append (menu, "item4", NULL);
    1102    g_menu_append (menu, "item5", NULL);
    1103    g_menu_append (menu, "item6", NULL);
    1104    g_menu_remove (menu, 0);
    1105    g_menu_remove (menu, 0);
    1106  
    1107    while (items_changed_count < 6)
    1108      g_main_context_iteration (NULL, TRUE);
    1109  
    1110    g_assert_cmpint (items_changed_count, ==, 6);
    1111  
    1112    g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4);
    1113  
    1114    /* After destroying the proxy and waiting a bit, we don't get any more
    1115     * items-changed signals */
    1116    g_object_unref (proxy);
    1117  
    1118    g_timeout_add (100, stop_loop, loop);
    1119    g_main_loop_run (loop);
    1120  
    1121    g_menu_remove (menu, 0);
    1122    g_menu_remove (menu, 0);
    1123  
    1124    g_timeout_add (100, stop_loop, loop);
    1125    g_main_loop_run (loop);
    1126  
    1127    g_assert_cmpint (items_changed_count, ==, 6);
    1128  
    1129    g_dbus_connection_unexport_menu_model (exporter_connection, export_id);
    1130    g_object_unref (menu);
    1131  
    1132    g_main_loop_unref (loop);
    1133    cancel_timeout (timeout_id);
    1134  }
    1135  
    1136  static void
    1137  test_dbus_subscriptions (void)
    1138  {
    1139    GDBusConnection *bus;
    1140  
    1141    bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
    1142    do_subscriptions (bus, bus);
    1143    g_object_unref (bus);
    1144  }
    1145  
    1146  static void
    1147  test_dbus_peer_subscriptions (void)
    1148  {
    1149  #ifdef _GLIB_ADDRESS_SANITIZER
    1150    g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313");
    1151    (void) peer_connection_up;
    1152    (void) peer_connection_down;
    1153  #else
    1154    PeerConnection peer;
    1155  
    1156    peer_connection_up (&peer);
    1157    do_subscriptions (peer.server_connection, peer.client_connection);
    1158    peer_connection_down (&peer);
    1159  #endif
    1160  }
    1161  
    1162  static gpointer
    1163  do_modify (gpointer data)
    1164  {
    1165    RandomMenu *menu = data;
    1166    GRand *rand;
    1167    gint i;
    1168  
    1169    rand = g_rand_new_with_seed (g_test_rand_int ());
    1170  
    1171    for (i = 0; i < 10000; i++)
    1172      {
    1173        random_menu_change (menu, rand);
    1174      }
    1175  
    1176    g_rand_free (rand);
    1177  
    1178    return NULL;
    1179  }
    1180  
    1181  static gpointer
    1182  do_export (gpointer data)
    1183  {
    1184    GMenuModel *menu = data;
    1185    gint i;
    1186    GDBusConnection *bus;
    1187    gchar *path;
    1188    GError *error = NULL;
    1189    guint id;
    1190  
    1191    bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
    1192    path = g_strdup_printf ("/%p", data);
    1193  
    1194    for (i = 0; i < 10000; i++)
    1195      {
    1196        id = g_dbus_connection_export_menu_model (bus, path, menu, &error);
    1197        g_assert_no_error (error);
    1198        g_dbus_connection_unexport_menu_model (bus, id);
    1199        while (g_main_context_iteration (NULL, FALSE));
    1200      }
    1201  
    1202    g_free (path);
    1203  
    1204    g_object_unref (bus);
    1205  
    1206    return NULL;
    1207  }
    1208  
    1209  static void
    1210  test_dbus_threaded (void)
    1211  {
    1212    RandomMenu *menu[10];
    1213    GThread *call[10];
    1214    GThread *export[10];
    1215    gint i;
    1216  
    1217    for (i = 0; i < 10; i++)
    1218      {
    1219        GRand *rand = g_rand_new_with_seed (g_test_rand_int ());
    1220        menu[i] = random_menu_new (rand, 2);
    1221        call[i] = g_thread_new ("call", do_modify, menu[i]);
    1222        export[i] = g_thread_new ("export", do_export, menu[i]);
    1223        g_rand_free (rand);
    1224      }
    1225  
    1226    for (i = 0; i < 10; i++)
    1227      {
    1228        g_thread_join (call[i]);
    1229        g_thread_join (export[i]);
    1230      }
    1231  
    1232    for (i = 0; i < 10; i++)
    1233      g_object_unref (menu[i]);
    1234  }
    1235  
    1236  static void
    1237  test_attributes (void)
    1238  {
    1239    GMenu *menu;
    1240    GMenuItem *item;
    1241    GVariant *v;
    1242  
    1243    menu = g_menu_new ();
    1244  
    1245    item = g_menu_item_new ("test", NULL);
    1246    g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
    1247    g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
    1248  
    1249    g_menu_item_set_attribute (item, "double", "d", 1.5);
    1250    v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
    1251    g_menu_item_set_attribute_value (item, "complex", v);
    1252    g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
    1253  
    1254    g_menu_append_item (menu, item);
    1255  
    1256    g_menu_item_set_attribute (item, "double", "d", G_PI);
    1257  
    1258    g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
    1259  
    1260    v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
    1261    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
    1262    g_variant_unref (v);
    1263  
    1264    v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
    1265    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
    1266    g_variant_unref (v);
    1267  
    1268    v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
    1269    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
    1270    g_variant_unref (v);
    1271  
    1272    v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
    1273    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
    1274    g_variant_unref (v);
    1275  
    1276    g_menu_remove_all (menu);
    1277  
    1278    g_object_unref (menu);
    1279    g_object_unref (item);
    1280  }
    1281  
    1282  static void
    1283  test_attribute_iter (void)
    1284  {
    1285    GMenu *menu;
    1286    GMenuItem *item;
    1287    const gchar *name;
    1288    GVariant *v;
    1289    GMenuAttributeIter *iter;
    1290    GHashTable *found;
    1291  
    1292    menu = g_menu_new ();
    1293  
    1294    item = g_menu_item_new ("test", NULL);
    1295    g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
    1296    g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
    1297  
    1298    g_menu_item_set_attribute (item, "double", "d", 1.5);
    1299    v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
    1300    g_menu_item_set_attribute_value (item, "complex", v);
    1301    g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
    1302  
    1303    g_menu_append_item (menu, item);
    1304  
    1305    g_menu_item_set_attribute (item, "double", "d", G_PI);
    1306  
    1307    g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
    1308  
    1309    found = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); 
    1310  
    1311    iter = g_menu_model_iterate_item_attributes (G_MENU_MODEL (menu), 0);
    1312    while (g_menu_attribute_iter_get_next (iter, &name, &v))
    1313      g_hash_table_insert (found, g_strdup (name), v);
    1314    g_object_unref (iter);
    1315  
    1316    g_assert_cmpint (g_hash_table_size (found), ==, 6);
    1317    
    1318    v = g_hash_table_lookup (found, "label");
    1319    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
    1320  
    1321    v = g_hash_table_lookup (found, "boolean");
    1322    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
    1323   
    1324    v = g_hash_table_lookup (found, "string");
    1325    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
    1326  
    1327    v = g_hash_table_lookup (found, "double");
    1328    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
    1329  
    1330    v = g_hash_table_lookup (found, "complex");
    1331    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
    1332  
    1333    v = g_hash_table_lookup (found, "test-123");
    1334    g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
    1335  
    1336    g_hash_table_unref (found);
    1337  
    1338    g_menu_remove_all (menu);
    1339  
    1340    g_object_unref (menu);
    1341    g_object_unref (item);
    1342  }
    1343  
    1344  static void
    1345  test_links (void)
    1346  {
    1347    GMenu *menu;
    1348    GMenuModel *m;
    1349    GMenuModel *x;
    1350    GMenuItem *item;
    1351  
    1352    m = G_MENU_MODEL (g_menu_new ());
    1353    g_menu_append (G_MENU (m), "test", NULL);
    1354  
    1355    menu = g_menu_new ();
    1356  
    1357    item = g_menu_item_new ("test2", NULL);
    1358    g_menu_item_set_link (item, "submenu", m);
    1359    g_menu_prepend_item (menu, item);
    1360    g_object_unref (item);
    1361  
    1362    item = g_menu_item_new ("test1", NULL);
    1363    g_menu_item_set_link (item, "section", m);
    1364    g_menu_insert_item (menu, 0, item);
    1365    g_object_unref (item);
    1366  
    1367    item = g_menu_item_new ("test3", NULL);
    1368    g_menu_item_set_link (item, "wallet", m);
    1369    g_menu_insert_item (menu, 1000, item);
    1370    g_object_unref (item);
    1371  
    1372    item = g_menu_item_new ("test4", NULL);
    1373    g_menu_item_set_link (item, "purse", m);
    1374    g_menu_item_set_link (item, "purse", NULL);
    1375    g_menu_append_item (menu, item);
    1376    g_object_unref (item);
    1377  
    1378    g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
    1379  
    1380    x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
    1381    g_assert (x == m);
    1382    g_object_unref (x);
    1383  
    1384    x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
    1385    g_assert (x == m);
    1386    g_object_unref (x);
    1387  
    1388    x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
    1389    g_assert (x == m);
    1390    g_object_unref (x);
    1391  
    1392    x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
    1393    g_assert (x == NULL);
    1394  
    1395    g_object_unref (m);
    1396    g_object_unref (menu);
    1397  }
    1398  
    1399  static void
    1400  test_mutable (void)
    1401  {
    1402    GMenu *menu;
    1403  
    1404    menu = g_menu_new ();
    1405    g_menu_append (menu, "test", "test");
    1406  
    1407    g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
    1408    g_menu_freeze (menu);
    1409    g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
    1410  
    1411    g_object_unref (menu);
    1412  }
    1413  
    1414  static void
    1415  test_convenience (void)
    1416  {
    1417    GMenu *m1, *m2;
    1418    GMenu *sub;
    1419    GMenuItem *item;
    1420  
    1421    m1 = g_menu_new ();
    1422    m2 = g_menu_new ();
    1423    sub = g_menu_new ();
    1424  
    1425    g_menu_prepend (m1, "label1", "do::something");
    1426    g_menu_insert (m2, 0, "label1", "do::something");
    1427  
    1428    g_menu_append (m1, "label2", "do::somethingelse");
    1429    g_menu_insert (m2, -1, "label2", "do::somethingelse");
    1430  
    1431    g_menu_insert_section (m1, 10, "label3", G_MENU_MODEL (sub));
    1432    item = g_menu_item_new_section ("label3", G_MENU_MODEL (sub));
    1433    g_menu_insert_item (m2, 10, item);
    1434    g_object_unref (item);
    1435  
    1436    g_menu_prepend_section (m1, "label4", G_MENU_MODEL (sub));
    1437    g_menu_insert_section (m2, 0, "label4", G_MENU_MODEL (sub));
    1438  
    1439    g_menu_append_section (m1, "label5", G_MENU_MODEL (sub));
    1440    g_menu_insert_section (m2, -1, "label5", G_MENU_MODEL (sub));
    1441  
    1442    g_menu_insert_submenu (m1, 5, "label6", G_MENU_MODEL (sub));
    1443    item = g_menu_item_new_submenu ("label6", G_MENU_MODEL (sub));
    1444    g_menu_insert_item (m2, 5, item);
    1445    g_object_unref (item);
    1446  
    1447    g_menu_prepend_submenu (m1, "label7", G_MENU_MODEL (sub));
    1448    g_menu_insert_submenu (m2, 0, "label7", G_MENU_MODEL (sub));
    1449  
    1450    g_menu_append_submenu (m1, "label8", G_MENU_MODEL (sub));
    1451    g_menu_insert_submenu (m2, -1, "label8", G_MENU_MODEL (sub));
    1452  
    1453    assert_menus_equal (G_MENU_MODEL (m1), G_MENU_MODEL (m2));
    1454  
    1455    g_object_unref (m1);
    1456    g_object_unref (m2);
    1457    g_object_unref (sub);
    1458  }
    1459  
    1460  static void
    1461  test_menuitem (void)
    1462  {
    1463    GMenu *menu;
    1464    GMenu *submenu;
    1465    GMenuItem *item;
    1466    GIcon *icon;
    1467    gboolean b;
    1468    gchar *s;
    1469  
    1470    menu = g_menu_new ();
    1471    submenu = g_menu_new ();
    1472  
    1473    item = g_menu_item_new ("label", "action");
    1474    g_menu_item_set_attribute (item, "attribute", "b", TRUE);
    1475    g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, G_MENU_MODEL (submenu));
    1476    g_menu_append_item (menu, item);
    1477  
    1478    icon = g_themed_icon_new ("bla");
    1479    g_menu_item_set_icon (item, icon);
    1480    g_object_unref (icon);
    1481  
    1482    g_assert (g_menu_item_get_attribute (item, "attribute", "b", &b));
    1483    g_assert (b);
    1484  
    1485    g_menu_item_set_action_and_target (item, "action", "(bs)", TRUE, "string");
    1486    g_assert (g_menu_item_get_attribute (item, "target", "(bs)", &b, &s));
    1487    g_assert (b);
    1488    g_assert_cmpstr (s, ==, "string");
    1489    g_free (s);
    1490  
    1491    g_object_unref (item);
    1492  
    1493    item = g_menu_item_new_from_model (G_MENU_MODEL (menu), 0);
    1494    assert_menuitem_equal (item, G_MENU_MODEL (menu), 0);
    1495    g_object_unref (item);
    1496  
    1497    g_object_unref (menu);
    1498    g_object_unref (submenu);
    1499  }
    1500  
    1501  static GDBusInterfaceInfo *
    1502  org_gtk_Menus_get_interface (void)
    1503  {
    1504    static GDBusInterfaceInfo *interface_info;
    1505  
    1506    if (interface_info == NULL)
    1507      {
    1508        GError *error = NULL;
    1509        GDBusNodeInfo *info;
    1510  
    1511        info = g_dbus_node_info_new_for_xml ("<node>"
    1512                                             "  <interface name='org.gtk.Menus'>"
    1513                                             "    <method name='Start'>"
    1514                                             "      <arg type='au' name='groups' direction='in'/>"
    1515                                             "      <arg type='a(uuaa{sv})' name='content' direction='out'/>"
    1516                                             "    </method>"
    1517                                             "    <method name='End'>"
    1518                                             "      <arg type='au' name='groups' direction='in'/>"
    1519                                             "    </method>"
    1520                                             "    <signal name='Changed'>"
    1521                                             "      arg type='a(uuuuaa{sv})' name='changes'/>"
    1522                                             "    </signal>"
    1523                                             "  </interface>"
    1524                                             "</node>", &error);
    1525        if (info == NULL)
    1526          g_error ("%s\n", error->message);
    1527        interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
    1528        g_assert (interface_info != NULL);
    1529        g_dbus_interface_info_ref (interface_info);
    1530        g_dbus_node_info_unref (info);
    1531      }
    1532  
    1533    return interface_info;
    1534  }
    1535  
    1536  static void
    1537  g_menu_exporter_method_call (GDBusConnection       *connection,
    1538                               const gchar           *sender,
    1539                               const gchar           *object_path,
    1540                               const gchar           *interface_name,
    1541                               const gchar           *method_name,
    1542                               GVariant              *parameters,
    1543                               GDBusMethodInvocation *invocation,
    1544                               gpointer               user_data)
    1545  {
    1546    const struct {
    1547      guint position;
    1548      guint removed;
    1549    } data[] = {
    1550        { -2, 4 },
    1551        { 0, 3 },
    1552        { 4, 1 }
    1553    };
    1554    gsize i;
    1555    GError *error = NULL;
    1556  
    1557    g_dbus_method_invocation_return_value (invocation, g_variant_new_parsed ("@(a(uuaa{sv})) ([(0, 0, [{ 'label': <'test'> }])],)"));
    1558  
    1559    /* invalid signatures */
    1560    g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed",
    1561                                   g_variant_new_parsed ("([(1, 2, 3)],)"), &error);
    1562    g_assert_no_error (error);
    1563  
    1564    /* add an item at an invalid position */
    1565    g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*");
    1566    g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed",
    1567                                   g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [{ 'label': <'test'> }])],)", 0, 0, 2, 0),
    1568                                   &error);
    1569    g_assert_no_error (error);
    1570  
    1571    for (i = 0; i < G_N_ELEMENTS (data); i++)
    1572      {
    1573        GVariant *params;
    1574  
    1575        g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*invalid*");
    1576        params = g_variant_new_parsed ("@(a(uuuuaa{sv})) ([(%u, %u, %u, %u, [])],)", 0, 0, data[i].position, data[i].removed);
    1577        g_dbus_connection_emit_signal (connection, sender, "/", "org.gtk.Menus", "Changed", params, &error);
    1578        g_assert_no_error (error);
    1579      }
    1580  }
    1581  
    1582  static void
    1583  menu_changed (GMenuModel *menu,
    1584               gint        position,
    1585                gint        removed,
    1586                gint        added,
    1587                gpointer    user_data)
    1588  {
    1589    unsigned int *counter = user_data;
    1590  
    1591    *counter += 1;
    1592  }
    1593  
    1594  static void
    1595  test_input_validation (void)
    1596  {
    1597    const GDBusInterfaceVTable vtable = {
    1598      g_menu_exporter_method_call, NULL, NULL, { NULL, }
    1599    };
    1600    GError *error = NULL;
    1601    GDBusConnection *bus;
    1602    GDBusMenuModel *proxy;
    1603    guint id;
    1604    const gchar *bus_name;
    1605    GMainLoop *loop;
    1606    unsigned int n_signal_emissions = 0;
    1607    gulong signal_id;
    1608  
    1609    g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/861");
    1610  
    1611    bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
    1612    g_assert_no_error (error);
    1613  
    1614    id = g_dbus_connection_register_object (bus, "/", org_gtk_Menus_get_interface (),
    1615                                            &vtable, NULL, NULL, &error);
    1616    g_assert_no_error (error);
    1617  
    1618    bus_name = g_dbus_connection_get_unique_name (bus);
    1619    proxy = g_dbus_menu_model_get (bus, bus_name, "/");
    1620  
    1621    signal_id = g_signal_connect (proxy, "items-changed", G_CALLBACK (menu_changed), &n_signal_emissions);
    1622  
    1623    /* get over laziness */
    1624    g_menu_model_get_n_items (G_MENU_MODEL (proxy));
    1625  
    1626    loop = g_main_loop_new (NULL, FALSE);
    1627    g_timeout_add (100, stop_loop, loop);
    1628    g_main_loop_run (loop);
    1629  
    1630    /* "items-changed" should only be emitted for the initial contents of
    1631     * the menu. Subsequent calls are all invalid.
    1632     */
    1633    g_assert_cmpuint (n_signal_emissions, ==, 1);
    1634  
    1635    g_test_assert_expected_messages ();
    1636  
    1637    g_main_loop_unref (loop);
    1638    g_dbus_connection_unregister_object (bus, id);
    1639    g_signal_handler_disconnect (proxy, signal_id);
    1640    g_object_unref (proxy);
    1641    g_object_unref (bus);
    1642  }
    1643  
    1644  /* Epilogue {{{1 */
    1645  int
    1646  main (int argc, char **argv)
    1647  {
    1648    gboolean ret;
    1649  
    1650    g_test_init (&argc, &argv, NULL);
    1651  
    1652    session_bus_up ();
    1653  
    1654    g_test_add_func ("/gmenu/equality", test_equality);
    1655    g_test_add_func ("/gmenu/random", test_random);
    1656    g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip);
    1657    g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions);
    1658    g_test_add_func ("/gmenu/dbus/threaded", test_dbus_threaded);
    1659    g_test_add_func ("/gmenu/dbus/peer/roundtrip", test_dbus_peer_roundtrip);
    1660    g_test_add_func ("/gmenu/dbus/peer/subscriptions", test_dbus_peer_subscriptions);
    1661    g_test_add_func ("/gmenu/attributes", test_attributes);
    1662    g_test_add_func ("/gmenu/attributes/iterate", test_attribute_iter);
    1663    g_test_add_func ("/gmenu/links", test_links);
    1664    g_test_add_func ("/gmenu/mutable", test_mutable);
    1665    g_test_add_func ("/gmenu/convenience", test_convenience);
    1666    g_test_add_func ("/gmenu/menuitem", test_menuitem);
    1667    g_test_add_func ("/gmenu/input-validation", test_input_validation);
    1668  
    1669    ret = g_test_run ();
    1670  
    1671    session_bus_down ();
    1672  
    1673    return ret;
    1674  }
    1675  /* vim:set foldmethod=marker: */