/*
 * Copyright © 2010 Codethink Limited
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the licence, or (at your option) any later version.
 *
 * See the included COPYING file for more information.
 *
 * Author: Ryan Lortie <desrt@desrt.ca>
 */

#include "config.h"

#include <glib/gvariant-internal.h>
#include <string.h>
#include <stdlib.h>
#include <glib.h>

#define BASIC "bynqiuxthdsog?"
#define N_BASIC (G_N_ELEMENTS (BASIC) - 1)

#define INVALIDS "cefjklpwz&@^$"
#define N_INVALIDS (G_N_ELEMENTS (INVALIDS) - 1)

/* see comment in gvariant-serialiser.c about this madness.
 *
 * we use this to get testing of non-strictly-aligned GVariant instances
 * on machines that can tolerate it.  it is necessary to support this
 * because some systems have malloc() that returns non-8-aligned
 * pointers.  it is necessary to have special support in the tests
 * because on most machines malloc() is 8-aligned.
 */
#define ALIGN_BITS (sizeof (struct { char a; union {                       \
                      guint64 x; void *y; gdouble z; } b; }) - 9)

static gboolean
randomly (gdouble prob)
{
  return g_test_rand_double_range (0, 1) < prob;
}

/* corecursion */
static GVariantType *
append_tuple_type_string (GString *, GString *, gboolean, gint);

/* append a random GVariantType to a GString
 * append a description of the type to another GString
 * return what the type is
 */
static GVariantType *
append_type_string (GString  *string,
                    GString  *description,
                    gboolean  definite,
                    gint      depth)
{
  if (!depth-- || randomly (0.3))
    {
      gchar b = BASIC[g_test_rand_int_range (0, N_BASIC - definite)];
      g_string_append_c (string, b);
      g_string_append_c (description, b);

      switch (b)
        {
        case 'b':
          return g_variant_type_copy (G_VARIANT_TYPE_BOOLEAN);
        case 'y':
          return g_variant_type_copy (G_VARIANT_TYPE_BYTE);
        case 'n':
          return g_variant_type_copy (G_VARIANT_TYPE_INT16);
        case 'q':
          return g_variant_type_copy (G_VARIANT_TYPE_UINT16);
        case 'i':
          return g_variant_type_copy (G_VARIANT_TYPE_INT32);
        case 'u':
          return g_variant_type_copy (G_VARIANT_TYPE_UINT32);
        case 'x':
          return g_variant_type_copy (G_VARIANT_TYPE_INT64);
        case 't':
          return g_variant_type_copy (G_VARIANT_TYPE_UINT64);
        case 'h':
          return g_variant_type_copy (G_VARIANT_TYPE_HANDLE);
        case 'd':
          return g_variant_type_copy (G_VARIANT_TYPE_DOUBLE);
        case 's':
          return g_variant_type_copy (G_VARIANT_TYPE_STRING);
        case 'o':
          return g_variant_type_copy (G_VARIANT_TYPE_OBJECT_PATH);
        case 'g':
          return g_variant_type_copy (G_VARIANT_TYPE_SIGNATURE);
        case '?':
          return g_variant_type_copy (G_VARIANT_TYPE_BASIC);
        default:
          g_assert_not_reached ();
        }
    }
  else
    {
      GVariantType *result;

      switch (g_test_rand_int_range (0, definite ? 5 : 7))
        {
        case 0:
          {
            GVariantType *element;

            g_string_append_c (string, 'a');
            g_string_append (description, "a of ");
            element = append_type_string (string, description,
                                          definite, depth);
            result = g_variant_type_new_array (element);
            g_variant_type_free (element);
          }

          g_assert (g_variant_type_is_array (result));
          break;

        case 1:
          {
            GVariantType *element;

            g_string_append_c (string, 'm');
            g_string_append (description, "m of ");
            element = append_type_string (string, description,
                                          definite, depth);
            result = g_variant_type_new_maybe (element);
            g_variant_type_free (element);
          }

          g_assert (g_variant_type_is_maybe (result));
          break;

        case 2:
          result = append_tuple_type_string (string, description,
                                             definite, depth);

          g_assert (g_variant_type_is_tuple (result));
          break;

        case 3:
          {
            GVariantType *key, *value;

            g_string_append_c (string, '{');
            g_string_append (description, "e of [");
            key = append_type_string (string, description, definite, 0);
            g_string_append (description, ", ");
            value = append_type_string (string, description, definite, depth);
            g_string_append_c (description, ']');
            g_string_append_c (string, '}');
            result = g_variant_type_new_dict_entry (key, value);
            g_variant_type_free (key);
            g_variant_type_free (value);
          }

          g_assert (g_variant_type_is_dict_entry (result));
          break;

        case 4:
          g_string_append_c (string, 'v');
          g_string_append_c (description, 'V');
          result = g_variant_type_copy (G_VARIANT_TYPE_VARIANT);
          g_assert (g_variant_type_equal (result, G_VARIANT_TYPE_VARIANT));
          break;

        case 5:
          g_string_append_c (string, '*');
          g_string_append_c (description, 'S');
          result = g_variant_type_copy (G_VARIANT_TYPE_ANY);
          g_assert (g_variant_type_equal (result, G_VARIANT_TYPE_ANY));
          break;

        case 6:
          g_string_append_c (string, 'r');
          g_string_append_c (description, 'R');
          result = g_variant_type_copy (G_VARIANT_TYPE_TUPLE);
          g_assert (g_variant_type_is_tuple (result));
          break;

        default:
          g_assert_not_reached ();
        }

      return result;
    }
}

static GVariantType *
append_tuple_type_string (GString  *string,
                          GString  *description,
                          gboolean  definite,
                          gint      depth)
{
  GVariantType *result, *other_result;
  GVariantType **types;
  gint size;
  gint i;

  g_string_append_c (string, '(');
  g_string_append (description, "t of [");

  size = g_test_rand_int_range (0, 20);
  types = g_new (GVariantType *, size + 1);

  for (i = 0; i < size; i++)
    {
      types[i] = append_type_string (string, description, definite, depth);

      if (i < size - 1)
        g_string_append (description, ", ");
    }

  types[i] = NULL;

  g_string_append_c (description, ']');
  g_string_append_c (string, ')');

  result = g_variant_type_new_tuple ((gpointer) types, size);
  other_result = g_variant_type_new_tuple ((gpointer) types, -1);
  g_assert (g_variant_type_equal (result, other_result));
  g_variant_type_free (other_result);
  for (i = 0; i < size; i++)
    g_variant_type_free (types[i]);
  g_free (types);

  return result;
}

/* given a valid type string, make it invalid */
static gchar *
invalid_mutation (const gchar *type_string)
{
  gboolean have_parens, have_braces;

  /* it's valid, so '(' implies ')' and same for '{' and '}' */
  have_parens = strchr (type_string, '(') != NULL;
  have_braces = strchr (type_string, '{') != NULL;

  if (have_parens && have_braces && randomly (0.3))
    {
      /* swap a paren and a brace */
      gchar *pp, *bp;
      gint np, nb;
      gchar p, b;
      gchar *new;

      new = g_strdup (type_string);

      if (randomly (0.5))
        p = '(', b = '{';
      else
        p = ')', b = '}';

      np = nb = 0;
      pp = bp = new - 1;

      /* count number of parens/braces */
      while ((pp = strchr (pp + 1, p))) np++;
      while ((bp = strchr (bp + 1, b))) nb++;

      /* randomly pick one of each */
      np = g_test_rand_int_range (0, np) + 1;
      nb = g_test_rand_int_range (0, nb) + 1;

      /* find it */
      pp = bp = new - 1;
      while (np--) pp = strchr (pp + 1, p);
      while (nb--) bp = strchr (bp + 1, b);

      /* swap */
      g_assert (*bp == b && *pp == p);
      *bp = p;
      *pp = b;

      return new;
    }

  if ((have_parens || have_braces) && randomly (0.3))
    {
      /* drop a paren/brace */
      gchar *new;
      gchar *pp;
      gint np;
      gchar p;

      if (have_parens)
        if (randomly (0.5)) p = '('; else p = ')';
      else
        if (randomly (0.5)) p = '{'; else p = '}';

      new = g_strdup (type_string);

      np = 0;
      pp = new - 1;
      while ((pp = strchr (pp + 1, p))) np++;
      np = g_test_rand_int_range (0, np) + 1;
      pp = new - 1;
      while (np--) pp = strchr (pp + 1, p);
      g_assert (*pp == p);

      while (*pp)
        {
          *pp = *(pp + 1);
          pp++;
        }

      return new;
    }

  /* else, perform a random mutation at a random point */
  {
    gint length, n;
    gchar *new;
    gchar p;

    if (randomly (0.3))
      {
        /* insert a paren/brace */
        if (randomly (0.5))
          if (randomly (0.5)) p = '('; else p = ')';
        else
          if (randomly (0.5)) p = '{'; else p = '}';
      }
    else if (randomly (0.5))
      {
        /* insert junk */
        p = INVALIDS[g_test_rand_int_range (0, N_INVALIDS)];
      }
    else
      {
        /* truncate */
        p = '\0';
      }


    length = strlen (type_string);
    new = g_malloc (length + 2);
    n = g_test_rand_int_range (0, length);
    memcpy (new, type_string, n);
    new[n] = p;
    memcpy (new + n + 1, type_string + n, length - n);
    new[length + 1] = '\0';

    return new;
  }
}

/* describe a type using the same language as is generated
 * while generating the type with append_type_string
 */
static gchar *
describe_type (const GVariantType *type)
{
  gchar *result;

  if (g_variant_type_is_container (type))
    {
      g_assert (!g_variant_type_is_basic (type));

      if (g_variant_type_is_array (type))
        {
          gchar *subtype = describe_type (g_variant_type_element (type));
          result = g_strdup_printf ("a of %s", subtype);
          g_free (subtype);
        }
      else if (g_variant_type_is_maybe (type))
        {
          gchar *subtype = describe_type (g_variant_type_element (type));
          result = g_strdup_printf ("m of %s", subtype);
          g_free (subtype);
        }
      else if (g_variant_type_is_tuple (type))
        {
          if (!g_variant_type_equal (type, G_VARIANT_TYPE_TUPLE))
            {
              const GVariantType *sub;
              GString *string;
              gint length;
              gint i;

              string = g_string_new ("t of [");

              length = g_variant_type_n_items (type);
              sub = g_variant_type_first (type);
              for (i = 0; i < length; i++)
                {
                  gchar *subtype = describe_type (sub);
                  g_string_append (string, subtype);
                  g_free (subtype);

                  if ((sub = g_variant_type_next (sub)))
                    g_string_append (string, ", ");
                }
              g_assert (sub == NULL);
              g_string_append_c (string, ']');

              result = g_string_free (string, FALSE);
            }
          else
            result = g_strdup ("R");
        }
      else if (g_variant_type_is_dict_entry (type))
        {
          gchar *key, *value, *key2, *value2;

          key = describe_type (g_variant_type_key (type));
          value = describe_type (g_variant_type_value (type));
          key2 = describe_type (g_variant_type_first (type));
          value2 = describe_type (
            g_variant_type_next (g_variant_type_first (type)));
          g_assert (g_variant_type_next (g_variant_type_next (
            g_variant_type_first (type))) == NULL);
          g_assert_cmpstr (key, ==, key2);
          g_assert_cmpstr (value, ==, value2);
          result = g_strjoin ("", "e of [", key, ", ", value, "]", NULL);
          g_free (key2);
          g_free (value2);
          g_free (key);
          g_free (value);
        }
      else if (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT))
        {
          result = g_strdup ("V");
        }
      else
        g_assert_not_reached ();
    }
  else
    {
      if (g_variant_type_is_definite (type))
        {
          g_assert (g_variant_type_is_basic (type));

          if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
            result = g_strdup ("b");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
            result = g_strdup ("y");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16))
            result = g_strdup ("n");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
            result = g_strdup ("q");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
            result = g_strdup ("i");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
            result = g_strdup ("u");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64))
            result = g_strdup ("x");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64))
            result = g_strdup ("t");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_HANDLE))
            result = g_strdup ("h");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
            result = g_strdup ("d");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
            result = g_strdup ("s");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH))
            result = g_strdup ("o");
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
            result = g_strdup ("g");
          else
            g_assert_not_reached ();
        }
      else
        {
          if (g_variant_type_equal (type, G_VARIANT_TYPE_ANY))
            {
              result = g_strdup ("S");
            }
          else if (g_variant_type_equal (type, G_VARIANT_TYPE_BASIC))
            {
              result = g_strdup ("?");
            }
          else
            g_assert_not_reached ();
        }
    }

  return result;
}

/* given a type string, replace one of the indefinite type characters in
 * it with a matching type (possibly the same type).
 */
static gchar *
generate_subtype (const gchar *type_string)
{
  GVariantType *replacement;
  GString *result, *junk;
  gint length, n = 0, l;

  result = g_string_new (NULL);
  junk = g_string_new (NULL);

  /* count the number of indefinite type characters */
  for (length = 0; type_string[length]; length++)
    n += type_string[length] == 'r' ||
         type_string[length] == '?' ||
         type_string[length] == '*';
  /* length now is strlen (type_string) */

  /* pick one at random to replace */
  n = g_test_rand_int_range (0, n) + 1;

  /* find it */
  l = -1;
  while (n--) l += strcspn (type_string + l + 1, "r?*") + 1;
  g_assert (type_string[l] == 'r' ||
            type_string[l] == '?' ||
            type_string[l] == '*');

  /* store up to that point in a GString */
  g_string_append_len (result, type_string, l);

  /* then store the replacement in the GString */
  if (type_string[l] == 'r')
    replacement = append_tuple_type_string (result, junk, FALSE, 3);

  else if (type_string[l] == '?')
    replacement = append_type_string (result, junk, FALSE, 0);

  else if (type_string[l] == '*')
    replacement = append_type_string (result, junk, FALSE, 3);

  else
    g_assert_not_reached ();

  /* ensure the replacement has the proper type */
  g_assert (g_variant_type_is_subtype_of (replacement,
                                          (gpointer) &type_string[l]));

  /* store the rest from the original type string */
  g_string_append (result, type_string + l + 1);

  g_variant_type_free (replacement);
  g_string_free (junk, TRUE);

  return g_string_free (result, FALSE);
}

struct typestack
{
  const GVariantType *type;
  struct typestack *parent;
};

/* given an indefinite type string, replace one of the indefinite
 * characters in it with a matching type and ensure that the result is a
 * subtype of the original.  repeat.
 */
static void
subtype_check (const gchar      *type_string,
               struct typestack *parent_ts)
{
  struct typestack ts, *node;
  gchar *subtype;
  gint depth = 0;

  subtype = generate_subtype (type_string);

  ts.type = G_VARIANT_TYPE (subtype);
  ts.parent = parent_ts;

  for (node = &ts; node; node = node->parent)
    {
      /* this type should be a subtype of each parent type */
      g_assert (g_variant_type_is_subtype_of (ts.type, node->type));

      /* it should only be a supertype when it is exactly equal */
      g_assert (g_variant_type_is_subtype_of (node->type, ts.type) ==
                g_variant_type_equal (ts.type, node->type));

      depth++;
    }

  if (!g_variant_type_is_definite (ts.type) && depth < 5)
    {
      /* the type is still indefinite and we haven't repeated too many
       * times.  go once more.
       */

      subtype_check (subtype, &ts);
    }

  g_free (subtype);
}

static void
test_gvarianttype (void)
{
  gint i;

  for (i = 0; i < 2000; i++)
    {
      GString *type_string, *description;
      GVariantType *type, *other_type;
      const GVariantType *ctype;
      gchar *invalid;
      gchar *desc;

      type_string = g_string_new (NULL);
      description = g_string_new (NULL);

      /* generate a random type, its type string and a description
       *
       * exercises type constructor functions and g_variant_type_copy()
       */
      type = append_type_string (type_string, description, FALSE, 6);

      /* convert the type string to a type and ensure that it is equal
       * to the one produced with the type constructor routines
       */
      ctype = G_VARIANT_TYPE (type_string->str);
      g_assert (g_variant_type_equal (ctype, type));
      g_assert (g_variant_type_hash (ctype) == g_variant_type_hash (type));
      g_assert (g_variant_type_is_subtype_of (ctype, type));
      g_assert (g_variant_type_is_subtype_of (type, ctype));

      /* check if the type is indefinite */
      if (!g_variant_type_is_definite (type))
        {
          struct typestack ts = { type, NULL };

          /* if it is indefinite, then replace one of the indefinite
           * characters with a matching type and ensure that the result
           * is a subtype of the original type.  repeat.
           */
          subtype_check (type_string->str, &ts);
        }
      else
        /* ensure that no indefinite characters appear */
        g_assert (strcspn (type_string->str, "r?*") == type_string->len);


      /* describe the type.
       *
       * exercises the type iterator interface
       */
      desc = describe_type (type);

      /* make sure the description matches */
      g_assert_cmpstr (desc, ==, description->str);
      g_free (desc);

      /* make an invalid mutation to the type and make sure the type
       * validation routines catch it */
      invalid = invalid_mutation (type_string->str);
      g_assert (g_variant_type_string_is_valid (type_string->str));
      g_assert (!g_variant_type_string_is_valid (invalid));
      g_free (invalid);

      /* concatenate another type to the type string and ensure that
       * the result is recognised as being invalid
       */
      other_type = append_type_string (type_string, description, FALSE, 2);

      g_string_free (description, TRUE);
      g_string_free (type_string, TRUE);
      g_variant_type_free (other_type);
      g_variant_type_free (type);
    }
}

#define ALIGNED(x, y)   (((x + (y - 1)) / y) * y)

/* do our own calculation of the fixed_size and alignment of a type
 * using a simple algorithm to make sure the "fancy" one in the
 * implementation is correct.
 */
static void
calculate_type_info (const GVariantType *type,
                     gsize              *fixed_size,
                     guint              *alignment)
{
  if (g_variant_type_is_array (type) ||
      g_variant_type_is_maybe (type))
    {
      calculate_type_info (g_variant_type_element (type), NULL, alignment);

      if (fixed_size)
        *fixed_size = 0;
    }
  else if (g_variant_type_is_tuple (type) ||
           g_variant_type_is_dict_entry (type))
    {
      if (g_variant_type_n_items (type))
        {
          const GVariantType *sub;
          gboolean variable;
          gsize size;
          guint al;

          variable = FALSE;
          size = 0;
          al = 0;

          sub = g_variant_type_first (type);
          do
            {
              gsize this_fs;
              guint this_al;

              calculate_type_info (sub, &this_fs, &this_al);

              al = MAX (al, this_al);

              if (!this_fs)
                {
                  variable = TRUE;
                  size = 0;
                }

              if (!variable)
                {
                  size = ALIGNED (size, this_al);
                  size += this_fs;
                }
            }
          while ((sub = g_variant_type_next (sub)));

          size = ALIGNED (size, al);

          if (alignment)
            *alignment = al;

          if (fixed_size)
            *fixed_size = size;
        }
      else
        {
          if (fixed_size)
            *fixed_size = 1;

          if (alignment)
            *alignment = 1;
        }
    }
  else
    {
      gint fs, al;

      if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN) ||
          g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
        {
          al = fs = 1;
        }

      else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16) ||
               g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
        {
          al = fs = 2;
        }

      else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32) ||
               g_variant_type_equal (type, G_VARIANT_TYPE_UINT32) ||
               g_variant_type_equal (type, G_VARIANT_TYPE_HANDLE))
        {
          al = fs = 4;
        }

      else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64) ||
               g_variant_type_equal (type, G_VARIANT_TYPE_UINT64) ||
               g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
        {
          al = fs = 8;
        }
      else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING) ||
               g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH) ||
               g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
        {
          al = 1;
          fs = 0;
        }
      else if (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT))
        {
          al = 8;
          fs = 0;
        }
      else
        g_assert_not_reached ();

      if (fixed_size)
        *fixed_size = fs;

      if (alignment)
        *alignment = al;
    }
}

/* same as the describe_type() function above, but iterates over
 * typeinfo instead of types.
 */
static gchar *
describe_info (GVariantTypeInfo *info)
{
  gchar *result;

  switch (g_variant_type_info_get_type_char (info))
    {
    case G_VARIANT_TYPE_INFO_CHAR_MAYBE:
      {
        gchar *element;

        element = describe_info (g_variant_type_info_element (info));
        result = g_strdup_printf ("m of %s", element);
        g_free (element);
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_ARRAY:
      {
        gchar *element;

        element = describe_info (g_variant_type_info_element (info));
        result = g_strdup_printf ("a of %s", element);
        g_free (element);
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_TUPLE:
      {
        const gchar *sep = "";
        GString *string;
        gint length;
        gint i;

        string = g_string_new ("t of [");
        length = g_variant_type_info_n_members (info);

        for (i = 0; i < length; i++)
          {
            const GVariantMemberInfo *minfo;
            gchar *subtype;

            g_string_append (string, sep);
            sep = ", ";

            minfo = g_variant_type_info_member_info (info, i);
            subtype = describe_info (minfo->type_info);
            g_string_append (string, subtype);
            g_free (subtype);
          }

        g_string_append_c (string, ']');

        result = g_string_free (string, FALSE);
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY:
      {
        const GVariantMemberInfo *keyinfo, *valueinfo;
        gchar *key, *value;

        g_assert_cmpint (g_variant_type_info_n_members (info), ==, 2);
        keyinfo = g_variant_type_info_member_info (info, 0);
        valueinfo = g_variant_type_info_member_info (info, 1);
        key = describe_info (keyinfo->type_info);
        value = describe_info (valueinfo->type_info);
        result = g_strjoin ("", "e of [", key, ", ", value, "]", NULL);
        g_free (key);
        g_free (value);
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_VARIANT:
      result = g_strdup ("V");
      break;

    default:
      result = g_strdup (g_variant_type_info_get_type_string (info));
      g_assert_cmpint (strlen (result), ==, 1);
      break;
    }

  return result;
}

/* check that the O(1) method of calculating offsets meshes with the
 * results of simple iteration.
 */
static void
check_offsets (GVariantTypeInfo   *info,
               const GVariantType *type)
{
  gint flavour;
  gint length;

  length = g_variant_type_info_n_members (info);
  g_assert_cmpint (length, ==, g_variant_type_n_items (type));

  /* the 'flavour' is the low order bits of the ending point of
   * variable-size items in the tuple.  this lets us test that the type
   * info is correct for various starting alignments.
   */
  for (flavour = 0; flavour < 8; flavour++)
    {
      const GVariantType *subtype;
      gsize last_offset_index;
      gsize last_offset;
      gsize position;
      gint i;

      subtype = g_variant_type_first (type);
      last_offset_index = -1;
      last_offset = 0;
      position = 0;

      /* go through the tuple, keeping track of our position */
      for (i = 0; i < length; i++)
        {
          gsize fixed_size;
          guint alignment;

          calculate_type_info (subtype, &fixed_size, &alignment);

          position = ALIGNED (position, alignment);

          /* compare our current aligned position (ie: the start of this
           * item) to the start offset that would be calculated if we
           * used the type info
           */
          {
            const GVariantMemberInfo *member;
            gsize start;

            member = g_variant_type_info_member_info (info, i);
            g_assert_cmpint (member->i, ==, last_offset_index);

            /* do the calculation using the typeinfo */
            start = last_offset;
            start += member->a;
            start &= member->b;
            start |= member->c;

            /* did we reach the same spot? */
            g_assert_cmpint (start, ==, position);
          }

          if (fixed_size)
            {
              /* fixed size.  add that size. */
              position += fixed_size;
            }
          else
            {
              /* variable size.  do the flavouring. */
              while ((position & 0x7) != flavour)
                position++;

              /* and store the offset, just like it would be in the
               * serialised data.
               */
              last_offset = position;
              last_offset_index++;
            }

          /* next type */
          subtype = g_variant_type_next (subtype);
        }

      /* make sure we used up exactly all the types */
      g_assert (subtype == NULL);
    }
}

static void
test_gvarianttypeinfo (void)
{
  gint i;

  for (i = 0; i < 2000; i++)
    {
      GString *type_string, *description;
      gsize fixed_size1, fixed_size2;
      guint alignment1, alignment2;
      GVariantTypeInfo *info;
      GVariantType *type;
      gchar *desc;

      type_string = g_string_new (NULL);
      description = g_string_new (NULL);

      /* random type */
      type = append_type_string (type_string, description, TRUE, 6);

      /* create a typeinfo for it */
      info = g_variant_type_info_get (type);

      /* make sure the typeinfo has the right type string */
      g_assert_cmpstr (g_variant_type_info_get_type_string (info), ==,
                       type_string->str);

      /* calculate the alignment and fixed size, compare to the
       * typeinfo's calculations
       */
      calculate_type_info (type, &fixed_size1, &alignment1);
      g_variant_type_info_query (info, &alignment2, &fixed_size2);
      g_assert_cmpint (fixed_size1, ==, fixed_size2);
      g_assert_cmpint (alignment1, ==, alignment2 + 1);

      /* test the iteration functions over typeinfo structures by
       * "describing" the typeinfo and verifying equality.
       */
      desc = describe_info (info);
      g_assert_cmpstr (desc, ==, description->str);

      /* do extra checks for containers */
      if (g_variant_type_is_array (type) ||
          g_variant_type_is_maybe (type))
        {
          const GVariantType *element;
          gsize efs1, efs2;
          guint ea1, ea2;

          element = g_variant_type_element (type);
          calculate_type_info (element, &efs1, &ea1);
          g_variant_type_info_query_element (info, &ea2, &efs2);
          g_assert_cmpint (efs1, ==, efs2);
          g_assert_cmpint (ea1, ==, ea2 + 1);

          g_assert_cmpint (ea1, ==, alignment1);
          g_assert_cmpint (0, ==, fixed_size1);
        }
      else if (g_variant_type_is_tuple (type) ||
               g_variant_type_is_dict_entry (type))
        {
          /* make sure the "magic constants" are working */
          check_offsets (info, type);
        }

      g_string_free (type_string, TRUE);
      g_string_free (description, TRUE);
      g_variant_type_info_unref (info);
      g_variant_type_free (type);
      g_free (desc);
    }

  g_variant_type_info_assert_no_infos ();
}

#define MAX_FIXED_MULTIPLIER    256
#define MAX_INSTANCE_SIZE       1024
#define MAX_ARRAY_CHILDREN      128
#define MAX_TUPLE_CHILDREN      128

/* this function generates a random type such that all characteristics
 * that are "interesting" to the serialiser are tested.
 *
 * this basically means:
 *   - test different alignments
 *   - test variable sized items and fixed sized items
 *   - test different fixed sizes
 */
static gchar *
random_type_string (void)
{
  const guchar base_types[] = "ynix";
  guchar base_type;

  base_type = base_types[g_test_rand_int_range (0, 4)];

  if (g_test_rand_bit ())
    /* construct a fixed-sized type */
    {
      char type_string[MAX_FIXED_MULTIPLIER];
      guint multiplier;
      guint i = 0;

      multiplier = g_test_rand_int_range (1, sizeof type_string - 1);

      type_string[i++] = '(';
      while (multiplier--)
        type_string[i++] = base_type;
      type_string[i++] = ')';

      return g_strndup (type_string, i);
    }
  else
    /* construct a variable-sized type */
    {
      char type_string[2] = { 'a', base_type };

      return g_strndup (type_string, 2);
    }
}

typedef struct
{
  GVariantTypeInfo *type_info;
  guint alignment;
  gsize size;
  gboolean is_fixed_sized;

  guint32 seed;

#define INSTANCE_MAGIC    1287582829
  guint magic;
} RandomInstance;

static RandomInstance *
random_instance (GVariantTypeInfo *type_info)
{
  RandomInstance *instance;

  instance = g_slice_new (RandomInstance);

  if (type_info == NULL)
    {
      gchar *str = random_type_string ();
      instance->type_info = g_variant_type_info_get (G_VARIANT_TYPE (str));
      g_free (str);
    }
  else
    instance->type_info = g_variant_type_info_ref (type_info);

  instance->seed = g_test_rand_int ();

  g_variant_type_info_query (instance->type_info,
                             &instance->alignment,
                             &instance->size);

  instance->is_fixed_sized = instance->size != 0;

  if (!instance->is_fixed_sized)
    instance->size = g_test_rand_int_range (0, MAX_INSTANCE_SIZE);

  instance->magic = INSTANCE_MAGIC;

  return instance;
}

static void
random_instance_free (RandomInstance *instance)
{
  g_variant_type_info_unref (instance->type_info);
  g_slice_free (RandomInstance, instance);
}

static void
append_instance_size (RandomInstance *instance,
                      gsize          *offset)
{
  *offset += (-*offset) & instance->alignment;
  *offset += instance->size;
}

static void
random_instance_write (RandomInstance *instance,
                       guchar         *buffer)
{
  GRand *rand;
  gint i;

  g_assert_cmpint ((gsize) buffer & ALIGN_BITS & instance->alignment, ==, 0);

  rand = g_rand_new_with_seed (instance->seed);
  for (i = 0; i < instance->size; i++)
    buffer[i] = g_rand_int (rand);
  g_rand_free (rand);
}

static void
append_instance_data (RandomInstance  *instance,
                      guchar         **buffer)
{
  while (((gsize) *buffer) & instance->alignment)
    *(*buffer)++ = '\0';

  random_instance_write (instance, *buffer);
  *buffer += instance->size;
}

static gboolean
random_instance_assert (RandomInstance *instance,
                        guchar         *buffer,
                        gsize           size)
{
  GRand *rand;
  gint i;

  g_assert_cmpint ((gsize) buffer & ALIGN_BITS & instance->alignment, ==, 0);
  g_assert_cmpint (size, ==, instance->size);

  rand = g_rand_new_with_seed (instance->seed);
  for (i = 0; i < instance->size; i++)
    {
      guchar byte = g_rand_int (rand);

      g_assert (buffer[i] == byte);
    }
  g_rand_free (rand);

  return i == instance->size;
}

static gboolean
random_instance_check (RandomInstance *instance,
                       guchar         *buffer,
                       gsize           size)
{
  GRand *rand;
  gint i;

  g_assert_cmpint ((gsize) buffer & ALIGN_BITS & instance->alignment, ==, 0);

  if (size != instance->size)
    return FALSE;

  rand = g_rand_new_with_seed (instance->seed);
  for (i = 0; i < instance->size; i++)
    if (buffer[i] != (guchar) g_rand_int (rand))
      break;
  g_rand_free (rand);

  return i == instance->size;
}

static void
random_instance_filler (GVariantSerialised *serialised,
                        gpointer            data)
{
  RandomInstance *instance = data;

  g_assert (instance->magic == INSTANCE_MAGIC);

  if (serialised->type_info == NULL)
    serialised->type_info = instance->type_info;

  if (serialised->size == 0)
    serialised->size = instance->size;

  g_assert (serialised->type_info == instance->type_info);
  g_assert (serialised->size == instance->size);

  if (serialised->data)
    random_instance_write (instance, serialised->data);
}

static gsize
calculate_offset_size (gsize body_size,
                       gsize n_offsets)
{
  if (body_size == 0)
    return 0;

  if (body_size + n_offsets <= G_MAXUINT8)
    return 1;

  if (body_size + 2 * n_offsets <= G_MAXUINT16)
    return 2;

  if (body_size + 4 * n_offsets <= G_MAXUINT32)
    return 4;

  /* the test case won't generate anything bigger */
  g_assert_not_reached ();
}

static gpointer
flavoured_malloc (gsize size, gsize flavour)
{
  g_assert (flavour < 8);

  if (size == 0)
    return NULL;

  return ((gchar *) g_malloc (size + flavour)) + flavour;
}

static void
flavoured_free (gpointer data,
                gsize flavour)
{
  if (!data)
    return;
  g_free (((gchar *) data) - flavour);
}

static gpointer
align_malloc (gsize size)
{
  gpointer mem;

#ifdef HAVE_POSIX_MEMALIGN
  if (posix_memalign (&mem, 8, size))
    g_error ("posix_memalign failed");
#else
  /* NOTE: there may be platforms that lack posix_memalign() and also
   * have malloc() that returns non-8-aligned.  if so, we need to try
   * harder here.
   */
  mem = malloc (size);
#endif

  return mem;
}

static void
align_free (gpointer mem)
{
  free (mem);
}

static void
append_offset (guchar **offset_ptr,
               gsize    offset,
               guint    offset_size)
{
  union
  {
    guchar bytes[sizeof (gsize)];
    gsize integer;
  } tmpvalue;

  tmpvalue.integer = GSIZE_TO_LE (offset);
  memcpy (*offset_ptr, tmpvalue.bytes, offset_size);
  *offset_ptr += offset_size;
}

static void
prepend_offset (guchar **offset_ptr,
                gsize    offset,
                guint    offset_size)
{
  union
  {
    guchar bytes[sizeof (gsize)];
    gsize integer;
  } tmpvalue;

  *offset_ptr -= offset_size;
  tmpvalue.integer = GSIZE_TO_LE (offset);
  memcpy (*offset_ptr, tmpvalue.bytes, offset_size);
}

static void
test_maybe (void)
{
  GVariantTypeInfo *type_info;
  RandomInstance *instance;
  gsize needed_size;
  guchar *data;

  instance = random_instance (NULL);

  {
    const gchar *element;
    gchar *tmp;

    element = g_variant_type_info_get_type_string (instance->type_info);
    tmp = g_strdup_printf ("m%s", element);
    type_info = g_variant_type_info_get (G_VARIANT_TYPE (tmp));
    g_free (tmp);
  }

  needed_size = g_variant_serialiser_needed_size (type_info,
                                                  random_instance_filler,
                                                  NULL, 0);
  g_assert_cmpint (needed_size, ==, 0);

  needed_size = g_variant_serialiser_needed_size (type_info,
                                                  random_instance_filler,
                                                  (gpointer *) &instance, 1);

  if (instance->is_fixed_sized)
    g_assert_cmpint (needed_size, ==, instance->size);
  else
    g_assert_cmpint (needed_size, ==, instance->size + 1);

  {
    guchar *ptr;

    ptr = data = align_malloc (needed_size);
    append_instance_data (instance, &ptr);

    if (!instance->is_fixed_sized)
      *ptr++ = '\0';

    g_assert_cmpint (ptr - data, ==, needed_size);
  }

  {
    guint alignment;
    guint flavour;

    alignment = (instance->alignment & ALIGN_BITS) + 1;

    for (flavour = 0; flavour < 8; flavour += alignment)
      {
        GVariantSerialised serialised;
        GVariantSerialised child;

        serialised.type_info = type_info;
        serialised.data = flavoured_malloc (needed_size, flavour);
        serialised.size = needed_size;

        g_variant_serialiser_serialise (serialised,
                                        random_instance_filler,
                                        (gpointer *) &instance, 1);
        child = g_variant_serialised_get_child (serialised, 0);
        g_assert (child.type_info == instance->type_info);
        random_instance_assert (instance, child.data, child.size);
        g_variant_type_info_unref (child.type_info);
        flavoured_free (serialised.data, flavour);
      }
  }

  g_variant_type_info_unref (type_info);
  random_instance_free (instance);
  align_free (data);
}

static void
test_maybes (void)
{
  guint i;

  for (i = 0; i < 1000; i++)
    test_maybe ();

  g_variant_type_info_assert_no_infos ();
}

static void
test_array (void)
{
  GVariantTypeInfo *element_info;
  GVariantTypeInfo *array_info;
  RandomInstance **instances;
  gsize needed_size;
  gsize offset_size;
  guint n_children;
  guchar *data;

  {
    gchar *element_type, *array_type;

    element_type = random_type_string ();
    array_type = g_strdup_printf ("a%s", element_type);

    element_info = g_variant_type_info_get (G_VARIANT_TYPE (element_type));
    array_info = g_variant_type_info_get (G_VARIANT_TYPE (array_type));
    g_assert (g_variant_type_info_element (array_info) == element_info);

    g_free (element_type);
    g_free (array_type);
  }

  {
    guint i;

    n_children = g_test_rand_int_range (0, MAX_ARRAY_CHILDREN);
    instances = g_new (RandomInstance *, n_children);
    for (i = 0; i < n_children; i++)
      instances[i] = random_instance (element_info);
  }

  needed_size = g_variant_serialiser_needed_size (array_info,
                                                  random_instance_filler,
                                                  (gpointer *) instances,
                                                  n_children);

  {
    gsize element_fixed_size;
    gsize body_size = 0;
    guint i;

    for (i = 0; i < n_children; i++)
      append_instance_size (instances[i], &body_size);

    g_variant_type_info_query (element_info, NULL, &element_fixed_size);

    if (!element_fixed_size)
      {
        offset_size = calculate_offset_size (body_size, n_children);

        if (offset_size == 0)
          offset_size = 1;
      }
    else
      offset_size = 0;

    g_assert_cmpint (needed_size, ==, body_size + n_children * offset_size);
  }

  {
    guchar *offset_ptr, *body_ptr;
    guint i;

    body_ptr = data = align_malloc (needed_size);
    offset_ptr = body_ptr + needed_size - offset_size * n_children;

    for (i = 0; i < n_children; i++)
      {
        append_instance_data (instances[i], &body_ptr);
        append_offset (&offset_ptr, body_ptr - data, offset_size);
      }

    g_assert (body_ptr == data + needed_size - offset_size * n_children);
    g_assert (offset_ptr == data + needed_size);
  }

  {
    guint alignment;
    gsize flavour;
    guint i;

    g_variant_type_info_query (array_info, &alignment, NULL);
    alignment = (alignment & ALIGN_BITS) + 1;

    for (flavour = 0; flavour < 8; flavour += alignment)
      {
        GVariantSerialised serialised;

        serialised.type_info = array_info;
        serialised.data = flavoured_malloc (needed_size, flavour);
        serialised.size = needed_size;

        g_variant_serialiser_serialise (serialised, random_instance_filler,
                                        (gpointer *) instances, n_children);

        g_assert (memcmp (serialised.data, data, serialised.size) == 0);
        g_assert (g_variant_serialised_n_children (serialised) == n_children);

        for (i = 0; i < n_children; i++)
          {
            GVariantSerialised child;

            child = g_variant_serialised_get_child (serialised, i);
            g_assert (child.type_info == instances[i]->type_info);
            random_instance_assert (instances[i], child.data, child.size);
            g_variant_type_info_unref (child.type_info);
          }

        flavoured_free (serialised.data, flavour);
      }
  }

  {
    guint i;

    for (i = 0; i < n_children; i++)
      random_instance_free (instances[i]);
    g_free (instances);
  }

  g_variant_type_info_unref (element_info);
  g_variant_type_info_unref (array_info);
  align_free (data);
}

static void
test_arrays (void)
{
  guint i;

  for (i = 0; i < 100; i++)
    test_array ();

  g_variant_type_info_assert_no_infos ();
}

static void
test_tuple (void)
{
  GVariantTypeInfo *type_info;
  RandomInstance **instances;
  gboolean fixed_size;
  gsize needed_size;
  gsize offset_size;
  guint n_children;
  guint alignment;
  guchar *data;

  n_children = g_test_rand_int_range (0, MAX_TUPLE_CHILDREN);
  instances = g_new (RandomInstance *, n_children);

  {
    GString *type_string;
    guint i;

    fixed_size = TRUE;
    alignment = 0;

    type_string = g_string_new ("(");
    for (i = 0; i < n_children; i++)
      {
        const gchar *str;

        instances[i] = random_instance (NULL);

        alignment |= instances[i]->alignment;
        if (!instances[i]->is_fixed_sized)
          fixed_size = FALSE;

        str = g_variant_type_info_get_type_string (instances[i]->type_info);
        g_string_append (type_string, str);
      }
    g_string_append_c (type_string, ')');

    type_info = g_variant_type_info_get (G_VARIANT_TYPE (type_string->str));
    g_string_free (type_string, TRUE);
  }

  needed_size = g_variant_serialiser_needed_size (type_info,
                                                  random_instance_filler,
                                                  (gpointer *) instances,
                                                  n_children);
  {
    gsize body_size = 0;
    gsize offsets = 0;
    guint i;

    for (i = 0; i < n_children; i++)
      {
        append_instance_size (instances[i], &body_size);

        if (i != n_children - 1 && !instances[i]->is_fixed_sized)
          offsets++;
      }

    if (fixed_size)
      {
        body_size += (-body_size) & alignment;

        g_assert ((body_size == 0) == (n_children == 0));
        if (n_children == 0)
          body_size = 1;
      }

    offset_size = calculate_offset_size (body_size, offsets);
    g_assert_cmpint (needed_size, ==, body_size + offsets * offset_size);
  }

  {
    guchar *body_ptr;
    guchar *ofs_ptr;
    guint i;

    body_ptr = data = align_malloc (needed_size);
    ofs_ptr = body_ptr + needed_size;

    for (i = 0; i < n_children; i++)
      {
        append_instance_data (instances[i], &body_ptr);

        if (i != n_children - 1 && !instances[i]->is_fixed_sized)
          prepend_offset (&ofs_ptr, body_ptr - data, offset_size);
      }

    if (fixed_size)
      {
        while (((gsize) body_ptr) & alignment)
          *body_ptr++ = '\0';

        g_assert ((body_ptr == data) == (n_children == 0));
        if (n_children == 0)
          *body_ptr++ = '\0';

      }


    g_assert (body_ptr == ofs_ptr);
  }

  {
    gsize flavour;
    guint i;

    alignment = (alignment & ALIGN_BITS) + 1;

    for (flavour = 0; flavour < 8; flavour += alignment)
      {
        GVariantSerialised serialised;

        serialised.type_info = type_info;
        serialised.data = flavoured_malloc (needed_size, flavour);
        serialised.size = needed_size;

        g_variant_serialiser_serialise (serialised, random_instance_filler,
                                        (gpointer *) instances, n_children);

        g_assert (memcmp (serialised.data, data, serialised.size) == 0);
        g_assert (g_variant_serialised_n_children (serialised) == n_children);

        for (i = 0; i < n_children; i++)
          {
            GVariantSerialised child;

            child = g_variant_serialised_get_child (serialised, i);
            g_assert (child.type_info == instances[i]->type_info);
            random_instance_assert (instances[i], child.data, child.size);
            g_variant_type_info_unref (child.type_info);
          }

        flavoured_free (serialised.data, flavour);
      }
  }

  {
    guint i;

    for (i = 0; i < n_children; i++)
      random_instance_free (instances[i]);
    g_free (instances);
  }

  g_variant_type_info_unref (type_info);
  align_free (data);
}

static void
test_tuples (void)
{
  guint i;

  for (i = 0; i < 100; i++)
    test_tuple ();

  g_variant_type_info_assert_no_infos ();
}

static void
test_variant (void)
{
  GVariantTypeInfo *type_info;
  RandomInstance *instance;
  const gchar *type_string;
  gsize needed_size;
  guchar *data;
  gsize len;

  type_info = g_variant_type_info_get (G_VARIANT_TYPE_VARIANT);
  instance = random_instance (NULL);

  type_string = g_variant_type_info_get_type_string (instance->type_info);
  len = strlen (type_string);

  needed_size = g_variant_serialiser_needed_size (type_info,
                                                  random_instance_filler,
                                                  (gpointer *) &instance, 1);

  g_assert_cmpint (needed_size, ==, instance->size + 1 + len);

  {
    guchar *ptr;

    ptr = data = align_malloc (needed_size);
    append_instance_data (instance, &ptr);
    *ptr++ = '\0';
    memcpy (ptr, type_string, len);
    ptr += len;

    g_assert (data + needed_size == ptr);
  }

  {
    gsize alignment;
    gsize flavour;

    /* variants are always 8-aligned */
    alignment = ALIGN_BITS + 1;

    for (flavour = 0; flavour < 8; flavour += alignment)
      {
        GVariantSerialised serialised;
        GVariantSerialised child;

        serialised.type_info = type_info;
        serialised.data = flavoured_malloc (needed_size, flavour);
        serialised.size = needed_size;

        g_variant_serialiser_serialise (serialised, random_instance_filler,
                                        (gpointer *) &instance, 1);

        g_assert (memcmp (serialised.data, data, serialised.size) == 0);
        g_assert (g_variant_serialised_n_children (serialised) == 1);

        child = g_variant_serialised_get_child (serialised, 0);
        g_assert (child.type_info == instance->type_info);
        random_instance_check (instance, child.data, child.size);

        g_variant_type_info_unref (child.type_info);
        flavoured_free (serialised.data, flavour);
      }
  }

  g_variant_type_info_unref (type_info);
  random_instance_free (instance);
  align_free (data);
}

static void
test_variants (void)
{
  guint i;

  for (i = 0; i < 100; i++)
    test_variant ();

  g_variant_type_info_assert_no_infos ();
}

static void
test_strings (void)
{
  struct {
    guint flags;
    guint size;
    gconstpointer data;
  } test_cases[] = {
#define is_nval           0
#define is_string         1
#define is_objpath        is_string | 2
#define is_sig            is_string | 4
    { is_sig,       1, "" },
    { is_nval,      0, NULL },
    { is_nval,     13, "hello\xffworld!" },
    { is_string,   13, "hello world!" },
    { is_nval,     13, "hello world\0" },
    { is_nval,     13, "hello\0world!" },
    { is_nval,     12, "hello world!" },
    { is_nval,     13, "hello world!\xff" },

    { is_objpath,   2, "/" },
    { is_objpath,   3, "/a" },
    { is_string,    3, "//" },
    { is_objpath,  11, "/some/path" },
    { is_string,   12, "/some/path/" },
    { is_nval,     11, "/some\0path" },
    { is_string,   11, "/some\\path" },
    { is_string,   12, "/some//path" },
    { is_string,   12, "/some-/path" },

    { is_sig,       2, "i" },
    { is_sig,       2, "s" },
    { is_sig,       5, "(si)" },
    { is_string,    4, "(si" },
    { is_string,    2, "*" },
    { is_sig,       3, "ai" },
    { is_string,    3, "mi" },
    { is_string,    2, "r" },
    { is_sig,      15, "(yyy{sv}ssiai)" },
    { is_string,   16, "(yyy{yv}ssiai))" },
    { is_string,   15, "(yyy{vv}ssiai)" },
    { is_string,   15, "(yyy{sv)ssiai}" }
  };
  guint i;

  for (i = 0; i < G_N_ELEMENTS (test_cases); i++)
    {
      guint flags;

      flags = g_variant_serialiser_is_string (test_cases[i].data,
                                              test_cases[i].size)
        ? 1 : 0;

      flags |= g_variant_serialiser_is_object_path (test_cases[i].data,
                                                    test_cases[i].size)
        ? 2 : 0;

      flags |= g_variant_serialiser_is_signature (test_cases[i].data,
                                                  test_cases[i].size)
        ? 4 : 0;

      g_assert (flags == test_cases[i].flags);
    }
}

typedef struct _TreeInstance TreeInstance;
struct _TreeInstance
{
  GVariantTypeInfo *info;

  TreeInstance **children;
  gsize n_children;

  union {
    guint64 integer;
    gdouble floating;
    gchar string[32];
  } data;
  gsize data_size;
};

static GVariantType *
make_random_definite_type (int depth)
{
  GString *description;
  GString *type_string;
  GVariantType *type;

  description = g_string_new (NULL);
  type_string = g_string_new (NULL);
  type = append_type_string (type_string, description, TRUE, depth);
  g_string_free (description, TRUE);
  g_string_free (type_string, TRUE);

  return type;
}

static void
make_random_string (gchar              *string,
                    gsize               size,
                    const GVariantType *type)
{
  gint i;

  /* create strings that are valid signature strings */
#define good_chars "bynqiuxthdsog"

  for (i = 0; i < size - 1; i++)
    string[i] = good_chars[g_test_rand_int_range (0, strlen (good_chars))];
  string[i] = '\0';

  /* in case we need an object path, prefix a '/' */
  if (*g_variant_type_peek_string (type) == 'o')
    string[0] = '/';

#undef good_chars
}

static TreeInstance *
tree_instance_new (const GVariantType *type,
                   int                 depth)
{
  const GVariantType *child_type = NULL;
  GVariantType *mytype = NULL;
  TreeInstance *instance;
  gboolean is_tuple_type;

  if (type == NULL)
    type = mytype = make_random_definite_type (depth);

  instance = g_slice_new (TreeInstance);
  instance->info = g_variant_type_info_get (type);
  instance->children = NULL;
  instance->n_children = 0;
  instance->data_size = 0;

  is_tuple_type = FALSE;

  switch (*g_variant_type_peek_string (type))
    {
    case G_VARIANT_TYPE_INFO_CHAR_MAYBE:
      instance->n_children = g_test_rand_int_range (0, 2);
      child_type = g_variant_type_element (type);
      break;

    case G_VARIANT_TYPE_INFO_CHAR_ARRAY:
      instance->n_children = g_test_rand_int_range (0, MAX_ARRAY_CHILDREN);
      child_type = g_variant_type_element (type);
      break;

    case G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY:
    case G_VARIANT_TYPE_INFO_CHAR_TUPLE:
      instance->n_children = g_variant_type_n_items (type);
      child_type = g_variant_type_first (type);
      is_tuple_type = TRUE;
      break;

    case G_VARIANT_TYPE_INFO_CHAR_VARIANT:
      instance->n_children = 1;
      child_type = NULL;
      break;

    case 'b':
      instance->data.integer = g_test_rand_int_range (0, 2);
      instance->data_size = 1;
      break;

    case 'y':
      instance->data.integer = g_test_rand_int ();
      instance->data_size = 1;
      break;

    case 'n': case 'q':
      instance->data.integer = g_test_rand_int ();
      instance->data_size = 2;
      break;

    case 'i': case 'u': case 'h':
      instance->data.integer = g_test_rand_int ();
      instance->data_size = 4;
      break;

    case 'x': case 't':
      instance->data.integer = g_test_rand_int ();
      instance->data.integer <<= 32;
      instance->data.integer |= (guint32) g_test_rand_int ();
      instance->data_size = 8;
      break;

    case 'd':
      instance->data.floating = g_test_rand_double ();
      instance->data_size = 8;
      break;

    case 's': case 'o': case 'g':
      instance->data_size = g_test_rand_int_range (10, 20);
      make_random_string (instance->data.string, instance->data_size, type);
      break;
    }

  if (instance->data_size == 0)
    /* no data -> it is a container */
    {
      guint i;

      instance->children = g_new (TreeInstance *, instance->n_children);

      for (i = 0; i < instance->n_children; i++)
        {
          instance->children[i] = tree_instance_new (child_type, depth - 1);

          if (is_tuple_type)
            child_type = g_variant_type_next (child_type);
        }

      g_assert (!is_tuple_type || child_type == NULL);
    }

  g_variant_type_free (mytype);

  return instance;
}

static void
tree_instance_free (TreeInstance *instance)
{
  gint i;

  g_variant_type_info_unref (instance->info);
  for (i = 0; i < instance->n_children; i++)
    tree_instance_free (instance->children[i]);
  g_free (instance->children);
  g_slice_free (TreeInstance, instance);
}

static gboolean i_am_writing_byteswapped;

static void
tree_filler (GVariantSerialised *serialised,
             gpointer            data)
{
  TreeInstance *instance = data;

  if (serialised->type_info == NULL)
    serialised->type_info = instance->info;

  if (instance->data_size == 0)
    /* is a container */
    {
      if (serialised->size == 0)
        serialised->size =
          g_variant_serialiser_needed_size (instance->info, tree_filler,
                                            (gpointer *) instance->children,
                                            instance->n_children);

      if (serialised->data)
        g_variant_serialiser_serialise (*serialised, tree_filler,
                                        (gpointer *) instance->children,
                                        instance->n_children);
    }
  else
    /* it is a leaf */
    {
      if (serialised->size == 0)
        serialised->size = instance->data_size;

      if (serialised->data)
        {
          switch (instance->data_size)
            {
            case 1:
              *serialised->data = instance->data.integer;
              break;

            case 2:
              {
                guint16 value = instance->data.integer;

                if (i_am_writing_byteswapped)
                  value = GUINT16_SWAP_LE_BE (value);

                *(guint16 *) serialised->data = value;
              }
              break;

            case 4:
              {
                guint32 value = instance->data.integer;

                if (i_am_writing_byteswapped)
                  value = GUINT32_SWAP_LE_BE (value);

                *(guint32 *) serialised->data = value;
              }
              break;

            case 8:
              {
                guint64 value = instance->data.integer;

                if (i_am_writing_byteswapped)
                  value = GUINT64_SWAP_LE_BE (value);

                *(guint64 *) serialised->data = value;
              }
              break;

            default:
              memcpy (serialised->data,
                      instance->data.string,
                      instance->data_size);
              break;
            }
        }
    }
}

static gboolean
check_tree (TreeInstance       *instance,
            GVariantSerialised  serialised)
{
  if (instance->info != serialised.type_info)
    return FALSE;

  if (instance->data_size == 0)
    /* is a container */
    {
      gint i;

      if (g_variant_serialised_n_children (serialised) !=
          instance->n_children)
        return FALSE;

      for (i = 0; i < instance->n_children; i++)
        {
          GVariantSerialised child;
          gpointer data = NULL;
          gboolean ok;

          child = g_variant_serialised_get_child (serialised, i);
          if (child.size && child.data == NULL)
            child.data = data = g_malloc0 (child.size);
          ok = check_tree (instance->children[i], child);
          g_variant_type_info_unref (child.type_info);
          g_free (data);

          if (!ok)
            return FALSE;
        }

      return TRUE;
    }
  else
    /* it is a leaf */
    {
      switch (instance->data_size)
        {
        case 1:
          g_assert (serialised.size == 1);
          return *(guint8 *) serialised.data ==
                  (guint8) instance->data.integer;

        case 2:
          g_assert (serialised.size == 2);
          return *(guint16 *) serialised.data ==
                  (guint16) instance->data.integer;

        case 4:
          g_assert (serialised.size == 4);
          return *(guint32 *) serialised.data ==
                  (guint32) instance->data.integer;

        case 8:
          g_assert (serialised.size == 8);
          return *(guint64 *) serialised.data ==
                  (guint64) instance->data.integer;

        default:
          if (serialised.size != instance->data_size)
            return FALSE;

          return memcmp (serialised.data,
                         instance->data.string,
                         instance->data_size) == 0;
        }
    }
}

static void
serialise_tree (TreeInstance       *tree,
                GVariantSerialised *serialised)
{
  GVariantSerialised empty = {  };

  *serialised = empty;
  tree_filler (serialised, tree);
  serialised->data = g_malloc (serialised->size);
  tree_filler (serialised, tree);
}

static void
test_byteswap (void)
{
  GVariantSerialised one, two;
  TreeInstance *tree;

  tree = tree_instance_new (NULL, 3);
  serialise_tree (tree, &one);

  i_am_writing_byteswapped = TRUE;
  serialise_tree (tree, &two);
  i_am_writing_byteswapped = FALSE;

  g_variant_serialised_byteswap (two);

  g_assert_cmpint (one.size, ==, two.size);
  g_assert (memcmp (one.data, two.data, one.size) == 0);

  tree_instance_free (tree);
  g_free (one.data);
  g_free (two.data);
}

static void
test_byteswaps (void)
{
  int i;

  for (i = 0; i < 200; i++)
    test_byteswap ();

  g_variant_type_info_assert_no_infos ();
}

static void
test_fuzz (gdouble *fuzziness)
{
  GVariantSerialised serialised;
  TreeInstance *tree;

  /* make an instance */
  tree = tree_instance_new (NULL, 3);

  /* serialise it */
  serialise_tree (tree, &serialised);

  g_assert (g_variant_serialised_is_normal (serialised));
  g_assert (check_tree (tree, serialised));

  if (serialised.size)
    {
      gboolean fuzzed = FALSE;
      gboolean a, b;

      while (!fuzzed)
        {
          gint i;

          for (i = 0; i < serialised.size; i++)
            if (randomly (*fuzziness))
              {
                serialised.data[i] += g_test_rand_int_range (1, 256);
                fuzzed = TRUE;
              }
        }

      /* at least one byte in the serialised data has changed.
       *
       * this means that at least one of the following is true:
       *
       *    - the serialised data now represents a different value:
       *        check_tree() will return FALSE
       *
       *    - the serialised data is in non-normal form:
       *        g_variant_serialiser_is_normal() will return FALSE
       *
       * we always do both checks to increase exposure of the serialiser
       * to corrupt data.
       */
      a = g_variant_serialised_is_normal (serialised);
      b = check_tree (tree, serialised);

      g_assert (!a || !b);
    }

  tree_instance_free (tree);
  g_free (serialised.data);
}


static void
test_fuzzes (gpointer data)
{
  gdouble fuzziness;
  int i;

  fuzziness = GPOINTER_TO_INT (data) / 100.;

  for (i = 0; i < 200; i++)
    test_fuzz (&fuzziness);

  g_variant_type_info_assert_no_infos ();
}

static GVariant *
tree_instance_get_gvariant (TreeInstance *tree)
{
  const GVariantType *type;
  GVariant *result;

  type = (GVariantType *) g_variant_type_info_get_type_string (tree->info);

  switch (g_variant_type_info_get_type_char (tree->info))
    {
    case G_VARIANT_TYPE_INFO_CHAR_MAYBE:
      {
        const GVariantType *child_type;
        GVariant *child;

        if (tree->n_children)
          child = tree_instance_get_gvariant (tree->children[0]);
        else
          child = NULL;

        child_type = g_variant_type_element (type);

        if (child != NULL && randomly (0.5))
          child_type = NULL;

        result = g_variant_new_maybe (child_type, child);
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_ARRAY:
      {
        const GVariantType *child_type;
        GVariant **children;
        gint i;

        children = g_new (GVariant *, tree->n_children);
        for (i = 0; i < tree->n_children; i++)
          children[i] = tree_instance_get_gvariant (tree->children[i]);

        child_type = g_variant_type_element (type);

        if (i > 0 && randomly (0.5))
          child_type = NULL;

        result = g_variant_new_array (child_type, children, tree->n_children);
        g_free (children);
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_TUPLE:
      {
        GVariant **children;
        gint i;

        children = g_new (GVariant *, tree->n_children);
        for (i = 0; i < tree->n_children; i++)
          children[i] = tree_instance_get_gvariant (tree->children[i]);

        result = g_variant_new_tuple (children, tree->n_children);
        g_free (children);
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY:
      {
        GVariant *key, *val;

        g_assert (tree->n_children == 2);

        key = tree_instance_get_gvariant (tree->children[0]);
        val = tree_instance_get_gvariant (tree->children[1]);

        result = g_variant_new_dict_entry (key, val);
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_VARIANT:
      {
        GVariant *value;

        g_assert (tree->n_children == 1);

        value = tree_instance_get_gvariant (tree->children[0]);
        result = g_variant_new_variant (value);
      }
      break;

    case 'b':
      result = g_variant_new_boolean (tree->data.integer > 0);
      break;

    case 'y':
      result = g_variant_new_byte (tree->data.integer);
      break;

    case 'n':
      result = g_variant_new_int16 (tree->data.integer);
      break;

    case 'q':
      result = g_variant_new_uint16 (tree->data.integer);
      break;

    case 'i':
      result = g_variant_new_int32 (tree->data.integer);
      break;

    case 'u':
      result = g_variant_new_uint32 (tree->data.integer);
      break;

    case 'x':
      result = g_variant_new_int64 (tree->data.integer);
      break;

    case 't':
      result = g_variant_new_uint64 (tree->data.integer);
      break;

    case 'h':
      result = g_variant_new_handle (tree->data.integer);
      break;

    case 'd':
      result = g_variant_new_double (tree->data.floating);
      break;

    case 's':
      result = g_variant_new_string (tree->data.string);
      break;

    case 'o':
      result = g_variant_new_object_path (tree->data.string);
      break;

    case 'g':
      result = g_variant_new_signature (tree->data.string);
      break;

    default:
      g_assert_not_reached ();
    }

  return result;
}

static gboolean
tree_instance_check_gvariant (TreeInstance *tree,
                              GVariant     *value)
{
  const GVariantType *type;

  type = (GVariantType *) g_variant_type_info_get_type_string (tree->info);
  g_assert (g_variant_is_of_type (value, type));

  switch (g_variant_type_info_get_type_char (tree->info))
    {
    case G_VARIANT_TYPE_INFO_CHAR_MAYBE:
      {
        GVariant *child;
        gboolean equal;

        child = g_variant_get_maybe (value);

        if (child != NULL && tree->n_children == 1)
          equal = tree_instance_check_gvariant (tree->children[0], child);
        else if (child == NULL && tree->n_children == 0)
          equal = TRUE;
        else
          equal = FALSE;

        if (child != NULL)
          g_variant_unref (child);

        return equal;
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_ARRAY:
    case G_VARIANT_TYPE_INFO_CHAR_TUPLE:
    case G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY:
      {
        gsize i;

        if (g_variant_n_children (value) != tree->n_children)
          return FALSE;

        for (i = 0; i < tree->n_children; i++)
          {
            GVariant *child;
            gboolean equal;

            child = g_variant_get_child_value (value, i);
            equal = tree_instance_check_gvariant (tree->children[i], child);
            g_variant_unref (child);

            if (!equal)
              return FALSE;
          }

        return TRUE;
      }
      break;

    case G_VARIANT_TYPE_INFO_CHAR_VARIANT:
      {
        const gchar *str1, *str2;
        GVariant *child;
        gboolean equal;

        child = g_variant_get_variant (value);
        str1 = g_variant_get_type_string (child);
        str2 = g_variant_type_info_get_type_string (tree->children[0]->info);
        /* GVariant only keeps one copy of type strings around */
        equal = str1 == str2 &&
                tree_instance_check_gvariant (tree->children[0], child);

        g_variant_unref (child);

        return equal;
      }
      break;

    case 'b':
      return g_variant_get_boolean (value) == tree->data.integer;

    case 'y':
      return g_variant_get_byte (value) == (guchar) tree->data.integer;

    case 'n':
      return g_variant_get_int16 (value) == (gint16) tree->data.integer;

    case 'q':
      return g_variant_get_uint16 (value) == (guint16) tree->data.integer;

    case 'i':
      return g_variant_get_int32 (value) == (gint32) tree->data.integer;

    case 'u':
      return g_variant_get_uint32 (value) == (guint32) tree->data.integer;

    case 'x':
      return g_variant_get_int64 (value) == (gint64) tree->data.integer;

    case 't':
      return g_variant_get_uint64 (value) == (guint64) tree->data.integer;

    case 'h':
      return g_variant_get_handle (value) == (gint32) tree->data.integer;

    case 'd':
      {
        gdouble floating = g_variant_get_double (value);

        return memcmp (&floating, &tree->data.floating, sizeof floating) == 0;
      }

    case 's':
    case 'o':
    case 'g':
      return strcmp (g_variant_get_string (value, NULL),
                     tree->data.string) == 0;

    default:
      g_assert_not_reached ();
    }
}

static void
tree_instance_build_gvariant (TreeInstance    *tree,
                              GVariantBuilder *builder,
                              gboolean         guess_ok)
{
  const GVariantType *type;

  type = (GVariantType *) g_variant_type_info_get_type_string (tree->info);

  if (g_variant_type_is_container (type))
    {
      gsize i;

      /* force GVariantBuilder to guess the type half the time */
      if (guess_ok && randomly (0.5))
        {
          if (g_variant_type_is_array (type) && tree->n_children)
            type = G_VARIANT_TYPE_ARRAY;

          if (g_variant_type_is_maybe (type) && tree->n_children)
            type = G_VARIANT_TYPE_MAYBE;

          if (g_variant_type_is_tuple (type))
            type = G_VARIANT_TYPE_TUPLE;

          if (g_variant_type_is_dict_entry (type))
            type = G_VARIANT_TYPE_DICT_ENTRY;
        }
      else
        guess_ok = FALSE;

      g_variant_builder_open (builder, type);

      for (i = 0; i < tree->n_children; i++)
        tree_instance_build_gvariant (tree->children[i], builder, guess_ok);

      g_variant_builder_close (builder);
    }
  else
    g_variant_builder_add_value (builder, tree_instance_get_gvariant (tree));
}


static gboolean
tree_instance_check_iter (TreeInstance *tree,
                          GVariantIter *iter)
{
  GVariant *value;

  value = g_variant_iter_next_value (iter);

  if (g_variant_is_container (value))
    {
      gsize i;

      iter = g_variant_iter_new (value);
      g_variant_unref (value);

      if (g_variant_iter_n_children (iter) != tree->n_children)
        {
          g_variant_iter_free (iter);
          return FALSE;
        }

      for (i = 0; i < tree->n_children; i++)
        if (!tree_instance_check_iter (tree->children[i], iter))
          {
            g_variant_iter_free (iter);
            return FALSE;
          }

      g_assert (g_variant_iter_next_value (iter) == NULL);
      g_variant_iter_free (iter);

      return TRUE;
    }

  else
    {
      gboolean equal;

      equal = tree_instance_check_gvariant (tree, value);
      g_variant_unref (value);

      return equal;
    }
}

static void
test_container (void)
{
  TreeInstance *tree;
  GVariant *value;
  gchar *s1, *s2;

  tree = tree_instance_new (NULL, 3);
  value = g_variant_ref_sink (tree_instance_get_gvariant (tree));

  s1 = g_variant_print (value, TRUE);
  g_assert (tree_instance_check_gvariant (tree, value));

  g_variant_get_data (value);

  s2 = g_variant_print (value, TRUE);
  g_assert (tree_instance_check_gvariant (tree, value));

  g_assert_cmpstr (s1, ==, s2);

  if (g_variant_is_container (value))
    {
      GVariantBuilder builder;
      GVariantIter iter;
      GVariant *built;
      GVariant *val;
      gchar *s3;

      g_variant_builder_init (&builder, G_VARIANT_TYPE_VARIANT);
      tree_instance_build_gvariant (tree, &builder, TRUE);
      built = g_variant_builder_end (&builder);
      g_variant_ref_sink (built);
      g_variant_get_data (built);
      val = g_variant_get_variant (built);

      s3 = g_variant_print (val, TRUE);
      g_assert_cmpstr (s1, ==, s3);

      g_variant_iter_init (&iter, built);
      g_assert (tree_instance_check_iter (tree, &iter));
      g_assert (g_variant_iter_next_value (&iter) == NULL);

      g_variant_unref (built);
      g_variant_unref (val);
      g_free (s3);
    }

  tree_instance_free (tree);
  g_variant_unref (value);
  g_free (s2);
  g_free (s1);
}

static void
test_utf8 (void)
{
  const gchar invalid[] = "hello\xffworld";
  GVariant *value;

  /* ensure that the test data is not valid utf8... */
  g_assert (!g_utf8_validate (invalid, -1, NULL));

  /* load the data untrusted */
  value = g_variant_new_from_data (G_VARIANT_TYPE_STRING,
                                   invalid, sizeof invalid,
                                   FALSE, NULL, NULL);

  /* ensure that the problem is caught and we get valid UTF-8 */
  g_assert (g_utf8_validate (g_variant_get_string (value, NULL), -1, NULL));
  g_variant_unref (value);


  /* now load it trusted */
  value = g_variant_new_from_data (G_VARIANT_TYPE_STRING,
                                   invalid, sizeof invalid,
                                   TRUE, NULL, NULL);

  /* ensure we get the invalid data (ie: make sure that time wasn't
   * wasted on validating data that was marked as trusted)
   */
  g_assert (g_variant_get_string (value, NULL) == invalid);
  g_variant_unref (value);
}

static void
test_containers (void)
{
  gint i;

  for (i = 0; i < 100; i++)
    {
      test_container ();
    }

  g_variant_type_info_assert_no_infos ();
}

static void
test_format_strings (void)
{
  GVariantType *type;
  const gchar *end;

  g_assert (g_variant_format_string_scan ("i", NULL, &end) && *end == '\0');
  g_assert (g_variant_format_string_scan ("@i", NULL, &end) && *end == '\0');
  g_assert (g_variant_format_string_scan ("@ii", NULL, &end) && *end == 'i');
  g_assert (g_variant_format_string_scan ("^a&s", NULL, &end) && *end == '\0');
  g_assert (g_variant_format_string_scan ("(^as)", NULL, &end) &&
            *end == '\0');
  g_assert (!g_variant_format_string_scan ("(^s)", NULL, &end));
  g_assert (!g_variant_format_string_scan ("(^a)", NULL, &end));
  g_assert (!g_variant_format_string_scan ("(z)", NULL, &end));
  g_assert (!g_variant_format_string_scan ("az", NULL, &end));
  g_assert (!g_variant_format_string_scan ("{**}", NULL, &end));
  g_assert (!g_variant_format_string_scan ("{@**}", NULL, &end));
  g_assert (g_variant_format_string_scan ("{@y*}", NULL, &end) &&
            *end == '\0');
  g_assert (g_variant_format_string_scan ("{yv}", NULL, &end) &&
            *end == '\0');
  g_assert (!g_variant_format_string_scan ("{&?v}", NULL, &end));
  g_assert (g_variant_format_string_scan ("{@?v}", NULL, &end) &&
            *end == '\0');
  g_assert (!g_variant_format_string_scan ("{&@sv}", NULL, &end));
  g_assert (!g_variant_format_string_scan ("{@&sv}", NULL, &end));
  g_assert (g_variant_format_string_scan ("{&sv}", NULL, &end) &&
            *end == '\0');
  g_assert (!g_variant_format_string_scan ("{vv}", NULL, &end));
  g_assert (!g_variant_format_string_scan ("{y}", NULL, &end));
  g_assert (!g_variant_format_string_scan ("{yyy}", NULL, &end));
  g_assert (!g_variant_format_string_scan ("{ya}", NULL, &end));
  g_assert (g_variant_format_string_scan ("&s", NULL, &end) && *end == '\0');
  g_assert (!g_variant_format_string_scan ("&as", NULL, &end));
  g_assert (!g_variant_format_string_scan ("@z", NULL, &end));
  g_assert (!g_variant_format_string_scan ("az", NULL, &end));
  g_assert (!g_variant_format_string_scan ("a&s", NULL, &end));

  type = g_variant_format_string_scan_type ("mm(@xy^a&s*?@?)", NULL, &end);
  g_assert (type && *end == '\0');
  g_assert (g_variant_type_equal (type, G_VARIANT_TYPE ("mm(xyas*?\?)")));
  g_variant_type_free (type);

  type = g_variant_format_string_scan_type ("mm(@xy^a&*?@?)", NULL, NULL);
  g_assert (type == NULL);
}

static void
exit_on_abort (int signal)
{
  exit (signal);
}

static gboolean
do_failed_test (const gchar *pattern)
{
  if (g_test_trap_fork (1000000, G_TEST_TRAP_SILENCE_STDERR))
    {
      signal (SIGABRT, exit_on_abort);
      return TRUE;
    }

  g_test_trap_assert_failed ();
  g_test_trap_assert_stderr (pattern);

  return FALSE;
}

static void
test_invalid_varargs (void)
{
  if (!g_test_undefined ())
    return;

  if (do_failed_test ("*GVariant format string*"))
    {
      g_variant_new ("z");
      abort ();
    }

  if (do_failed_test ("*valid GVariant format string as a prefix*"))
    {
      const gchar *end;

      g_variant_new_va ("z", &end, NULL);
      abort ();
    }

  if (do_failed_test ("*type of `q' but * has a type of `y'*"))
    {
      g_variant_get (g_variant_new ("y", 'a'), "q");
      abort ();
    }
}

static void
check_and_free (GVariant    *value,
                const gchar *str)
{
  gchar *valstr = g_variant_print (value, FALSE);
  g_assert_cmpstr (str, ==, valstr);
  g_variant_unref (value);
  g_free (valstr);
}

static void
test_varargs (void)
{
  {
    GVariantBuilder array;

    g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);
    g_variant_builder_add_parsed (&array, "{'size', <(%i, %i)> }", 800, 600);
    g_variant_builder_add (&array, "{sv}", "title",
                           g_variant_new_string ("Test case"));
    g_variant_builder_add_value (&array,
      g_variant_new_dict_entry (g_variant_new_string ("temperature"),
                                g_variant_new_variant (
                                  g_variant_new_double (37.5))));
    check_and_free (g_variant_new ("(ma{sv}m(a{sv})ma{sv}ii)",
                                   NULL, FALSE, NULL, &array, 7777, 8888),
                    "(nothing, nothing, {'size': <(800, 600)>, "
                                        "'title': <'Test case'>, "
                                        "'temperature': <37.5>}, "
                     "7777, 8888)");

    check_and_free (g_variant_new ("(imimimmimmimmi)",
                                   123,
                                   FALSE, 321,
                                   TRUE, 123,
                                   FALSE, TRUE, 321,
                                   TRUE, FALSE, 321,
                                   TRUE, TRUE, 123),
                    "(123, nothing, 123, nothing, just nothing, 123)");

    check_and_free (g_variant_new ("(ybnixd)",
                                   'a', 1, 22, 33, (guint64) 44, 5.5),
                    "(0x61, true, 22, 33, 44, 5.5)");

    check_and_free (g_variant_new ("(@y?*rv)",
                                   g_variant_new ("y", 'a'),
                                   g_variant_new ("y", 'b'),
                                   g_variant_new ("y", 'c'),
                                   g_variant_new ("(y)", 'd'),
                                   g_variant_new ("y", 'e')),
                    "(0x61, 0x62, 0x63, (0x64,), <byte 0x65>)");
  }

  {
    GVariantBuilder array;
    GVariantIter iter;
    GVariant *value;
    gchar *number;
    gboolean just;
    gint i, val;

    g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);
    for (i = 0; i < 100; i++)
      {
        number = g_strdup_printf ("%d", i);
        g_variant_builder_add (&array, "s", number);
        g_free (number);
      }

    value = g_variant_builder_end (&array);
    g_variant_iter_init (&iter, value);

    i = 0;
    while (g_variant_iter_loop (&iter, "s", &number))
      {
        gchar *check = g_strdup_printf ("%d", i++);
        g_assert_cmpstr (number, ==, check);
        g_free (check);
      }
    g_assert (number == NULL);
    g_assert (i == 100);

    g_variant_unref (value);

    g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);
    for (i = 0; i < 100; i++)
      g_variant_builder_add (&array, "mi", i % 2 == 0, i);
    value = g_variant_builder_end (&array);

    i = 0;
    g_variant_iter_init (&iter, value);
    while (g_variant_iter_loop (&iter, "mi", NULL, &val))
      g_assert (val == i++ || val == 0);
    g_assert (i == 100);

    i = 0;
    g_variant_iter_init (&iter, value);
    while (g_variant_iter_loop (&iter, "mi", &just, &val))
      {
        gint this = i++;

        if (this % 2 == 0)
          {
            g_assert (just);
            g_assert (val == this);
          }
        else
          {
            g_assert (!just);
            g_assert (val == 0);
          }
      }
    g_assert (i == 100);

    g_variant_unref (value);
  }

  {
    const gchar *strvector[] = {"/hello", "/world", NULL};
    const gchar *test_strs[] = {"/foo", "/bar", "/baz" };
    GVariantBuilder builder;
    GVariantIter *array;
    GVariantIter tuple;
    const gchar **strv;
    gchar **my_strv;
    GVariant *value;
    gchar *str;
    gint i;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
    g_variant_builder_add (&builder, "s", "/foo");
    g_variant_builder_add (&builder, "s", "/bar");
    g_variant_builder_add (&builder, "s", "/baz");
    value = g_variant_new("(as^as^a&s)", &builder, strvector, strvector);
    g_variant_iter_init (&tuple, value);
    g_variant_iter_next (&tuple, "as", &array);

    i = 0;
    while (g_variant_iter_loop (array, "s", &str))
      g_assert_cmpstr (str, ==, test_strs[i++]);
    g_assert (i == 3);

    g_variant_iter_free (array);

    /* start over */
    g_variant_iter_init (&tuple, value);
    g_variant_iter_next (&tuple, "as", &array);

    i = 0;
    while (g_variant_iter_loop (array, "&s", &str))
      g_assert_cmpstr (str, ==, test_strs[i++]);
    g_assert (i == 3);

    g_variant_iter_free (array);

    g_variant_iter_next (&tuple, "^a&s", &strv);
    g_variant_iter_next (&tuple, "^as", &my_strv);

    g_assert_cmpstr (strv[0], ==, "/hello");
    g_assert_cmpstr (strv[1], ==, "/world");
    g_assert (strv[2] == NULL);
    g_assert_cmpstr (my_strv[0], ==, "/hello");
    g_assert_cmpstr (my_strv[1], ==, "/world");
    g_assert (my_strv[2] == NULL);

    g_variant_unref (value);
    g_strfreev (my_strv);
    g_free (strv);
  }

  {
    const gchar *strvector[] = {"/hello", "/world", NULL};
    const gchar *test_strs[] = {"/foo", "/bar", "/baz" };
    GVariantBuilder builder;
    GVariantIter *array;
    GVariantIter tuple;
    const gchar **strv;
    gchar **my_strv;
    GVariant *value;
    gchar *str;
    gint i;

    g_variant_builder_init (&builder, G_VARIANT_TYPE_OBJECT_PATH_ARRAY);
    g_variant_builder_add (&builder, "o", "/foo");
    g_variant_builder_add (&builder, "o", "/bar");
    g_variant_builder_add (&builder, "o", "/baz");
    value = g_variant_new("(ao^ao^a&o)", &builder, strvector, strvector);
    g_variant_iter_init (&tuple, value);
    g_variant_iter_next (&tuple, "ao", &array);

    i = 0;
    while (g_variant_iter_loop (array, "o", &str))
      g_assert_cmpstr (str, ==, test_strs[i++]);
    g_assert (i == 3);

    g_variant_iter_free (array);

    /* start over */
    g_variant_iter_init (&tuple, value);
    g_variant_iter_next (&tuple, "ao", &array);

    i = 0;
    while (g_variant_iter_loop (array, "&o", &str))
      g_assert_cmpstr (str, ==, test_strs[i++]);
    g_assert (i == 3);

    g_variant_iter_free (array);

    g_variant_iter_next (&tuple, "^a&o", &strv);
    g_variant_iter_next (&tuple, "^ao", &my_strv);

    g_assert_cmpstr (strv[0], ==, "/hello");
    g_assert_cmpstr (strv[1], ==, "/world");
    g_assert (strv[2] == NULL);
    g_assert_cmpstr (my_strv[0], ==, "/hello");
    g_assert_cmpstr (my_strv[1], ==, "/world");
    g_assert (my_strv[2] == NULL);

    g_variant_unref (value);
    g_strfreev (my_strv);
    g_free (strv);
  }

  {
    const gchar *strvector[] = { "i", "ii", "iii", "iv", "v", "vi", NULL };
    GVariantBuilder builder;
    GVariantIter iter;
    GVariantIter *i2;
    GVariantIter *i3;
    GVariant *value;
    GVariant *sub;
    gchar **strv;
    gint i;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("aas"));
    g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
    for (i = 0; i < 6; i++)
      if (i & 1)
        g_variant_builder_add (&builder, "s", strvector[i]);
      else
        g_variant_builder_add (&builder, "&s", strvector[i]);
    g_variant_builder_close (&builder);
    g_variant_builder_add (&builder, "^as", strvector);
    g_variant_builder_add (&builder, "^as", strvector);
    value = g_variant_new ("aas", &builder);

    g_variant_iter_init (&iter, value);
    while (g_variant_iter_loop (&iter, "^as", &strv))
      for (i = 0; i < 6; i++)
        g_assert_cmpstr (strv[i], ==, strvector[i]);

    g_variant_iter_init (&iter, value);
    while (g_variant_iter_loop (&iter, "^a&s", &strv))
      for (i = 0; i < 6; i++)
        g_assert_cmpstr (strv[i], ==, strvector[i]);

    g_variant_iter_init (&iter, value);
    while (g_variant_iter_loop (&iter, "as", &i2))
      {
        gchar *str;

        i = 0;
        while (g_variant_iter_loop (i2, "s", &str))
          g_assert_cmpstr (str, ==, strvector[i++]);
        g_assert (i == 6);
      }

    g_variant_iter_init (&iter, value);
    i3 = g_variant_iter_copy (&iter);
    while (g_variant_iter_loop (&iter, "@as", &sub))
      {
        gchar *str = g_variant_print (sub, TRUE);
        g_assert_cmpstr (str, ==,
                         "['i', 'ii', 'iii', 'iv', 'v', 'vi']");
        g_free (str);
      }

  if (do_failed_test ("*NULL has already been returned*"))
    {
      g_variant_iter_next_value (&iter);
      abort ();
    }


    while (g_variant_iter_loop (i3, "*", &sub))
      {
        gchar *str = g_variant_print (sub, TRUE);
        g_assert_cmpstr (str, ==,
                         "['i', 'ii', 'iii', 'iv', 'v', 'vi']");
        g_free (str);
      }

    g_variant_iter_free (i3);

    for (i = 0; i < g_variant_n_children (value); i++)
      {
        gint j;

        g_variant_get_child (value, i, "*", &sub);

        for (j = 0; j < g_variant_n_children (sub); j++)
          {
            const gchar *str = NULL;
            GVariant *cval;

            g_variant_get_child (sub, j, "&s", &str);
            g_assert_cmpstr (str, ==, strvector[j]);

            cval = g_variant_get_child_value (sub, j);
            g_variant_get (cval, "&s", &str);
            g_assert_cmpstr (str, ==, strvector[j]);
            g_variant_unref (cval);
          }

        g_variant_unref (sub);
      }

    g_variant_unref (value);
  }

  {
    gboolean justs[10];
    GVariant *value;

    GVariant *vval;
    guchar byteval;
    gboolean bval;
    gint16 i16val;
    guint16 u16val;
    gint32 i32val;
    guint32 u32val;
    gint64 i64val;
    guint64 u64val;
    gdouble dval;
    gint32 hval;

    /* test all 'nothing' */
    value = g_variant_new ("(mymbmnmqmimumxmtmhmdmv)",
                           FALSE, 'a',
                           FALSE, TRUE,
                           FALSE, (gint16) 123,
                           FALSE, (guint16) 123,
                           FALSE, (gint32) 123,
                           FALSE, (guint32) 123,
                           FALSE, (gint64) 123,
                           FALSE, (guint64) 123,
                           FALSE, (gint32) -1,
                           FALSE, (gdouble) 37.5,
                           NULL);

    /* both NULL */
    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL);

    /* NULL values */
    memset (justs, 1, sizeof justs);
    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
                   &justs[0], NULL,
                   &justs[1], NULL,
                   &justs[2], NULL,
                   &justs[3], NULL,
                   &justs[4], NULL,
                   &justs[5], NULL,
                   &justs[6], NULL,
                   &justs[7], NULL,
                   &justs[8], NULL,
                   &justs[9], NULL,
                   NULL);
    g_assert (!(justs[0] || justs[1] || justs[2] || justs[3] || justs[4] ||
                justs[5] || justs[6] || justs[7] || justs[8] || justs[9]));

    /* both non-NULL */
    memset (justs, 1, sizeof justs);
    byteval = i16val = u16val = i32val = u32val = i64val = u64val = hval = 88;
    vval = (void *) 1;
    bval = TRUE;
    dval = 88.88;
    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
                   &justs[0], &byteval,
                   &justs[1], &bval,
                   &justs[2], &i16val,
                   &justs[3], &u16val,
                   &justs[4], &i32val,
                   &justs[5], &u32val,
                   &justs[6], &i64val,
                   &justs[7], &u64val,
                   &justs[8], &hval,
                   &justs[9], &dval,
                   &vval);
    g_assert (!(justs[0] || justs[1] || justs[2] || justs[3] || justs[4] ||
                justs[5] || justs[6] || justs[7] || justs[8] || justs[9]));
    g_assert (byteval == '\0' && bval == FALSE);
    g_assert (i16val == 0 && u16val == 0 && i32val == 0 &&
              u32val == 0 && i64val == 0 && u64val == 0 &&
              hval == 0 && dval == 0.0);
    g_assert (vval == NULL);

    /* NULL justs */
    byteval = i16val = u16val = i32val = u32val = i64val = u64val = hval = 88;
    vval = (void *) 1;
    bval = TRUE;
    dval = 88.88;
    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
                   NULL, &byteval,
                   NULL, &bval,
                   NULL, &i16val,
                   NULL, &u16val,
                   NULL, &i32val,
                   NULL, &u32val,
                   NULL, &i64val,
                   NULL, &u64val,
                   NULL, &hval,
                   NULL, &dval,
                   &vval);
    g_assert (byteval == '\0' && bval == FALSE);
    g_assert (i16val == 0 && u16val == 0 && i32val == 0 &&
              u32val == 0 && i64val == 0 && u64val == 0 &&
              hval == 0 && dval == 0.0);
    g_assert (vval == NULL);

    g_variant_unref (value);


    /* test all 'just' */
    value = g_variant_new ("(mymbmnmqmimumxmtmhmdmv)",
                           TRUE, 'a',
                           TRUE, TRUE,
                           TRUE, (gint16) 123,
                           TRUE, (guint16) 123,
                           TRUE, (gint32) 123,
                           TRUE, (guint32) 123,
                           TRUE, (gint64) 123,
                           TRUE, (guint64) 123,
                           TRUE, (gint32) -1,
                           TRUE, (gdouble) 37.5,
                           g_variant_new ("()"));

    /* both NULL */
    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL, NULL,
                   NULL);

    /* NULL values */
    memset (justs, 0, sizeof justs);
    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
                   &justs[0], NULL,
                   &justs[1], NULL,
                   &justs[2], NULL,
                   &justs[3], NULL,
                   &justs[4], NULL,
                   &justs[5], NULL,
                   &justs[6], NULL,
                   &justs[7], NULL,
                   &justs[8], NULL,
                   &justs[9], NULL,
                   NULL);
    g_assert (justs[0] && justs[1] && justs[2] && justs[3] && justs[4] &&
              justs[5] && justs[6] && justs[7] && justs[8] && justs[9]);

    /* both non-NULL */
    memset (justs, 0, sizeof justs);
    byteval = i16val = u16val = i32val = u32val = i64val = u64val = hval = 88;
    vval = (void *) 1;
    bval = FALSE;
    dval = 88.88;
    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
                   &justs[0], &byteval,
                   &justs[1], &bval,
                   &justs[2], &i16val,
                   &justs[3], &u16val,
                   &justs[4], &i32val,
                   &justs[5], &u32val,
                   &justs[6], &i64val,
                   &justs[7], &u64val,
                   &justs[8], &hval,
                   &justs[9], &dval,
                   &vval);
    g_assert (justs[0] && justs[1] && justs[2] && justs[3] && justs[4] &&
              justs[5] && justs[6] && justs[7] && justs[8] && justs[9]);
    g_assert (byteval == 'a' && bval == TRUE);
    g_assert (i16val == 123 && u16val == 123 && i32val == 123 &&
              u32val == 123 && i64val == 123 && u64val == 123 &&
              hval == -1 && dval == 37.5);
    g_assert (g_variant_is_of_type (vval, G_VARIANT_TYPE_UNIT));
    g_variant_unref (vval);

    /* NULL justs */
    byteval = i16val = u16val = i32val = u32val = i64val = u64val = hval = 88;
    vval = (void *) 1;
    bval = TRUE;
    dval = 88.88;
    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
                   NULL, &byteval,
                   NULL, &bval,
                   NULL, &i16val,
                   NULL, &u16val,
                   NULL, &i32val,
                   NULL, &u32val,
                   NULL, &i64val,
                   NULL, &u64val,
                   NULL, &hval,
                   NULL, &dval,
                   &vval);
    g_assert (byteval == 'a' && bval == TRUE);
    g_assert (i16val == 123 && u16val == 123 && i32val == 123 &&
              u32val == 123 && i64val == 123 && u64val == 123 &&
              hval == -1 && dval == 37.5);
    g_assert (g_variant_is_of_type (vval, G_VARIANT_TYPE_UNIT));
    g_variant_unref (vval);

    g_variant_unref (value);
  }

  {
    GVariant *value;
    gchar *str;

    value = g_variant_new ("(masas)", NULL, NULL);
    g_variant_ref_sink (value);

    str = g_variant_print (value, TRUE);
    g_assert_cmpstr (str, ==, "(@mas nothing, @as [])");
    g_variant_unref (value);
    g_free (str);

    if (do_failed_test ("*which type of empty array*"))
      g_variant_new ("(a{s*})", NULL);
  }

  g_variant_type_info_assert_no_infos ();
}

static void
hash_get (GVariant    *value,
          const gchar *format,
          ...)
{
  const gchar *endptr = NULL;
  gboolean hash;
  va_list ap;

  hash = g_str_has_suffix (format, "#");

  va_start (ap, format);
  g_variant_get_va (value, format, hash ? &endptr : NULL, &ap);
  va_end (ap);

  if (hash)
    g_assert (*endptr == '#');
}

static GVariant *
hash_new (const gchar *format,
          ...)
{
  const gchar *endptr = NULL;
  GVariant *value;
  gboolean hash;
  va_list ap;

  hash = g_str_has_suffix (format, "#");

  va_start (ap, format);
  value = g_variant_new_va (format, hash ? &endptr : NULL, &ap);
  va_end (ap);

  if (hash)
    g_assert (*endptr == '#');

  return value;
}

static void
test_valist (void)
{
  GVariant *value;
  gint32 x;

  x = 0;
  value = hash_new ("i", 234);
  hash_get (value, "i", &x);
  g_assert (x == 234);
  g_variant_unref (value);

  x = 0;
  value = hash_new ("i#", 234);
  hash_get (value, "i#", &x);
  g_assert (x == 234);
  g_variant_unref (value);

  g_variant_type_info_assert_no_infos ();
}

static void
test_builder_memory (void)
{
  GVariantBuilder *hb;
  GVariantBuilder sb;

  hb = g_variant_builder_new  (G_VARIANT_TYPE_ARRAY);
  g_variant_builder_open (hb, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_open (hb, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_open (hb, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_add (hb, "s", "some value");
  g_variant_builder_ref (hb);
  g_variant_builder_unref (hb);
  g_variant_builder_unref (hb);

  hb = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
  g_variant_builder_unref (hb);

  hb = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
  g_variant_builder_clear (hb);
  g_variant_builder_unref (hb);

  g_variant_builder_init (&sb, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_open (&sb, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_open (&sb, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_add (&sb, "s", "some value");
  g_variant_builder_clear (&sb);

  g_variant_type_info_assert_no_infos ();
}

static void
test_hashing (void)
{
  GVariant *items[4096];
  GHashTable *table;
  gint i;

  table = g_hash_table_new_full (g_variant_hash, g_variant_equal,
                                 (GDestroyNotify ) g_variant_unref,
                                 NULL);

  for (i = 0; i < G_N_ELEMENTS (items); i++)
    {
      TreeInstance *tree;
      gint j;

 again:
      tree = tree_instance_new (NULL, 0);
      items[i] = tree_instance_get_gvariant (tree);
      tree_instance_free (tree);

      for (j = 0; j < i; j++)
        if (g_variant_equal (items[i], items[j]))
          {
            g_variant_unref (items[i]);
            goto again;
          }

      g_hash_table_insert (table,
                           g_variant_ref_sink (items[i]),
                           GINT_TO_POINTER (i));
    }

  for (i = 0; i < G_N_ELEMENTS (items); i++)
    {
      gpointer result;

      result = g_hash_table_lookup (table, items[i]);
      g_assert_cmpint (GPOINTER_TO_INT (result), ==, i);
    }

  g_hash_table_unref (table);

  g_variant_type_info_assert_no_infos ();
}

static void
test_gv_byteswap ()
{
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
# define native16(x)  x, 0
# define swapped16(x) 0, x
#else
# define native16(x)  0, x
# define swapped16(x) x, 0
#endif
  /* all kinds of of crazy randomised testing already performed on the
   * byteswapper in the /gvariant/serialiser/byteswap test and all kinds
   * of crazy randomised testing performed against the serialiser
   * normalisation functions in the /gvariant/serialiser/fuzz/ tests.
   *
   * just test a few simple cases here to make sure they each work
   */
  guchar validbytes[] = { 'a', '\0', swapped16(66), 2,
                          0,
                          'b', '\0', swapped16(77), 2,
                          5, 11 };
  guchar corruptbytes[] = { 'a', '\0', swapped16(66), 2,
                            0,
                            'b', '\0', swapped16(77), 2,
                            6, 11 };
  guint valid_data[4], corrupt_data[4];
  GVariant *value, *swapped;
  gchar *string, *string2;

  memcpy (valid_data, validbytes, sizeof validbytes);
  memcpy (corrupt_data, corruptbytes, sizeof corruptbytes);

  /* trusted */
  value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"),
                                   valid_data, sizeof validbytes, TRUE,
                                   NULL, NULL);
  swapped = g_variant_byteswap (value);
  g_variant_unref (value);
  g_assert (g_variant_get_size (swapped) == 13);
  string = g_variant_print (swapped, FALSE);
  g_variant_unref (swapped);
  g_assert_cmpstr (string, ==, "[('a', 66), ('b', 77)]");
  g_free (string);

  /* untrusted but valid */
  value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"),
                                   valid_data, sizeof validbytes, FALSE,
                                   NULL, NULL);
  swapped = g_variant_byteswap (value);
  g_variant_unref (value);
  g_assert (g_variant_get_size (swapped) == 13);
  string = g_variant_print (swapped, FALSE);
  g_variant_unref (swapped);
  g_assert_cmpstr (string, ==, "[('a', 66), ('b', 77)]");
  g_free (string);

  /* untrusted, invalid */
  value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"),
                                   corrupt_data, sizeof corruptbytes, FALSE,
                                   NULL, NULL);
  string = g_variant_print (value, FALSE);
  swapped = g_variant_byteswap (value);
  g_variant_unref (value);
  g_assert (g_variant_get_size (swapped) == 13);
  value = g_variant_byteswap (swapped);
  g_variant_unref (swapped);
  string2 = g_variant_print (value, FALSE);
  g_assert (g_variant_get_size (value) == 13);
  g_variant_unref (value);
  g_assert_cmpstr (string, ==, string2);
  g_free (string2);
  g_free (string);
}

static void
test_parser (void)
{
  TreeInstance *tree;
  GVariant *parsed;
  GVariant *value;
  gchar *pt, *p;
  gchar *res;

  tree = tree_instance_new (NULL, 3);
  value = tree_instance_get_gvariant (tree);
  tree_instance_free (tree);

  pt = g_variant_print (value, TRUE);
  p = g_variant_print (value, FALSE);

  parsed = g_variant_parse (NULL, pt, NULL, NULL, NULL);
  res = g_variant_print (parsed, FALSE);
  g_assert_cmpstr (p, ==, res);
  g_variant_unref (parsed);
  g_free (res);

  parsed = g_variant_parse (g_variant_get_type (value), p,
                            NULL, NULL, NULL);
  res = g_variant_print (parsed, TRUE);
  g_assert_cmpstr (pt, ==, res);
  g_variant_unref (parsed);
  g_free (res);

  g_variant_unref (value);
  g_free (pt);
  g_free (p);
}

static void
test_parses (void)
{
  gint i;

  for (i = 0; i < 100; i++)
    {
      test_parser ();
    }

  /* mini test */
  {
    GError *error = NULL;
    gchar str[128];
    GVariant *val;
    gchar *p, *p2;

    for (i = 0; i < 127; i++)
      str[i] = i + 1;
    str[i] = 0;

    val = g_variant_new_string (str);
    p = g_variant_print (val, FALSE);
    g_variant_unref (val);

    val = g_variant_parse (NULL, p, NULL, NULL, &error);
    p2 = g_variant_print (val, FALSE);

    g_assert_cmpstr (str, ==, g_variant_get_string (val, NULL));
    g_assert_cmpstr (p, ==, p2);

    g_variant_unref (val);
    g_free (p2);
    g_free (p);
  }

  /* another mini test */
  {
    const gchar *end;
    GVariant *value;

    value = g_variant_parse (G_VARIANT_TYPE_INT32, "1 2 3", NULL, &end, NULL);
    g_assert_cmpint (g_variant_get_int32 (value), ==, 1);
    /* make sure endptr returning works */
    g_assert_cmpstr (end, ==, " 2 3");
    g_variant_unref (value);
  }

  /* unicode mini test */
  {
    /* ał𝄞 */
    const gchar orig[] = "a\xc5\x82\xf0\x9d\x84\x9e \t\n";
    GVariant *value;
    gchar *printed;

    value = g_variant_new_string (orig);
    printed = g_variant_print (value, FALSE);
    g_variant_unref (value);

    g_assert_cmpstr (printed, ==, "'a\xc5\x82\xf0\x9d\x84\x9e \\t\\n'");
    value = g_variant_parse (NULL, printed, NULL, NULL, NULL);
    g_assert_cmpstr (g_variant_get_string (value, NULL), ==, orig);
    g_variant_unref (value);
    g_free (printed);
  }

  /* inf/nan mini test */
  {
    const gchar *tests[] = { "inf", "-inf", "nan" };
    GVariant *value;
    gchar *printed;
    gint i;

    for (i = 0; i < G_N_ELEMENTS (tests); i++)
      {
        GError *error = NULL;
        value = g_variant_parse (NULL, tests[i], NULL, NULL, &error);
        printed = g_variant_print (value, FALSE);
        g_assert (g_str_has_prefix (printed, tests[i]));
        g_free (printed);
        g_variant_unref (value);
      }
  }

  g_variant_type_info_assert_no_infos ();
}

static void
test_parse_failures (void)
{
  const gchar *test[] = {
    "[1, 2,",                   "6:",              "expected value",
    "",                         "0:",              "expected value",
    "(1, 2,",                   "6:",              "expected value",
    "<1",                       "2:",              "expected `>'",
    "[]",                       "0-2:",            "unable to infer",
    "(,",                       "1:",              "expected value",
    "[4,'']",                   "1-2,3-5:",        "common type",
    "[4, '', 5]",               "1-2,4-6:",        "common type",
    "['', 4, 5]",               "1-3,5-6:",        "common type",
    "[4, 5, '']",               "1-2,7-9:",        "common type",
    "[[4], [], ['']]",          "1-4,10-14:",      "common type",
    "[[], [4], ['']]",          "5-8,10-14:",      "common type",
    "just",                     "4:",              "expected value",
    "nothing",                  "0-7:",            "unable to infer",
    "just [4, '']",             "6-7,9-11:",       "common type",
    "[[4,'']]",                 "2-3,4-6:",        "common type",
    "([4,''],)",                "2-3,4-6:",        "common type",
    "(4)",                      "2:",              "`,'",
    "{}",                       "0-2:",            "unable to infer",
    "{[1,2],[3,4]}",            "0-13:",           "basic types",
    "{[1,2]:[3,4]}",            "0-13:",           "basic types",
    "justt",                    "0-5:",            "unknown keyword",
    "nothng",                   "0-6:",            "unknown keyword",
    "uint33",                   "0-6:",            "unknown keyword",
    "@mi just ''",              "9-11:",           "can not parse as",
    "@ai ['']",                 "5-7:",            "can not parse as",
    "@(i) ('',)",               "6-8:",            "can not parse as",
    "[[], 5]",                  "1-3,5-6:",        "common type",
    "[[5], 5]",                 "1-4,6-7:",        "common type",
    "5 5",                      "2:",              "expected end of input",
    "[5, [5, '']]",             "5-6,8-10:",       "common type",
    "@i just 5",                "3-9:",            "can not parse as",
    "@i nothing",               "3-10:",           "can not parse as",
    "@i []",                    "3-5:",            "can not parse as",
    "@i ()",                    "3-5:",            "can not parse as",
    "@ai (4,)",                 "4-8:",            "can not parse as",
    "@(i) []",                  "5-7:",            "can not parse as",
    "(5 5)",                    "3:",              "expected `,'",
    "[5 5]",                    "3:",              "expected `,' or `]'",
    "(5, 5 5)",                 "6:",              "expected `,' or `)'",
    "[5, 5 5]",                 "6:",              "expected `,' or `]'",
    "<@i []>",                  "4-6:",            "can not parse as",
    "<[5 5]>",                  "4:",              "expected `,' or `]'",
    "{[4,''],5}",               "2-3,4-6:",        "common type",
    "{5,[4,'']}",               "4-5,6-8:",        "common type",
    "@i {1,2}",                 "3-8:",            "can not parse as",
    "{@i '', 5}",               "4-6:",            "can not parse as",
    "{5, @i ''}",               "7-9:",            "can not parse as",
    "@ai {}",                   "4-6:",            "can not parse as",
    "{@i '': 5}",               "4-6:",            "can not parse as",
    "{5: @i ''}",               "7-9:",            "can not parse as",
    "{<4,5}",                   "3:",              "expected `>'",
    "{4,<5}",                   "5:",              "expected `>'",
    "{4,5,6}",                  "4:",              "expected `}'",
    "{5 5}",                    "3:",              "expected `:' or `,'",
    "{4: 5: 6}",                "5:",              "expected `,' or `}'",
    "{4:5,<6:7}",               "7:",              "expected `>'",
    "{4:5,6:<7}",               "9:",              "expected `>'",
    "{4:5,6 7}",                "7:",              "expected `:'",
    "@o 'foo'",                 "3-8:",            "object path",
    "@g 'zzz'",                 "3-8:",            "signature",
    "@i true",                  "3-7:",            "can not parse as",
    "@z 4",                     "0-2:",            "invalid type",
    "@a* []",                   "0-3:",            "definite",
    "@ai [3 3]",                "7:",              "expected `,' or `]'",
    "18446744073709551616",     "0-20:",           "too big for any type",
    "-18446744073709551616",    "0-21:",           "too big for any type",
    "byte 256",                 "5-8:",            "out of range for type",
    "byte -1",                  "5-7:",            "out of range for type",
    "int16 32768",              "6-11:",           "out of range for type",
    "int16 -32769",             "6-12:",           "out of range for type",
    "uint16 -1",                "7-9:",            "out of range for type",
    "uint16 65536",             "7-12:",           "out of range for type",
    "2147483648",               "0-10:",           "out of range for type",
    "-2147483649",              "0-11:",           "out of range for type",
    "uint32 -1",                "7-9:",            "out of range for type",
    "uint32 4294967296",        "7-17:",           "out of range for type",
    "@x 9223372036854775808",   "3-22:",           "out of range for type",
    "@x -9223372036854775809",  "3-23:",           "out of range for type",
    "@t -1",                    "3-5:",            "out of range for type",
    "@t 18446744073709551616",  "3-23:",           "too big for any type",
    "handle 2147483648",        "7-17:",           "out of range for type",
    "handle -2147483649",       "7-18:",           "out of range for type",
    "1.798e308",                "0-9:",            "too big for any type",
    "37.5a488",                 "4-5:",            "invalid character",
    "0x7ffgf",                  "5-6:",            "invalid character",
    "07758",                    "4-5:",            "invalid character",
    "123a5",                    "3-4:",            "invalid character",
    "@ai 123",                  "4-7:",            "can not parse as",
    "'\"\\'",                   "0-4:",            "unterminated string",
    "'\"\\'\\",                 "0-5:",            "unterminated string",
    "boolean 4",                "8-9:",            "can not parse as",
    "int32 true",               "6-10:",           "can not parse as",
    "[double 5, int32 5]",      "1-9,11-18:",      "common type",
    "string 4",                 "7-8:",            "can not parse as"
  };
  gint i;

  for (i = 0; i < G_N_ELEMENTS (test); i += 3)
    {
      GError *error = NULL;
      GVariant *value;

      value = g_variant_parse (NULL, test[i], NULL, NULL, &error);
      g_assert (value == NULL);

      if (!strstr (error->message, test[i+2]))
        g_error ("test %d: Can't find `%s' in `%s'", i / 3,
                 test[i+2], error->message);

      if (!g_str_has_prefix (error->message, test[i+1]))
        g_error ("test %d: Expected location `%s' in `%s'", i / 3,
                 test[i+1], error->message);

      g_error_free (error);
    }
}

static void
test_parse_positional (void)
{
  GVariant *value;
  check_and_free (g_variant_new_parsed ("[('one', 1), (%s, 2),"
                                        " ('three', %i)]", "two", 3),
                  "[('one', 1), ('two', 2), ('three', 3)]");
  value = g_variant_new_parsed ("[('one', 1), (%s, 2),"
                                " ('three', %u)]", "two", 3);
  g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("a(su)")));
  check_and_free (value, "[('one', 1), ('two', 2), ('three', 3)]");
  check_and_free (g_variant_new_parsed ("{%s:%i}", "one", 1), "{'one': 1}");

  if (g_test_undefined ())
    {
      if (do_failed_test ("*GVariant format string*"))
        {
          g_variant_new_parsed ("%z");
          abort ();
        }

      if (do_failed_test ("*can not parse as*"))
        {
          g_variant_new_parsed ("uint32 %i", 2);
          abort ();
        }

      if (do_failed_test ("*expected GVariant of type `i'*"))
        {
          g_variant_new_parsed ("%@i", g_variant_new_uint32 (2));
          abort ();
        }
    }
}

static void
test_floating (void)
{
  GVariant *value;

  value = g_variant_new_int32 (42);
  g_assert (g_variant_is_floating (value));
  g_variant_ref_sink (value);
  g_assert (!g_variant_is_floating (value));
  g_variant_unref (value);
}

static void
test_bytestring (void)
{
  const gchar *test_string = "foo,bar,baz,quux,\xffoooo";
  GVariant *value;
  gchar **strv;
  gchar *str;
  const gchar *const_str;
  GVariant *untrusted_empty;

  strv = g_strsplit (test_string, ",", 0);

  value = g_variant_new_bytestring_array ((const gchar **) strv, -1);
  g_assert (g_variant_is_floating (value));
  g_strfreev (strv);

  str = g_variant_print (value, FALSE);
  g_variant_unref (value);

  value = g_variant_parse (NULL, str, NULL, NULL, NULL);
  g_free (str);

  strv = g_variant_dup_bytestring_array (value, NULL);
  g_variant_unref (value);

  str = g_strjoinv (",", strv);
  g_strfreev (strv);

  g_assert_cmpstr (str, ==, test_string);
  g_free (str);

  strv = g_strsplit (test_string, ",", 0);
  value = g_variant_new ("(^aay^a&ay^ay^&ay)",
                         strv, strv, strv[0], strv[0]);
  g_strfreev (strv);

  g_variant_get_child (value, 0, "^a&ay", &strv);
  str = g_strjoinv (",", strv);
  g_free (strv);
  g_assert_cmpstr (str, ==, test_string);
  g_free (str);

  g_variant_get_child (value, 0, "^aay", &strv);
  str = g_strjoinv (",", strv);
  g_strfreev (strv);
  g_assert_cmpstr (str, ==, test_string);
  g_free (str);

  g_variant_get_child (value, 1, "^a&ay", &strv);
  str = g_strjoinv (",", strv);
  g_free (strv);
  g_assert_cmpstr (str, ==, test_string);
  g_free (str);

  g_variant_get_child (value, 1, "^aay", &strv);
  str = g_strjoinv (",", strv);
  g_strfreev (strv);
  g_assert_cmpstr (str, ==, test_string);
  g_free (str);

  g_variant_get_child (value, 2, "^ay", &str);
  g_assert_cmpstr (str, ==, "foo");
  g_free (str);

  g_variant_get_child (value, 2, "^&ay", &str);
  g_assert_cmpstr (str, ==, "foo");

  g_variant_get_child (value, 3, "^ay", &str);
  g_assert_cmpstr (str, ==, "foo");
  g_free (str);

  g_variant_get_child (value, 3, "^&ay", &str);
  g_assert_cmpstr (str, ==, "foo");
  g_variant_unref (value);

  untrusted_empty = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), NULL, 0, FALSE, NULL, NULL);
  value = g_variant_get_normal_form (untrusted_empty);
  const_str = g_variant_get_bytestring (value);
  (void) const_str;
  g_variant_unref (value);
  g_variant_unref (untrusted_empty);
}

static void
test_lookup_value (void)
{
  struct {
    const gchar *dict, *key, *value;
  } cases[] = {
    { "@a{ss} {'x':  'y'}",   "x",  "'y'" },
    { "@a{ss} {'x':  'y'}",   "y"         },
    { "@a{os} {'/x': 'y'}",   "/x", "'y'" },
    { "@a{os} {'/x': 'y'}",   "/y"        },
    { "@a{sv} {'x':  <'y'>}", "x",  "'y'" },
    { "@a{sv} {'x':  <5>}",   "x",  "5"   },
    { "@a{sv} {'x':  <'y'>}", "y"         }
  };
  gint i;

  for (i = 0; i < G_N_ELEMENTS (cases); i++)
    {
      GVariant *dictionary;
      GVariant *value;
      gchar *p;
      
      dictionary = g_variant_parse (NULL, cases[i].dict, NULL, NULL, NULL);
      value = g_variant_lookup_value (dictionary, cases[i].key, NULL);
      g_variant_unref (dictionary);

      if (value == NULL && cases[i].value == NULL)
        continue;

      g_assert (value && cases[i].value);
      p = g_variant_print (value, FALSE);
      g_assert_cmpstr (cases[i].value, ==, p);
      g_variant_unref (value);
      g_free (p);
    }
}

static void
test_lookup (void)
{
  const gchar *str;
  GVariant *dict;
  gboolean ok;
  gint num;

  dict = g_variant_parse (NULL,
                          "{'a': <5>, 'b': <'c'>}",
                          NULL, NULL, NULL);

  ok = g_variant_lookup (dict, "a", "i", &num);
  g_assert (ok);
  g_assert_cmpint (num, ==, 5);

  ok = g_variant_lookup (dict, "a", "&s", &str);
  g_assert (!ok);

  ok = g_variant_lookup (dict, "q", "&s", &str);
  g_assert (!ok);

  ok = g_variant_lookup (dict, "b", "i", &num);
  g_assert (!ok);

  ok = g_variant_lookup (dict, "b", "&s", &str);
  g_assert (ok);
  g_assert_cmpstr (str, ==, "c");

  ok = g_variant_lookup (dict, "q", "&s", &str);
  g_assert (!ok);

  g_variant_unref (dict);
}

static void
test_compare (void)
{
  GVariant *a;
  GVariant *b;

  a = g_variant_new_byte (5);
  b = g_variant_new_byte (6);
  g_assert (g_variant_compare (a, b) < 0);
  g_variant_unref (a);
  g_variant_unref (b);
  a = g_variant_new_string ("abc");
  b = g_variant_new_string ("abd");
  g_assert (g_variant_compare (a, b) < 0);
  g_variant_unref (a);
  g_variant_unref (b);
  a = g_variant_new_boolean (FALSE);
  b = g_variant_new_boolean (TRUE);
  g_assert (g_variant_compare (a, b) < 0);
  g_variant_unref (a);
  g_variant_unref (b);
}

static void
test_fixed_array (void)
{
  GVariant *a;
  gint32 values[5];
  const gint32 *elts;
  gsize n_elts;
  gint i;

  n_elts = 0;
  a = g_variant_new_parsed ("[1,2,3,4,5]");
  elts = g_variant_get_fixed_array (a, &n_elts, sizeof (gint32));
  g_assert (n_elts == 5);
  for (i = 0; i < 5; i++)
    g_assert_cmpint (elts[i], ==, i + 1);
  g_variant_unref (a);

  n_elts = 0;
  for (i = 0; i < 5; i++)
    values[i] = i + 1;
  a = g_variant_new_fixed_array (G_VARIANT_TYPE_INT32, values,
                                 G_N_ELEMENTS (values), sizeof (values[0]));
  g_assert_cmpstr (g_variant_get_type_string (a), ==, "ai");
  elts = g_variant_get_fixed_array (a, &n_elts, sizeof (gint32));
  g_assert (n_elts == 5);
  for (i = 0; i < 5; i++)
    g_assert_cmpint (elts[i], ==, i + 1);
  g_variant_unref (a);
}

int
main (int argc, char **argv)
{
  gint i;

  g_test_init (&argc, &argv, NULL);

  g_test_add_func ("/gvariant/type", test_gvarianttype);
  g_test_add_func ("/gvariant/typeinfo", test_gvarianttypeinfo);
  g_test_add_func ("/gvariant/serialiser/maybe", test_maybes);
  g_test_add_func ("/gvariant/serialiser/array", test_arrays);
  g_test_add_func ("/gvariant/serialiser/tuple", test_tuples);
  g_test_add_func ("/gvariant/serialiser/variant", test_variants);
  g_test_add_func ("/gvariant/serialiser/strings", test_strings);
  g_test_add_func ("/gvariant/serialiser/byteswap", test_byteswaps);

  for (i = 1; i <= 20; i += 4)
    {
      char *testname;

      testname = g_strdup_printf ("/gvariant/serialiser/fuzz/%d%%", i);
      g_test_add_data_func (testname, GINT_TO_POINTER (i),
                            (gpointer) test_fuzzes);
      g_free (testname);
    }

  g_test_add_func ("/gvariant/utf8", test_utf8);
  g_test_add_func ("/gvariant/containers", test_containers);
  g_test_add_func ("/gvariant/format-strings", test_format_strings);
  g_test_add_func ("/gvariant/invalid-varargs", test_invalid_varargs);
  g_test_add_func ("/gvariant/varargs", test_varargs);
  g_test_add_func ("/gvariant/valist", test_valist);
  g_test_add_func ("/gvariant/builder-memory", test_builder_memory);
  g_test_add_func ("/gvariant/hashing", test_hashing);
  g_test_add_func ("/gvariant/byteswap", test_gv_byteswap);
  g_test_add_func ("/gvariant/parser", test_parses);
  g_test_add_func ("/gvariant/parse-failures", test_parse_failures);
  g_test_add_func ("/gvariant/parse-positional", test_parse_positional);
  g_test_add_func ("/gvariant/floating", test_floating);
  g_test_add_func ("/gvariant/bytestring", test_bytestring);
  g_test_add_func ("/gvariant/lookup-value", test_lookup_value);
  g_test_add_func ("/gvariant/lookup", test_lookup);
  g_test_add_func ("/gvariant/compare", test_compare);
  g_test_add_func ("/gvariant/fixed-array", test_fixed_array);

  return g_test_run ();
}
