1/*
2 * Copyright (C) 2011 Red Hat, Inc.
3 *
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.
7 *
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.
11 *
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.
20 *
21 * Author: Colin Walters <walters@verbum.org>
22 */
23
24#include "config.h"
25
26#include <glib.h>
27#include <string.h>
28
29static char *echo_prog_path;
30
31static void
32multithreaded_test_run (GThreadFunc function)
33{
34 guint i;
35 GPtrArray *threads = g_ptr_array_new ();
36 guint n_threads;
37
38 /* Limit to 64, otherwise we may hit file descriptor limits and such */
39 n_threads = MIN (g_get_num_processors () * 2, 64);
40
41 for (i = 0; i < n_threads; i++)
42 {
43 GThread *thread;
44
45 thread = g_thread_new (name: "test", func: function, GUINT_TO_POINTER (i));
46 g_ptr_array_add (array: threads, data: thread);
47 }
48
49 for (i = 0; i < n_threads; i++)
50 {
51 gpointer ret;
52 ret = g_thread_join (g_ptr_array_index (threads, i));
53 g_assert_cmpint (GPOINTER_TO_UINT (ret), ==, i);
54 }
55 g_ptr_array_free (array: threads, TRUE);
56}
57
58static gpointer
59test_spawn_sync_multithreaded_instance (gpointer data)
60{
61 guint tnum = GPOINTER_TO_UINT (data);
62 GError *error = NULL;
63 GPtrArray *argv;
64 char *arg;
65 char *stdout_str;
66 int estatus;
67
68 arg = g_strdup_printf (format: "thread %u", tnum);
69
70 argv = g_ptr_array_new ();
71 g_ptr_array_add (array: argv, data: echo_prog_path);
72 g_ptr_array_add (array: argv, data: arg);
73 g_ptr_array_add (array: argv, NULL);
74
75 g_spawn_sync (NULL, argv: (char**)argv->pdata, NULL, flags: G_SPAWN_DEFAULT, NULL, NULL, standard_output: &stdout_str, NULL, exit_status: &estatus, error: &error);
76 g_assert_no_error (error);
77 g_assert_cmpstr (arg, ==, stdout_str);
78 g_free (mem: arg);
79 g_free (mem: stdout_str);
80 g_ptr_array_free (array: argv, TRUE);
81
82 return GUINT_TO_POINTER (tnum);
83}
84
85static void
86test_spawn_sync_multithreaded (void)
87{
88 multithreaded_test_run (function: test_spawn_sync_multithreaded_instance);
89}
90
91typedef struct {
92 GMainLoop *loop;
93 gboolean child_exited;
94 gboolean stdout_done;
95 GString *stdout_buf;
96} SpawnAsyncMultithreadedData;
97
98static gboolean
99on_child_exited (GPid pid,
100 gint status,
101 gpointer datap)
102{
103 SpawnAsyncMultithreadedData *data = datap;
104
105 data->child_exited = TRUE;
106 if (data->child_exited && data->stdout_done)
107 g_main_loop_quit (loop: data->loop);
108
109 return G_SOURCE_REMOVE;
110}
111
112static gboolean
113on_child_stdout (GIOChannel *channel,
114 GIOCondition condition,
115 gpointer datap)
116{
117 char buf[1024];
118 GError *error = NULL;
119 gsize bytes_read;
120 GIOStatus status;
121 SpawnAsyncMultithreadedData *data = datap;
122
123 read:
124 status = g_io_channel_read_chars (channel, buf, count: sizeof (buf), bytes_read: &bytes_read, error: &error);
125 if (status == G_IO_STATUS_NORMAL)
126 {
127 g_string_append_len (string: data->stdout_buf, val: buf, len: (gssize) bytes_read);
128 if (bytes_read == sizeof (buf))
129 goto read;
130 }
131 else if (status == G_IO_STATUS_EOF)
132 {
133 g_string_append_len (string: data->stdout_buf, val: buf, len: (gssize) bytes_read);
134 data->stdout_done = TRUE;
135 }
136 else if (status == G_IO_STATUS_ERROR)
137 {
138 g_error ("Error reading from child stdin: %s", error->message);
139 }
140
141 if (data->child_exited && data->stdout_done)
142 g_main_loop_quit (loop: data->loop);
143
144 return !data->stdout_done;
145}
146
147static gpointer
148test_spawn_async_multithreaded_instance (gpointer thread_data)
149{
150 guint tnum = GPOINTER_TO_UINT (thread_data);
151 GError *error = NULL;
152 GPtrArray *argv;
153 char *arg;
154 GPid pid;
155 GMainContext *context;
156 GMainLoop *loop;
157 GIOChannel *channel;
158 GSource *source;
159 int child_stdout_fd;
160 SpawnAsyncMultithreadedData data;
161
162 context = g_main_context_new ();
163 loop = g_main_loop_new (context, TRUE);
164
165 arg = g_strdup_printf (format: "thread %u", tnum);
166
167 argv = g_ptr_array_new ();
168 g_ptr_array_add (array: argv, data: echo_prog_path);
169 g_ptr_array_add (array: argv, data: arg);
170 g_ptr_array_add (array: argv, NULL);
171
172 g_spawn_async_with_pipes (NULL, argv: (char**)argv->pdata, NULL, flags: G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, child_pid: &pid, NULL,
173 standard_output: &child_stdout_fd, NULL, error: &error);
174 g_assert_no_error (error);
175 g_ptr_array_free (array: argv, TRUE);
176
177 data.loop = loop;
178 data.stdout_done = FALSE;
179 data.child_exited = FALSE;
180 data.stdout_buf = g_string_new (init: 0);
181
182 source = g_child_watch_source_new (pid);
183 g_source_set_callback (source, func: (GSourceFunc)on_child_exited, data: &data, NULL);
184 g_source_attach (source, context);
185 g_source_unref (source);
186
187 channel = g_io_channel_unix_new (fd: child_stdout_fd);
188 source = g_io_create_watch (channel, condition: G_IO_IN | G_IO_HUP);
189 g_source_set_callback (source, func: (GSourceFunc)on_child_stdout, data: &data, NULL);
190 g_source_attach (source, context);
191 g_source_unref (source);
192
193 g_main_loop_run (loop);
194
195 g_assert (data.child_exited);
196 g_assert (data.stdout_done);
197 g_assert_cmpstr (data.stdout_buf->str, ==, arg);
198 g_string_free (string: data.stdout_buf, TRUE);
199
200 g_io_channel_unref (channel);
201 g_main_context_unref (context);
202 g_main_loop_unref (loop);
203
204 g_free (mem: arg);
205
206 return GUINT_TO_POINTER (tnum);
207}
208
209static void
210test_spawn_async_multithreaded (void)
211{
212 multithreaded_test_run (function: test_spawn_async_multithreaded_instance);
213}
214
215int
216main (int argc,
217 char *argv[])
218{
219 char *dirname;
220 int ret;
221
222 g_test_init (argc: &argc, argv: &argv, NULL);
223
224 dirname = g_path_get_dirname (file_name: argv[0]);
225 echo_prog_path = g_build_filename (first_element: dirname, "test-spawn-echo" EXEEXT, NULL);
226 if (!g_file_test (filename: echo_prog_path, test: G_FILE_TEST_EXISTS))
227 {
228 g_free (mem: echo_prog_path);
229 echo_prog_path = g_build_filename (first_element: dirname, "lt-test-spawn-echo" EXEEXT, NULL);
230 }
231 g_free (mem: dirname);
232
233 g_assert (g_file_test (echo_prog_path, G_FILE_TEST_EXISTS));
234
235 g_test_add_func (testpath: "/gthread/spawn-sync", test_func: test_spawn_sync_multithreaded);
236 g_test_add_func (testpath: "/gthread/spawn-async", test_func: test_spawn_async_multithreaded);
237
238 ret = g_test_run();
239
240 g_free (mem: echo_prog_path);
241
242 return ret;
243}
244

source code of gtk/subprojects/glib/glib/tests/spawn-multithreaded.c