/* GVariant to dbus-glib escape hatch
 *
 * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
 *
 * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later OR LGPL-2.1-or-later
 *
 * Licensed under the Academic Free License version 2.1
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Alternatively, at your option, you can redistribute and/or modify
 * this single file under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * that license, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <config.h>
#undef G_DISABLE_ASSERT

#include <dbus/dbus-glib.h>
#include <gio/gio.h>

/**
 * test_g_variant_equivalent:
 *
 * The function g_variant_equal() cannot be used for dictionaries because it
 * cares about the ordering of dictionaries, which breaks our tests.
 */
static gboolean
test_g_variant_equivalent (GVariant *one,
    GVariant *two)
{
  if (!g_variant_type_equal (
        g_variant_get_type (one),
        g_variant_get_type (two)))
    {
      return FALSE;
    }
  else if (g_variant_is_of_type (one, G_VARIANT_TYPE_DICTIONARY) &&
           g_variant_is_of_type (two, G_VARIANT_TYPE_DICTIONARY))
    {
      GHashTable *hash;
      GVariantIter iter;
      GVariant *child;
      gboolean equal = TRUE;

      if (g_variant_n_children (one) != g_variant_n_children (two))
        return FALSE;

      /* pack @one into a hash table */
      hash = g_hash_table_new_full (g_variant_hash, g_variant_equal,
          (GDestroyNotify) g_variant_unref, (GDestroyNotify) g_variant_unref);

      g_variant_iter_init (&iter, one);
      while ((child = g_variant_iter_next_value (&iter)))
        {
          g_hash_table_insert (hash,
              g_variant_get_child_value (child, 0),
              g_variant_get_child_value (child, 1));
          g_variant_unref (child);
        }

      /* now iterate @two to check for the keys in @hash */
      g_variant_iter_init (&iter, two);
      while (equal && (child = g_variant_iter_next_value (&iter)))
        {
          GVariant *k, *v1, *v2;

          k = g_variant_get_child_value (child, 0);
          v1 = g_variant_get_child_value (child, 1);

          v2 = g_hash_table_lookup (hash, k);

          if (v2 == NULL || !test_g_variant_equivalent (v1, v2))
            equal = FALSE;
          else
            g_hash_table_remove (hash, k);

          g_variant_unref (k);
          g_variant_unref (v1);
          g_variant_unref (child);
        }

      if (g_hash_table_size (hash) > 0)
        equal = FALSE;

      g_hash_table_destroy (hash);

      return equal;
    }
  else if (g_variant_is_container (one) &&
           g_variant_is_container (two))
    {
      guint i, size;
      gboolean equal = TRUE;

      if (g_variant_n_children (one) != g_variant_n_children (two))
        return FALSE;

      size = g_variant_n_children (one);
      for (i = 0; equal && i < size; i++)
        {
          GVariant *child1, *child2;

          child1 = g_variant_get_child_value (one, i);
          child2 = g_variant_get_child_value (two, i);

          equal = test_g_variant_equivalent (child1, child2);

          g_variant_unref (child1);
          g_variant_unref (child2);
        }

      return equal;
    }
  else
    {
      return g_variant_equal (one, two);
    }
}

#define assert_g_variant_equivalent(a,e) \
  assert_g_variant_equivalent_internal (__FILE__, __LINE__, \
      #a, a, #e, e)

static void
assert_g_variant_equivalent_internal (
    const gchar *file, gint line,
    const gchar *actual_name, GVariant *actual,
    const gchar *expected_name, GVariant *expected);

static void
assert_g_variant_equivalent_internal (const gchar *file,
    gint line,
    const gchar *actual_name,
    GVariant *actual,
    const gchar *expected_name,
    GVariant *expected)
{
  if (!test_g_variant_equivalent (actual, expected))
    {
      gchar *a = g_variant_print (actual, TRUE);
      gchar *e = g_variant_print (expected, TRUE);

      g_error ("%s:%d: Variants should have been equal:\n"
          "%s = %s\n"
          "%s = %s", file, line, actual_name, a, expected_name, e);
      /* no point in freeing the strings, we've just crashed anyway */
    }
}

/* test_g_variant_equivalent tests */
static void
test_simple_equiv (void)
{
  GVariant *v1, *v2;

  v1 = g_variant_new_int32 (1984);
  v2 = g_variant_new_int32 (1984);

  g_assert_true (test_g_variant_equivalent (v1, v2));

  g_variant_unref (v1);
  g_variant_unref (v2);
}

static void
test_simple_not_equiv (void)
{
  GVariant *v1, *v2;

  v1 = g_variant_new_int32 (1982);
  v2 = g_variant_new_int32 (1984);

  g_assert_false (test_g_variant_equivalent (v1, v2));

  g_variant_unref (v1);
  g_variant_unref (v2);
}

static void
test_array_not_equiv (void)
{
  GVariantBuilder b;
  GVariant *v1, *v2;

  g_variant_builder_init (&b, G_VARIANT_TYPE ("av"));
  g_variant_builder_add (&b, "v", g_variant_new_int32 (1984));
  g_variant_builder_add (&b, "v", g_variant_new_string ("Orwell"));
  g_variant_builder_add (&b, "v", g_variant_new_object_path ("/cats/escher"));
  v1 = g_variant_builder_end (&b);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("av"));
  /* note the order has changed */
  g_variant_builder_add (&b, "v", g_variant_new_string ("Orwell"));
  g_variant_builder_add (&b, "v", g_variant_new_int32 (1984));
  g_variant_builder_add (&b, "v", g_variant_new_object_path ("/cats/escher"));
  v2 = g_variant_builder_end (&b);

  g_assert_false (test_g_variant_equivalent (v1, v2));

  g_variant_unref (v1);
  g_variant_unref (v2);
}

static void
test_map_equiv (void)
{
  GVariantBuilder b;
  GVariant *v1, *v2;

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{os}"));
  g_variant_builder_add (&b, "{os}", "/cats/escher", "Escher Moonbeam");
  g_variant_builder_add (&b, "{os}", "/cats/harvey", "Harvey Nomcat");
  g_variant_builder_add (&b, "{os}", "/cats/josh", "Josh Smith");
  v1 = g_variant_builder_end (&b);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{os}"));
  /* note the order has changed */
  g_variant_builder_add (&b, "{os}", "/cats/harvey", "Harvey Nomcat");
  g_variant_builder_add (&b, "{os}", "/cats/escher", "Escher Moonbeam");
  g_variant_builder_add (&b, "{os}", "/cats/josh", "Josh Smith");
  v2 = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (v1, v2));

  g_variant_unref (v1);
  g_variant_unref (v2);
}

static void
test_map_not_equiv1 (void)
{
  GVariantBuilder b;
  GVariant *v1, *v2;

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{os}"));
  g_variant_builder_add (&b, "{os}", "/cats/escher", "Escher Moonbeam");
  g_variant_builder_add (&b, "{os}", "/cats/harvey", "Harvey Nomcat");
  g_variant_builder_add (&b, "{os}", "/cats/josh", "Josh Smith");
  v1 = g_variant_builder_end (&b);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{os}"));
  g_variant_builder_add (&b, "{os}", "/cats/escher", "Escher Moonbeam");
  g_variant_builder_add (&b, "{os}", "/cats/harvey", "Harvey Nomcat");
  g_variant_builder_add (&b, "{os}", "/cats/josh", "Josh Smith");
  g_variant_builder_add (&b, "{os}", "/cats/rory", "Rory Cat");
  v2 = g_variant_builder_end (&b);

  g_assert_false (test_g_variant_equivalent (v1, v2));

  g_variant_unref (v1);
  g_variant_unref (v2);
}

static void
test_map_not_equiv2 (void)
{
  GVariantBuilder b;
  GVariant *v1, *v2;

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{os}"));
  g_variant_builder_add (&b, "{os}", "/cats/escher", "Escher Moonbeam");
  g_variant_builder_add (&b, "{os}", "/cats/harvey", "Harvey Nomcat");
  g_variant_builder_add (&b, "{os}", "/cats/josh", "Josh Smith");
  v1 = g_variant_builder_end (&b);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{os}"));
  g_variant_builder_add (&b, "{os}", "/cats/escher", "Escher Moonbeam");
  g_variant_builder_add (&b, "{os}", "/cats/harvey", "Harvey Nomcat");
  g_variant_builder_add (&b, "{os}", "/cats/josh", "Josh Cat");
  v2 = g_variant_builder_end (&b);

  g_assert_false (test_g_variant_equivalent (v1, v2));

  g_variant_unref (v1);
  g_variant_unref (v2);
}

/* dbus_g_value_build_g_variant tests */
static void
test_i (void)
{
  GValue v = { 0, };
  GVariant *var, *varc;

  g_value_init (&v, G_TYPE_INT);
  g_value_set_int (&v, 1984);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  varc = g_variant_new_int32 (1984);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_s (void)
{
  GValue v = { 0, };
  GVariant *var, *varc;

  g_value_init (&v, G_TYPE_STRING);
  g_value_set_static_string (&v, "Orwell");

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  varc = g_variant_new_string ("Orwell");

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_o (void)
{
  GValue v = { 0, };
  GVariant *var, *varc;

  g_value_init (&v, DBUS_TYPE_G_OBJECT_PATH);
  g_value_set_boxed (&v, "/cats/escher");

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  varc = g_variant_new_object_path ("/cats/escher");

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_us (void)
{
  GValue v = { 0, };
  GVariant *var, *varc;
  GType us = dbus_g_type_get_struct ("GValueArray",
      G_TYPE_UINT,
      G_TYPE_STRING,
      G_TYPE_INVALID);

  g_value_init (&v, us);
  g_value_take_boxed (&v, dbus_g_type_specialized_construct (us));
  dbus_g_type_struct_set (&v,
      0, 1984,
      1, "Orwell",
      G_MAXUINT);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  varc = g_variant_new ("(us)", 1984, "Orwell");

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_a_os (void)
{
  GValue v = { 0, };
  GHashTable *map;
  GVariantBuilder b;
  GVariant *var, *varc;
  GType a_os = dbus_g_type_get_map ("GHashTable",
      DBUS_TYPE_G_OBJECT_PATH,
      G_TYPE_STRING);

  g_value_init (&v, a_os);
  map = dbus_g_type_specialized_construct (a_os);

  g_hash_table_insert (map,
      g_strdup ("/cats/escher"), g_strdup ("Escher Moonbeam"));
  g_hash_table_insert (map,
      g_strdup ("/cats/harvey"), g_strdup ("Harvey Nomcat"));
  g_hash_table_insert (map,
      g_strdup ("/cats/josh"), g_strdup ("Josh Smith"));
  g_value_take_boxed (&v, map);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("a{os}"));
  g_variant_builder_add (&b, "{os}", "/cats/escher", "Escher Moonbeam");
  g_variant_builder_add (&b, "{os}", "/cats/harvey", "Harvey Nomcat");
  g_variant_builder_add (&b, "{os}", "/cats/josh", "Josh Smith");
  varc = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_av (void)
{
  GValue v = { 0, }, *v2;
  GVariantBuilder b;
  GVariant *var, *varc;
  GType av = dbus_g_type_get_collection ("GPtrArray", G_TYPE_VALUE);
  GPtrArray *array;

  g_value_init (&v, av);
  array = dbus_g_type_specialized_construct (av);

  v2 = g_new0 (GValue, 1);
  g_value_init (v2, G_TYPE_INT);
  g_value_set_int (v2, 1984);
  g_ptr_array_add (array, v2);

  v2 = g_new0 (GValue, 1);
  g_value_init (v2, G_TYPE_STRING);
  g_value_set_static_string (v2, "Orwell");
  g_ptr_array_add (array, v2);

  v2 = g_new0 (GValue, 1);
  g_value_init (v2, DBUS_TYPE_G_OBJECT_PATH);
  g_value_set_boxed (v2, "/cats/escher");
  g_ptr_array_add (array, v2);

  g_value_take_boxed (&v, array);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("av"));
  g_variant_builder_add (&b, "v", g_variant_new_int32 (1984));
  g_variant_builder_add (&b, "v", g_variant_new_string ("Orwell"));
  g_variant_builder_add (&b, "v", g_variant_new_object_path ("/cats/escher"));
  varc = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_ab (void)
{
  GValue v = { 0, };
  gboolean bools[] = { TRUE, FALSE };
  GVariantBuilder b;
  GVariant *var, *varc;
  GType ab = dbus_g_type_get_collection ("GArray", G_TYPE_BOOLEAN);
  GArray *array;

  g_value_init (&v, ab);
  array = dbus_g_type_specialized_construct (ab);

  g_array_append_vals (array, bools, 2);
  g_assert_cmpint (g_array_index (array, gboolean, 0), ==, TRUE);
  g_assert_cmpint (g_array_index (array, gboolean, 1), ==, FALSE);

  g_value_take_boxed (&v, array);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("ab"));
  g_variant_builder_add (&b, "b", TRUE);
  g_variant_builder_add (&b, "b", FALSE);
  varc = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_ai (void)
{
  GValue v = { 0, };
  gint ints[] = { 1984, 1066 };
  GVariantBuilder b;
  GVariant *var, *varc;
  GType ai = dbus_g_type_get_collection ("GArray", G_TYPE_INT);
  GArray *array;

  g_value_init (&v, ai);
  array = dbus_g_type_specialized_construct (ai);

  g_array_append_vals (array, ints, 2);
  g_assert_cmpint (g_array_index (array, gint, 0), ==, 1984);
  g_assert_cmpint (g_array_index (array, gint, 1), ==, 1066);

  g_value_take_boxed (&v, array);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("ai"));
  g_variant_builder_add (&b, "i", 1984);
  g_variant_builder_add (&b, "i", 1066);
  varc = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_au (void)
{
  GValue v = { 0, };
  guint uints[] = { 1984, 1066 };
  GVariantBuilder b;
  GVariant *var, *varc;
  GType au = dbus_g_type_get_collection ("GArray", G_TYPE_UINT);
  GArray *array;

  g_value_init (&v, au);
  array = dbus_g_type_specialized_construct (au);

  g_array_append_vals (array, uints, 2);
  g_assert_cmpuint (g_array_index (array, guint, 0), ==, 1984);
  g_assert_cmpuint (g_array_index (array, guint, 1), ==, 1066);

  g_value_take_boxed (&v, array);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("au"));
  g_variant_builder_add (&b, "u", 1984);
  g_variant_builder_add (&b, "u", 1066);
  varc = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_ax (void)
{
  GValue v = { 0, };
  gint64 ints[] = { G_GINT64_CONSTANT (-0xAAABBBBCCCCDDDD), 1066 };
  GVariantBuilder b;
  GVariant *var, *varc;
  GType ax = dbus_g_type_get_collection ("GArray", G_TYPE_INT64);
  GArray *array;

  g_value_init (&v, ax);
  array = dbus_g_type_specialized_construct (ax);

  g_array_append_vals (array, ints, 2);
  g_assert_cmpint ((g_array_index (array, gint64, 0)
        / G_GINT64_CONSTANT (0x100000000)), ==,
      -0xAAABBBB);
  g_assert_cmpuint ((-(g_array_index (array, gint64, 0)))
      % G_GINT64_CONSTANT (0x100000000), ==, 0xCCCCDDDDu);
  g_assert_cmpint ((g_array_index (array, gint64, 1)
        / G_GINT64_CONSTANT (0x100000000)), ==, 0);
  g_assert_cmpuint ((g_array_index (array, gint64, 1))
      % G_GINT64_CONSTANT (0x100000000), ==, 1066);

  g_value_take_boxed (&v, array);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("ax"));
  g_variant_builder_add (&b, "x", G_GINT64_CONSTANT (-0xAAABBBBCCCCDDDD));
  g_variant_builder_add (&b, "x", G_GINT64_CONSTANT (1066));
  varc = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_at (void)
{
  GValue v = { 0, };
  guint64 uints[] = { G_GUINT64_CONSTANT (0xAAAABBBBCCCCDDDD), 1066 };
  GVariantBuilder b;
  GVariant *var, *varc;
  GType at = dbus_g_type_get_collection ("GArray", G_TYPE_UINT64);
  GArray *array;

  g_value_init (&v, at);
  array = dbus_g_type_specialized_construct (at);

  g_array_append_vals (array, uints, 2);
  g_assert_cmpuint ((g_array_index (array, guint64, 0)
        / G_GUINT64_CONSTANT (0x100000000)), ==, 0xAAAABBBBu);
  g_assert_cmpuint ((g_array_index (array, guint64, 0)
        % G_GUINT64_CONSTANT (0x100000000)), ==, 0xCCCCDDDDu);
  g_assert_cmpuint ((g_array_index (array, guint64, 1)
        / G_GUINT64_CONSTANT (0x100000000)), ==, 0);
  g_assert_cmpuint ((g_array_index (array, guint64, 1)
        % G_GUINT64_CONSTANT (0x100000000)), ==, 1066);

  g_value_take_boxed (&v, array);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("at"));
  g_variant_builder_add (&b, "t", G_GUINT64_CONSTANT (0xAAAABBBBCCCCDDDD));
  g_variant_builder_add (&b, "t", G_GUINT64_CONSTANT (1066));
  varc = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_ay (void)
{
  GValue v = { 0, };
  guchar bytes[] = { 23, 42 };
  GVariantBuilder b;
  GVariant *var, *varc;
  GType ay = dbus_g_type_get_collection ("GArray", G_TYPE_UCHAR);
  GArray *array;

  g_value_init (&v, ay);
  array = dbus_g_type_specialized_construct (ay);

  g_array_append_vals (array, bytes, 2);
  g_assert_cmpint (g_array_index (array, guchar, 0), ==, 23);
  g_assert_cmpint (g_array_index (array, guchar, 1), ==, 42);

  g_value_take_boxed (&v, array);

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  g_variant_builder_init (&b, G_VARIANT_TYPE ("ay"));
  g_variant_builder_add (&b, "y", 23);
  g_variant_builder_add (&b, "y", 42);
  varc = g_variant_builder_end (&b);

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_g (void)
{
  GValue v = { 0, };
  GVariant *var, *varc;

  g_value_init (&v, DBUS_TYPE_G_SIGNATURE);
  g_value_set_boxed (&v, "a{u(ua{sa{sv}})}");

  var = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);

  varc = g_variant_new_signature ("a{u(ua{sa{sv}})}");

  g_assert_true (test_g_variant_equivalent (var, varc));

  g_variant_unref (var);
  g_variant_unref (varc);
}

static void
test_roundtrip (gconstpointer user_data)
{
  const gchar *text = user_data;
  GVariant *before, *after;
  GValue v = { 0 };

  before = g_variant_new_parsed (text);
  dbus_g_value_parse_g_variant (before, &v);
  after = dbus_g_value_build_g_variant (&v);
  g_value_unset (&v);
  assert_g_variant_equivalent (before, after);
  g_variant_unref (before);
  g_variant_unref (after);
}

static void
test_parse_basic (void)
{
  GVariant *variant;
  GValue v = { 0 };

  variant = g_variant_new_parsed ("'o hai'");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_STRING));
  g_assert_cmpstr (g_value_get_string (&v), ==, "o hai");
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("objectpath '/hello/world'");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==,
      g_type_name (DBUS_TYPE_G_OBJECT_PATH));
  g_assert_cmpstr ((gchar *) g_value_get_boxed (&v), ==, "/hello/world");
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("signature 'a{sv}'");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==,
      g_type_name (DBUS_TYPE_G_SIGNATURE));
  g_assert_cmpstr ((gchar *) g_value_get_boxed (&v), ==, "a{sv}");
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("23.5");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_DOUBLE));
  /* this is chosen to be exactly representable in binary; we use inequalities
   * to work around -Wfloat-equal */
  g_assert_cmpfloat (g_value_get_double (&v), >=, 23.5);
  g_assert_cmpfloat (g_value_get_double (&v), <=, 23.5);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("byte 42");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_UCHAR));
  g_assert_cmpuint (g_value_get_uchar (&v), ==, 42);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("uint16 16");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_UINT));
  g_assert_cmpuint (g_value_get_uint (&v), ==, 16);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("uint32 32");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_UINT));
  g_assert_cmpuint (g_value_get_uint (&v), ==, 32);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("uint64 64");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_UINT64));
  g_assert_cmpuint ((guint) g_value_get_uint64 (&v), ==, 64);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("int16 -16");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_INT));
  g_assert_cmpint (g_value_get_int (&v), ==, -16);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("int32 -32");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_INT));
  g_assert_cmpint (g_value_get_int (&v), ==, -32);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("int64 -64");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==, g_type_name (G_TYPE_INT64));
  g_assert_cmpint ((gint) g_value_get_int64 (&v), ==, -64);
  g_value_unset (&v);
  g_variant_unref (variant);
}

static void
test_parse_array (void)
{
  GVariant *variant;
  GValue v = { 0 };
  GArray *a;

  /* We can't test the 16-bit cases via a round-trip, because information is
   * lost. */

  variant = g_variant_new_parsed ("[uint16 16, uint16 1600]");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==,
      g_type_name (DBUS_TYPE_G_UINT_ARRAY));
  a = g_value_get_boxed (&v);
  g_assert_cmpuint (a->len, ==, 2);
  g_assert_cmpuint (g_array_index (a, guint, 0), ==, 16);
  g_assert_cmpuint (g_array_index (a, guint, 1), ==, 1600);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("[int16 -16, int16 -1600]");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==,
      g_type_name (DBUS_TYPE_G_INT_ARRAY));
  a = g_value_get_boxed (&v);
  g_assert_cmpuint (a->len, ==, 2);
  g_assert_cmpint (g_array_index (a, gint, 0), ==, -16);
  g_assert_cmpint (g_array_index (a, gint, 1), ==, -1600);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("@aq []");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==,
      g_type_name (DBUS_TYPE_G_UINT_ARRAY));
  a = g_value_get_boxed (&v);
  g_assert_cmpuint (a->len, ==, 0);
  g_value_unset (&v);
  g_variant_unref (variant);

  variant = g_variant_new_parsed ("@an []");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==,
      g_type_name (DBUS_TYPE_G_INT_ARRAY));
  a = g_value_get_boxed (&v);
  g_assert_cmpuint (a->len, ==, 0);
  g_value_unset (&v);
  g_variant_unref (variant);
}

static void
test_parse_string_hash (void)
{
  GVariant *variant;
  GHashTable *h;
  GValue v = { 0 };

  variant = g_variant_new_parsed ("@a{ss} {'foo': 'bar'}");
  dbus_g_value_parse_g_variant (variant, &v);
  g_assert_cmpstr (G_VALUE_TYPE_NAME (&v), ==,
      g_type_name (DBUS_TYPE_G_STRING_STRING_HASHTABLE));
  h = g_value_get_boxed (&v);
  g_assert_cmpuint (g_hash_table_size (h), ==, 1);
  g_assert_cmpstr (g_hash_table_lookup (h, "foo"), ==, "bar");
  g_value_unset (&v);
  g_variant_unref (variant);
}

int
main (int argc,
    char **argv)
{
  g_type_init ();
  dbus_g_type_specialized_init ();

  g_test_init (&argc, &argv, NULL);

  /* test_g_variant_equivalent tests */
  g_test_add_func ("/test_g_variant_equivalent/test_simple_equiv",
      test_simple_equiv);
  g_test_add_func ("/test_g_variant_equivalent/test_simple_not_equiv",
      test_simple_not_equiv);
  g_test_add_func ("/test_g_variant_equivalent/test_array_not_equiv",
      test_array_not_equiv);
  g_test_add_func ("/test_g_variant_equivalent/test_map_equiv",
      test_map_equiv);
  g_test_add_func ("/test_g_variant_equivalent/test_map_not_equiv1",
      test_map_not_equiv1);
  g_test_add_func ("/test_g_variant_equivalent/test_map_not_equiv2",
      test_map_not_equiv2);

  /* dbus_g_value_build_g_variant tests */
  g_test_add_func ("/gvalue-to-gvariant/i", test_i);
  g_test_add_func ("/gvalue-to-gvariant/s", test_s);
  g_test_add_func ("/gvalue-to-gvariant/o", test_o);
  g_test_add_func ("/gvalue-to-gvariant/us", test_us);
  g_test_add_func ("/gvalue-to-gvariant/a{os}", test_a_os);
  g_test_add_func ("/gvalue-to-gvariant/av", test_av);
  g_test_add_func ("/gvalue-to-gvariant/ab", test_ab);
  g_test_add_func ("/gvalue-to-gvariant/ai", test_ai);
  g_test_add_func ("/gvalue-to-gvariant/au", test_au);
  g_test_add_func ("/gvalue-to-gvariant/ax", test_ax);
  g_test_add_func ("/gvalue-to-gvariant/at", test_at);
  g_test_add_func ("/gvalue-to-gvariant/ay", test_ay);
  g_test_add_func ("/gvalue-to-gvariant/g", test_g);

  /* dbus_g_value_parse_g_variant tests */
  g_test_add_func ("/parse-gvariant/basic", test_parse_basic);
  g_test_add_func ("/parse-gvariant/array", test_parse_array);
  g_test_add_func ("/parse-gvariant/string_hash", test_parse_string_hash);

  /* round-trips */
  g_test_add_data_func ("/parse-gvariant/roundtrip/u",
      "@u 42", test_roundtrip);
  g_test_add_data_func ("/parse-gvariant/roundtrip/non_empty_array",
      "@ai [23, 42]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_array", "@ai []", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aav", "[[<'bees'>]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aau", "[[uint32 666]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aax", "[[int64 666]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aat", "[[uint64 666]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aas", "[['a', 'b']]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aao",
      "[[objectpath '/a', objectpath '/b']]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aag", "[[signature 'ab', signature 'g']]",
      test_roundtrip);
  g_test_add_data_func ("/roundtrip/aad", "[[5.25, 3.5]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aay", "@aay [[1, 2]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aay", "@aay [[]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aab", "[[true, false]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aab", "@aab [[]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/aa_asv", "[[@a{sv} {'x': <'y'>}]]",
      test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_av", "@av []", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_hash", "@a{uu} {}", test_roundtrip);
  g_test_add_data_func ("/roundtrip/easy_string_hash",
      "@a{ss} {'foo': 'bar'}", test_roundtrip);
  g_test_add_data_func ("/roundtrip/non_empty_asv",
      "@a{sv} {'badger': <42>, 'mushroom': <objectpath '/'>, 'snake': <''>}",
      test_roundtrip);
  g_test_add_data_func ("/roundtrip/variant_nesting", "<<<42>>>",
      test_roundtrip);
  g_test_add_data_func ("/roundtrip/tuple", "(23, 42, true)",
      test_roundtrip);
  g_test_add_data_func ("/roundtrip/nested", "[[[(1, 2)]]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aaa", "@aaav [[]]",
      test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aa_asv", "@aaa{sv} [[]]",
      test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aa_struct", "@aa(us) [[]]",
      test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aaas", "@aaas [[]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aax", "@aax [[]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aat", "@aat [[]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aad", "@aad [[]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aao", "@aao [[]]", test_roundtrip);
  g_test_add_data_func ("/roundtrip/empty_aag", "@aag [[]]", test_roundtrip);

  return g_test_run ();
}
