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