From 2a37bc0dc6cc013d01836b45c2eea6b1bd6d80e5 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Sun, 22 Apr 2012 15:18:50 -0400 Subject: [PATCH] gio: add a proxy test program Test GProxy, GProxyResolver, GProxyAddress, and GProxyAddressEnumerator, plus GSocketClient's proxy-resolving codepaths. --- gio/tests/Makefile.am | 4 + gio/tests/proxy-test.c | 1077 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1081 insertions(+) create mode 100644 gio/tests/proxy-test.c diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index 9829aa0..d565df5 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -59,6 +59,7 @@ TEST_PROGS += \ network-monitor \ fileattributematcher \ resources \ + proxy-test \ $(NULL) if HAVE_DBUS_DAEMON @@ -453,6 +454,9 @@ gapplication_example_actions_LDADD = $(progs_ldadd) gmenumodel_SOURCES = gmenumodel.c gdbus-sessionbus.h gdbus-sessionbus.c gmenumodel_LDADD = $(progs_ldadd) +proxy_test_SOURCES = proxy-test.c +proxy_test_LDADD = $(progs_ldadd) + schema_tests = \ schema-tests/array-default-not-in-choices.gschema.xml \ schema-tests/bad-choice.gschema.xml \ diff --git a/gio/tests/proxy-test.c b/gio/tests/proxy-test.c new file mode 100644 index 0000000..079b9ad --- /dev/null +++ b/gio/tests/proxy-test.c @@ -0,0 +1,1077 @@ +/* GLib testing framework examples and tests + * + * Copyright 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_34 +#include + +/* Overview: + * + * We have an echo server, two proxy servers, two GProxy + * implementations, and a GProxyResolver implementation. + * + * The echo server runs at @server.server_addr (on + * @server.server_port). + * + * The two proxy servers, A and B, run on @proxy_a.port and + * @proxy_b.port, with @proxy_a.uri and @proxy_b.uri pointing to them. + * The "negotiation" with the two proxies is just sending the single + * letter "a" or "b" and receiving it back in uppercase; the proxy + * then connects to @server_addr. + * + * Proxy A supports "alpha://" URIs, and does not support hostname + * resolution, and Proxy B supports "beta://" URIs, and does support + * hostname resolution (but it just ignores the hostname and always + * connects to @server_addr anyway). + * + * The GProxyResolver (GTestProxyResolver) looks at its URI and + * returns [ "direct://" ] for "simple://" URIs, and [ proxy_a.uri, + * proxy_b.uri ] for other URIs. + */ + +typedef struct { + gchar *proxy_command; + gchar *supported_protocol; + + GSocket *server; + GThread *thread; + GCancellable *cancellable; + gchar *uri; + gushort port; + + GSocket *client_sock, *server_sock; + GMainLoop *loop; + + GError *last_error; +} ProxyData; + +static ProxyData proxy_a, proxy_b; + +typedef struct { + GSocket *server; + GThread *server_thread; + GCancellable *cancellable; + GSocketAddress *server_addr; + gushort server_port; +} ServerData; + +static ServerData server; + +static gchar **last_proxies; + +static GSocketClient *client; + + +/**************************************/ +/* Test GProxyResolver implementation */ +/**************************************/ + +typedef struct { + GObject parent_instance; +} GTestProxyResolver; + +typedef struct { + GObjectClass parent_class; +} GTestProxyResolverClass; + +static void g_test_proxy_resolver_iface_init (GProxyResolverInterface *iface); + +#define g_test_proxy_resolver_get_type _g_test_proxy_resolver_get_type +G_DEFINE_TYPE_WITH_CODE (GTestProxyResolver, g_test_proxy_resolver, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_PROXY_RESOLVER, + g_test_proxy_resolver_iface_init) + g_io_extension_point_implement (G_PROXY_RESOLVER_EXTENSION_POINT_NAME, + g_define_type_id, + "test", + 0)) + +static void +g_test_proxy_resolver_init (GTestProxyResolver *resolver) +{ +} + +static gboolean +g_test_proxy_resolver_is_supported (GProxyResolver *resolver) +{ + return TRUE; +} + +static gchar ** +g_test_proxy_resolver_lookup (GProxyResolver *resolver, + const gchar *uri, + GCancellable *cancellable, + GError **error) +{ + gchar **proxies; + + g_assert (last_proxies == NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + proxies = g_new (gchar *, 3); + + if (!strncmp (uri, "simple://", 4)) + { + proxies[0] = g_strdup ("direct://"); + proxies[1] = NULL; + } + else + { + /* Proxy A can only deal with "alpha://" URIs, not + * "beta://", but we always return both URIs + * anyway so we can test error handling when the first + * fails. + */ + proxies[0] = g_strdup (proxy_a.uri); + proxies[1] = g_strdup (proxy_b.uri); + proxies[2] = NULL; + } + + last_proxies = g_strdupv (proxies); + + return proxies; +} + +static void +g_test_proxy_resolver_lookup_async (GProxyResolver *resolver, + const gchar *uri, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GError *error = NULL; + GSimpleAsyncResult *simple; + gchar **proxies; + + proxies = g_test_proxy_resolver_lookup (resolver, uri, cancellable, &error); + + simple = g_simple_async_result_new (G_OBJECT (resolver), + callback, user_data, + g_test_proxy_resolver_lookup_async); + + if (proxies == NULL) + g_simple_async_result_take_error (simple, error); + else + g_simple_async_result_set_op_res_gpointer (simple, proxies, (GDestroyNotify) g_strfreev); + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +static gchar ** +g_test_proxy_resolver_lookup_finish (GProxyResolver *resolver, + GAsyncResult *result, + GError **error) +{ + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + gchar **proxies; + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + proxies = g_simple_async_result_get_op_res_gpointer (simple); + return g_strdupv (proxies); + } + + return NULL; +} + +static void +g_test_proxy_resolver_class_init (GTestProxyResolverClass *resolver_class) +{ +} + +static void +g_test_proxy_resolver_iface_init (GProxyResolverInterface *iface) +{ + iface->is_supported = g_test_proxy_resolver_is_supported; + iface->lookup = g_test_proxy_resolver_lookup; + iface->lookup_async = g_test_proxy_resolver_lookup_async; + iface->lookup_finish = g_test_proxy_resolver_lookup_finish; +} + + +/****************************************/ +/* Test proxy implementation base class */ +/****************************************/ + +typedef struct { + GObject parent; + + ProxyData *proxy_data; +} GProxyBase; + +typedef struct { + GObjectClass parent_class; +} GProxyBaseClass; + +#define g_proxy_base_get_type _g_proxy_base_get_type +G_DEFINE_ABSTRACT_TYPE (GProxyBase, g_proxy_base, G_TYPE_OBJECT) + +static void +g_proxy_base_init (GProxyBase *proxy) +{ +} + +static GIOStream * +g_proxy_base_connect (GProxy *proxy, + GIOStream *io_stream, + GProxyAddress *proxy_address, + GCancellable *cancellable, + GError **error) +{ + ProxyData *data = ((GProxyBase *) proxy)->proxy_data; + const gchar *protocol; + GOutputStream *ostream; + GInputStream *istream; + gchar response; + + g_assert_no_error (data->last_error); + + protocol = g_proxy_address_get_destination_protocol (proxy_address); + if (strcmp (protocol, data->supported_protocol) != 0) + { + g_set_error_literal (&data->last_error, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Unsupported protocol"); + goto fail; + } + + ostream = g_io_stream_get_output_stream (io_stream); + if (g_output_stream_write (ostream, data->proxy_command, 1, cancellable, + &data->last_error) != 1) + goto fail; + + istream = g_io_stream_get_input_stream (io_stream); + if (g_input_stream_read (istream, &response, 1, cancellable, + &data->last_error) != 1) + goto fail; + + if (response != g_ascii_toupper (*data->proxy_command)) + { + g_set_error_literal (&data->last_error, + G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed"); + goto fail; + } + + return g_object_ref (io_stream); + + fail: + g_propagate_error (error, g_error_copy (data->last_error)); + return NULL; +} + +static void +g_proxy_base_connect_async (GProxy *proxy, + GIOStream *io_stream, + GProxyAddress *proxy_address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GError *error = NULL; + GSimpleAsyncResult *simple; + GIOStream *proxy_io_stream; + + simple = g_simple_async_result_new (G_OBJECT (proxy), + callback, user_data, + g_proxy_base_connect_async); + + proxy_io_stream = g_proxy_connect (proxy, io_stream, proxy_address, + cancellable, &error); + if (proxy_io_stream) + { + g_simple_async_result_set_op_res_gpointer (simple, proxy_io_stream, + g_object_unref); + } + else + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +static GIOStream * +g_proxy_base_connect_finish (GProxy *proxy, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple)); +} + +static void +g_proxy_base_class_init (GProxyBaseClass *class) +{ +} + + +/********************************************/ +/* Test proxy implementation #1 ("Proxy A") */ +/********************************************/ + +typedef GProxyBase GProxyA; +typedef GProxyBaseClass GProxyAClass; + +static void g_proxy_a_iface_init (GProxyInterface *proxy_iface); + +#define g_proxy_a_get_type _g_proxy_a_get_type +G_DEFINE_TYPE_WITH_CODE (GProxyA, g_proxy_a, g_proxy_base_get_type (), + G_IMPLEMENT_INTERFACE (G_TYPE_PROXY, + g_proxy_a_iface_init) + g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME, + g_define_type_id, + "proxy-a", + 0)) + +static void +g_proxy_a_init (GProxyA *proxy) +{ + ((GProxyBase *) proxy)->proxy_data = &proxy_a; +} + +static gboolean +g_proxy_a_supports_hostname (GProxy *proxy) +{ + return FALSE; +} + +static void +g_proxy_a_class_init (GProxyAClass *class) +{ +} + +static void +g_proxy_a_iface_init (GProxyInterface *proxy_iface) +{ + proxy_iface->connect = g_proxy_base_connect; + proxy_iface->connect_async = g_proxy_base_connect_async; + proxy_iface->connect_finish = g_proxy_base_connect_finish; + proxy_iface->supports_hostname = g_proxy_a_supports_hostname; +} + +/********************************************/ +/* Test proxy implementation #2 ("Proxy B") */ +/********************************************/ + +typedef GProxyBase GProxyB; +typedef GProxyBaseClass GProxyBClass; + +static void g_proxy_b_iface_init (GProxyInterface *proxy_iface); + +#define g_proxy_b_get_type _g_proxy_b_get_type +G_DEFINE_TYPE_WITH_CODE (GProxyB, g_proxy_b, g_proxy_base_get_type (), + G_IMPLEMENT_INTERFACE (G_TYPE_PROXY, + g_proxy_b_iface_init) + g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME, + g_define_type_id, + "proxy-b", + 0)) + +static void +g_proxy_b_init (GProxyB *proxy) +{ + ((GProxyBase *) proxy)->proxy_data = &proxy_b; +} + +static gboolean +g_proxy_b_supports_hostname (GProxy *proxy) +{ + return TRUE; +} + +static void +g_proxy_b_class_init (GProxyBClass *class) +{ +} + +static void +g_proxy_b_iface_init (GProxyInterface *proxy_iface) +{ + proxy_iface->connect = g_proxy_base_connect; + proxy_iface->connect_async = g_proxy_base_connect_async; + proxy_iface->connect_finish = g_proxy_base_connect_finish; + proxy_iface->supports_hostname = g_proxy_b_supports_hostname; +} + + +/***********************************/ +/* The proxy server implementation */ +/***********************************/ + +static gboolean +proxy_bytes (GSocket *socket, + GIOCondition condition, + gpointer user_data) +{ + ProxyData *proxy = user_data; + gssize nread, nwrote, total; + gchar buffer[8]; + GSocket *out_socket; + GError *error = NULL; + + nread = g_socket_receive_with_blocking (socket, buffer, sizeof (buffer), + TRUE, NULL, &error); + if (nread == -1) + { + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED); + return FALSE; + } + else + g_assert_no_error (error); + + if (nread == 0) + { + g_main_loop_quit (proxy->loop); + return FALSE; + } + + if (socket == proxy->client_sock) + out_socket = proxy->server_sock; + else + out_socket = proxy->client_sock; + + for (total = 0; total < nread; total += nwrote) + { + nwrote = g_socket_send_with_blocking (out_socket, + buffer + total, nread - total, + TRUE, NULL, &error); + g_assert_no_error (error); + } + + return TRUE; +} + +static gpointer +proxy_thread (gpointer user_data) +{ + ProxyData *proxy = user_data; + GError *error = NULL; + gssize nread, nwrote; + gchar command[2] = { 0, 0 }; + GMainContext *context; + GSource *source; + + context = g_main_context_new (); + proxy->loop = g_main_loop_new (context, FALSE); + + while (TRUE) + { + proxy->client_sock = g_socket_accept (proxy->server, proxy->cancellable, &error); + if (!proxy->client_sock) + { + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + break; + } + else + g_assert_no_error (error); + + nread = g_socket_receive (proxy->client_sock, command, 1, NULL, &error); + g_assert_no_error (error); + + if (nread == 0) + { + g_clear_object (&proxy->client_sock); + continue; + } + + g_assert_cmpint (nread, ==, 1); + g_assert_cmpstr (command, ==, proxy->proxy_command); + + *command = g_ascii_toupper (*command); + nwrote = g_socket_send (proxy->client_sock, command, 1, NULL, &error); + g_assert_no_error (error); + g_assert_cmpint (nwrote, ==, 1); + + proxy->server_sock = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + g_socket_connect (proxy->server_sock, server.server_addr, NULL, &error); + g_assert_no_error (error); + + source = g_socket_create_source (proxy->client_sock, G_IO_IN, NULL); + g_source_set_callback (source, (GSourceFunc)proxy_bytes, proxy, NULL); + g_source_attach (source, context); + g_source_unref (source); + + source = g_socket_create_source (proxy->server_sock, G_IO_IN, NULL); + g_source_set_callback (source, (GSourceFunc)proxy_bytes, proxy, NULL); + g_source_attach (source, context); + g_source_unref (source); + + g_main_loop_run (proxy->loop); + + g_socket_close (proxy->client_sock, &error); + g_assert_no_error (error); + g_clear_object (&proxy->client_sock); + + g_socket_close (proxy->server_sock, &error); + g_assert_no_error (error); + g_clear_object (&proxy->server_sock); + } + + g_main_loop_unref (proxy->loop); + g_main_context_unref (context); + + g_object_unref (proxy->server); + g_object_unref (proxy->cancellable); + + return NULL; +} + +static void +create_proxy (ProxyData *proxy, + gchar proxy_protocol, + const gchar *destination_protocol, + GCancellable *cancellable) +{ + GError *error = NULL; + GSocketAddress *addr; + GInetAddress *iaddr; + + proxy->proxy_command = g_strdup_printf ("%c", proxy_protocol); + proxy->supported_protocol = g_strdup (destination_protocol); + proxy->cancellable = g_object_ref (cancellable); + + proxy->server = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + iaddr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4); + addr = g_inet_socket_address_new (iaddr, 0); + g_object_unref (iaddr); + + g_socket_bind (proxy->server, addr, TRUE, &error); + g_assert_no_error (error); + g_object_unref (addr); + + addr = g_socket_get_local_address (proxy->server, &error); + proxy->port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr)); + proxy->uri = g_strdup_printf ("proxy-%c://127.0.0.1:%u", + g_ascii_tolower (proxy_protocol), + proxy->port); + g_object_unref (addr); + + g_socket_listen (proxy->server, &error); + g_assert_no_error (error); + + proxy->thread = g_thread_new ("proxy", proxy_thread, proxy); +} + + + +/**************************/ +/* The actual echo server */ +/**************************/ + +static gpointer +echo_server_thread (gpointer user_data) +{ + ServerData *data = user_data; + GSocket *sock; + GError *error = NULL; + gssize nread, nwrote; + gchar buf[128]; + + while (TRUE) + { + sock = g_socket_accept (data->server, data->cancellable, &error); + if (!sock) + { + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + break; + } + else + g_assert_no_error (error); + + while (TRUE) + { + nread = g_socket_receive (sock, buf, sizeof (buf), NULL, &error); + g_assert_no_error (error); + g_assert_cmpint (nread, >=, 0); + + if (nread == 0) + break; + + nwrote = g_socket_send (sock, buf, nread, NULL, &error); + g_assert_no_error (error); + g_assert_cmpint (nwrote, ==, nread); + } + + g_socket_close (sock, &error); + g_assert_no_error (error); + g_object_unref (sock); + } + + g_object_unref (data->server); + g_object_unref (data->cancellable); + + return NULL; +} + +static void +create_server (ServerData *data, GCancellable *cancellable) +{ + GError *error = NULL; + GSocketAddress *addr; + GInetAddress *iaddr; + + data->cancellable = g_object_ref (cancellable); + + data->server = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + g_socket_set_blocking (data->server, TRUE); + iaddr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4); + addr = g_inet_socket_address_new (iaddr, 0); + g_object_unref (iaddr); + + g_socket_bind (data->server, addr, TRUE, &error); + g_assert_no_error (error); + g_object_unref (addr); + + data->server_addr = g_socket_get_local_address (data->server, &error); + g_assert_no_error (error); + + data->server_port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (data->server_addr)); + + g_socket_listen (data->server, &error); + g_assert_no_error (error); + + data->server_thread = g_thread_new ("server", echo_server_thread, data); +} + + +/****************************************/ +/* We made it! Now for the actual test! */ +/****************************************/ + +static void +setup_test (gpointer fixture, + gconstpointer user_data) +{ +} + +static void +teardown_test (gpointer fixture, + gconstpointer user_data) +{ + if (last_proxies) + { + g_strfreev (last_proxies); + last_proxies = NULL; + } + g_clear_error (&proxy_a.last_error); + g_clear_error (&proxy_b.last_error); +} + + +static const gchar *testbuf = "0123456789abcdef"; + +static void +do_echo_test (GSocketConnection *conn) +{ + GIOStream *iostream = G_IO_STREAM (conn); + GInputStream *istream = g_io_stream_get_input_stream (iostream); + GOutputStream *ostream = g_io_stream_get_output_stream (iostream); + gssize nread, total; + gsize nwrote; + gchar buf[128]; + GError *error = NULL; + + g_output_stream_write_all (ostream, testbuf, strlen (testbuf), + &nwrote, NULL, &error); + g_assert_no_error (error); + g_assert_cmpint (nwrote, ==, strlen (testbuf)); + + for (total = 0; total < nwrote; total += nread) + { + nread = g_input_stream_read (istream, + buf + total, sizeof (buf) - total, + NULL, &error); + g_assert_no_error (error); + g_assert_cmpint (nread, >, 0); + } + + buf[total] = '\0'; + g_assert_cmpstr (buf, ==, testbuf); +} + +static void +async_got_conn (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSocketConnection **conn = user_data; + GError *error = NULL; + + *conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (source), + result, &error); + g_assert_no_error (error); +} + +static void +async_got_error (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GError **error = user_data; + + g_assert (error != NULL && *error == NULL); + g_socket_client_connect_finish (G_SOCKET_CLIENT (source), + result, error); + g_assert (*error != NULL); +} + + +static void +assert_direct (GSocketConnection *conn) +{ + GSocketAddress *addr; + GError *error = NULL; + + g_assert_cmpint (g_strv_length (last_proxies), ==, 1); + g_assert_cmpstr (last_proxies[0], ==, "direct://"); + g_assert_no_error (proxy_a.last_error); + g_assert_no_error (proxy_b.last_error); + + addr = g_socket_connection_get_remote_address (conn, &error); + g_assert_no_error (error); + g_assert (!G_IS_PROXY_ADDRESS (addr)); +} + +static void +test_direct_sync (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + gchar *uri; + GError *error = NULL; + + /* The simple:// URI should not require any proxy. */ + + uri = g_strdup_printf ("simple://127.0.0.1:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_free (uri); + g_assert_no_error (error); + + assert_direct (conn); + do_echo_test (conn); + g_object_unref (conn); +} + +static void +test_direct_async (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + gchar *uri; + + /* The simple:// URI should not require any proxy. */ + uri = g_strdup_printf ("simple://127.0.0.1:%u", server.server_port); + conn = NULL; + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_conn, &conn); + g_free (uri); + while (conn == NULL) + g_main_context_iteration (NULL, TRUE); + + assert_direct (conn); + do_echo_test (conn); + g_object_unref (conn); +} + +static void +assert_single (GSocketConnection *conn) +{ + GSocketAddress *addr; + const gchar *proxy_uri; + gushort proxy_port; + GError *error = NULL; + + g_assert_cmpint (g_strv_length (last_proxies), ==, 2); + g_assert_cmpstr (last_proxies[0], ==, proxy_a.uri); + g_assert_cmpstr (last_proxies[1], ==, proxy_b.uri); + g_assert_no_error (proxy_a.last_error); + g_assert_no_error (proxy_b.last_error); + + addr = g_socket_connection_get_remote_address (conn, &error); + g_assert_no_error (error); + g_assert (G_IS_PROXY_ADDRESS (addr)); + proxy_uri = g_proxy_address_get_uri (G_PROXY_ADDRESS (addr)); + g_assert_cmpstr (proxy_uri, ==, proxy_a.uri); + proxy_port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr)); + g_assert_cmpint (proxy_port, ==, proxy_a.port); +} + +static void +test_single_sync (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + GError *error = NULL; + gchar *uri; + + /* The alpha:// URI should be proxied via Proxy A */ + uri = g_strdup_printf ("alpha://127.0.0.1:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_free (uri); + g_assert_no_error (error); + + assert_single (conn); + + do_echo_test (conn); + g_object_unref (conn); +} + +static void +test_single_async (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + gchar *uri; + + /* The alpha:// URI should be proxied via Proxy A */ + uri = g_strdup_printf ("alpha://127.0.0.1:%u", server.server_port); + conn = NULL; + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_conn, &conn); + g_free (uri); + while (conn == NULL) + g_main_context_iteration (NULL, TRUE); + + assert_single (conn); + do_echo_test (conn); + g_object_unref (conn); +} + +static void +assert_multiple (GSocketConnection *conn) +{ + GSocketAddress *addr; + const gchar *proxy_uri; + gushort proxy_port; + GError *error = NULL; + + g_assert_cmpint (g_strv_length (last_proxies), ==, 2); + g_assert_cmpstr (last_proxies[0], ==, proxy_a.uri); + g_assert_cmpstr (last_proxies[1], ==, proxy_b.uri); + g_assert_error (proxy_a.last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_assert_no_error (proxy_b.last_error); + + addr = g_socket_connection_get_remote_address (conn, &error); + g_assert_no_error (error); + g_assert (G_IS_PROXY_ADDRESS (addr)); + proxy_uri = g_proxy_address_get_uri (G_PROXY_ADDRESS (addr)); + g_assert_cmpstr (proxy_uri, ==, proxy_b.uri); + proxy_port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr)); + g_assert_cmpint (proxy_port, ==, proxy_b.port); +} + +static void +test_multiple_sync (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + GError *error = NULL; + gchar *uri; + + /* The beta:// URI should be proxied via Proxy B, after failing + * via Proxy A. + */ + uri = g_strdup_printf ("beta://127.0.0.1:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_free (uri); + g_assert_no_error (error); + + assert_multiple (conn); + do_echo_test (conn); + g_object_unref (conn); +} + +static void +test_multiple_async (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + gchar *uri; + + /* The beta:// URI should be proxied via Proxy B, after failing + * via Proxy A. + */ + uri = g_strdup_printf ("beta://127.0.0.1:%u", server.server_port); + conn = NULL; + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_conn, &conn); + g_free (uri); + while (conn == NULL) + g_main_context_iteration (NULL, TRUE); + + assert_multiple (conn); + do_echo_test (conn); + g_object_unref (conn); +} + +static void +test_dns (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + GError *error = NULL; + gchar *uri; + + /* The simple:// and alpha:// URIs should fail with a DNS error, + * but the beta:// URI should succeed, because we pass it to + * Proxy B without trying to resolve it first + */ + + /* simple */ + uri = g_strdup_printf ("simple://no-such-host.xx:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_assert_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND); + g_clear_error (&error); + + g_assert_no_error (proxy_a.last_error); + g_assert_no_error (proxy_b.last_error); + teardown_test (NULL, NULL); + + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_error, &error); + while (error == NULL) + g_main_context_iteration (NULL, TRUE); + g_assert_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND); + g_clear_error (&error); + g_free (uri); + + g_assert_no_error (proxy_a.last_error); + g_assert_no_error (proxy_b.last_error); + teardown_test (NULL, NULL); + + /* alpha */ + uri = g_strdup_printf ("alpha://no-such-host.xx:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + /* Since Proxy A fails, @client will try Proxy B too, which won't + * load an alpha:// URI. + */ + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + + g_assert_no_error (proxy_a.last_error); + g_assert_error (proxy_b.last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + teardown_test (NULL, NULL); + + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_error, &error); + while (error == NULL) + g_main_context_iteration (NULL, TRUE); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + g_free (uri); + + g_assert_no_error (proxy_a.last_error); + g_assert_error (proxy_b.last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + teardown_test (NULL, NULL); + + /* beta */ + uri = g_strdup_printf ("beta://no-such-host.xx:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_assert_no_error (error); + g_clear_object (&conn); + + g_assert_no_error (proxy_a.last_error); + g_assert_no_error (proxy_b.last_error); + teardown_test (NULL, NULL); + + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_conn, &conn); + while (conn == NULL) + g_main_context_iteration (NULL, TRUE); + g_clear_object (&conn); + g_free (uri); + + g_assert_no_error (proxy_a.last_error); + g_assert_no_error (proxy_b.last_error); + teardown_test (NULL, NULL); +} + +int +main (int argc, + char *argv[]) +{ + GCancellable *cancellable; + gint result; + + g_type_init (); + g_test_init (&argc, &argv, NULL); + + /* Register stuff. The dummy g_proxy_get_default_for_protocol() call + * is to force _g_io_modules_ensure_extension_points_registered() to + * get called, so we can then register a proxy resolver extension + * point. + */ + g_proxy_get_default_for_protocol ("foo"); + g_test_proxy_resolver_get_type (); + g_proxy_a_get_type (); + g_proxy_b_get_type (); + g_setenv ("GIO_USE_PROXY_RESOLVER", "test", TRUE); + + cancellable = g_cancellable_new (); + create_server (&server, cancellable); + create_proxy (&proxy_a, 'a', "alpha", cancellable); + create_proxy (&proxy_b, 'b', "beta", cancellable); + + client = g_socket_client_new (); + g_assert_cmpint (g_socket_client_get_enable_proxy (client), ==, TRUE); + + g_test_add_vtable ("/proxy/direct_sync", 0, NULL, setup_test, test_direct_sync, teardown_test); + g_test_add_vtable ("/proxy/direct_async", 0, NULL, setup_test, test_direct_async, teardown_test); + g_test_add_vtable ("/proxy/single_sync", 0, NULL, setup_test, test_single_sync, teardown_test); + g_test_add_vtable ("/proxy/single_async", 0, NULL, setup_test, test_single_async, teardown_test); + g_test_add_vtable ("/proxy/multiple_sync", 0, NULL, setup_test, test_multiple_sync, teardown_test); + g_test_add_vtable ("/proxy/multiple_async", 0, NULL, setup_test, test_multiple_async, teardown_test); + g_test_add_vtable ("/proxy/dns", 0, NULL, setup_test, test_dns, teardown_test); + + result = g_test_run(); + + g_object_unref (client); + + g_cancellable_cancel (cancellable); + g_thread_join (proxy_a.thread); + g_thread_join (proxy_b.thread); + g_thread_join (server.server_thread); + + return result; +} + -- 2.7.4