add tests for apache mod_php5 and xmlrpc-epi-php
authorDan Winship <danw@src.gnome.org>
Mon, 19 Jun 2006 16:57:47 +0000 (16:57 +0000)
committerDan Winship <danw@src.gnome.org>
Mon, 19 Jun 2006 16:57:47 +0000 (16:57 +0000)
        * configure.in: add tests for apache mod_php5 and xmlrpc-epi-php

        * tests/xmlrpc-test.c: XML-RPC regression test

        * tests/xmlrpc-server.php: PHP server for xmlrpc-test

        * tests/httpd.conf.in: add php stuff

        * tests/apache-wrapper.c (apache_cleanup): Use "graceful-stop"
        rather than "stop", so that it stops listening on the socket
        before exiting, so that we can immediately start another apache
        (eg, in "make check").

        * libsoup/soup-date.c (soup_mktime_utc): Fix a bug in leap-year
        counting.

        * libsoup/soup-xmlrpc-message.c
        (soup_xmlrpc_message_write_datetime): rename from
        "..._write_time", to make it consistent with the XML-RPC type name
        and the corresponding SoupXmlrpcResponse method. Also, fix it to
        use the same ISO 8601 format as the spec, and use the right value
        for the seconds field.
        (soup_xmlrpc_message_write_base64): Change the buf arg to a
        gconstpointer rather than a const char *.

        * libsoup/soup-xmlrpc-response.c (soup_xmlrpc_value_get_base64):
        Return a GByteArray containing the decoded data, rather than
        the base64-encoded string.
        (soup_xmlrpc_value_dump_internal): Update for that (and don't
        leak it).
        (soup_xmlrpc_value_array_get_iterator,
        soup_xmlrpc_value_array_iterator_get_value): Make these actually
        work.

13 files changed:
ChangeLog
configure.in
libsoup/soup-date.c
libsoup/soup-xmlrpc-message.c
libsoup/soup-xmlrpc-message.h
libsoup/soup-xmlrpc-response.c
libsoup/soup-xmlrpc-response.h
tests/.cvsignore
tests/Makefile.am
tests/apache-wrapper.c
tests/httpd.conf.in
tests/xmlrpc-server.php [new file with mode: 0644]
tests/xmlrpc-test.c [new file with mode: 0644]

index 1423e26..c93debc 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,39 @@
+2006-06-14  Dan Winship  <danw@novell.com>
+
+       * configure.in: add tests for apache mod_php5 and xmlrpc-epi-php
+
+       * tests/xmlrpc-test.c: XML-RPC regression test
+
+       * tests/xmlrpc-server.php: PHP server for xmlrpc-test
+
+       * tests/httpd.conf.in: add php stuff
+
+       * tests/apache-wrapper.c (apache_cleanup): Use "graceful-stop"
+       rather than "stop", so that it stops listening on the socket
+       before exiting, so that we can immediately start another apache
+       (eg, in "make check").
+
+       * libsoup/soup-date.c (soup_mktime_utc): Fix a bug in leap-year
+       counting.
+
+       * libsoup/soup-xmlrpc-message.c
+       (soup_xmlrpc_message_write_datetime): rename from
+       "..._write_time", to make it consistent with the XML-RPC type name
+       and the corresponding SoupXmlrpcResponse method. Also, fix it to
+       use the same ISO 8601 format as the spec, and use the right value
+       for the seconds field.
+       (soup_xmlrpc_message_write_base64): Change the buf arg to a
+       gconstpointer rather than a const char *.
+
+       * libsoup/soup-xmlrpc-response.c (soup_xmlrpc_value_get_base64):
+       Return a GByteArray containing the decoded data, rather than
+       the base64-encoded string.
+       (soup_xmlrpc_value_dump_internal): Update for that (and don't
+       leak it).
+       (soup_xmlrpc_value_array_get_iterator,
+       soup_xmlrpc_value_array_iterator_get_value): Make these actually
+       work.
+
 2006-06-12  Dan Winship  <danw@novell.com>
 
        * configure.in: 2.2.94
index 9ae01f0..4bf8f4a 100644 (file)
@@ -256,6 +256,32 @@ else
 fi
 AM_CONDITIONAL(HAVE_APACHE, test $have_apache = 1)
 
+if test "$have_apache" = 1; then
+    AC_MSG_CHECKING([for mod_php5])
+    if test -f $APACHE_MODULE_DIR/mod_php5.so; then
+       have_php=yes
+       IF_HAVE_PHP=""
+    else
+       have_php=no
+       IF_HAVE_PHP="#"
+    fi
+    AC_MSG_RESULT($have_php)
+
+    if test "$have_php" = yes; then
+       AC_MSG_CHECKING([for xmlrpc-epi-php])
+       if php5 --rf xmlrpc_server_create | grep -q "does not exist"; then
+           have_xmlrpc_epi_php=no
+       else
+           have_xmlrpc_epi_php=yes
+       fi
+       AC_MSG_RESULT($have_xmlrpc_epi_php)
+    fi
+fi
+
+AC_SUBST(IF_HAVE_PHP)
+AM_CONDITIONAL(HAVE_XMLRPC_EPI_PHP, test $have_xmlrpc_epi_php = yes)
+
+
 dnl *************************
 dnl *** Output Everything ***
 dnl *************************
index 3085ae6..90c37d8 100644 (file)
@@ -68,7 +68,7 @@ soup_mktime_utc (struct tm *tm)
        tt = (tm->tm_year - 70) * 365;
        tt += (tm->tm_year - 68) / 4;
        tt += days_before[tm->tm_mon] + tm->tm_mday - 1;
-       if (tm->tm_year % 4 == 2 && tm->tm_mon < 2)
+       if (tm->tm_year % 4 == 0 && tm->tm_mon < 2)
                tt--;
        tt = ((((tt * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60 + tm->tm_sec;
        return tt;
index 4f37999..d061282 100644 (file)
@@ -202,7 +202,7 @@ soup_xmlrpc_message_write_double (SoupXmlrpcMessage *msg, double d)
 }
 
 void
-soup_xmlrpc_message_write_time (SoupXmlrpcMessage *msg, const time_t timeval)
+soup_xmlrpc_message_write_datetime (SoupXmlrpcMessage *msg, const time_t timeval)
 {
        SoupXmlrpcMessagePrivate *priv;
        struct tm time;
@@ -212,7 +212,7 @@ soup_xmlrpc_message_write_time (SoupXmlrpcMessage *msg, const time_t timeval)
        priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg);
 
        soup_gmtime (&timeval, &time);
-       strftime (str, 128, "%Y%m%dT%H%M%s", &time);
+       strftime (str, 128, "%Y%m%dT%H:%M:%S", &time);
 
        priv->last_node = xmlNewChild (priv->last_node, NULL, (xmlChar *)"value", NULL);
        xmlNewTextChild (priv->last_node, NULL, (xmlChar *)"dateTime.iso8601", (xmlChar *)str);
@@ -220,7 +220,7 @@ soup_xmlrpc_message_write_time (SoupXmlrpcMessage *msg, const time_t timeval)
 }
 
 void
-soup_xmlrpc_message_write_base64 (SoupXmlrpcMessage *msg, const char *buf, int len)
+soup_xmlrpc_message_write_base64 (SoupXmlrpcMessage *msg, gconstpointer buf, int len)
 {
        SoupXmlrpcMessagePrivate *priv;
        char *str;
index a86b2a6..f74c405 100644 (file)
@@ -36,39 +36,39 @@ GType soup_xmlrpc_message_get_type (void);
 SoupXmlrpcMessage *soup_xmlrpc_message_new          (const char *uri_string);
 SoupXmlrpcMessage *soup_xmlrpc_message_new_from_uri (const SoupUri *uri);
 
-void soup_xmlrpc_message_start_call    (SoupXmlrpcMessage *msg,
-                                       const char        *method_name);
-void soup_xmlrpc_message_end_call      (SoupXmlrpcMessage *msg);
-
-void soup_xmlrpc_message_start_param   (SoupXmlrpcMessage *msg);
-void soup_xmlrpc_message_end_param     (SoupXmlrpcMessage *msg);
-
-void soup_xmlrpc_message_write_int     (SoupXmlrpcMessage *msg,
-                                       long               i);
-void soup_xmlrpc_message_write_boolean (SoupXmlrpcMessage *msg,
-                                       gboolean           b);
-void soup_xmlrpc_message_write_string  (SoupXmlrpcMessage *msg,
-                                       const char        *str);
-void soup_xmlrpc_message_write_double  (SoupXmlrpcMessage *msg,
-                                       double             d);
-void soup_xmlrpc_message_write_time    (SoupXmlrpcMessage *msg,
-                                       const time_t       timeval);
-void soup_xmlrpc_message_write_base64  (SoupXmlrpcMessage *msg,
-                                       const char        *buf,
-                                       int                len);
-
-void soup_xmlrpc_message_start_struct  (SoupXmlrpcMessage *msg);
-void soup_xmlrpc_message_end_struct    (SoupXmlrpcMessage *msg);
-
-void soup_xmlrpc_message_start_member  (SoupXmlrpcMessage *msg,
-                                       const char        *name);
-void soup_xmlrpc_message_end_member    (SoupXmlrpcMessage *msg);
-
-void soup_xmlrpc_message_start_array   (SoupXmlrpcMessage *msg);
-void soup_xmlrpc_message_end_array     (SoupXmlrpcMessage *msg);
-
-xmlChar *soup_xmlrpc_message_to_string (SoupXmlrpcMessage *msg);
-void     soup_xmlrpc_message_persist   (SoupXmlrpcMessage *msg);
+void soup_xmlrpc_message_start_call     (SoupXmlrpcMessage *msg,
+                                        const char        *method_name);
+void soup_xmlrpc_message_end_call       (SoupXmlrpcMessage *msg);
+
+void soup_xmlrpc_message_start_param    (SoupXmlrpcMessage *msg);
+void soup_xmlrpc_message_end_param      (SoupXmlrpcMessage *msg);
+
+void soup_xmlrpc_message_write_int      (SoupXmlrpcMessage *msg,
+                                        long               i);
+void soup_xmlrpc_message_write_boolean  (SoupXmlrpcMessage *msg,
+                                        gboolean           b);
+void soup_xmlrpc_message_write_string   (SoupXmlrpcMessage *msg,
+                                        const char        *str);
+void soup_xmlrpc_message_write_double   (SoupXmlrpcMessage *msg,
+                                        double             d);
+void soup_xmlrpc_message_write_datetime (SoupXmlrpcMessage *msg,
+                                        const time_t       timeval);
+void soup_xmlrpc_message_write_base64   (SoupXmlrpcMessage *msg,
+                                        gconstpointer      buf,
+                                        int                len);
+
+void soup_xmlrpc_message_start_struct   (SoupXmlrpcMessage *msg);
+void soup_xmlrpc_message_end_struct     (SoupXmlrpcMessage *msg);
+
+void soup_xmlrpc_message_start_member   (SoupXmlrpcMessage *msg,
+                                        const char        *name);
+void soup_xmlrpc_message_end_member     (SoupXmlrpcMessage *msg);
+
+void soup_xmlrpc_message_start_array    (SoupXmlrpcMessage *msg);
+void soup_xmlrpc_message_end_array      (SoupXmlrpcMessage *msg);
+
+xmlChar *soup_xmlrpc_message_to_string  (SoupXmlrpcMessage *msg);
+void     soup_xmlrpc_message_persist    (SoupXmlrpcMessage *msg);
 
 SoupXmlrpcResponse *soup_xmlrpc_message_parse_response (SoupXmlrpcMessage *msg);
 
index 8153b42..ab7abeb 100644 (file)
@@ -356,12 +356,13 @@ soup_xmlrpc_value_get_datetime (SoupXmlrpcValue *value, time_t *timeval)
        return TRUE;
 }
 
-/* FIXME: this is broken; it returns the encoded value, not decoded */
 gboolean
-soup_xmlrpc_value_get_base64 (SoupXmlrpcValue *value, char **buf)
+soup_xmlrpc_value_get_base64 (SoupXmlrpcValue *value, GByteArray **data)
 {
        xmlNode *xml;
        xmlChar *content;
+       char *decoded;
+       int len;
 
        xml = (xmlNode *) value;
        if (strcmp ((char *)xml->name, "value"))
@@ -371,9 +372,13 @@ soup_xmlrpc_value_get_base64 (SoupXmlrpcValue *value, char **buf)
                return FALSE;
 
        content = xmlNodeGetContent (xml);
-       *buf = content ? g_strdup ((char *)content) : g_strdup ("");
+       decoded = soup_base64_decode ((const char *)content, &len);
        xmlFree (content);
 
+       *data = g_byte_array_new ();
+       g_byte_array_append (*data, (guchar *)decoded, len);
+       g_free (decoded);
+
        return TRUE;
 }
 
@@ -443,11 +448,13 @@ soup_xmlrpc_value_array_get_iterator (SoupXmlrpcValue *value, SoupXmlrpcValueArr
 
        xml = (xmlNode *) value;
 
-       if (!xml->children || strcmp((char *)xml->children->name, "data") == 0 || xml->children->next)
+       if (!xml->children || strcmp((char *)xml->children->name, "array") != 0 ||
+           xml->children->next || !xml->children->children ||
+           strcmp((char *)xml->children->children->name, "data") != 0 ||
+           xml->children->children->next)
                return FALSE;
 
-       *iter = (SoupXmlrpcValueArrayIterator *) xml->children;
-
+       *iter = (SoupXmlrpcValueArrayIterator *) xml->children->children->children;
        return TRUE;
 }
 
@@ -476,17 +483,7 @@ gboolean
 soup_xmlrpc_value_array_iterator_get_value (SoupXmlrpcValueArrayIterator *iter,
                                            SoupXmlrpcValue **value)
 {
-       xmlNode *xml;
-
-       xml = (xmlNode *) iter;
-
-       if (!xml || strcmp((char *)xml->name, "data"))
-               return FALSE;
-       xml = exactly_one_child (xml);
-       if (!xml)
-               return FALSE;
-
-       *value = (SoupXmlrpcValue *) xml;
+       *value = (SoupXmlrpcValue *) iter;
 
        return TRUE;
 }
@@ -525,6 +522,7 @@ soup_xmlrpc_value_dump_internal (SoupXmlrpcValue *value, int d)
        char *str;
        double f;
        time_t timeval;
+       GByteArray *base64;
        GHashTable *hash;
        SoupXmlrpcValueArrayIterator *iter;
 
@@ -580,10 +578,19 @@ soup_xmlrpc_value_dump_internal (SoupXmlrpcValue *value, int d)
 
                case SOUP_XMLRPC_VALUE_TYPE_BASE64:
                        indent (d);
-                       if (!soup_xmlrpc_value_get_base64 (value, &str))
+                       if (!soup_xmlrpc_value_get_base64 (value, &base64))
                                g_printerr ("BAD BASE64\n");
-                       else
-                               g_printerr ("BASE64: %s\n", str);
+                       else {
+                               GString *hex = g_string_new (NULL);
+                               int i;
+
+                               for (i = 0; i < base64->len; i++)
+                                       g_string_append_printf (hex, "%02x", base64->data[i]);
+
+                               g_printerr ("BASE64: %s\n", hex->str);
+                               g_string_free (hex, TRUE);
+                               g_byte_array_free (base64, TRUE);
+                       }
 
                        break;
 
index 10c2dd7..f2207c7 100644 (file)
@@ -65,7 +65,7 @@ gboolean soup_xmlrpc_value_get_string   (SoupXmlrpcValue  *value,
 gboolean soup_xmlrpc_value_get_datetime (SoupXmlrpcValue  *value,
                                         time_t           *timeval);
 gboolean soup_xmlrpc_value_get_base64   (SoupXmlrpcValue  *value,
-                                        char            **buf);
+                                        GByteArray      **data);
 
 gboolean soup_xmlrpc_value_get_struct   (SoupXmlrpcValue  *value,
                                         GHashTable      **table);
index 35b83c6..e1166ad 100644 (file)
@@ -12,3 +12,4 @@ revserver
 simple-httpd
 simple-proxy
 uri-parsing
+xmlrpc-test
index 8d05c13..41313bf 100644 (file)
@@ -15,7 +15,8 @@ noinst_PROGRAMS =     \
        revserver       \
        simple-httpd    \
        simple-proxy    \
-       uri-parsing
+       uri-parsing     \
+       xmlrpc-test
 
 auth_test_SOURCES = auth-test.c apache-wrapper.c apache-wrapper.h
 date_SOURCES = date.c
@@ -27,12 +28,16 @@ revserver_SOURCES = revserver.c
 simple_httpd_SOURCES = simple-httpd.c
 simple_proxy_SOURCES = simple-proxy.c
 uri_parsing_SOURCES = uri-parsing.c
+xmlrpc_test_SOURCES = xmlrpc-test.c apache-wrapper.c apache-wrapper.h
 
 if HAVE_APACHE
 APACHE_TESTS = auth-test
 endif
+if HAVE_XMLRPC_EPI_PHP
+XMLRPC_TESTS = xmlrpc-test
+endif
 
-TESTS = date uri-parsing $(APACHE_TESTS)
+TESTS = date uri-parsing $(APACHE_TESTS) $(XMLRPC_TESTS)
 
 EXTRA_DIST =           \
        libsoup.supp    \
@@ -40,4 +45,5 @@ EXTRA_DIST =          \
        test-key.pem    \
        htdigest        \
        htpasswd        \
-       httpd.conf.in
+       httpd.conf.in   \
+       xmlrpc-server.php
index 2f5cd8c..5c50735 100644 (file)
@@ -46,7 +46,7 @@ apache_init (void)
 void
 apache_cleanup (void)
 {
-       apache_cmd ("stop");
+       apache_cmd ("graceful-stop");
 }
 
 #endif /* HAVE_APACHE */
index eee3a52..80899ac 100644 (file)
@@ -4,6 +4,9 @@ ServerName 127.0.0.1
 Listen 127.0.0.1:47524
 
 PidFile @builddir@/httpd.pid
+DocumentRoot @srcdir@
+
+# Change this to "./error.log" if it's failing and you don't know why
 ErrorLog /dev/null
 
 LoadModule alias_module       @APACHE_MODULE_DIR@/mod_alias.so
@@ -12,8 +15,11 @@ LoadModule auth_digest_module @APACHE_MODULE_DIR@/mod_auth_digest.so
 LoadModule authn_file_module  @APACHE_MODULE_DIR@/mod_authn_file.so
 LoadModule authz_user_module  @APACHE_MODULE_DIR@/mod_authz_user.so
 LoadModule dir_module         @APACHE_MODULE_DIR@/mod_dir.so
+LoadModule mime_module        @APACHE_MODULE_DIR@/mod_mime.so
+@IF_HAVE_PHP@LoadModule php5_module        @APACHE_MODULE_DIR@/mod_php5.so
 
 DirectoryIndex httpd.pid
+AddType application/x-httpd-php .php
 
 Alias /Basic/realm1/realm2/realm1 @builddir@
 Alias /Basic/realm1/realm2 @builddir@
diff --git a/tests/xmlrpc-server.php b/tests/xmlrpc-server.php
new file mode 100644 (file)
index 0000000..cf20948
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+
+function sum ($method_name, $params, $app_data)
+{
+       $sum = 0;
+       foreach ($params[0] as $val) {
+               $sum = $sum + $val;
+       }
+       return $sum;
+}
+
+function countBools ($method_name, $params, $app_data)
+{
+       $counts["true"] = $counts["false"] = 0;
+       foreach ($params[0] as $val) {
+               if (xmlrpc_get_type ($val) != "boolean")
+                       return "bad value: $val";
+
+               if ($val)
+                       $counts["true"] = $counts["true"] + 1;
+               else
+                       $counts["false"] = $counts["false"] + 1;
+       }
+       return $counts;
+}
+
+function md5sum ($method_name, $params, $app_data)
+{
+       $val = md5 ($params[0]->scalar, true);
+       xmlrpc_set_type ($val, "base64");
+       return $val;
+}
+
+function dateChange ($method_name, $params, $app_data)
+{
+       $date_str = $params[0]["date"]->scalar;
+       $date = strptime ($date_str, "%Y%m%dT%H:%M:%S");
+
+       foreach ($params[0] as $name => $val) {
+               if ($name == "date")
+                       continue;
+               $date[$name] = $val;
+       }
+
+       $ret = sprintf ("%04d%02d%02dT%02d:%02d:%02d",
+                       $date["tm_year"] + 1900, $date["tm_mon"] + 1,
+                       $date["tm_mday"], $date["tm_hour"],
+                       $date["tm_min"], $date["tm_sec"]);
+       xmlrpc_set_type ($ret, "datetime");
+       return $ret;
+}
+
+function echo_ ($method_name, $params, $app_data)
+{
+       return $params[0];
+}
+
+# Work around xmlrpc-epi-php lossage; otherwise the datetime values
+# we return will sometimes get a DST adjustment we don't want.
+putenv ("TZ=");
+
+$xmlrpc_server = xmlrpc_server_create ();
+xmlrpc_server_register_method($xmlrpc_server, "sum", "sum");
+xmlrpc_server_register_method($xmlrpc_server, "countBools", "countBools");
+xmlrpc_server_register_method($xmlrpc_server, "md5sum", "md5sum");
+xmlrpc_server_register_method($xmlrpc_server, "dateChange", "dateChange");
+xmlrpc_server_register_method($xmlrpc_server, "echo", "echo_");
+
+$response = xmlrpc_server_call_method ($xmlrpc_server, $HTTP_RAW_POST_DATA,
+                                      0, array ("output_type" => "xml"));
+echo ($response);
+
+xmlrpc_server_destroy ($xmlrpc_server);
+
+?>
diff --git a/tests/xmlrpc-test.c b/tests/xmlrpc-test.c
new file mode 100644 (file)
index 0000000..2a80f06
--- /dev/null
@@ -0,0 +1,457 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#include <string.h>
+#include <unistd.h>
+
+#include <libsoup/soup.h>
+#include <libsoup/soup-date.h>
+#include <libsoup/soup-md5-utils.h>
+#include <libsoup/soup-xmlrpc-message.h>
+#include <libsoup/soup-xmlrpc-response.h>
+
+#include "apache-wrapper.h"
+
+SoupSession *session;
+static const char *uri = "http://localhost:47524/xmlrpc-server.php";
+int debug;
+
+static const char *const value_type[] = {
+       "BAD",
+       "int",
+       "boolean",
+       "string",
+       "double",
+       "datetime",
+       "base64",
+       "struct",
+       "array"
+};
+
+static SoupXmlrpcValue *
+do_xmlrpc (SoupXmlrpcMessage *xmsg, SoupXmlrpcValueType type)
+{
+       SoupMessage *msg = SOUP_MESSAGE (xmsg);
+       SoupXmlrpcResponse *response;
+       SoupXmlrpcValue *value;
+       int status;
+
+       soup_xmlrpc_message_persist (xmsg);
+       status = soup_session_send_message (session, msg);
+
+       if (debug > 1) {
+               printf ("\n%.*s\n%d %s\n%.*s\n",
+                       msg->request.length, msg->request.body,
+                       msg->status_code, msg->reason_phrase,
+                       msg->response.length, msg->response.body);
+       }
+
+       if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
+               printf ("ERROR: %d %s\n", status, msg->reason_phrase);
+               return FALSE;
+       }
+
+       response = soup_xmlrpc_message_parse_response (xmsg);
+       if (!response || soup_xmlrpc_response_is_fault (response)) {
+               if (!response)
+                       printf ("ERROR: no response\n");
+               else
+                       printf ("ERROR: fault\n");
+               return FALSE;
+       }
+
+       value = soup_xmlrpc_response_get_value (response);
+       if (!value) {
+               printf ("ERROR: no value?\n");
+               return NULL;
+       } else if (soup_xmlrpc_value_get_type (value) != type) {
+               printf ("ERROR: wrong value type; expected %s, got %s\n",
+                       value_type[type], value_type[soup_xmlrpc_value_get_type (value)]);
+               return NULL;
+       }
+
+       return value;
+}
+
+static gboolean
+test_sum (void)
+{
+       SoupXmlrpcMessage *msg;
+       SoupXmlrpcValue *value;
+       int i, val, sum;
+       long result;
+
+       printf ("sum (array of int -> int): ");
+
+       msg = soup_xmlrpc_message_new (uri);
+       soup_xmlrpc_message_start_call (msg, "sum");
+       soup_xmlrpc_message_start_param (msg);
+       soup_xmlrpc_message_start_array (msg);
+       for (i = sum = 0; i < 10; i++) {
+               val = rand () % 100;
+               if (debug)
+                       printf ("%s%d", i == 0 ? "[" : ", ", val);
+               soup_xmlrpc_message_write_int (msg, val);
+               sum += val;
+       }
+       if (debug)
+               printf ("] -> ");
+       soup_xmlrpc_message_end_array (msg);
+       soup_xmlrpc_message_end_param (msg);
+       soup_xmlrpc_message_end_call (msg);
+
+       value = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_INT);
+       if (!value)
+               return FALSE;
+
+       if (!soup_xmlrpc_value_get_int (value, &result)) {
+               printf ("wrong type?\n");
+               return FALSE;
+       }
+
+       if (debug)
+               printf ("%ld: ", result);
+       printf ("%s\n", result == sum ? "OK!" : "WRONG!");
+       return result == sum;
+}
+
+static gboolean
+test_countBools (void)
+{
+       SoupXmlrpcMessage *msg;
+       SoupXmlrpcValue *value;
+       int i, trues, falses;
+       long ret_trues, ret_falses;
+       gboolean val, ok;
+       GHashTable *result;
+
+       printf ("countBools (array of boolean -> struct of ints): ");
+
+       msg = soup_xmlrpc_message_new (uri);
+       soup_xmlrpc_message_start_call (msg, "countBools");
+       soup_xmlrpc_message_start_param (msg);
+       soup_xmlrpc_message_start_array (msg);
+       for (i = trues = falses = 0; i < 10; i++) {
+               val = rand () > (RAND_MAX / 2);
+               if (debug)
+                       printf ("%s%c", i == 0 ? "[" : ", ", val ? 'T' : 'F');
+               soup_xmlrpc_message_write_boolean (msg, val);
+               if (val)
+                       trues++;
+               else
+                       falses++;
+       }
+       if (debug)
+               printf ("] -> ");
+       soup_xmlrpc_message_end_array (msg);
+       soup_xmlrpc_message_end_param (msg);
+       soup_xmlrpc_message_end_call (msg);
+
+       value = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_STRUCT);
+       if (!value)
+               return FALSE;
+
+       if (!soup_xmlrpc_value_get_struct (value, &result)) {
+               printf ("wrong type?\n");
+               return FALSE;
+       }
+
+       if (!soup_xmlrpc_value_get_int (g_hash_table_lookup (result, "true"), &ret_trues)) {
+               printf ("NO 'true' value in response\n");
+               return FALSE;
+       }
+       if (!soup_xmlrpc_value_get_int (g_hash_table_lookup (result, "false"), &ret_falses)) {
+               printf ("NO 'false' value in response\n");
+               return FALSE;
+       }
+
+       if (debug)
+               printf ("{ true: %ld, false: %ld } ", ret_trues, ret_falses);
+       ok = (trues == ret_trues) && (falses == ret_falses);
+       printf ("%s\n", ok ? "OK!" : "WRONG!");
+       return ok;
+}
+
+static gboolean
+test_md5sum (void)
+{
+       SoupXmlrpcMessage *msg;
+       SoupXmlrpcValue *value;
+       GByteArray *result;
+       char data[512];
+       int i;
+       SoupMD5Context md5;
+       guchar digest[16];
+       gboolean ok;
+
+       printf ("md5sum (base64 -> base64): ");
+
+       msg = soup_xmlrpc_message_new (uri);
+       soup_xmlrpc_message_start_call (msg, "md5sum");
+       soup_xmlrpc_message_start_param (msg);
+       for (i = 0; i < sizeof (data); i++)
+               data[i] = (char)(rand () & 0xFF);
+       soup_xmlrpc_message_write_base64 (msg, data, sizeof (data));
+       soup_xmlrpc_message_end_param (msg);
+       soup_xmlrpc_message_end_call (msg);
+
+       value = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_BASE64);
+       if (!value)
+               return FALSE;
+
+       if (!soup_xmlrpc_value_get_base64 (value, &result)) {
+               printf ("wrong type?\n");
+               return FALSE;
+       }
+
+       if (result->len != 16) {
+               printf ("result has WRONG length (%d)\n", result->len);
+               g_byte_array_free (result, TRUE);
+               return FALSE;
+       }
+
+       soup_md5_init (&md5);
+       soup_md5_update (&md5, data, sizeof (data));
+       soup_md5_final (&md5, digest);
+
+       ok = (memcmp (digest, result->data, 16) == 0);
+       printf ("%s\n", ok ? "OK!" : "WRONG!");
+       g_byte_array_free (result, TRUE);
+       return ok;
+}
+
+static gboolean
+test_dateChange (void)
+{
+       SoupXmlrpcMessage *msg;
+       SoupXmlrpcValue *value;
+       struct tm tm;
+       time_t when, result;
+       char timestamp[128];
+
+       printf ("dateChange (struct of time and ints -> time): ");
+
+       msg = soup_xmlrpc_message_new (uri);
+       soup_xmlrpc_message_start_call (msg, "dateChange");
+       soup_xmlrpc_message_start_param (msg);
+       soup_xmlrpc_message_start_struct (msg);
+
+       soup_xmlrpc_message_start_member (msg, "date");
+       memset (&tm, 0, sizeof (tm));
+       tm.tm_year = 70 + (rand () % 50);
+       tm.tm_mon = rand () % 12;
+       tm.tm_mday = 1 + (rand () % 28);
+       tm.tm_hour = rand () % 24;
+       tm.tm_min = rand () % 60;
+       tm.tm_sec = rand () % 60;
+       when = soup_mktime_utc (&tm);
+       soup_xmlrpc_message_write_datetime (msg, when);
+       soup_xmlrpc_message_end_member (msg);
+
+       if (debug) {
+               strftime (timestamp, sizeof (timestamp),
+                         "%Y-%m-%dT%H:%M:%S", &tm);
+               printf ("{ date: %s", timestamp);
+       }
+
+       if (rand () % 3) {
+               tm.tm_year = 70 + (rand () % 50);
+               if (debug)
+                       printf (", tm_year: %d", tm.tm_year);
+               soup_xmlrpc_message_start_member (msg, "tm_year");
+               soup_xmlrpc_message_write_int (msg, tm.tm_year);
+               soup_xmlrpc_message_end_member (msg);
+       }
+       if (rand () % 3) {
+               tm.tm_mon = rand () % 12;
+               if (debug)
+                       printf (", tm_mon: %d", tm.tm_mon);
+               soup_xmlrpc_message_start_member (msg, "tm_mon");
+               soup_xmlrpc_message_write_int (msg, tm.tm_mon);
+               soup_xmlrpc_message_end_member (msg);
+       }
+       if (rand () % 3) {
+               tm.tm_mday = 1 + (rand () % 28);
+               if (debug)
+                       printf (", tm_mday: %d", tm.tm_mday);
+               soup_xmlrpc_message_start_member (msg, "tm_mday");
+               soup_xmlrpc_message_write_int (msg, tm.tm_mday);
+               soup_xmlrpc_message_end_member (msg);
+       }
+       if (rand () % 3) {
+               tm.tm_hour = rand () % 24;
+               if (debug)
+                       printf (", tm_hour: %d", tm.tm_hour);
+               soup_xmlrpc_message_start_member (msg, "tm_hour");
+               soup_xmlrpc_message_write_int (msg, tm.tm_hour);
+               soup_xmlrpc_message_end_member (msg);
+       }
+       if (rand () % 3) {
+               tm.tm_min = rand () % 60;
+               if (debug)
+                       printf (", tm_min: %d", tm.tm_min);
+               soup_xmlrpc_message_start_member (msg, "tm_min");
+               soup_xmlrpc_message_write_int (msg, tm.tm_min);
+               soup_xmlrpc_message_end_member (msg);
+       }
+       if (rand () % 3) {
+               tm.tm_sec = rand () % 60;
+               if (debug)
+                       printf (", tm_sec: %d", tm.tm_sec);
+               soup_xmlrpc_message_start_member (msg, "tm_sec");
+               soup_xmlrpc_message_write_int (msg, tm.tm_sec);
+               soup_xmlrpc_message_end_member (msg);
+       }
+       when = soup_mktime_utc (&tm);
+
+       if (debug)
+               printf (" } -> ");
+
+       soup_xmlrpc_message_end_struct (msg);
+       soup_xmlrpc_message_end_param (msg);
+       soup_xmlrpc_message_end_call (msg);
+
+       value = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_DATETIME);
+       if (!value)
+               return FALSE;
+
+       if (!soup_xmlrpc_value_get_datetime (value, &result)) {
+               printf ("wrong type?\n");
+               return FALSE;
+       }
+
+       if (debug) {
+               memset (&tm, 0, sizeof (tm));
+               soup_gmtime (&result, &tm);
+               strftime (timestamp, sizeof (timestamp),
+                         "%Y-%m-%dT%H:%M:%S", &tm);
+               printf ("%s: ", timestamp);
+       }
+
+       printf ("%s\n", (when == result) ? "OK!" : "WRONG!");
+       return (when == result);
+}
+
+static const char *const echo_strings[] = {
+       "This is a test",
+       "& so is this",
+       "and so is <this>",
+       "&amp; so is &lt;this&gt;"
+};
+#define N_ECHO_STRINGS G_N_ELEMENTS (echo_strings)
+
+static gboolean
+test_echo (void)
+{
+       SoupXmlrpcMessage *msg;
+       SoupXmlrpcValue *value, *elt;
+       SoupXmlrpcValueArrayIterator *iter;
+       char *echo;
+       int i;
+
+       printf ("echo (array of string -> array of string): ");
+
+       msg = soup_xmlrpc_message_new (uri);
+       soup_xmlrpc_message_start_call (msg, "echo");
+       soup_xmlrpc_message_start_param (msg);
+       soup_xmlrpc_message_start_array (msg);
+       for (i = 0; i < N_ECHO_STRINGS; i++) {
+               if (debug)
+                       printf ("%s\"%s\"", i == 0 ? "[" : ", ", echo_strings[i]);
+               soup_xmlrpc_message_write_string (msg, echo_strings[i]);
+       }
+       if (debug)
+               printf ("] -> ");
+       soup_xmlrpc_message_end_array (msg);
+       soup_xmlrpc_message_end_param (msg);
+       soup_xmlrpc_message_end_call (msg);
+
+       value = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_ARRAY);
+       if (!value)
+               return FALSE;
+
+       if (!soup_xmlrpc_value_array_get_iterator (value, &iter)) {
+               printf ("wrong type?\n");
+               return FALSE;
+       }
+       i = 0;
+       while (iter) {
+               if (!soup_xmlrpc_value_array_iterator_get_value (iter, &elt)) {
+                       printf (" WRONG! Can't get result element %d\n", i + 1);
+                       return FALSE;
+               }
+               if (!soup_xmlrpc_value_get_string (elt, &echo)) {
+                       printf (" WRONG! Result element %d is not a string", i + 1);
+                       return FALSE;
+               }
+               if (debug)
+                       printf ("%s\"%s\"", i == 0 ? "[" : ", ", echo);
+               if (strcmp (echo_strings[i], echo) != 0) {
+                       printf (" WRONG! Mismatch at %d\n", i + 1);
+                       return FALSE;
+               }
+
+               iter = soup_xmlrpc_value_array_iterator_next (iter);
+               i++;
+       }
+       if (debug)
+               printf ("] ");
+
+       printf ("%s\n", i == N_ECHO_STRINGS ? "OK!" : "WRONG! Too few results");
+       return i == N_ECHO_STRINGS;
+}
+
+static void
+usage (void)
+{
+       fprintf (stderr, "Usage: xmlrpc-test [-d] [-d]\n");
+       exit (1);
+}
+
+int
+main (int argc, char **argv)
+{
+       int opt, errors = 0;
+
+       g_type_init ();
+       g_thread_init (NULL);
+
+       while ((opt = getopt (argc, argv, "d")) != -1) {
+               switch (opt) {
+               case 'd':
+                       debug++;
+                       break;
+
+               case '?':
+                       usage ();
+                       break;
+               }
+       }
+
+       srand (time (NULL));
+
+       if (!apache_init ()) {
+               fprintf (stderr, "Could not start apache\n");
+               return 1;
+       }
+
+       session = soup_session_sync_new ();
+
+       if (!test_sum ())
+               errors++;
+       if (!test_countBools ())
+               errors++;
+       if (!test_md5sum ())
+               errors++;
+       if (!test_dateChange ())
+               errors++;
+       if (!test_echo ())
+               errors++;
+
+       apache_cleanup ();
+
+       printf ("\n%d errors\n", errors);
+       return errors > 0;
+}