2 * Copyright (C) 2011 Red Hat, Inc.
4 * This work is provided "as is"; redistribution and modification
5 * in whole or in part, in any medium, physical or electronic is
6 * permitted without restriction.
8 * This work is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 * In no event shall the authors or contributors be liable for any
13 * direct, indirect, incidental, special, exemplary, or consequential
14 * damages (including, but not limited to, procurement of substitute
15 * goods or services; loss of use, data, or profits; or business
16 * interruption) however caused and on any theory of liability, whether
17 * in contract, strict liability, or tort (including negligence or
18 * otherwise) arising in any way out of the use of this software, even
19 * if advised of the possibility of such damage.
21 * Author: Colin Walters <walters@verbum.org>
31 #include <glib-unix.h>
36 #define LINEEND "\r\n"
41 /* MinGW builds are likely done using a BASH-style shell, so run the
42 * normal script there, as on non-Windows builds, as it is more likely
43 * that one will run 'make check' in such shells to test the code
45 #if defined (G_OS_WIN32) && defined (_MSC_VER)
46 #define SCRIPT_EXT ".bat"
51 static char *echo_prog_path
;
52 static char *echo_script_path
;
56 gboolean child_exited
;
59 } SpawnAsyncMultithreadedData
;
62 on_child_exited (GPid pid
,
66 SpawnAsyncMultithreadedData
*data
= datap
;
68 data
->child_exited
= TRUE
;
69 if (data
->child_exited
&& data
->stdout_done
)
70 g_main_loop_quit (data
->loop
);
72 return G_SOURCE_REMOVE
;
76 on_child_stdout (GIOChannel
*channel
,
77 GIOCondition condition
,
83 SpawnAsyncMultithreadedData
*data
= datap
;
85 if (condition
& G_IO_IN
)
88 status
= g_io_channel_read_chars (channel
, buf
, sizeof (buf
), &bytes_read
, &error
);
89 g_assert_no_error (error
);
90 g_string_append_len (data
->stdout_buf
, buf
, (gssize
) bytes_read
);
91 if (status
== G_IO_STATUS_EOF
)
92 data
->stdout_done
= TRUE
;
94 if (condition
& G_IO_HUP
)
95 data
->stdout_done
= TRUE
;
96 if (condition
& G_IO_ERR
)
97 g_error ("Error reading from child stdin");
99 if (data
->child_exited
&& data
->stdout_done
)
100 g_main_loop_quit (data
->loop
);
102 return !data
->stdout_done
;
106 test_spawn_async (void)
109 GError
*error
= NULL
;
113 GMainContext
*context
;
118 SpawnAsyncMultithreadedData data
;
120 context
= g_main_context_new ();
121 loop
= g_main_loop_new (context
, TRUE
);
123 arg
= g_strdup_printf ("thread %d", tnum
);
125 argv
= g_ptr_array_new ();
126 g_ptr_array_add (argv
, echo_prog_path
);
127 g_ptr_array_add (argv
, arg
);
128 g_ptr_array_add (argv
, NULL
);
130 g_spawn_async_with_pipes (NULL
, (char**)argv
->pdata
, NULL
, G_SPAWN_DO_NOT_REAP_CHILD
, NULL
, NULL
, &pid
, NULL
,
131 &child_stdout_fd
, NULL
, &error
);
132 g_assert_no_error (error
);
133 g_ptr_array_free (argv
, TRUE
);
136 data
.stdout_done
= FALSE
;
137 data
.child_exited
= FALSE
;
138 data
.stdout_buf
= g_string_new (0);
140 source
= g_child_watch_source_new (pid
);
141 g_source_set_callback (source
, (GSourceFunc
)on_child_exited
, &data
, NULL
);
142 g_source_attach (source
, context
);
143 g_source_unref (source
);
145 channel
= g_io_channel_unix_new (child_stdout_fd
);
146 source
= g_io_create_watch (channel
, G_IO_IN
| G_IO_HUP
| G_IO_ERR
);
147 g_source_set_callback (source
, (GSourceFunc
)on_child_stdout
, &data
, NULL
);
148 g_source_attach (source
, context
);
149 g_source_unref (source
);
151 g_main_loop_run (loop
);
153 g_assert (data
.child_exited
);
154 g_assert (data
.stdout_done
);
155 g_assert_cmpstr (data
.stdout_buf
->str
, ==, arg
);
156 g_string_free (data
.stdout_buf
, TRUE
);
158 g_io_channel_unref (channel
);
159 g_main_context_unref (context
);
160 g_main_loop_unref (loop
);
165 /* Windows close() causes failure through the Invalid Parameter Handler
166 * Routine if the file descriptor does not exist.
175 /* Test g_spawn_async_with_fds() with a variety of different inputs */
177 test_spawn_async_with_fds (void)
184 /* Each test has 3 variable parameters: stdin, stdout, stderr */
186 NO_FD
, /* pass fd -1 (unset) */
187 FD_NEGATIVE
, /* pass fd of negative value (equivalent to unset) */
188 PIPE
, /* pass fd of new/unique pipe */
189 STDOUT_PIPE
, /* pass the same pipe as stdout */
191 { NO_FD
, NO_FD
, NO_FD
}, /* Test with no fds passed */
192 { NO_FD
, FD_NEGATIVE
, NO_FD
}, /* Test another negative fd value */
193 { PIPE
, PIPE
, PIPE
}, /* Test with unique fds passed */
194 { NO_FD
, PIPE
, STDOUT_PIPE
}, /* Test the same fd for stdout + stderr */
197 arg
= g_strdup_printf ("thread %d", tnum
);
199 argv
= g_ptr_array_new ();
200 g_ptr_array_add (argv
, echo_prog_path
);
201 g_ptr_array_add (argv
, arg
);
202 g_ptr_array_add (argv
, NULL
);
204 for (i
= 0; i
< G_N_ELEMENTS (tests
); i
++)
206 GError
*error
= NULL
;
208 GMainContext
*context
;
210 GIOChannel
*channel
= NULL
;
212 SpawnAsyncMultithreadedData data
;
213 enum fd_type
*fd_info
= tests
[i
];
214 gint test_pipe
[3][2];
217 for (j
= 0; j
< 3; j
++)
222 test_pipe
[j
][0] = -1;
223 test_pipe
[j
][1] = -1;
226 test_pipe
[j
][0] = -5;
227 test_pipe
[j
][1] = -5;
231 g_unix_open_pipe (test_pipe
[j
], FD_CLOEXEC
, &error
);
232 g_assert_no_error (error
);
234 g_assert_cmpint (_pipe (test_pipe
[j
], 4096, _O_BINARY
), >=, 0);
238 g_assert_cmpint (j
, ==, 2); /* only works for stderr */
239 test_pipe
[j
][0] = test_pipe
[1][0];
240 test_pipe
[j
][1] = test_pipe
[1][1];
243 g_assert_not_reached ();
247 context
= g_main_context_new ();
248 loop
= g_main_loop_new (context
, TRUE
);
250 g_spawn_async_with_fds (NULL
, (char**)argv
->pdata
, NULL
,
251 G_SPAWN_DO_NOT_REAP_CHILD
, NULL
, NULL
, &pid
,
252 test_pipe
[0][0], test_pipe
[1][1], test_pipe
[2][1],
254 g_assert_no_error (error
);
255 sane_close (test_pipe
[0][0]);
256 sane_close (test_pipe
[1][1]);
257 if (fd_info
[2] != STDOUT_PIPE
)
258 sane_close (test_pipe
[2][1]);
261 data
.stdout_done
= FALSE
;
262 data
.child_exited
= FALSE
;
263 data
.stdout_buf
= g_string_new (0);
265 source
= g_child_watch_source_new (pid
);
266 g_source_set_callback (source
, (GSourceFunc
)on_child_exited
, &data
, NULL
);
267 g_source_attach (source
, context
);
268 g_source_unref (source
);
270 if (test_pipe
[1][0] >= 0)
272 channel
= g_io_channel_unix_new (test_pipe
[1][0]);
273 source
= g_io_create_watch (channel
, G_IO_IN
| G_IO_HUP
| G_IO_ERR
);
274 g_source_set_callback (source
, (GSourceFunc
)on_child_stdout
,
276 g_source_attach (source
, context
);
277 g_source_unref (source
);
281 /* Don't check stdout data if we didn't pass a fd */
282 data
.stdout_done
= TRUE
;
285 g_main_loop_run (loop
);
287 g_assert_true (data
.child_exited
);
289 if (test_pipe
[1][0] >= 0)
291 /* Check for echo on stdout */
292 g_assert_true (data
.stdout_done
);
293 g_assert_cmpstr (data
.stdout_buf
->str
, ==, arg
);
294 g_io_channel_unref (channel
);
296 g_string_free (data
.stdout_buf
, TRUE
);
298 g_main_context_unref (context
);
299 g_main_loop_unref (loop
);
300 sane_close (test_pipe
[0][1]);
301 sane_close (test_pipe
[1][0]);
302 if (fd_info
[2] != STDOUT_PIPE
)
303 sane_close (test_pipe
[2][0]);
306 g_ptr_array_free (argv
, TRUE
);
311 test_spawn_sync (void)
314 GError
*error
= NULL
;
320 arg
= g_strdup_printf ("thread %d", tnum
);
322 argv
= g_ptr_array_new ();
323 g_ptr_array_add (argv
, echo_prog_path
);
324 g_ptr_array_add (argv
, arg
);
325 g_ptr_array_add (argv
, NULL
);
327 g_spawn_sync (NULL
, (char**)argv
->pdata
, NULL
, 0, NULL
, NULL
, &stdout_str
, NULL
, &estatus
, &error
);
328 g_assert_no_error (error
);
329 g_assert_cmpstr (arg
, ==, stdout_str
);
332 g_ptr_array_free (argv
, TRUE
);
335 /* Like test_spawn_sync but uses spawn flags that trigger the optimized
336 * posix_spawn codepath.
339 test_posix_spawn (void)
342 GError
*error
= NULL
;
347 GSpawnFlags flags
= G_SPAWN_CLOEXEC_PIPES
| G_SPAWN_LEAVE_DESCRIPTORS_OPEN
;
349 arg
= g_strdup_printf ("thread %d", tnum
);
351 argv
= g_ptr_array_new ();
352 g_ptr_array_add (argv
, echo_prog_path
);
353 g_ptr_array_add (argv
, arg
);
354 g_ptr_array_add (argv
, NULL
);
356 g_spawn_sync (NULL
, (char**)argv
->pdata
, NULL
, flags
, NULL
, NULL
, &stdout_str
, NULL
, &estatus
, &error
);
357 g_assert_no_error (error
);
358 g_assert_cmpstr (arg
, ==, stdout_str
);
361 g_ptr_array_free (argv
, TRUE
);
365 test_spawn_script (void)
367 GError
*error
= NULL
;
372 argv
= g_ptr_array_new ();
373 g_ptr_array_add (argv
, echo_script_path
);
374 g_ptr_array_add (argv
, NULL
);
376 g_spawn_sync (NULL
, (char**)argv
->pdata
, NULL
, 0, NULL
, NULL
, &stdout_str
, NULL
, &estatus
, &error
);
377 g_assert_no_error (error
);
378 g_assert_cmpstr ("echo" LINEEND
, ==, stdout_str
);
380 g_ptr_array_free (argv
, TRUE
);
383 /* Test that spawning a non-existent executable returns %G_SPAWN_ERROR_NOENT. */
385 test_spawn_nonexistent (void)
387 GError
*error
= NULL
;
388 GPtrArray
*argv
= NULL
;
389 gchar
*stdout_str
= NULL
;
390 gint exit_status
= -1;
392 argv
= g_ptr_array_new ();
393 g_ptr_array_add (argv
, "this does not exist");
394 g_ptr_array_add (argv
, NULL
);
396 g_spawn_sync (NULL
, (char**) argv
->pdata
, NULL
, 0, NULL
, NULL
, &stdout_str
,
397 NULL
, &exit_status
, &error
);
398 g_assert_error (error
, G_SPAWN_ERROR
, G_SPAWN_ERROR_NOENT
);
399 g_assert_null (stdout_str
);
400 g_assert_cmpint (exit_status
, ==, -1);
402 g_ptr_array_free (argv
, TRUE
);
404 g_clear_error (&error
);
414 g_test_init (&argc
, &argv
, NULL
);
416 dirname
= g_path_get_dirname (argv
[0]);
417 echo_prog_path
= g_build_filename (dirname
, "test-spawn-echo" EXEEXT
, NULL
);
418 if (!g_file_test (echo_prog_path
, G_FILE_TEST_EXISTS
))
420 g_free (echo_prog_path
);
421 echo_prog_path
= g_build_filename (dirname
, "lt-test-spawn-echo" EXEEXT
, NULL
);
423 echo_script_path
= g_build_filename (dirname
, "echo-script" SCRIPT_EXT
, NULL
);
424 if (!g_file_test (echo_script_path
, G_FILE_TEST_EXISTS
))
426 g_free (echo_script_path
);
427 echo_script_path
= g_test_build_filename (G_TEST_DIST
, "echo-script" SCRIPT_EXT
, NULL
);
431 g_assert (g_file_test (echo_prog_path
, G_FILE_TEST_EXISTS
));
432 g_assert (g_file_test (echo_script_path
, G_FILE_TEST_EXISTS
));
434 g_test_add_func ("/gthread/spawn-single-sync", test_spawn_sync
);
435 g_test_add_func ("/gthread/spawn-single-async", test_spawn_async
);
436 g_test_add_func ("/gthread/spawn-single-async-with-fds", test_spawn_async_with_fds
);
437 g_test_add_func ("/gthread/spawn-script", test_spawn_script
);
438 g_test_add_func ("/gthread/spawn/nonexistent", test_spawn_nonexistent
);
439 g_test_add_func ("/gthread/spawn-posix-spawn", test_posix_spawn
);
443 g_free (echo_script_path
);
444 g_free (echo_prog_path
);