1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2008-2011 Kouhei Sutou <kou@clear-code.com>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (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
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif /* HAVE_CONFIG_H */
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <errno.h>
28
29 #ifdef HAVE_SYS_WAIT_H
30 # include <sys/wait.h>
31 #endif
32
33 #include "cut-pipeline.h"
34 #include "cut-test-result.h"
35 #include "cut-runner.h"
36 #include "cut-experimental.h"
37 #include "cut-utils.h"
38
39 #ifdef G_OS_WIN32
40 # include <io.h>
41 # define pipe(phandles) _pipe(phandles, 4096, _O_BINARY)
42 #else
43 # include <unistd.h>
44 #endif
45
46 #define CUT_PIPELINE_GET_PRIVATE(obj) \
47 (G_TYPE_INSTANCE_GET_PRIVATE((obj), CUT_TYPE_PIPELINE, CutPipelinePrivate))
48
49 typedef struct _CutPipelinePrivate CutPipelinePrivate;
50 struct _CutPipelinePrivate
51 {
52 GPid pid;
53 guint process_source_id;
54 GIOChannel *child_out;
55 gint child_pipe[2];
56 };
57
58 static CutRunnerIface *parent_runner_iface;
59
60 static void runner_init (CutRunnerIface *iface);
61
62 G_DEFINE_TYPE_WITH_CODE(CutPipeline, cut_pipeline, CUT_TYPE_STREAM_READER,
63 G_IMPLEMENT_INTERFACE(CUT_TYPE_RUNNER, runner_init))
64
65 static void dispose (GObject *object);
66
67 static void runner_run_async (CutRunner *runner);
68
69 static void
cut_pipeline_class_init(CutPipelineClass * klass)70 cut_pipeline_class_init (CutPipelineClass *klass)
71 {
72 GObjectClass *gobject_class;
73
74 gobject_class = G_OBJECT_CLASS(klass);
75
76 gobject_class->dispose = dispose;
77
78 g_type_class_add_private(gobject_class, sizeof(CutPipelinePrivate));
79 }
80
81 static void
cut_pipeline_init(CutPipeline * pipeline)82 cut_pipeline_init (CutPipeline *pipeline)
83 {
84 CutPipelinePrivate *priv = CUT_PIPELINE_GET_PRIVATE(pipeline);
85
86 priv->process_source_id = 0;
87 priv->pid = 0;
88
89 priv->child_out = NULL;
90 priv->child_pipe[0] = -1;
91 priv->child_pipe[1] = -1;
92 }
93
94 static void
runner_init(CutRunnerIface * iface)95 runner_init (CutRunnerIface *iface)
96 {
97 parent_runner_iface = g_type_interface_peek_parent(iface);
98 iface->run_async = runner_run_async;
99 iface->run = NULL;
100 }
101
102 static void
remove_child_watch_func(CutPipelinePrivate * priv)103 remove_child_watch_func (CutPipelinePrivate *priv)
104 {
105 g_source_remove(priv->process_source_id);
106 priv->process_source_id = 0;
107 }
108
109 static void
close_child(CutPipelinePrivate * priv)110 close_child (CutPipelinePrivate *priv)
111 {
112 g_spawn_close_pid(priv->pid);
113 priv->pid = 0;
114 }
115
116 static void
unref_child_out_channel(CutPipelinePrivate * priv)117 unref_child_out_channel (CutPipelinePrivate *priv)
118 {
119 g_io_channel_unref(priv->child_out);
120 priv->child_out = NULL;
121 }
122
123 static void
dispose(GObject * object)124 dispose (GObject *object)
125 {
126 CutPipelinePrivate *priv = CUT_PIPELINE_GET_PRIVATE(object);
127
128 if (priv->process_source_id)
129 remove_child_watch_func(priv);
130
131 if (priv->pid)
132 close_child(priv);
133
134 if (priv->child_out)
135 unref_child_out_channel(priv);
136
137 G_OBJECT_CLASS(cut_pipeline_parent_class)->dispose(object);
138 }
139
140 CutRunContext *
cut_pipeline_new(void)141 cut_pipeline_new (void)
142 {
143 return g_object_new(CUT_TYPE_PIPELINE, NULL);
144 }
145
146 CutRunContext *
cut_pipeline_new_from_run_context(CutRunContext * run_context)147 cut_pipeline_new_from_run_context (CutRunContext *run_context)
148 {
149 return g_object_new(CUT_TYPE_PIPELINE,
150 "test-directory",
151 cut_run_context_get_test_directory(run_context),
152 "use-multi-thread",
153 cut_run_context_get_multi_thread(run_context),
154 "max-threads",
155 cut_run_context_get_max_threads(run_context),
156 "handle-signals",
157 cut_run_context_get_handle_signals(run_context),
158 "exclude-files",
159 cut_run_context_get_exclude_files(run_context),
160 "exclude-directories",
161 cut_run_context_get_exclude_directories(run_context),
162 "target-test-case-names",
163 cut_run_context_get_target_test_case_names(run_context),
164 "target-test-names",
165 cut_run_context_get_target_test_names(run_context),
166 "test-case-order",
167 cut_run_context_get_test_case_order(run_context),
168 "source-directory",
169 cut_run_context_get_source_directory(run_context),
170 "command-line-args",
171 cut_run_context_get_command_line_args(run_context),
172 "fatal-failures",
173 cut_run_context_get_fatal_failures(run_context),
174 "keep-opening-modules",
175 cut_run_context_get_keep_opening_modules(run_context),
176 "enable-convenience-attribute-definition",
177 cut_run_context_get_enable_convenience_attribute_definition(run_context),
178 NULL);
179 }
180
181 GQuark
cut_pipeline_error_quark(void)182 cut_pipeline_error_quark (void)
183 {
184 return g_quark_from_static_string("cut-pipeline-error-quark");
185 }
186
187 #define emit_error(pipeline, code, error, ...) do \
188 { \
189 CutPipeline *_pipeline; \
190 CutRunContext *_run_context; \
191 \
192 _pipeline = (pipeline); \
193 _run_context = CUT_RUN_CONTEXT(_pipeline); \
194 cut_run_context_emit_error(_run_context, CUT_PIPELINE_ERROR, \
195 code, error, \
196 __VA_ARGS__); \
197 cut_run_context_emit_complete_run(_run_context, FALSE); \
198 } while (0)
199
200 static void
reap_child(CutPipeline * pipeline,GPid pid)201 reap_child (CutPipeline *pipeline, GPid pid)
202 {
203 CutPipelinePrivate *priv = CUT_PIPELINE_GET_PRIVATE(pipeline);
204
205 if (priv->pid != pid)
206 return;
207
208 remove_child_watch_func(priv);
209 unref_child_out_channel(priv);
210 close_child(priv);
211 }
212
213 static gboolean
read_stream(CutPipeline * pipeline,GIOChannel * channel)214 read_stream (CutPipeline *pipeline, GIOChannel *channel)
215 {
216 CutStreamReader *reader;
217 reader = CUT_STREAM_READER(pipeline);
218 return cut_stream_reader_read_from_io_channel_to_end(reader, channel);
219 }
220
221 static void
child_watch_func(GPid pid,gint status,gpointer data)222 child_watch_func (GPid pid, gint status, gpointer data)
223 {
224 CutPipeline *pipeline = data;
225 CutPipelinePrivate *priv;
226
227 priv = CUT_PIPELINE_GET_PRIVATE(pipeline);
228 read_stream(pipeline, priv->child_out);
229 reap_child(pipeline, pid);
230 }
231
232 static GIOChannel *
create_child_out_channel(CutPipeline * pipeline)233 create_child_out_channel (CutPipeline *pipeline)
234 {
235 GIOChannel *channel;
236 CutStreamReader *reader;
237 CutPipelinePrivate *priv;
238
239 priv = CUT_PIPELINE_GET_PRIVATE(pipeline);
240 if (priv->child_pipe[CUT_READ] == -1)
241 return NULL;
242
243 #ifndef G_OS_WIN32
244 channel = g_io_channel_unix_new(priv->child_pipe[CUT_READ]);
245 #else
246 channel = g_io_channel_win32_new_fd(priv->child_pipe[CUT_READ]);
247 #endif
248 if (!channel)
249 return NULL;
250
251 g_io_channel_set_close_on_unref(channel, TRUE);
252
253 reader = CUT_STREAM_READER(pipeline);
254 cut_stream_reader_watch_io_channel(reader, channel);
255
256 return channel;
257 }
258
259 static gchar **
create_command_line_args_from_argv(CutPipeline * pipeline,const gchar ** argv)260 create_command_line_args_from_argv (CutPipeline *pipeline, const gchar **argv)
261 {
262 CutPipelinePrivate *priv;
263 CutRunContext *run_context;
264 gchar **new_argv;
265 gchar **copy;
266 const gchar *test_directory;
267 gchar *stream_fd;
268 guint i;
269 guint length;
270
271 priv = CUT_PIPELINE_GET_PRIVATE(pipeline);
272 run_context = CUT_RUN_CONTEXT(pipeline);
273
274 length = g_strv_length((gchar **)argv);
275 /* remove the last argument in which test directory is stored */
276 copy = g_new(gchar *, length);
277 for (i = 0; i < length - 1; i++) {
278 copy[i] = g_strdup(argv[i]);
279 }
280 copy[i] = NULL;
281
282 stream_fd = g_strdup_printf("--stream-fd=%d",
283 priv->child_pipe[CUT_WRITE]);
284 test_directory = cut_run_context_get_test_directory(run_context);
285 new_argv = cut_utils_strv_concat((const gchar **)copy,
286 "--ui=console",
287 "-v", "s",
288 "--notify", "no",
289 "--stream=xml",
290 stream_fd,
291 test_directory,
292 NULL);
293 g_free(stream_fd);
294 g_strfreev(copy);
295
296 return new_argv;
297 }
298
299 static void
append_arg(GArray * argv,const gchar * arg)300 append_arg (GArray *argv, const gchar *arg)
301 {
302 gchar *dupped_arg;
303
304 dupped_arg = g_strdup(arg);
305 g_array_append_val(argv, dupped_arg);
306 }
307
308 static void
append_arg_printf(GArray * argv,const gchar * format,...)309 append_arg_printf (GArray *argv, const gchar *format, ...)
310 {
311 gchar *arg;
312 va_list args;
313
314 va_start(args, format);
315 arg = g_strdup_vprintf(format, args);
316 va_end(args);
317
318 g_array_append_val(argv, arg);
319 }
320
321 static gchar **
create_command_line_args_from_parameters(CutPipeline * pipeline)322 create_command_line_args_from_parameters (CutPipeline *pipeline)
323 {
324 CutPipelinePrivate *priv;
325 CutRunContext *run_context;
326 GArray *argv;
327 const gchar *directory;
328 const gchar **strings;
329
330 priv = CUT_PIPELINE_GET_PRIVATE(pipeline);
331 run_context = CUT_RUN_CONTEXT(pipeline);
332
333 argv = g_array_new(TRUE, TRUE, sizeof(gchar *));
334
335 append_arg(argv, cut_utils_get_cutter_command_path());
336 append_arg(argv, "--verbose=silent");
337 append_arg(argv, "--notify=no");
338 append_arg(argv, "--stream=xml");
339 append_arg_printf(argv, "--stream-fd=%d", priv->child_pipe[CUT_WRITE]);
340
341 directory = cut_run_context_get_source_directory(run_context);
342 if (directory)
343 append_arg_printf(argv, "--source-directory=%s", directory);
344
345 if (cut_run_context_get_multi_thread(run_context))
346 append_arg(argv, "--multi-thread");
347
348 append_arg_printf(argv,
349 "--max-threads=%d",
350 cut_run_context_get_max_threads(run_context));
351
352 strings = cut_run_context_get_exclude_files(run_context);
353 while (strings && *strings) {
354 append_arg_printf(argv, "--exclude-file=%s", *strings);
355 strings++;
356 }
357
358 strings = cut_run_context_get_exclude_directories(run_context);
359 while (strings && *strings) {
360 append_arg_printf(argv, "--exclude-directory=%s", *strings);
361 strings++;
362 }
363
364 strings = cut_run_context_get_target_test_case_names(run_context);
365 while (strings && *strings) {
366 append_arg_printf(argv, "--test-case=%s", *strings);
367 strings++;
368 }
369
370 strings = cut_run_context_get_target_test_names(run_context);
371 while (strings && *strings) {
372 append_arg_printf(argv, "--name=%s", *strings);
373 strings++;
374 }
375
376 if (cut_run_context_get_fatal_failures(run_context))
377 append_arg(argv, "--fatal-failures");
378
379 append_arg(argv, cut_run_context_get_test_directory(run_context));
380
381 return (gchar **)(g_array_free(argv, FALSE));
382 }
383
384 static gchar **
create_command_line_args(CutPipeline * pipeline)385 create_command_line_args (CutPipeline *pipeline)
386 {
387 CutRunContext *run_context;
388 const gchar **original_argv;
389 gchar **new_args = NULL;
390
391 run_context = CUT_RUN_CONTEXT(pipeline);
392 g_return_val_if_fail(cut_run_context_get_test_directory(run_context) != NULL,
393 NULL);
394
395 original_argv = cut_run_context_get_command_line_args(run_context);
396
397 if (original_argv) {
398 new_args = create_command_line_args_from_argv(pipeline, original_argv);
399 } else {
400 new_args = create_command_line_args_from_parameters(pipeline);
401 }
402
403 return new_args;
404 }
405
406 #ifndef G_OS_WIN32
407 static void
setup_child(gpointer user_data)408 setup_child (gpointer user_data)
409 {
410 CutPipeline *pipeline = user_data;
411 CutPipelinePrivate *priv;
412
413 priv = CUT_PIPELINE_GET_PRIVATE(pipeline);
414
415 cut_utils_close_pipe(priv->child_pipe, CUT_READ);
416 }
417 #endif
418
419 static void
run_async(CutPipeline * pipeline)420 run_async (CutPipeline *pipeline)
421 {
422 gchar **command_line_args;
423 gboolean result;
424 GError *error = NULL;
425 CutPipelinePrivate *priv;
426
427 priv = CUT_PIPELINE_GET_PRIVATE(pipeline);
428
429 if (pipe(priv->child_pipe) < 0) {
430 emit_error(pipeline, CUT_PIPELINE_ERROR_PIPE, NULL,
431 "failed to create pipe: %s", g_strerror(errno));
432 return;
433 }
434
435 command_line_args = create_command_line_args(pipeline);
436 if (!command_line_args) {
437 emit_error(pipeline, CUT_PIPELINE_ERROR_COMMAND_LINE, NULL,
438 "failed to generate command line");
439 return;
440 }
441
442 result = g_spawn_async_with_pipes(NULL,
443 command_line_args,
444 NULL,
445 G_SPAWN_SEARCH_PATH |
446 G_SPAWN_LEAVE_DESCRIPTORS_OPEN |
447 G_SPAWN_DO_NOT_REAP_CHILD,
448 #ifndef G_OS_WIN32
449 setup_child,
450 #else
451 NULL,
452 #endif
453 pipeline,
454 &priv->pid,
455 NULL,
456 NULL,
457 NULL,
458 &error);
459 g_strfreev(command_line_args);
460 cut_utils_close_pipe(priv->child_pipe, CUT_WRITE);
461
462 if (!result) {
463 emit_error(pipeline, CUT_PIPELINE_ERROR_SPAWN, error,
464 "failed to spawn child process");
465 return;
466 }
467
468 if (priv->pid == (GPid)0) {
469 emit_error(pipeline, CUT_PIPELINE_ERROR_CHILD_PID,
470 error, "failed to get child PID");
471 return;
472 }
473
474 priv->child_out = create_child_out_channel(pipeline);
475 if (!priv->child_out) {
476 emit_error(pipeline, CUT_PIPELINE_ERROR_PIPE,
477 error, "failed to connect to child pipe");
478 return;
479 }
480
481 priv->process_source_id = g_child_watch_add(priv->pid,
482 child_watch_func,
483 pipeline);
484 }
485
486 static void
runner_run_async(CutRunner * runner)487 runner_run_async (CutRunner *runner)
488 {
489 CutPipeline *pipeline;
490
491 pipeline = CUT_PIPELINE(runner);
492 run_async(pipeline);
493 }
494
495 /*
496 vi:ts=4:nowrap:ai:expandtab:sw=4
497 */
498