/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <glib-object.h>
#include <inttypes.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>

#include <sys/time.h>

#include <thrift/c_glib/thrift.h>
#include <thrift/c_glib/protocol/thrift_binary_protocol.h>
#include <thrift/c_glib/protocol/thrift_compact_protocol.h>
#include <thrift/c_glib/protocol/thrift_multiplexed_protocol.h>
#include <thrift/c_glib/transport/thrift_buffered_transport.h>
#include <thrift/c_glib/transport/thrift_framed_transport.h>
#include <thrift/c_glib/transport/thrift_ssl_socket.h>
#include <thrift/c_glib/transport/thrift_socket.h>
#include <thrift/c_glib/transport/thrift_transport.h>

#include "../gen-c_glib/t_test_second_service.h"
#include "../gen-c_glib/t_test_thrift_test.h"

/* Handle SIGPIPE signals (indicating the server has closed the
   connection prematurely) by outputting an error message before
   exiting. */
static void
sigpipe_handler (int signal_number)
{
  THRIFT_UNUSED_VAR (signal_number);

  /* Flush standard output to make sure the test results so far are
     logged */
  fflush (stdout);

  fputs ("Broken pipe (server closed connection prematurely)\n", stderr);
  fflush (stderr);

  /* Re-raise the signal, this time invoking the default signal
     handler, to terminate the program */
  raise (SIGPIPE);
}

/* Compare two gint32 values. Used for sorting and finding integer
   values within a GList. */
static gint
gint32_compare (gconstpointer a, gconstpointer b)
{
  gint32 int32_a = *(gint32 *)a;
  gint32 int32_b = *(gint32 *)b;
  int result = 0;

  if (int32_a < int32_b)
    result = -1;
  else if (int32_a > int32_b)
    result = 1;

  return result;
}

/**
 * It gets a multiplexed protocol which uses a concrete protocol underneath
 * @param  protocol_name  the fully qualified protocol path (e.g. "binary:multi")
 * @param  transport      the underlying transport
 * @param  service_name   the single supported service name
 * @todo                  need to allow multiple services to fully test multiplexed
 * @return                a multiplexed protocol wrapping the correct underlying protocol
 */
ThriftProtocol *
get_multiplexed_protocol(gchar *protocol_name, ThriftTransport *transport, gchar *service_name)
{
  ThriftProtocol * multiplexed_protocol = NULL;

  if ( strncmp(protocol_name, "binary:", 7) == 0) {
    multiplexed_protocol = g_object_new (THRIFT_TYPE_BINARY_PROTOCOL,
                 "transport", transport,
                 NULL);
  } else if ( strncmp(protocol_name, "compact:", 8) == 0) {
    multiplexed_protocol = g_object_new (THRIFT_TYPE_COMPACT_PROTOCOL,
                 "transport", transport,
                 NULL);
  } else {
    fprintf(stderr, "Unknown multiplex protocol name: %s\n", protocol_name);
    return NULL;
  }

  return g_object_new (THRIFT_TYPE_MULTIPLEXED_PROTOCOL,
          "transport",      transport,
          "protocol",       multiplexed_protocol,
          "service-name",   service_name,
          NULL);
}

int
main (int argc, char **argv)
{
  static gchar *  host = NULL;
  static gint     port = 9090;
  static gchar *  path = NULL;
  static gboolean ssl  = FALSE;
  static gchar *  transport_option = NULL;
  static gchar *  protocol_option = NULL;
  static gint     num_tests = 1;

  static
    GOptionEntry option_entries[] ={
    { "host",            'h', 0, G_OPTION_ARG_STRING,   &host,
      "Host to connect (=localhost)", NULL },
    { "port",            'p', 0, G_OPTION_ARG_INT,      &port,
      "Port number to connect (=9090)", NULL },
    { "domain-socket",    0, 0, G_OPTION_ARG_STRING,   &path,
      "Unix socket domain path to connect", NULL },
    { "ssl",             's', 0, G_OPTION_ARG_NONE,     &ssl,
      "Enable SSL", NULL },
    { "transport",       't', 0, G_OPTION_ARG_STRING,   &transport_option,
      "Transport: buffered, framed (=buffered)", NULL },
    { "protocol",        'r', 0, G_OPTION_ARG_STRING,   &protocol_option,
      "Protocol: binary, compact, multi, multic (=binary)", NULL },
    { "testloops",       'n', 0, G_OPTION_ARG_INT,      &num_tests,
      "Number of tests (=1)", NULL },
    { NULL }
  };

  struct sigaction sigpipe_action;

  GType  socket_type    = THRIFT_TYPE_SOCKET;
  gchar *socket_name    = "ip";
  GType  transport_type = THRIFT_TYPE_BUFFERED_TRANSPORT;
  gchar *transport_name = "buffered";
  GType  protocol_type  = THRIFT_TYPE_BINARY_PROTOCOL;
  gchar *protocol_name  = "binary";

  ThriftSocket    *socket = NULL;
  ThriftTransport *transport = NULL;
  ThriftProtocol  *protocol = NULL;
  ThriftProtocol  *protocol2 = NULL;            // for multiplexed tests

  TTestThriftTestIf *test_client = NULL;
  TTestSecondServiceIf *second_service = NULL;  // for multiplexed tests

  struct timeval time_start, time_stop, time_elapsed;
  guint64 time_elapsed_usec, time_total_usec = 0;
  guint64 time_min_usec = G_MAXUINT64, time_max_usec = 0, time_avg_usec;

  GOptionContext *option_context;
  gboolean options_valid = TRUE;
  int test_num = 0;
  int fail_count = 0;
  GError *error = NULL;

#if (!GLIB_CHECK_VERSION (2, 36, 0))
  g_type_init ();
#endif

  /* Configure and parse our command-line options */
  option_context = g_option_context_new (NULL);
  g_option_context_add_main_entries (option_context,
                                     option_entries,
                                     NULL);
  if (!g_option_context_parse (option_context,
                               &argc,
                               &argv,
                               &error)) {
    fprintf (stderr, "%s\n", error->message);
    return 255;
  }
  g_option_context_free (option_context);

  /* Set remaining default values for unspecified options */
  if (host == NULL)
    host = g_strdup ("localhost");

  /* Validate the parsed options */
  if (protocol_option != NULL) {
    if (strncmp (protocol_option, "compact", 8) == 0) {
      protocol_type = THRIFT_TYPE_COMPACT_PROTOCOL;
      protocol_name = "compact";
    }
    else if (strncmp (protocol_option, "multi", 6) == 0) {
      protocol_type = THRIFT_TYPE_MULTIPLEXED_PROTOCOL;
      protocol_name = "binary:multi";
    }
    else if (strncmp (protocol_option, "multic", 7) == 0) {
      protocol_type = THRIFT_TYPE_MULTIPLEXED_PROTOCOL;
      protocol_name = "compact:multic";
    }
    else if (strncmp (protocol_option, "binary", 7) == 0) {
      printf("We are going with default protocol\n");
    }
    else {
      fprintf (stderr, "Unknown protocol type %s\n", protocol_option);
      options_valid = FALSE;
    }
  }

  if (transport_option != NULL) {
    if (strncmp (transport_option, "framed", 7) == 0) {
      transport_type = THRIFT_TYPE_FRAMED_TRANSPORT;
      transport_name = "framed";
    }
    else if (strncmp (transport_option, "buffered", 9) != 0) {
      fprintf (stderr, "Unknown transport type %s\n", transport_option);
      options_valid = FALSE;
    }
  }

  if (ssl) {
    socket_type = THRIFT_TYPE_SSL_SOCKET;
    socket_name = "ip-ssl";
    printf("Type name %s\n", g_type_name (socket_type));
  }

  if (!options_valid)
    return 254;

  if (path) {
    printf ("Connecting (%s/%s) to: %s/%s\n",
            transport_name,
            protocol_name,
            socket_name,
            path);
  } else {
    printf ("Connecting (%s/%s) to: %s/%s:%d\n",
            transport_name,
            protocol_name,
            socket_name,
            host,
            port);
  }

  /* Install our SIGPIPE handler, which outputs an error message to
     standard error before exiting so testers can know what
     happened */
  memset (&sigpipe_action, 0, sizeof (sigpipe_action));
  sigpipe_action.sa_handler = sigpipe_handler;
  sigpipe_action.sa_flags = SA_RESETHAND;
  sigaction (SIGPIPE, &sigpipe_action, NULL);

  if (ssl) {
    thrift_ssl_socket_initialize_openssl();
  }

  /* Establish all our connection objects */
  if (path) {
    socket = g_object_new (socket_type,
                           "path", path,
                           NULL);
  } else {
    socket = g_object_new (socket_type,
                           "hostname", host,
                           "port",     port,
                           NULL);
  }

  if (ssl && !thrift_ssl_load_cert_from_file(THRIFT_SSL_SOCKET(socket), "../keys/CA.pem")) {
    fprintf(stderr, "Unable to load validation certificate ../keys/CA.pem - did you run in the test/c_glib directory?\n");
    g_clear_object (&socket);
    return 253;
  }

  transport = g_object_new (transport_type,
                            "transport", socket,
                            NULL);

  if(protocol_type==THRIFT_TYPE_MULTIPLEXED_PROTOCOL) {
    // TODO: A multiplexed test should also test "Second" (see Java TestServer)
    // The context comes from the name of the thrift file. If multiple thrift
    // schemas are used we have to redo the way this is done.
    protocol = get_multiplexed_protocol(protocol_name, transport, "ThriftTest");
    if (NULL == protocol) {
      g_clear_object (&transport);
      g_clear_object (&socket);
      return 252;
    }

    // Make a second protocol and client running on the same multiplexed transport
    protocol2 = get_multiplexed_protocol(protocol_name, transport, "SecondService");
    second_service = g_object_new (T_TEST_TYPE_SECOND_SERVICE_CLIENT,
                                "input_protocol",  protocol2,
                                "output_protocol", protocol2,
                                NULL);

  }else{
    protocol = g_object_new (protocol_type,
           "transport", transport,
           NULL);
  }

  test_client = g_object_new (T_TEST_TYPE_THRIFT_TEST_CLIENT,
                              "input_protocol",  protocol,
                              "output_protocol", protocol,
                              NULL);

  /* Execute the actual tests */
  for (test_num = 0; test_num < num_tests; ++test_num) {
    if (thrift_transport_open (transport, &error)) {
      gchar   *string  = NULL;
      gboolean boolean = 0;
      gint8    byte    = 0;
      gint32   int32   = 0;
      gint64   int64   = 0;
      gdouble  dub     = 0;

      gint byte_thing, i32_thing, inner_byte_thing, inner_i32_thing;
      gint64 i64_thing, inner_i64_thing;

      TTestXtruct  *xtruct_out,  *xtruct_out2, *xtruct_in,  *inner_xtruct_in;
      TTestXtruct2 *xtruct2_out, *xtruct2_in;

      GHashTable *map_out, *map_in, *inner_map_in;
      GHashTable *set_out, *set_in;
      gpointer key, value;
      gint32 *i32_key_ptr, *i32_value_ptr;
      GHashTableIter hash_table_iter, inner_hash_table_iter;
      GList *keys_out, *keys_in, *keys_elem;

      GArray *list_out, *list_in;

      TTestNumberz numberz;
      TTestNumberz numberz2;

      TTestUserId user_id, *user_id_ptr, *user_id_ptr2;

      TTestInsanity *insanity_out, *insanity_in;
      GHashTable *user_map;
      GHashTableIter user_map_iter;
      GPtrArray *xtructs;

      TTestXception  *xception  = NULL;
      TTestXception2 *xception2 = NULL;

      gboolean oneway_result;
      struct timeval oneway_start, oneway_end, oneway_elapsed;
      gint oneway_elapsed_usec;

      gboolean first;
      gint32 i, j;

      if (path) {
        printf ("Test #%d, connect %s\n", test_num + 1, path);
      } else {
        printf ("Test #%d, connect %s:%d\n", test_num + 1, host, port);
      }
      gettimeofday (&time_start, NULL);

      /* These test routines have been ported from the C++ test
         client, care being taken to ensure their output remains as
         close as possible to the original to facilitate diffs.

         For simplicity comments have been omitted, but every routine
         has the same basic structure:

         - Create and populate data structures as necessary.

         - Format and output (to the console) a representation of the
           outgoing data.

         - Issue the remote method call to the server.

         - Format and output a representation of the returned data.

         - Verify the returned data matches what was expected.

         - Deallocate any created data structures.

         Note the recognized values and expected behaviour of each
         remote method are described in ThriftTest.thrift, which
         you'll find in the top-level "test" folder. */

      /**
       * VOID TEST
       */
      printf ("testVoid()");
      if (t_test_thrift_test_if_test_void (test_client, &error)) {
        printf (" = void\n");
      }
      else {
        if(error!=NULL){
          printf ("%s\n", error->message);
          g_error_free (error);
          error = NULL;
        }
        fail_count++;
      }

      /**
       * STRING TEST
       */
      printf ("testString(\"Test\")");
      if (t_test_thrift_test_if_test_string (test_client,
                                             &string,
                                             "Test",
                                             &error)) {
        printf (" = \"%s\"\n", string);
        if (strncmp (string, "Test", 5) != 0)
          fail_count++;

        g_free (string);
        string = NULL;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * Multiplexed Test - do this right in the middle of the normal Test Client run
       */
      if (second_service) {
        printf ("testSecondServiceMultiplexSecondTestString(\"2nd\")");
        if (t_test_second_service_if_secondtest_string (second_service,
                                                        &string,
                                                        "2nd",
                                                        &error)) {
          printf (" = \"%s\"\n", string);
          if (strcmp (string, "testString(\"2nd\")") != 0) {
            ++fail_count;
          }

          g_free (string);
          string = NULL;
        } else {
          printf ("%s\n", error->message);
          g_error_free (error);
          error = NULL;

          ++fail_count;
        }
      }

      /**
       * BOOL TEST
       */
      printf ("testByte(true)");
      if (t_test_thrift_test_if_test_bool (test_client,
                                           &boolean,
                                           1,
                                           &error)) {
        printf (" = %s\n", boolean ? "true" : "false");
        if (boolean != 1)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }
      printf ("testByte(false)");
      if (t_test_thrift_test_if_test_bool (test_client,
                                           &boolean,
                                           0,
                                           &error)) {
        printf (" = %s\n", boolean ? "true" : "false");
        if (boolean != 0)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * BYTE TEST
       */
      printf ("testByte(1)");
      if (t_test_thrift_test_if_test_byte (test_client,
                                           &byte,
                                           1,
                                           &error)) {
        printf (" = %d\n", byte);
        if (byte != 1)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }
      printf ("testByte(-1)");
      if (t_test_thrift_test_if_test_byte (test_client,
                                           &byte,
                                           -1,
                                           &error)) {
        printf (" = %d\n", byte);
        if (byte != -1)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * I32 TEST
       */
      printf ("testI32(-1)");
      if (t_test_thrift_test_if_test_i32 (test_client,
                                          &int32,
                                          -1,
                                          &error)) {
        printf (" = %d\n", int32);
        if (int32 != -1)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * I64 TEST
       */
      printf ("testI64(-34359738368)");
      if (t_test_thrift_test_if_test_i64 (test_client,
                                          &int64,
                                          (gint64)-34359738368,
                                          &error)) {
        printf (" = %" PRId64 "\n", int64);
        if (int64 != (gint64)-34359738368)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * DOUBLE TEST
       */
      printf("testDouble(-5.2098523)");
      if (t_test_thrift_test_if_test_double (test_client,
                                             &dub,
                                             -5.2098523,
                                             &error)) {
        printf (" = %f\n", dub);
        if ((dub - (-5.2098523)) > 0.001)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * BINARY TEST
       */
      printf ("testBinary(empty)");
      GByteArray *emptyArray = g_byte_array_new();
      GByteArray *result = NULL;
      if (t_test_thrift_test_if_test_binary (test_client,
                                             &result,
                                             emptyArray,
                                             &error)) {
        GBytes *response = g_byte_array_free_to_bytes(result);  // frees result
        result = NULL;
        gsize siz = g_bytes_get_size(response);
        if (siz == 0) {
          printf(" = empty\n");
        } else {
          printf(" = not empty (%ld bytes)\n", (long)siz);
          ++fail_count;
        }
        g_bytes_unref(response);
      } else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }
      g_byte_array_unref(emptyArray);
      emptyArray = NULL;

      // TODO: add testBinary() with data
      printf ("testBinary([-128..127]) = {");
      const signed char bin_data[256]
        = {-128, -127, -126, -125, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -114,
           -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, -99,
           -98,  -97,  -96,  -95,  -94,  -93,  -92,  -91,  -90,  -89,  -88,  -87,  -86,  -85,  -84,
           -83,  -82,  -81,  -80,  -79,  -78,  -77,  -76,  -75,  -74,  -73,  -72,  -71,  -70,  -69,
           -68,  -67,  -66,  -65,  -64,  -63,  -62,  -61,  -60,  -59,  -58,  -57,  -56,  -55,  -54,
           -53,  -52,  -51,  -50,  -49,  -48,  -47,  -46,  -45,  -44,  -43,  -42,  -41,  -40,  -39,
           -38,  -37,  -36,  -35,  -34,  -33,  -32,  -31,  -30,  -29,  -28,  -27,  -26,  -25,  -24,
           -23,  -22,  -21,  -20,  -19,  -18,  -17,  -16,  -15,  -14,  -13,  -12,  -11,  -10,  -9,
           -8,   -7,   -6,   -5,   -4,   -3,   -2,   -1,   0,    1,    2,    3,    4,    5,    6,
           7,    8,    9,    10,   11,   12,   13,   14,   15,   16,   17,   18,   19,   20,   21,
           22,   23,   24,   25,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,
           37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,
           52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,   64,   65,   66,
           67,   68,   69,   70,   71,   72,   73,   74,   75,   76,   77,   78,   79,   80,   81,
           82,   83,   84,   85,   86,   87,   88,   89,   90,   91,   92,   93,   94,   95,   96,
           97,   98,   99,   100,  101,  102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
           112,  113,  114,  115,  116,  117,  118,  119,  120,  121,  122,  123,  124,  125,  126,
           127};
      GByteArray *fullArray = g_byte_array_new();
      g_byte_array_append(fullArray, (guint8 *)(&bin_data[0]), 256);
      if (t_test_thrift_test_if_test_binary (test_client,
                                             &result,
                                             fullArray,
                                             &error)) {
        GBytes *response = g_byte_array_free_to_bytes(result);  // frees result
        result = NULL;
        gsize siz = g_bytes_get_size(response);
        gconstpointer ptr = g_bytes_get_data(response, &siz);
        if (siz == 256) {
          gboolean first = 1;
          gboolean failed = 0;
          int i;

          for (i = 0; i < 256; ++i) {
            if (!first)
              printf(",");
            else
              first = 0;
            int val = ((signed char *)ptr)[i];
            printf("%d", val);
            if (!failed && val != i - 128) {
              failed = 1;
            }
          }
          printf("} ");
          if (failed) {
            printf("FAIL (bad content) size %ld OK\n", (long)siz);
            ++fail_count;
          } else {
            printf("OK size %ld OK\n", (long)siz);
          }
        } else {
          printf(" = bad size %ld\n", (long)siz);
          ++fail_count;
        }
        g_bytes_unref(response);
      } else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }
      g_byte_array_unref(fullArray);
      fullArray = NULL;

      /**
       * STRUCT TEST
       */
      printf ("testStruct({\"Zero\", 1, -3, -5})");
      xtruct_out = g_object_new (T_TEST_TYPE_XTRUCT,
                                 "string_thing", "Zero",
                                 "byte_thing",        1,
                                 "i32_thing",        -3,
                                 "i64_thing",      -5LL,
                                 NULL);
      xtruct_in = g_object_new (T_TEST_TYPE_XTRUCT, NULL);

      if (t_test_thrift_test_if_test_struct (test_client,
                                             &xtruct_in,
                                             xtruct_out,
                                             &error)) {
        g_object_get (xtruct_in,
                      "string_thing", &string,
                      "byte_thing",   &byte_thing,
                      "i32_thing",    &i32_thing,
                      "i64_thing",    &i64_thing,
                      NULL);

        printf (" = {\"%s\", %d, %d, %" PRId64 "}\n",
                string,
                byte_thing,
                i32_thing,
                i64_thing);
        if ((string == NULL || strncmp (string, "Zero", 5) != 0) ||
            byte_thing != 1 ||
            i32_thing != -3 ||
            i64_thing != (gint64)-5)
          fail_count++;

        if (string) {
          g_free (string);
          string = NULL;
        }
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }
      // g_clear_object(&xtruct_out); used below
      g_clear_object(&xtruct_in);

      /**
       * NESTED STRUCT TEST
       */
      printf ("testNest({1, {\"Zero\", 1, -3, -5}), 5}");
      xtruct2_out = g_object_new (T_TEST_TYPE_XTRUCT2,
                                  "byte_thing",            1,
                                  "struct_thing", xtruct_out,
                                  "i32_thing",             5,
                                  NULL);
      xtruct2_in = g_object_new (T_TEST_TYPE_XTRUCT2, NULL);

      if (t_test_thrift_test_if_test_nest (test_client,
                                           &xtruct2_in,
                                           xtruct2_out,
                                           &error)) {
        g_object_get (xtruct2_in,
                      "byte_thing",   &byte_thing,
                      "struct_thing", &xtruct_in,
                      "i32_thing",    &i32_thing,
                      NULL);
        g_object_get (xtruct_in,
                      "string_thing", &string,
                      "byte_thing",   &inner_byte_thing,
                      "i32_thing",    &inner_i32_thing,
                      "i64_thing",    &inner_i64_thing,
                      NULL);

        printf (" = {%d, {\"%s\", %d, %d, %" PRId64 "}, %d}\n",
                byte_thing,
                string,
                inner_byte_thing,
                inner_i32_thing,
                inner_i64_thing,
                i32_thing);
        if (byte_thing != 1 ||
            (string == NULL || strncmp (string, "Zero", 5) != 0) ||
            inner_byte_thing != 1 ||
            inner_i32_thing != -3 ||
            inner_i64_thing != (gint64)-5 ||
            i32_thing != 5)
          fail_count++;

        if (string) {
          g_free(string);
          string = NULL;
        }
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      g_clear_object(&xtruct_in);
      g_clear_object(&xtruct2_in);
      g_clear_object(&xtruct2_out);
      g_clear_object(&xtruct_out);

      /**
       * MAP TEST
       */
      map_out = g_hash_table_new_full (g_int_hash,
                                       g_int_equal,
                                       g_free,
                                       g_free);
      for (i = 0; i < 5; ++i) {
        i32_key_ptr   = g_malloc (sizeof *i32_key_ptr);
        i32_value_ptr = g_malloc (sizeof *i32_value_ptr);

        *i32_key_ptr   = i;
        *i32_value_ptr = i - 10;

        g_hash_table_insert (map_out, i32_key_ptr, i32_value_ptr);
      }
      printf ("testMap({");
      first = TRUE;
      g_hash_table_iter_init (&hash_table_iter, map_out);
      while (g_hash_table_iter_next (&hash_table_iter,
                                     &key,
                                     &value)) {
        if (first)
          first = FALSE;
        else
          printf (", ");

        printf ("%d => %d", *(gint32 *)key, *(gint32 *)value);
      }
      printf ("})");

      map_in = g_hash_table_new_full (g_int_hash,
                                      g_int_equal,
                                      g_free,
                                      g_free);

      if (t_test_thrift_test_if_test_map (test_client,
                                          &map_in,
                                          map_out,
                                          &error)) {
        printf (" = {");
        first = TRUE;
        g_hash_table_iter_init (&hash_table_iter, map_in);
        while (g_hash_table_iter_next (&hash_table_iter,
                                       &key,
                                       &value)) {
          if (first)
            first = FALSE;
          else
            printf (", ");

          printf ("%d => %d", *(gint32 *)key, *(gint32 *)value);
        }
        printf ("}\n");

        if (g_hash_table_size (map_in) != g_hash_table_size (map_out))
          fail_count++;
        else {
          g_hash_table_iter_init (&hash_table_iter, map_out);
          while (g_hash_table_iter_next (&hash_table_iter,
                                         &key,
                                         &value)) {
            gpointer in_value = g_hash_table_lookup (map_in, key);
            if (in_value == NULL ||
                *(gint32 *)in_value != *(gint32 *)value) {
              fail_count++;
              break;
            }
          }
        }
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      g_hash_table_unref (map_in);
      g_hash_table_unref (map_out);

      /**
       * STRING MAP TEST
       */
      map_out = g_hash_table_new_full (g_str_hash,
                                       g_str_equal,
                                       NULL,
                                       NULL);
      g_hash_table_insert (map_out, "a",    "2");
      g_hash_table_insert (map_out, "b",    "blah");
      g_hash_table_insert (map_out, "some", "thing");
      printf ("testStringMap({");
      first = TRUE;
      g_hash_table_iter_init (&hash_table_iter, map_out);
      while (g_hash_table_iter_next (&hash_table_iter,
                                     &key,
                                     &value)) {
        if (first)
          first = FALSE;
        else
          printf (", ");

        printf ("\"%s\" => \"%s\"", (gchar *)key, (gchar *)value);
      }
      printf (")}");

      map_in = g_hash_table_new_full (g_str_hash,
                                      g_str_equal,
                                      g_free,
                                      g_free);

      if (t_test_thrift_test_if_test_string_map (test_client,
                                                 &map_in,
                                                 map_out,
                                                 &error)) {
        printf (" = {");
        first = TRUE;
        g_hash_table_iter_init (&hash_table_iter, map_in);
        while (g_hash_table_iter_next (&hash_table_iter,
                                       &key,
                                       &value)) {
          if (first)
            first = FALSE;
          else
            printf (", ");

          printf ("\"%s\" => \"%s\"", (gchar *)key, (gchar *)value);
        }
        printf ("}\n");

        if (g_hash_table_size (map_in) != g_hash_table_size (map_out))
          fail_count++;
        else {
          g_hash_table_iter_init (&hash_table_iter, map_out);
          while (g_hash_table_iter_next (&hash_table_iter,
                                         &key,
                                         &value)) {
            gpointer in_value = g_hash_table_lookup (map_in, key);
            if (in_value == NULL ||
                strcmp ((gchar *)in_value, (gchar *)value) != 0) {
              fail_count++;
              break;
            }
          }
        }
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      g_hash_table_unref (map_in);
      g_hash_table_unref (map_out);

      /**
       * SET TEST
       */
      set_out = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, NULL);
      for (i = -2; i < 3; ++i) {
        i32_key_ptr = g_malloc (sizeof *i32_key_ptr);
        *i32_key_ptr = i;

        g_hash_table_insert (set_out, i32_key_ptr, NULL);
      }
      printf ("testSet({");
      first = TRUE;
      keys_out = g_hash_table_get_keys (set_out);
      keys_elem = keys_out;
      while (keys_elem != NULL) {
        if (first)
          first = FALSE;
        else
          printf (", ");

        printf ("%d", *(gint32 *)keys_elem->data);

        keys_elem = keys_elem->next;
      }
      printf ("})");

      set_in = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, NULL);

      if (t_test_thrift_test_if_test_set (test_client,
                                          &set_in,
                                          set_out,
                                          &error)) {
        printf(" = {");
        first = TRUE;
        keys_in = g_hash_table_get_keys (set_in);
        keys_elem = keys_in;
        while (keys_elem != NULL) {
          if (first)
            first = FALSE;
          else
            printf (", ");

          printf ("%d", *(gint32 *)keys_elem->data);

          keys_elem = keys_elem->next;
        }
        printf ("}\n");

        if (g_list_length (keys_in) != g_list_length (keys_out))
          fail_count++;
        else {
          keys_elem = keys_out;
          while (keys_elem != NULL) {
            if (g_list_find_custom (keys_in,
                                    keys_elem->data,
                                    gint32_compare) == NULL) {
              fail_count++;
              break;
            }

            keys_elem = keys_elem->next;
          }
        }

        g_list_free (keys_in);
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      g_hash_table_unref (set_in);
      g_list_free (keys_out);
      g_hash_table_unref (set_out);

      /**
       * LIST TEST
       */
      list_out = g_array_new (FALSE, TRUE, sizeof (gint32));
      for (i = -2; i < 3; ++i) {
        g_array_append_val (list_out, i);
      }
      printf ("testList({");
      first = TRUE;
      for (i = 0; i < (gint32)list_out->len; ++i) {
        if (first)
          first = FALSE;
        else
          printf (", ");

        printf ("%d", g_array_index (list_out, gint32, i));
      }
      printf ("})");

      list_in = g_array_new (FALSE, TRUE, sizeof (gint32));

      if (t_test_thrift_test_if_test_list (test_client,
                                           &list_in,
                                           list_out,
                                           &error)) {
        printf (" = {");
        first = TRUE;
        for (i = 0; i < (gint32)list_in->len; ++i) {
          if (first)
            first = FALSE;
          else
            printf (", ");

          printf ("%d", g_array_index (list_in, gint32, i));
        }
        printf ("}\n");

        if (list_in->len != list_out->len ||
            memcmp (list_in->data,
                    list_out->data,
                    list_in->len * sizeof (gint32)) != 0)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      g_array_unref (list_in);
      g_array_unref (list_out);

      /**
       * ENUM TEST
       */
      printf("testEnum(ONE)");
      if (t_test_thrift_test_if_test_enum (test_client,
                                           &numberz,
                                           T_TEST_NUMBERZ_ONE,
                                           &error)) {
        printf(" = %d\n", numberz);
        if (numberz != T_TEST_NUMBERZ_ONE)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      printf("testEnum(TWO)");
      if (t_test_thrift_test_if_test_enum (test_client,
                                           &numberz,
                                           T_TEST_NUMBERZ_TWO,
                                           &error)) {
        printf(" = %d\n", numberz);
        if (numberz != T_TEST_NUMBERZ_TWO)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      printf("testEnum(THREE)");
      if (t_test_thrift_test_if_test_enum (test_client,
                                           &numberz,
                                           T_TEST_NUMBERZ_THREE,
                                           &error)) {
        printf(" = %d\n", numberz);
        if (numberz != T_TEST_NUMBERZ_THREE)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      printf("testEnum(FIVE)");
      if (t_test_thrift_test_if_test_enum (test_client,
                                           &numberz,
                                           T_TEST_NUMBERZ_FIVE,
                                           &error)) {
        printf(" = %d\n", numberz);
        if (numberz != T_TEST_NUMBERZ_FIVE)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      printf("testEnum(EIGHT)");
      if (t_test_thrift_test_if_test_enum (test_client,
                                           &numberz,
                                           T_TEST_NUMBERZ_EIGHT,
                                           &error)) {
        printf(" = %d\n", numberz);
        if (numberz != T_TEST_NUMBERZ_EIGHT)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * TYPEDEF TEST
       */
      printf ("testTypedef(309858235082523)");
      if (t_test_thrift_test_if_test_typedef (test_client,
                                              &user_id,
                                              309858235082523LL,
                                              &error)) {
        printf(" = %" PRId64 "\n", user_id);
        if (user_id != 309858235082523LL)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * NESTED MAP TEST
       */
      printf ("testMapMap(1)");
      map_in = g_hash_table_new_full (g_int_hash,
                                      g_int_equal,
                                      g_free,
                                      (GDestroyNotify)g_hash_table_unref);
      if (t_test_thrift_test_if_test_map_map (test_client,
                                              &map_in,
                                              1,
                                              &error)) {
        g_hash_table_iter_init (&hash_table_iter, map_in);

        printf (" = {");
        while (g_hash_table_iter_next (&hash_table_iter,
                                       &key,
                                       &value)) {
          printf ("%d => {", *(gint32 *)key);

          g_hash_table_iter_init (&inner_hash_table_iter,
                                  (GHashTable *)value);
          while (g_hash_table_iter_next (&inner_hash_table_iter,
                                         &key,
                                         &value)) {
            printf ("%d => %d, ", *(gint32 *)key, *(gint32 *)value);
          }

          printf ("}, ");
        }
        printf ("}\n");

        if (g_hash_table_size (map_in) != 2)
          fail_count++;
        else {
          gint32 inner_keys[] = {1, 2, 3, 4};
          gint32 i32_key;

          i32_key = -4;
          inner_map_in = g_hash_table_lookup (map_in, &i32_key);
          if (inner_map_in == NULL ||
              g_hash_table_size (inner_map_in) != 4)
            fail_count++;
          else {
            keys_in = g_hash_table_get_keys (inner_map_in);
            keys_in = g_list_sort (keys_in, gint32_compare);

            for (i = 0; i < 4; i++) {
              keys_elem = g_list_nth (keys_in, 3 - i);

              if (*(gint32 *)keys_elem->data != (-1 * inner_keys[i]) ||
                  *(gint32 *)g_hash_table_lookup (inner_map_in,
                                                  keys_elem->data) !=
                  (-1 * inner_keys[i])) {
                fail_count++;
                break;
              }
            }

            g_list_free (keys_in);
          }

          i32_key = 4;
          inner_map_in = g_hash_table_lookup (map_in, &i32_key);
          if (inner_map_in == NULL ||
              g_hash_table_size (inner_map_in) != 4)
            fail_count++;
          else {
            keys_in = g_hash_table_get_keys (inner_map_in);
            keys_in = g_list_sort (keys_in, gint32_compare);

            for (i = 0; i < 4; i++) {
              keys_elem = g_list_nth (keys_in, i);

              if (*(gint32 *)keys_elem->data != inner_keys[i] ||
                  *(gint32 *)g_hash_table_lookup (inner_map_in,
                                                  keys_elem->data) !=
                  inner_keys[i]) {
                fail_count++;
                break;
              }
            }

            g_list_free (keys_in);
          }
        }
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      g_hash_table_unref (map_in);

      /**
       * INSANITY TEST
       */
      insanity_out = g_object_new (T_TEST_TYPE_INSANITY, NULL);
      g_object_get (insanity_out,
                    "userMap", &user_map,
                    "xtructs", &xtructs,
                    NULL);

      numberz = T_TEST_NUMBERZ_FIVE;
      numberz2 = T_TEST_NUMBERZ_EIGHT;
      user_id_ptr = g_malloc (sizeof *user_id_ptr);
      *user_id_ptr = 5;
      user_id_ptr2 = g_malloc (sizeof *user_id_ptr);
      *user_id_ptr2 = 8;
      g_hash_table_insert (user_map, (gpointer)numberz, user_id_ptr);
      g_hash_table_insert (user_map, (gpointer)numberz2, user_id_ptr2);
      g_hash_table_unref (user_map);

      xtruct_out = g_object_new (T_TEST_TYPE_XTRUCT,
                                 "string_thing", "Hello2",
                                 "byte_thing",   2,
                                 "i32_thing",    2,
                                 "i64_thing",    2LL,
                                 NULL);
      xtruct_out2 = g_object_new (T_TEST_TYPE_XTRUCT,
                                 "string_thing", "Goodbye4",
                                 "byte_thing",   4,
                                 "i32_thing",    4,
                                 "i64_thing",    4LL,
                                 NULL);
      g_ptr_array_add (xtructs, xtruct_out2);
      g_ptr_array_add (xtructs, xtruct_out);
      g_ptr_array_unref (xtructs);

      map_in = g_hash_table_new_full (g_int64_hash,
                                      g_int64_equal,
                                      g_free,
                                      (GDestroyNotify)g_hash_table_unref);

      printf("testInsanity()");
      if (t_test_thrift_test_if_test_insanity (test_client,
                                               &map_in,
                                               insanity_out,
                                               &error)) {
        printf (" = {");
        g_hash_table_iter_init (&hash_table_iter, map_in);
        while (g_hash_table_iter_next (&hash_table_iter,
                                       &key,
                                       &value)) {
          printf ("%" PRId64 " => {", *(TTestUserId *)key);

          g_hash_table_iter_init (&inner_hash_table_iter,
                                  (GHashTable *)value);
          while (g_hash_table_iter_next (&inner_hash_table_iter,
                                         &key,
                                         &value)) {
            printf ("%d => {", (TTestNumberz)key);

            g_object_get ((TTestInsanity *)value,
                          "userMap", &user_map,
                          "xtructs", &xtructs,
                          NULL);

            printf ("{");
            g_hash_table_iter_init (&user_map_iter, user_map);
            while (g_hash_table_iter_next (&user_map_iter,
                                           &key,
                                           &value)) {
              printf ("%d => %" PRId64 ", ",
                      (TTestNumberz)key,
                      *(TTestUserId *)value);
            }
            printf ("}, ");
            g_hash_table_unref (user_map);

            printf("{");
            for (i = 0; i < (gint32)xtructs->len; ++i) {
              xtruct_in = g_ptr_array_index (xtructs, i);
              g_object_get (xtruct_in,
                            "string_thing", &string,
                            "byte_thing",   &byte_thing,
                            "i32_thing",    &i32_thing,
                            "i64_thing",    &i64_thing,
                            NULL);

              printf ("{\"%s\", %d, %d, %" PRId64 "}, ",
                      string,
                      byte_thing,
                      i32_thing,
                      i64_thing);
            }
            printf ("}");
            g_ptr_array_unref (xtructs);

            printf ("}, ");
          }
          printf("}, ");
        }
        printf("}\n");

        if (g_hash_table_size (map_in) != 2)
          fail_count++;
        else {
          TTestNumberz numberz_key_values[] = {
            T_TEST_NUMBERZ_TWO, T_TEST_NUMBERZ_THREE
          };
          gint user_map_values[] = { 5, 8 };
          TTestUserId user_id_key;

          user_id_key = 1;
          inner_map_in = g_hash_table_lookup (map_in, &user_id_key);
          if (inner_map_in == NULL ||
              g_hash_table_size (inner_map_in) != 2)
            fail_count++;
          else {
            TTestNumberz numberz_key;

            for (i = 0; i < 2; ++i) {
              numberz_key = numberz_key_values[i];
              insanity_in =
                g_hash_table_lookup (inner_map_in,
                                     (gconstpointer)numberz_key);
              if (insanity_in == NULL)
                fail_count++;
              else {
                g_object_get (insanity_in,
                              "userMap", &user_map,
                              "xtructs", &xtructs,
                              NULL);

                if (user_map == NULL)
                  fail_count++;
                else {
                  if (g_hash_table_size (user_map) != 2)
                    fail_count++;
                  else {
                    for (j = 0; j < 2; ++j) {
                      numberz_key = (TTestNumberz)user_map_values[j];

                      value =
                        g_hash_table_lookup (user_map,
                                             (gconstpointer)numberz_key);
                      if (value == NULL ||
                          *(TTestUserId *)value != (TTestUserId)user_map_values[j])
                        fail_count++;
                    }
                  }

                  g_hash_table_unref (user_map);
                }

                if (xtructs == NULL)
                  fail_count++;
                else {
                  if (xtructs->len != 2)
                    fail_count++;
                  else {
                    xtruct_in = g_ptr_array_index (xtructs, 0);
                    g_object_get (xtruct_in,
                                  "string_thing", &string,
                                  "byte_thing",   &byte_thing,
                                  "i32_thing",    &i32_thing,
                                  "i64_thing",    &i64_thing,
                                  NULL);
                    if ((string == NULL ||
                         strncmp (string, "Goodbye4", 9) != 0) ||
                        byte_thing != 4 ||
                        i32_thing != 4 ||
                        i64_thing != 4)
                      fail_count++;

                    if (string != NULL)
                      g_free (string);

                    xtruct_in = g_ptr_array_index (xtructs, 1);
                    g_object_get (xtruct_in,
                                  "string_thing", &string,
                                  "byte_thing",   &byte_thing,
                                  "i32_thing",    &i32_thing,
                                  "i64_thing",    &i64_thing,
                                  NULL);
                    if ((string == NULL ||
                         strncmp (string, "Hello2", 7) != 0) ||
                        byte_thing != 2 ||
                        i32_thing != 2 ||
                        i64_thing != 2)
                      fail_count++;

                    if (string != NULL)
                      g_free (string);
                  }

                  g_ptr_array_unref (xtructs);
                }
              }
            }
          }

          user_id_key = 2;
          inner_map_in = g_hash_table_lookup (map_in, &user_id_key);
          if (inner_map_in == NULL ||
              g_hash_table_size (inner_map_in) != 1)
            fail_count++;
          else {
            insanity_in =
              g_hash_table_lookup (inner_map_in,
                                   (gconstpointer)T_TEST_NUMBERZ_SIX);
            if (insanity_in == NULL)
              fail_count++;
            else {
              g_object_get (insanity_in,
                            "userMap", &user_map,
                            "xtructs", &xtructs,
                            NULL);

              if (user_map == NULL)
                fail_count++;
              else {
                if (g_hash_table_size (user_map) != 0)
                  fail_count++;

                g_hash_table_unref (user_map);
              }

              if (xtructs == NULL)
                fail_count++;
              else {
                if (xtructs->len != 0)
                  fail_count++;

                g_ptr_array_unref (xtructs);
              }
            }
          }
        }
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      g_hash_table_unref (map_in);
      g_clear_object (&insanity_out);

      /* test exception */
      printf ("testClient.testException(\"Xception\") =>");
      if (!t_test_thrift_test_if_test_exception (test_client,
                                                 "Xception",
                                                 &xception,
                                                 &error) &&
          xception != NULL) {
        g_object_get (xception,
                      "errorCode", &int32,
                      "message",   &string,
                      NULL);
        printf ("  {%u, \"%s\"}\n", int32, string);
        g_free (string);

        g_clear_object (&xception);

        g_error_free (error);
        error = NULL;
      }
      else {
        printf ("  void\nFAILURE\n");
        fail_count++;

        if (xception != NULL) {
          g_object_unref (xception);
          xception = NULL;
        }

        if (error != NULL) {
          g_error_free (error);
          error = NULL;
        }
      }

      printf ("testClient.testException(\"TException\") =>");
      if (!t_test_thrift_test_if_test_exception (test_client,
                                                 "TException",
                                                 &xception,
                                                 &error) &&
          xception == NULL &&
          error != NULL) {
        printf ("  Caught TException\n");

        g_error_free (error);
        error = NULL;
      }
      else {
        printf ("  void\nFAILURE\n");
        fail_count++;

        g_clear_object (&xception);

        if (error != NULL) {
          g_error_free (error);
          error = NULL;
        }
      }

      printf ("testClient.testException(\"success\") =>");
      if (t_test_thrift_test_if_test_exception (test_client,
                                                "success",
                                                &xception,
                                                &error))
        printf ("  void\n");
      else {
        printf ("  void\nFAILURE\n");
        fail_count++;

        g_clear_object (&xception);

        g_error_free (error);
        error = NULL;
      }

      g_assert (error == NULL);

      /* test multi exception */
      printf ("testClient.testMultiException(\"Xception\", \"test 1\") =>");
      xtruct_in = g_object_new (T_TEST_TYPE_XTRUCT, NULL);
      if (!t_test_thrift_test_if_test_multi_exception (test_client,
                                                       &xtruct_in,
                                                       "Xception",
                                                       "test 1",
                                                       &xception,
                                                       &xception2,
                                                       &error) &&
          xception != NULL &&
          xception2 == NULL) {
        g_object_get (xception,
                      "errorCode", &int32,
                      "message",   &string,
                      NULL);
        printf ("  {%u, \"%s\"}\n", int32, string);
        g_free (string);

        g_object_unref (xception);
        xception = NULL;

        g_error_free (error);
        error = NULL;
      }
      else {
        printf ("  result\nFAILURE\n");
        fail_count++;

        g_clear_object (&xception);
        g_clear_object (&xception2);

        if (error != NULL) {
          g_error_free (error);
          error = NULL;
        }
      }
      g_object_unref (xtruct_in);

      printf ("testClient.testMultiException(\"Xception2\", \"test 2\") =>");
      xtruct_in = g_object_new (T_TEST_TYPE_XTRUCT, NULL);
      if (!t_test_thrift_test_if_test_multi_exception (test_client,
                                                       &xtruct_in,
                                                       "Xception2",
                                                       "test 2",
                                                       &xception,
                                                       &xception2,
                                                       &error) &&
          xception == NULL &&
          xception2 != NULL) {
        g_object_get (xception2,
                      "errorCode",    &int32,
                      "struct_thing", &inner_xtruct_in,
                      NULL);
        g_object_get (inner_xtruct_in,
                      "string_thing", &string,
                      NULL);
        printf ("  {%u, {\"%s\"}}\n", int32, string);
        g_free (string);

        g_clear_object (&inner_xtruct_in);
        g_clear_object (&xception2);

        g_error_free (error);
        error = NULL;
      }
      else {
        printf ("  result\nFAILURE\n");
        fail_count++;

        g_clear_object (&xception);
        g_clear_object (&xception2);

        if (error != NULL) {
          g_error_free (error);
          error = NULL;
        }
      }
      g_clear_object (&xtruct_in);

      printf ("testClient.testMultiException(\"success\", \"test 3\") =>");
      xtruct_in = g_object_new (T_TEST_TYPE_XTRUCT, NULL);
      if (t_test_thrift_test_if_test_multi_exception (test_client,
                                                      &xtruct_in,
                                                      "success",
                                                      "test 3",
                                                      &xception,
                                                      &xception2,
                                                      &error) &&
          xception == NULL &&
          xception2 == NULL) {
        g_object_get (xtruct_in,
                      "string_thing", &string,
                      NULL);
        printf ("  {{\"%s\"}}\n", string);
        g_free (string);
      }
      else {
        printf ("  result\nFAILURE\n");
        fail_count++;

        g_clear_object (&xception);
        g_clear_object (&xception2);

        if (error != NULL) {
          g_error_free (error);
          error = NULL;
        }
      }
      g_clear_object (&xtruct_in);

      /* test oneway void */
      printf ("testClient.testOneway(1) =>");
      gettimeofday (&oneway_start, NULL);
      oneway_result = t_test_thrift_test_if_test_oneway (test_client,
                                                         1,
                                                         &error);
      gettimeofday (&oneway_end, NULL);
      timersub (&oneway_end, &oneway_start, &oneway_elapsed);
      oneway_elapsed_usec =
        oneway_elapsed.tv_sec * 1000 * 1000 + oneway_elapsed.tv_usec;

      if (oneway_result) {
        if (oneway_elapsed_usec > 200 * 1000) {
          printf ("  FAILURE - took %.2f ms\n",
                  (double)oneway_elapsed_usec / 1000.0);
          fail_count++;
        }
        else
          printf ("  success - took %.2f ms\n",
                  (double)oneway_elapsed_usec / 1000.0);
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      /**
       * redo a simple test after the oneway to make sure we aren't "off by
       * one" -- if the server treated oneway void like normal void, this next
       * test will fail since it will get the void confirmation rather than
       * the correct result. In this circumstance, the client will receive the
       * error:
       *
       *   application error: Wrong method name
       */
      /**
       * I32 TEST
       */
      printf ("re-test testI32(-1)");
      if (t_test_thrift_test_if_test_i32 (test_client,
                                          &int32,
                                          -1,
                                          &error)) {
        printf (" = %d\n", int32);
        if (int32 != -1)
          fail_count++;
      }
      else {
        printf ("%s\n", error->message);
        g_error_free (error);
        error = NULL;

        fail_count++;
      }

      gettimeofday (&time_stop, NULL);
      timersub (&time_stop, &time_start, &time_elapsed);
      time_elapsed_usec =
        time_elapsed.tv_sec * 1000 * 1000 + time_elapsed.tv_usec;

      printf("Total time: %" PRIu64 " us\n", time_elapsed_usec);

      time_total_usec += time_elapsed_usec;
      if (time_elapsed_usec < time_min_usec)
        time_min_usec = time_elapsed_usec;
      if (time_elapsed_usec > time_max_usec)
        time_max_usec = time_elapsed_usec;

      thrift_transport_close (transport, &error);
    }
    else {
      printf ("Connect failed: %s\n", error->message);
      g_error_free (error);
      error = NULL;

      return 1;
    }
  }

  /* All done---output statistics */
  puts ("\nAll tests done.");
  printf("Number of failures: %d\n", fail_count);

  time_avg_usec = time_total_usec / num_tests;

  printf ("Min time: %" PRIu64 " us\n", time_min_usec);
  printf ("Max time: %" PRIu64 " us\n", time_max_usec);
  printf ("Avg time: %" PRIu64 " us\n", time_avg_usec);

  g_clear_object(&second_service);
  g_clear_object(&protocol2);
  g_clear_object(&test_client);
  g_clear_object(&protocol);
  g_clear_object(&transport);
  g_clear_object(&socket);

  if (ssl) {
    thrift_ssl_socket_finalize_openssl();
  }

  return fail_count;
}
