Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2011 Red Hat, Inc.
3 : : *
4 : : * SPDX-License-Identifier: LicenseRef-old-glib-tests
5 : : *
6 : : * This work is provided "as is"; redistribution and modification
7 : : * in whole or in part, in any medium, physical or electronic is
8 : : * permitted without restriction.
9 : : *
10 : : * This work 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.
13 : : *
14 : : * In no event shall the authors or contributors be liable for any
15 : : * direct, indirect, incidental, special, exemplary, or consequential
16 : : * damages (including, but not limited to, procurement of substitute
17 : : * goods or services; loss of use, data, or profits; or business
18 : : * interruption) however caused and on any theory of liability, whether
19 : : * in contract, strict liability, or tort (including negligence or
20 : : * otherwise) arising in any way out of the use of this software, even
21 : : * if advised of the possibility of such damage.
22 : : *
23 : : * Author: Colin Walters <walters@verbum.org>
24 : : */
25 : :
26 : : #include "config.h"
27 : :
28 : : #include <stdlib.h>
29 : :
30 : : #include <glib.h>
31 : : #include <string.h>
32 : :
33 : : #include <sys/types.h>
34 : :
35 : : static char *echo_prog_path;
36 : :
37 : : #ifdef G_OS_WIN32
38 : : static char *sleep_prog_path;
39 : : #endif
40 : :
41 : : #ifdef G_OS_UNIX
42 : : #include <unistd.h>
43 : : #endif
44 : :
45 : : #ifdef G_OS_WIN32
46 : : #include <windows.h>
47 : : #endif
48 : :
49 : : typedef struct
50 : : {
51 : : GMainLoop *main_loop;
52 : : gint *n_alive; /* (atomic) */
53 : : gint ttl; /* seconds */
54 : : GMainLoop *thread_main_loop; /* (nullable) */
55 : : } SpawnChildsData;
56 : :
57 : : static GPid
58 : 4 : get_a_child (gint ttl)
59 : : {
60 : : GPid pid;
61 : :
62 : : #ifdef G_OS_WIN32
63 : : STARTUPINFO si;
64 : : PROCESS_INFORMATION pi;
65 : : gchar *cmdline;
66 : : wchar_t *cmdline_utf16;
67 : :
68 : : memset (&si, 0, sizeof (si));
69 : : si.cb = sizeof (&si);
70 : : memset (&pi, 0, sizeof (pi));
71 : :
72 : : cmdline = g_strdup_printf ("%s %d", sleep_prog_path, ttl);
73 : :
74 : : cmdline_utf16 = g_utf8_to_utf16 (cmdline, -1, NULL, NULL, NULL);
75 : : g_assert_nonnull (cmdline_utf16);
76 : :
77 : : if (!CreateProcess (NULL, cmdline_utf16, NULL, NULL,
78 : : FALSE, 0, NULL, NULL, &si, &pi))
79 : : g_error ("CreateProcess failed: %s",
80 : : g_win32_error_message (GetLastError ()));
81 : :
82 : : g_free (cmdline_utf16);
83 : : g_free (cmdline);
84 : :
85 : : CloseHandle (pi.hThread);
86 : : pid = pi.hProcess;
87 : :
88 : : return pid;
89 : : #else
90 : 4 : pid = fork ();
91 [ - + ]: 4 : if (pid < 0)
92 : 0 : exit (1);
93 : :
94 [ + - ]: 4 : if (pid > 0)
95 : 4 : return pid;
96 : :
97 : 0 : sleep (ttl);
98 : 0 : _exit (0);
99 : : #endif /* G_OS_WIN32 */
100 : : }
101 : :
102 : : static void
103 : 4 : child_watch_callback (GPid pid, gint status, gpointer user_data)
104 : : {
105 : 4 : SpawnChildsData *data = user_data;
106 : :
107 : 4 : g_test_message ("Child %" G_PID_FORMAT " (ttl %d) exited, status %d",
108 : : pid, data->ttl, status);
109 : :
110 : 4 : g_spawn_close_pid (pid);
111 : :
112 [ + + ]: 4 : if (g_atomic_int_dec_and_test (data->n_alive))
113 : 2 : g_main_loop_quit (data->main_loop);
114 [ + + ]: 4 : if (data->thread_main_loop != NULL)
115 : 2 : g_main_loop_quit (data->thread_main_loop);
116 : 4 : }
117 : :
118 : : static gpointer
119 : 2 : start_thread (gpointer user_data)
120 : : {
121 : : GMainLoop *new_main_loop;
122 : : GSource *source;
123 : : GPid pid;
124 : 2 : SpawnChildsData *data = user_data;
125 : 2 : gint ttl = data->ttl;
126 : 2 : GMainContext *new_main_context = NULL;
127 : :
128 : 2 : new_main_context = g_main_context_new ();
129 : 2 : new_main_loop = g_main_loop_new (new_main_context, FALSE);
130 : 2 : data->thread_main_loop = new_main_loop;
131 : :
132 : 2 : pid = get_a_child (ttl);
133 : 2 : source = g_child_watch_source_new (pid);
134 : 2 : g_source_set_callback (source,
135 : : (GSourceFunc) child_watch_callback, data, NULL);
136 : 2 : g_source_attach (source, g_main_loop_get_context (new_main_loop));
137 : 2 : g_source_unref (source);
138 : :
139 : 2 : g_test_message ("Created pid: %" G_PID_FORMAT " (ttl %d)", pid, ttl);
140 : :
141 : 2 : g_main_loop_run (new_main_loop);
142 : 2 : g_main_loop_unref (new_main_loop);
143 : 2 : g_main_context_unref (new_main_context);
144 : :
145 : 2 : return NULL;
146 : : }
147 : :
148 : : static gboolean
149 : 0 : quit_loop (gpointer data)
150 : : {
151 : 0 : GMainLoop *main_loop = data;
152 : :
153 : 0 : g_main_loop_quit (main_loop);
154 : :
155 : 0 : return TRUE;
156 : : }
157 : :
158 : : static void
159 : 1 : test_spawn_childs (void)
160 : : {
161 : : GPid pid;
162 : 1 : GMainLoop *main_loop = NULL;
163 : 1 : SpawnChildsData child1_data = { 0, }, child2_data = { 0, };
164 : : gint n_alive;
165 : : guint timeout_id;
166 : :
167 : 1 : main_loop = g_main_loop_new (NULL, FALSE);
168 : :
169 : : #ifdef G_OS_WIN32
170 : : g_assert_no_errno (system ("cd ."));
171 : : #else
172 : 1 : g_assert_no_errno (system ("true"));
173 : : #endif
174 : :
175 : 1 : n_alive = 2;
176 : 1 : timeout_id = g_timeout_add_seconds (30, quit_loop, main_loop);
177 : :
178 : 1 : child1_data.main_loop = main_loop;
179 : 1 : child1_data.ttl = 1;
180 : 1 : child1_data.n_alive = &n_alive;
181 : 1 : pid = get_a_child (child1_data.ttl);
182 : 1 : g_child_watch_add (pid,
183 : : (GChildWatchFunc) child_watch_callback,
184 : : &child1_data);
185 : :
186 : 1 : child2_data.main_loop = main_loop;
187 : 1 : child2_data.ttl = 2;
188 : 1 : child2_data.n_alive = &n_alive;
189 : 1 : pid = get_a_child (child2_data.ttl);
190 : 1 : g_child_watch_add (pid,
191 : : (GChildWatchFunc) child_watch_callback,
192 : : &child2_data);
193 : :
194 : 1 : g_main_loop_run (main_loop);
195 : 1 : g_main_loop_unref (main_loop);
196 : 1 : g_source_remove (timeout_id);
197 : :
198 : 1 : g_assert_cmpint (g_atomic_int_get (&n_alive), ==, 0);
199 : 1 : }
200 : :
201 : : static void
202 : 1 : test_spawn_childs_threads (void)
203 : : {
204 : 1 : GMainLoop *main_loop = NULL;
205 : 1 : SpawnChildsData thread1_data = { 0, }, thread2_data = { 0, };
206 : : gint n_alive;
207 : : guint timeout_id;
208 : : GThread *thread1, *thread2;
209 : :
210 : 1 : main_loop = g_main_loop_new (NULL, FALSE);
211 : :
212 : : #ifdef G_OS_WIN32
213 : : g_assert_no_errno (system ("cd ."));
214 : : #else
215 : 1 : g_assert_no_errno (system ("true"));
216 : : #endif
217 : :
218 : 1 : n_alive = 2;
219 : 1 : timeout_id = g_timeout_add_seconds (30, quit_loop, main_loop);
220 : :
221 : 1 : thread1_data.main_loop = main_loop;
222 : 1 : thread1_data.n_alive = &n_alive;
223 : 1 : thread1_data.ttl = 1; /* seconds */
224 : 1 : thread1 = g_thread_new (NULL, start_thread, &thread1_data);
225 : :
226 : 1 : thread2_data.main_loop = main_loop;
227 : 1 : thread2_data.n_alive = &n_alive;
228 : 1 : thread2_data.ttl = 2; /* seconds */
229 : 1 : thread2 = g_thread_new (NULL, start_thread, &thread2_data);
230 : :
231 : 1 : g_main_loop_run (main_loop);
232 : 1 : g_main_loop_unref (main_loop);
233 : 1 : g_source_remove (timeout_id);
234 : :
235 : 1 : g_assert_cmpint (g_atomic_int_get (&n_alive), ==, 0);
236 : :
237 : 1 : g_thread_join (g_steal_pointer (&thread2));
238 : 1 : g_thread_join (g_steal_pointer (&thread1));
239 : 1 : }
240 : :
241 : : static void
242 : 2 : multithreaded_test_run (GThreadFunc function)
243 : : {
244 : : guint i;
245 : 2 : GPtrArray *threads = g_ptr_array_new ();
246 : : guint n_threads;
247 : :
248 : : /* Limit to 64, otherwise we may hit file descriptor limits and such */
249 [ + - ]: 2 : n_threads = MIN (g_get_num_processors () * 2, 64);
250 : :
251 [ + + ]: 82 : for (i = 0; i < n_threads; i++)
252 : : {
253 : : GThread *thread;
254 : :
255 : 80 : thread = g_thread_new ("test", function, GUINT_TO_POINTER (i));
256 : 80 : g_ptr_array_add (threads, thread);
257 : : }
258 : :
259 [ + + ]: 82 : for (i = 0; i < n_threads; i++)
260 : : {
261 : : gpointer ret;
262 : 80 : ret = g_thread_join (g_ptr_array_index (threads, i));
263 : 80 : g_assert_cmpint (GPOINTER_TO_UINT (ret), ==, i);
264 : : }
265 : 2 : g_ptr_array_free (threads, TRUE);
266 : 2 : }
267 : :
268 : : static gpointer
269 : 40 : test_spawn_sync_multithreaded_instance (gpointer data)
270 : : {
271 : 40 : guint tnum = GPOINTER_TO_UINT (data);
272 : 40 : GError *error = NULL;
273 : : GPtrArray *argv;
274 : : char *arg;
275 : : char *stdout_str;
276 : : int estatus;
277 : :
278 : 40 : arg = g_strdup_printf ("thread %u", tnum);
279 : :
280 : 40 : argv = g_ptr_array_new ();
281 : 40 : g_ptr_array_add (argv, echo_prog_path);
282 : 40 : g_ptr_array_add (argv, arg);
283 : 40 : g_ptr_array_add (argv, NULL);
284 : :
285 : 40 : g_spawn_sync (NULL, (char**)argv->pdata, NULL, G_SPAWN_DEFAULT, NULL, NULL, &stdout_str, NULL, &estatus, &error);
286 : 40 : g_assert_no_error (error);
287 : 40 : g_assert_cmpstr (arg, ==, stdout_str);
288 : 40 : g_free (arg);
289 : 40 : g_free (stdout_str);
290 : 40 : g_ptr_array_free (argv, TRUE);
291 : :
292 : 40 : return GUINT_TO_POINTER (tnum);
293 : : }
294 : :
295 : : static void
296 : 1 : test_spawn_sync_multithreaded (void)
297 : : {
298 : 1 : multithreaded_test_run (test_spawn_sync_multithreaded_instance);
299 : 1 : }
300 : :
301 : : typedef struct {
302 : : GMainLoop *loop;
303 : : gboolean child_exited;
304 : : gboolean stdout_done;
305 : : GString *stdout_buf;
306 : : } SpawnAsyncMultithreadedData;
307 : :
308 : : static gboolean
309 : 40 : on_child_exited (GPid pid,
310 : : gint status,
311 : : gpointer datap)
312 : : {
313 : 40 : SpawnAsyncMultithreadedData *data = datap;
314 : :
315 : 40 : data->child_exited = TRUE;
316 [ + - + + ]: 40 : if (data->child_exited && data->stdout_done)
317 : 11 : g_main_loop_quit (data->loop);
318 : :
319 : 40 : return G_SOURCE_REMOVE;
320 : : }
321 : :
322 : : static gboolean
323 : 80 : on_child_stdout (GIOChannel *channel,
324 : : GIOCondition condition,
325 : : gpointer datap)
326 : : {
327 : : char buf[1024];
328 : 80 : GError *error = NULL;
329 : : gsize bytes_read;
330 : : GIOStatus status;
331 : 80 : SpawnAsyncMultithreadedData *data = datap;
332 : :
333 : 80 : read:
334 : 80 : status = g_io_channel_read_chars (channel, buf, sizeof (buf), &bytes_read, &error);
335 [ + + ]: 80 : if (status == G_IO_STATUS_NORMAL)
336 : : {
337 [ - + ]: 40 : g_string_append_len (data->stdout_buf, buf, (gssize) bytes_read);
338 [ - + ]: 40 : if (bytes_read == sizeof (buf))
339 : 0 : goto read;
340 : : }
341 [ + - ]: 40 : else if (status == G_IO_STATUS_EOF)
342 : : {
343 [ - + ]: 40 : g_string_append_len (data->stdout_buf, buf, (gssize) bytes_read);
344 : 40 : data->stdout_done = TRUE;
345 : : }
346 [ # # ]: 0 : else if (status == G_IO_STATUS_ERROR)
347 : : {
348 : 0 : g_error ("Error reading from child stdin: %s", error->message);
349 : : }
350 : :
351 [ + + + - ]: 80 : if (data->child_exited && data->stdout_done)
352 : 29 : g_main_loop_quit (data->loop);
353 : :
354 : 80 : return !data->stdout_done;
355 : : }
356 : :
357 : : static gpointer
358 : 40 : test_spawn_async_multithreaded_instance (gpointer thread_data)
359 : : {
360 : 40 : guint tnum = GPOINTER_TO_UINT (thread_data);
361 : 40 : GError *error = NULL;
362 : : GPtrArray *argv;
363 : : char *arg;
364 : : GPid pid;
365 : : GMainContext *context;
366 : : GMainLoop *loop;
367 : : GIOChannel *channel;
368 : : GSource *source;
369 : : int child_stdout_fd;
370 : : SpawnAsyncMultithreadedData data;
371 : :
372 : 40 : context = g_main_context_new ();
373 : 40 : loop = g_main_loop_new (context, TRUE);
374 : :
375 : 40 : arg = g_strdup_printf ("thread %u", tnum);
376 : :
377 : 40 : argv = g_ptr_array_new ();
378 : 40 : g_ptr_array_add (argv, echo_prog_path);
379 : 40 : g_ptr_array_add (argv, arg);
380 : 40 : g_ptr_array_add (argv, NULL);
381 : :
382 : 40 : g_spawn_async_with_pipes (NULL, (char**)argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL,
383 : : &child_stdout_fd, NULL, &error);
384 : 40 : g_assert_no_error (error);
385 : 40 : g_ptr_array_free (argv, TRUE);
386 : :
387 : 40 : data.loop = loop;
388 : 40 : data.stdout_done = FALSE;
389 : 40 : data.child_exited = FALSE;
390 : 40 : data.stdout_buf = g_string_new (0);
391 : :
392 : 40 : source = g_child_watch_source_new (pid);
393 : 40 : g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
394 : 40 : g_source_attach (source, context);
395 : 40 : g_source_unref (source);
396 : :
397 : 40 : channel = g_io_channel_unix_new (child_stdout_fd);
398 : 40 : source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP);
399 : 40 : g_source_set_callback (source, (GSourceFunc)on_child_stdout, &data, NULL);
400 : 40 : g_source_attach (source, context);
401 : 40 : g_source_unref (source);
402 : :
403 : 40 : g_main_loop_run (loop);
404 : :
405 : 40 : g_assert_true (data.child_exited);
406 : 40 : g_assert_true (data.stdout_done);
407 : 40 : g_assert_cmpstr (data.stdout_buf->str, ==, arg);
408 : 40 : g_string_free (data.stdout_buf, TRUE);
409 : :
410 : 40 : g_io_channel_unref (channel);
411 : 40 : g_main_context_unref (context);
412 : 40 : g_main_loop_unref (loop);
413 : :
414 : 40 : g_free (arg);
415 : :
416 : 40 : return GUINT_TO_POINTER (tnum);
417 : : }
418 : :
419 : : static void
420 : 1 : test_spawn_async_multithreaded (void)
421 : : {
422 : 1 : multithreaded_test_run (test_spawn_async_multithreaded_instance);
423 : 1 : }
424 : :
425 : : int
426 : 1 : main (int argc,
427 : : char *argv[])
428 : : {
429 : : char *dirname;
430 : : int ret;
431 : :
432 : 1 : g_test_init (&argc, &argv, NULL);
433 : :
434 : 1 : dirname = g_path_get_dirname (argv[0]);
435 : 1 : echo_prog_path = g_build_filename (dirname, "test-spawn-echo" EXEEXT, NULL);
436 : :
437 : 1 : g_assert (g_file_test (echo_prog_path, G_FILE_TEST_EXISTS));
438 : : #ifdef G_OS_WIN32
439 : : sleep_prog_path = g_build_filename (dirname, "test-spawn-sleep" EXEEXT, NULL);
440 : : g_assert (g_file_test (sleep_prog_path, G_FILE_TEST_EXISTS));
441 : : #endif
442 : :
443 : 1 : g_clear_pointer (&dirname, g_free);
444 : :
445 : 1 : g_test_add_func ("/gthread/spawn-childs", test_spawn_childs);
446 : 1 : g_test_add_func ("/gthread/spawn-childs-threads", test_spawn_childs_threads);
447 : 1 : g_test_add_func ("/gthread/spawn-sync", test_spawn_sync_multithreaded);
448 : 1 : g_test_add_func ("/gthread/spawn-async", test_spawn_async_multithreaded);
449 : :
450 : 1 : ret = g_test_run();
451 : :
452 : 1 : g_free (echo_prog_path);
453 : :
454 : : #ifdef G_OS_WIN32
455 : : g_free (sleep_prog_path);
456 : : #endif
457 : :
458 : 1 : return ret;
459 : : }
|