1 | /* GLib testing framework examples and tests |
2 | * |
3 | * Copyright 2014 Red Hat, Inc. |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2.1 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library; if not, see |
17 | * <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include <gio/gio.h> |
21 | |
22 | static void |
23 | active_notify_cb (GSocketService *service, |
24 | GParamSpec *pspec, |
25 | gpointer data) |
26 | { |
27 | gboolean *success = (gboolean *)data; |
28 | |
29 | if (g_socket_service_is_active (service)) |
30 | *success = TRUE; |
31 | } |
32 | |
33 | static void |
34 | connected_cb (GObject *client, |
35 | GAsyncResult *result, |
36 | gpointer user_data) |
37 | { |
38 | GSocketService *service = G_SOCKET_SERVICE (user_data); |
39 | GSocketConnection *conn; |
40 | GError *error = NULL; |
41 | |
42 | g_assert_true (g_socket_service_is_active (service)); |
43 | |
44 | conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client), result, error: &error); |
45 | g_assert_no_error (error); |
46 | g_object_unref (object: conn); |
47 | |
48 | g_socket_service_stop (service); |
49 | g_assert_false (g_socket_service_is_active (service)); |
50 | } |
51 | |
52 | static void |
53 | test_start_stop (void) |
54 | { |
55 | gboolean success = FALSE; |
56 | GInetAddress *iaddr; |
57 | GSocketAddress *saddr, *listening_addr; |
58 | GSocketService *service; |
59 | GError *error = NULL; |
60 | GSocketClient *client; |
61 | |
62 | iaddr = g_inet_address_new_loopback (family: G_SOCKET_FAMILY_IPV4); |
63 | saddr = g_inet_socket_address_new (address: iaddr, port: 0); |
64 | g_object_unref (object: iaddr); |
65 | |
66 | /* instantiate with g_object_new so we can pass active = false */ |
67 | service = g_object_new (G_TYPE_SOCKET_SERVICE, first_property_name: "active" , FALSE, NULL); |
68 | g_assert_false (g_socket_service_is_active (service)); |
69 | |
70 | g_signal_connect (service, "notify::active" , G_CALLBACK (active_notify_cb), &success); |
71 | |
72 | g_socket_listener_add_address (G_SOCKET_LISTENER (service), |
73 | address: saddr, |
74 | type: G_SOCKET_TYPE_STREAM, |
75 | protocol: G_SOCKET_PROTOCOL_TCP, |
76 | NULL, |
77 | effective_address: &listening_addr, |
78 | error: &error); |
79 | g_assert_no_error (error); |
80 | g_object_unref (object: saddr); |
81 | |
82 | client = g_socket_client_new (); |
83 | g_socket_client_connect_async (client, |
84 | G_SOCKET_CONNECTABLE (listening_addr), |
85 | NULL, |
86 | callback: connected_cb, user_data: service); |
87 | g_object_unref (object: client); |
88 | g_object_unref (object: listening_addr); |
89 | |
90 | g_socket_service_start (service); |
91 | g_assert_true (g_socket_service_is_active (service)); |
92 | |
93 | do |
94 | g_main_context_iteration (NULL, TRUE); |
95 | while (!success); |
96 | |
97 | g_object_unref (object: service); |
98 | } |
99 | |
100 | GMutex mutex_712570; |
101 | GCond cond_712570; |
102 | gboolean finalized; /* (atomic) */ |
103 | |
104 | GType test_threaded_socket_service_get_type (void); |
105 | typedef GThreadedSocketService TestThreadedSocketService; |
106 | typedef GThreadedSocketServiceClass TestThreadedSocketServiceClass; |
107 | |
108 | G_DEFINE_TYPE (TestThreadedSocketService, test_threaded_socket_service, G_TYPE_THREADED_SOCKET_SERVICE) |
109 | |
110 | static void |
111 | test_threaded_socket_service_init (TestThreadedSocketService *service) |
112 | { |
113 | } |
114 | |
115 | static void |
116 | test_threaded_socket_service_finalize (GObject *object) |
117 | { |
118 | G_OBJECT_CLASS (test_threaded_socket_service_parent_class)->finalize (object); |
119 | |
120 | /* Signal the main thread that finalization completed successfully |
121 | * rather than hanging. |
122 | */ |
123 | g_atomic_int_set (&finalized, TRUE); |
124 | g_cond_signal (cond: &cond_712570); |
125 | g_mutex_unlock (mutex: &mutex_712570); |
126 | } |
127 | |
128 | static void |
129 | test_threaded_socket_service_class_init (TestThreadedSocketServiceClass *klass) |
130 | { |
131 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
132 | |
133 | object_class->finalize = test_threaded_socket_service_finalize; |
134 | } |
135 | |
136 | static gboolean |
137 | connection_cb (GThreadedSocketService *service, |
138 | GSocketConnection *connection, |
139 | GObject *source_object, |
140 | gpointer user_data) |
141 | { |
142 | GMainLoop *loop = user_data; |
143 | |
144 | /* Since the connection attempt has come through to be handled, stop the main |
145 | * thread waiting for it; this causes the #GSocketService to be stopped. */ |
146 | g_main_loop_quit (loop); |
147 | |
148 | /* Block until the main thread has dropped its ref to @service, so that we |
149 | * will drop the final ref from this thread. |
150 | */ |
151 | g_mutex_lock (mutex: &mutex_712570); |
152 | |
153 | /* The service should now have 1 ref owned by the current "run" |
154 | * signal emission, and another added by GThreadedSocketService for |
155 | * this thread. Both will be dropped after we return. |
156 | */ |
157 | g_assert_cmpint (G_OBJECT (service)->ref_count, ==, 2); |
158 | |
159 | return FALSE; |
160 | } |
161 | |
162 | static void |
163 | client_connected_cb (GObject *client, |
164 | GAsyncResult *result, |
165 | gpointer user_data) |
166 | { |
167 | GSocketConnection *conn; |
168 | GError *error = NULL; |
169 | |
170 | conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client), result, error: &error); |
171 | g_assert_no_error (error); |
172 | |
173 | g_object_unref (object: conn); |
174 | } |
175 | |
176 | static void |
177 | test_threaded_712570 (void) |
178 | { |
179 | GSocketService *service; |
180 | GSocketAddress *addr, *listening_addr; |
181 | GMainLoop *loop; |
182 | GSocketClient *client; |
183 | GError *error = NULL; |
184 | |
185 | g_test_bug (bug_uri_snippet: "712570" ); |
186 | |
187 | g_mutex_lock (mutex: &mutex_712570); |
188 | |
189 | service = g_object_new (object_type: test_threaded_socket_service_get_type (), NULL); |
190 | |
191 | addr = g_inet_socket_address_new_from_string (address: "127.0.0.1" , port: 0); |
192 | g_socket_listener_add_address (G_SOCKET_LISTENER (service), |
193 | address: addr, |
194 | type: G_SOCKET_TYPE_STREAM, |
195 | protocol: G_SOCKET_PROTOCOL_TCP, |
196 | NULL, |
197 | effective_address: &listening_addr, |
198 | error: &error); |
199 | g_assert_no_error (error); |
200 | g_object_unref (object: addr); |
201 | |
202 | loop = g_main_loop_new (NULL, FALSE); |
203 | g_signal_connect (service, "run" , G_CALLBACK (connection_cb), loop); |
204 | |
205 | client = g_socket_client_new (); |
206 | g_socket_client_connect_async (client, |
207 | G_SOCKET_CONNECTABLE (listening_addr), |
208 | NULL, |
209 | callback: client_connected_cb, user_data: loop); |
210 | g_object_unref (object: client); |
211 | g_object_unref (object: listening_addr); |
212 | |
213 | g_main_loop_run (loop); |
214 | g_main_loop_unref (loop); |
215 | |
216 | /* Stop the service and then wait for it to asynchronously cancel |
217 | * its outstanding accept() call (and drop the associated ref). |
218 | * At least one main context iteration is required in some circumstances |
219 | * to ensure that the cancellation actually happens. |
220 | */ |
221 | g_socket_service_stop (G_SOCKET_SERVICE (service)); |
222 | g_assert_false (g_socket_service_is_active (G_SOCKET_SERVICE (service))); |
223 | |
224 | do |
225 | g_main_context_iteration (NULL, TRUE); |
226 | while (G_OBJECT (service)->ref_count > 3); |
227 | |
228 | /* Wait some more iterations, as #GTask results are deferred to the next |
229 | * #GMainContext iteration, and propagation of a #GTask result takes an |
230 | * additional ref on the source object. */ |
231 | g_main_context_iteration (NULL, FALSE); |
232 | |
233 | /* Drop our ref, then unlock the mutex and wait for the service to be |
234 | * finalized. (Without the fix for 712570 it would hang forever here.) |
235 | */ |
236 | g_object_unref (object: service); |
237 | |
238 | while (!g_atomic_int_get (&finalized)) |
239 | g_cond_wait (cond: &cond_712570, mutex: &mutex_712570); |
240 | g_mutex_unlock (mutex: &mutex_712570); |
241 | } |
242 | |
243 | static void |
244 | closed_read_write_async_cb (GSocketConnection *conn, |
245 | GAsyncResult *result, |
246 | gpointer user_data) |
247 | { |
248 | GError *error = NULL; |
249 | gboolean res; |
250 | |
251 | res = g_io_stream_close_finish (G_IO_STREAM (conn), result, error: &error); |
252 | g_assert_no_error (error); |
253 | g_assert_true (res); |
254 | } |
255 | |
256 | typedef struct { |
257 | GSocketConnection *conn; |
258 | guint8 *data; |
259 | } WriteAsyncData; |
260 | |
261 | static void |
262 | written_read_write_async_cb (GOutputStream *ostream, |
263 | GAsyncResult *result, |
264 | gpointer user_data) |
265 | { |
266 | WriteAsyncData *data = user_data; |
267 | GError *error = NULL; |
268 | gboolean res; |
269 | gsize bytes_written; |
270 | GSocketConnection *conn; |
271 | |
272 | conn = data->conn; |
273 | |
274 | g_free (mem: data->data); |
275 | g_free (mem: data); |
276 | |
277 | res = g_output_stream_write_all_finish (stream: ostream, result, bytes_written: &bytes_written, error: &error); |
278 | g_assert_no_error (error); |
279 | g_assert_true (res); |
280 | g_assert_cmpuint (bytes_written, ==, 20); |
281 | |
282 | g_io_stream_close_async (G_IO_STREAM (conn), |
283 | G_PRIORITY_DEFAULT, |
284 | NULL, |
285 | callback: (GAsyncReadyCallback) closed_read_write_async_cb, |
286 | NULL); |
287 | g_object_unref (object: conn); |
288 | } |
289 | |
290 | static void |
291 | connected_read_write_async_cb (GObject *client, |
292 | GAsyncResult *result, |
293 | gpointer user_data) |
294 | { |
295 | GSocketConnection *conn; |
296 | GOutputStream *ostream; |
297 | GError *error = NULL; |
298 | WriteAsyncData *data; |
299 | gsize i; |
300 | GSocketConnection **sconn = user_data; |
301 | |
302 | conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client), result, error: &error); |
303 | g_assert_no_error (error); |
304 | g_assert_nonnull (conn); |
305 | |
306 | ostream = g_io_stream_get_output_stream (G_IO_STREAM (conn)); |
307 | |
308 | data = g_new0 (WriteAsyncData, 1); |
309 | data->conn = conn; |
310 | data->data = g_new0 (guint8, 20); |
311 | for (i = 0; i < 20; i++) |
312 | data->data[i] = i; |
313 | |
314 | g_output_stream_write_all_async (stream: ostream, |
315 | buffer: data->data, |
316 | count: 20, |
317 | G_PRIORITY_DEFAULT, |
318 | NULL, |
319 | callback: (GAsyncReadyCallback) written_read_write_async_cb, |
320 | user_data: data /* stolen */); |
321 | |
322 | *sconn = g_object_ref (conn); |
323 | } |
324 | |
325 | typedef struct { |
326 | GSocketConnection *conn; |
327 | GOutputVector *vectors; |
328 | guint n_vectors; |
329 | guint8 *data; |
330 | } WritevAsyncData; |
331 | |
332 | static void |
333 | writtenv_read_write_async_cb (GOutputStream *ostream, |
334 | GAsyncResult *result, |
335 | gpointer user_data) |
336 | { |
337 | WritevAsyncData *data = user_data; |
338 | GError *error = NULL; |
339 | gboolean res; |
340 | gsize bytes_written; |
341 | GSocketConnection *conn; |
342 | |
343 | conn = data->conn; |
344 | g_free (mem: data->data); |
345 | g_free (mem: data->vectors); |
346 | g_free (mem: data); |
347 | |
348 | res = g_output_stream_writev_all_finish (stream: ostream, result, bytes_written: &bytes_written, error: &error); |
349 | g_assert_no_error (error); |
350 | g_assert_true (res); |
351 | g_assert_cmpuint (bytes_written, ==, 20); |
352 | |
353 | g_io_stream_close_async (G_IO_STREAM (conn), |
354 | G_PRIORITY_DEFAULT, |
355 | NULL, |
356 | callback: (GAsyncReadyCallback) closed_read_write_async_cb, |
357 | NULL); |
358 | g_object_unref (object: conn); |
359 | } |
360 | |
361 | static void |
362 | connected_read_writev_async_cb (GObject *client, |
363 | GAsyncResult *result, |
364 | gpointer user_data) |
365 | { |
366 | GSocketConnection *conn; |
367 | GOutputStream *ostream; |
368 | GError *error = NULL; |
369 | WritevAsyncData *data; |
370 | gsize i; |
371 | GSocketConnection **sconn = user_data; |
372 | |
373 | conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client), result, error: &error); |
374 | g_assert_no_error (error); |
375 | g_assert_nonnull (conn); |
376 | |
377 | ostream = g_io_stream_get_output_stream (G_IO_STREAM (conn)); |
378 | |
379 | data = g_new0 (WritevAsyncData, 1); |
380 | data->conn = conn; |
381 | data->vectors = g_new0 (GOutputVector, 3); |
382 | data->n_vectors = 3; |
383 | data->data = g_new0 (guint8, 20); |
384 | for (i = 0; i < 20; i++) |
385 | data->data[i] = i; |
386 | |
387 | data->vectors[0].buffer = data->data; |
388 | data->vectors[0].size = 5; |
389 | data->vectors[1].buffer = data->data + 5; |
390 | data->vectors[1].size = 10; |
391 | data->vectors[2].buffer = data->data + 15; |
392 | data->vectors[2].size = 5; |
393 | |
394 | g_output_stream_writev_all_async (stream: ostream, |
395 | vectors: data->vectors, |
396 | n_vectors: data->n_vectors, |
397 | G_PRIORITY_DEFAULT, |
398 | NULL, |
399 | callback: (GAsyncReadyCallback) writtenv_read_write_async_cb, |
400 | user_data: data /* stolen */); |
401 | |
402 | *sconn = g_object_ref (conn); |
403 | } |
404 | |
405 | typedef struct { |
406 | GSocketConnection *conn; |
407 | guint8 *data; |
408 | } ReadAsyncData; |
409 | |
410 | static void |
411 | read_read_write_async_cb (GInputStream *istream, |
412 | GAsyncResult *result, |
413 | gpointer user_data) |
414 | { |
415 | ReadAsyncData *data = user_data; |
416 | GError *error = NULL; |
417 | gboolean res; |
418 | gsize bytes_read; |
419 | GSocketConnection *conn; |
420 | const guint8 expected_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; |
421 | |
422 | res = g_input_stream_read_all_finish (stream: istream, result, bytes_read: &bytes_read, error: &error); |
423 | g_assert_no_error (error); |
424 | g_assert_true (res); |
425 | |
426 | g_assert_cmpmem (expected_data, sizeof expected_data, data->data, bytes_read); |
427 | |
428 | conn = data->conn; |
429 | g_object_set_data (G_OBJECT (conn), key: "test-data-read" , GINT_TO_POINTER (TRUE)); |
430 | |
431 | g_free (mem: data->data); |
432 | g_free (mem: data); |
433 | |
434 | g_io_stream_close_async (G_IO_STREAM (conn), |
435 | G_PRIORITY_DEFAULT, |
436 | NULL, |
437 | callback: (GAsyncReadyCallback) closed_read_write_async_cb, |
438 | NULL); |
439 | g_object_unref (object: conn); |
440 | } |
441 | |
442 | static void |
443 | incoming_read_write_async_cb (GSocketService *service, |
444 | GSocketConnection *conn, |
445 | GObject *source_object, |
446 | gpointer user_data) |
447 | { |
448 | ReadAsyncData *data; |
449 | GSocketConnection **cconn = user_data; |
450 | GInputStream *istream; |
451 | |
452 | istream = g_io_stream_get_input_stream (G_IO_STREAM (conn)); |
453 | |
454 | data = g_new0 (ReadAsyncData, 1); |
455 | data->conn = g_object_ref (conn); |
456 | data->data = g_new0 (guint8, 20); |
457 | |
458 | g_input_stream_read_all_async (stream: istream, |
459 | buffer: data->data, |
460 | count: 20, |
461 | G_PRIORITY_DEFAULT, |
462 | NULL, |
463 | callback: (GAsyncReadyCallback) read_read_write_async_cb, |
464 | user_data: data /* stolen */); |
465 | |
466 | *cconn = g_object_ref (conn); |
467 | } |
468 | |
469 | static void |
470 | test_read_write_async_internal (gboolean writev) |
471 | { |
472 | GInetAddress *iaddr; |
473 | GSocketAddress *saddr, *listening_addr; |
474 | GSocketService *service; |
475 | GError *error = NULL; |
476 | GSocketClient *client; |
477 | GSocketConnection *sconn = NULL, *cconn = NULL; |
478 | |
479 | iaddr = g_inet_address_new_loopback (family: G_SOCKET_FAMILY_IPV4); |
480 | saddr = g_inet_socket_address_new (address: iaddr, port: 0); |
481 | g_object_unref (object: iaddr); |
482 | |
483 | service = g_socket_service_new (); |
484 | |
485 | g_socket_listener_add_address (G_SOCKET_LISTENER (service), |
486 | address: saddr, |
487 | type: G_SOCKET_TYPE_STREAM, |
488 | protocol: G_SOCKET_PROTOCOL_TCP, |
489 | NULL, |
490 | effective_address: &listening_addr, |
491 | error: &error); |
492 | g_assert_no_error (error); |
493 | g_object_unref (object: saddr); |
494 | |
495 | g_signal_connect (service, "incoming" , G_CALLBACK (incoming_read_write_async_cb), &sconn); |
496 | |
497 | client = g_socket_client_new (); |
498 | |
499 | if (writev) |
500 | g_socket_client_connect_async (client, |
501 | G_SOCKET_CONNECTABLE (listening_addr), |
502 | NULL, |
503 | callback: connected_read_writev_async_cb, |
504 | user_data: &cconn); |
505 | else |
506 | g_socket_client_connect_async (client, |
507 | G_SOCKET_CONNECTABLE (listening_addr), |
508 | NULL, |
509 | callback: connected_read_write_async_cb, |
510 | user_data: &cconn); |
511 | |
512 | g_object_unref (object: client); |
513 | g_object_unref (object: listening_addr); |
514 | |
515 | g_socket_service_start (service); |
516 | g_assert_true (g_socket_service_is_active (service)); |
517 | |
518 | do |
519 | { |
520 | g_main_context_iteration (NULL, TRUE); |
521 | } |
522 | while (!sconn || !cconn || |
523 | !g_io_stream_is_closed (G_IO_STREAM (sconn)) || |
524 | !g_io_stream_is_closed (G_IO_STREAM (cconn))); |
525 | |
526 | g_assert_true (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (sconn), "test-data-read" ))); |
527 | |
528 | g_object_unref (object: sconn); |
529 | g_object_unref (object: cconn); |
530 | g_object_unref (object: service); |
531 | } |
532 | |
533 | /* Test if connecting to a socket service and asynchronously writing data on |
534 | * one side followed by reading the same data on the other side of the |
535 | * connection works correctly |
536 | */ |
537 | static void |
538 | test_read_write_async (void) |
539 | { |
540 | test_read_write_async_internal (FALSE); |
541 | } |
542 | |
543 | /* Test if connecting to a socket service and asynchronously writing data on |
544 | * one side followed by reading the same data on the other side of the |
545 | * connection works correctly. This uses writev() instead of normal write(). |
546 | */ |
547 | static void |
548 | test_read_writev_async (void) |
549 | { |
550 | test_read_write_async_internal (TRUE); |
551 | } |
552 | |
553 | |
554 | int |
555 | main (int argc, |
556 | char *argv[]) |
557 | { |
558 | g_test_init (argc: &argc, argv: &argv, NULL); |
559 | |
560 | g_test_bug_base (uri_pattern: "http://bugzilla.gnome.org/" ); |
561 | |
562 | g_test_add_func (testpath: "/socket-service/start-stop" , test_func: test_start_stop); |
563 | g_test_add_func (testpath: "/socket-service/threaded/712570" , test_func: test_threaded_712570); |
564 | g_test_add_func (testpath: "/socket-service/read_write_async" , test_func: test_read_write_async); |
565 | g_test_add_func (testpath: "/socket-service/read_writev_async" , test_func: test_read_writev_async); |
566 | |
567 | return g_test_run(); |
568 | } |
569 | |