1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2018 Igalia S.L.
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General
18 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <gio/gio.h>
22
23 static void
24 on_connected (GObject *source_object,
25 GAsyncResult *result,
26 gpointer user_data)
27 {
28 GSocketConnection *conn;
29 GError *error = NULL;
30
31 conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);
32 g_assert_no_error (error);
33
34 g_object_unref (conn);
35 g_main_loop_quit (user_data);
36 }
37
38 static void
39 test_happy_eyeballs (void)
40 {
41 GSocketClient *client;
42 GSocketService *service;
43 GError *error = NULL;
44 guint16 port;
45 GMainLoop *loop;
46
47 loop = g_main_loop_new (NULL, FALSE);
48
49 service = g_socket_service_new ();
50 port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
51 g_assert_no_error (error);
52 g_socket_service_start (service);
53
54 /* All of the magic here actually happens in slow-connect-preload.c
55 * which as you would guess is preloaded. So this is just making a
56 * normal connection that happens to take 600ms each time. This will
57 * trigger the logic to make multiple parallel connections.
58 */
59 client = g_socket_client_new ();
60 g_socket_client_connect_to_host_async (client, "localhost", port, NULL, on_connected, loop);
61 g_main_loop_run (loop);
62
63 g_main_loop_unref (loop);
64 g_object_unref (service);
65 g_object_unref (client);
66 }
67
68 static void
69 on_connected_cancelled (GObject *source_object,
70 GAsyncResult *result,
71 gpointer user_data)
72 {
73 GSocketConnection *conn;
74 GError *error = NULL;
75
76 conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);
77 g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
78 g_assert_null (conn);
79
80 g_error_free (error);
81 g_main_loop_quit (user_data);
82 }
83
84 typedef struct
85 {
86 GCancellable *cancellable;
87 gboolean completed;
88 } EventCallbackData;
89
90 static void
91 on_event (GSocketClient *client,
92 GSocketClientEvent event,
93 GSocketConnectable *connectable,
94 GIOStream *connection,
95 EventCallbackData *data)
96 {
97 if (data->cancellable && event == G_SOCKET_CLIENT_CONNECTED)
98 {
99 g_cancellable_cancel (data->cancellable);
100 }
101 else if (event == G_SOCKET_CLIENT_COMPLETE)
102 {
103 data->completed = TRUE;
104 g_assert_null (connection);
105 }
106 }
107
108 static void
109 test_happy_eyeballs_cancel_delayed (void)
110 {
111 GSocketClient *client;
112 GSocketService *service;
113 GError *error = NULL;
114 guint16 port;
115 GMainLoop *loop;
116 EventCallbackData data = { NULL, FALSE };
117
118 /* This just tests that cancellation works as expected, still emits the completed signal,
119 * and never returns a connection */
120
121 loop = g_main_loop_new (NULL, FALSE);
122
123 service = g_socket_service_new ();
124 port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
125 g_assert_no_error (error);
126 g_socket_service_start (service);
127
128 client = g_socket_client_new ();
129 data.cancellable = g_cancellable_new ();
130 g_socket_client_connect_to_host_async (client, "localhost", port, data.cancellable, on_connected_cancelled, loop);
131 g_signal_connect (client, "event", G_CALLBACK (on_event), &data);
132 g_main_loop_run (loop);
133
134 g_assert_true (data.completed);
135 g_main_loop_unref (loop);
136 g_object_unref (service);
137 g_object_unref (client);
138 g_object_unref (data.cancellable);
139 }
140
141 static void
142 test_happy_eyeballs_cancel_instant (void)
143 {
144 GSocketClient *client;
145 GSocketService *service;
146 GError *error = NULL;
147 guint16 port;
148 GMainLoop *loop;
149 GCancellable *cancel;
150 EventCallbackData data = { NULL, FALSE };
151
152 /* This tests the same things as above, test_happy_eyeballs_cancel_delayed(), but
153 * with different timing since it sends an already cancelled cancellable */
154
155 loop = g_main_loop_new (NULL, FALSE);
156
157 service = g_socket_service_new ();
158 port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
159 g_assert_no_error (error);
160 g_socket_service_start (service);
161
162 client = g_socket_client_new ();
163 cancel = g_cancellable_new ();
164 g_cancellable_cancel (cancel);
165 g_socket_client_connect_to_host_async (client, "localhost", port, cancel, on_connected_cancelled, loop);
166 g_signal_connect (client, "event", G_CALLBACK (on_event), &data);
167 g_main_loop_run (loop);
168
169 g_assert_true (data.completed);
170 g_main_loop_unref (loop);
171 g_object_unref (service);
172 g_object_unref (client);
173 g_object_unref (cancel);
174 }
175
176 int
177 main (int argc, char *argv[])
178 {
179 g_test_init (&argc, &argv, NULL);
180
181 g_test_add_func ("/socket-client/happy-eyeballs/slow", test_happy_eyeballs);
182 g_test_add_func ("/socket-client/happy-eyeballs/cancellation/instant", test_happy_eyeballs_cancel_instant);
183 g_test_add_func ("/socket-client/happy-eyeballs/cancellation/delayed", test_happy_eyeballs_cancel_delayed);
184
185
186 return g_test_run ();
187 }