1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  *  Copyright (C) 2008-2014  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 <glib.h>
27 
28 #include <signal.h>
29 
30 #include "cut-test-iterator.h"
31 
32 #include "cut-run-context.h"
33 
34 #include "cut-test-result.h"
35 #include "cut-utils.h"
36 #include "cut-crash-backtrace.h"
37 
38 #include "../gcutter/gcut-error.h"
39 #include "../gcutter/gcut-marshalers.h"
40 
41 #define CUT_TEST_ITERATOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CUT_TYPE_TEST_ITERATOR, CutTestIteratorPrivate))
42 
43 typedef struct _CutTestIteratorPrivate	CutTestIteratorPrivate;
44 struct _CutTestIteratorPrivate
45 {
46     CutIteratedTestFunction iterated_test_function;
47     CutDataSetupFunction data_setup_function;
48 };
49 
50 enum
51 {
52     PROP_0,
53     PROP_ITERATED_TEST_FUNCTION,
54     PROP_DATA_SETUP_FUNCTION
55 };
56 
57 enum
58 {
59     READY,
60     START_TEST,
61     COMPLETE_TEST,
62     LAST_SIGNAL
63 };
64 
65 static gint signals[LAST_SIGNAL] = {0};
66 
67 G_DEFINE_TYPE(CutTestIterator, cut_test_iterator, CUT_TYPE_TEST_CONTAINER)
68 
69 static void dispose        (GObject         *object);
70 static void set_property   (GObject         *object,
71                             guint            prop_id,
72                             const GValue    *value,
73                             GParamSpec      *pspec);
74 static void get_property   (GObject         *object,
75                             guint            prop_id,
76                             GValue          *value,
77                             GParamSpec      *pspec);
78 
79 static gboolean run        (CutTest        *test,
80                             CutTestContext *test_context,
81                             CutRunContext  *run_context);
82 
83 static void
cut_test_iterator_class_init(CutTestIteratorClass * klass)84 cut_test_iterator_class_init (CutTestIteratorClass *klass)
85 {
86     GObjectClass *gobject_class;
87     CutTestClass *test_class;
88     GParamSpec *spec;
89 
90     gobject_class = G_OBJECT_CLASS(klass);
91     test_class = CUT_TEST_CLASS(klass);
92 
93     gobject_class->dispose      = dispose;
94     gobject_class->set_property = set_property;
95     gobject_class->get_property = get_property;
96 
97     test_class->run = run;
98 
99     spec = g_param_spec_pointer("iterated-test-function",
100                                 "Iterated Test Function",
101                                 "The function for iterated test",
102                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
103     g_object_class_install_property(gobject_class, PROP_ITERATED_TEST_FUNCTION,
104                                     spec);
105 
106     spec = g_param_spec_pointer("data-setup-function",
107                                 "Data Setup Function",
108                                 "The function for data setup",
109                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
110     g_object_class_install_property(gobject_class, PROP_DATA_SETUP_FUNCTION,
111                                     spec);
112 
113     signals[READY]
114         = g_signal_new("ready",
115                        G_TYPE_FROM_CLASS(klass),
116                        G_SIGNAL_RUN_LAST,
117                        G_STRUCT_OFFSET(CutTestIteratorClass, ready),
118                        NULL, NULL,
119                        g_cclosure_marshal_VOID__UINT,
120                        G_TYPE_NONE, 1, G_TYPE_UINT);
121 
122     signals[START_TEST]
123         = g_signal_new("start-iterated-test",
124                        G_TYPE_FROM_CLASS(klass),
125                        G_SIGNAL_RUN_LAST,
126                        G_STRUCT_OFFSET(CutTestIteratorClass,
127                                        start_iterated_test),
128                        NULL, NULL,
129                        _gcut_marshal_VOID__OBJECT_OBJECT,
130                        G_TYPE_NONE, 2, CUT_TYPE_TEST, CUT_TYPE_TEST_CONTEXT);
131 
132     signals[COMPLETE_TEST]
133         = g_signal_new("complete-iterated-test",
134                        G_TYPE_FROM_CLASS(klass),
135                        G_SIGNAL_RUN_LAST,
136                        G_STRUCT_OFFSET(CutTestIteratorClass,
137                                        complete_iterated_test),
138                        NULL, NULL,
139                        _gcut_marshal_VOID__OBJECT_OBJECT_BOOLEAN,
140                        G_TYPE_NONE, 3,
141                        CUT_TYPE_TEST, CUT_TYPE_TEST_CONTEXT, G_TYPE_BOOLEAN);
142 
143     g_type_class_add_private(gobject_class, sizeof(CutTestIteratorPrivate));
144 }
145 
146 static void
cut_test_iterator_init(CutTestIterator * test_iterator)147 cut_test_iterator_init (CutTestIterator *test_iterator)
148 {
149     CutTestIteratorPrivate *priv = CUT_TEST_ITERATOR_GET_PRIVATE(test_iterator);
150 
151     priv->iterated_test_function = NULL;
152     priv->data_setup_function = NULL;
153 }
154 
155 static void
dispose(GObject * object)156 dispose (GObject *object)
157 {
158     G_OBJECT_CLASS(cut_test_iterator_parent_class)->dispose(object);
159 }
160 
161 static void
set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)162 set_property (GObject      *object,
163               guint         prop_id,
164               const GValue *value,
165               GParamSpec   *pspec)
166 {
167     CutTestIteratorPrivate *priv = CUT_TEST_ITERATOR_GET_PRIVATE(object);
168 
169     switch (prop_id) {
170       case PROP_ITERATED_TEST_FUNCTION:
171         priv->iterated_test_function = g_value_get_pointer(value);
172         break;
173       case PROP_DATA_SETUP_FUNCTION:
174         priv->data_setup_function = g_value_get_pointer(value);
175         break;
176       default:
177         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
178         break;
179     }
180 }
181 
182 static void
get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)183 get_property (GObject    *object,
184               guint       prop_id,
185               GValue     *value,
186               GParamSpec *pspec)
187 {
188     CutTestIteratorPrivate *priv = CUT_TEST_ITERATOR_GET_PRIVATE(object);
189 
190     switch (prop_id) {
191       case PROP_ITERATED_TEST_FUNCTION:
192         g_value_set_pointer(value, priv->iterated_test_function);
193         break;
194       case PROP_DATA_SETUP_FUNCTION:
195         g_value_set_pointer(value, priv->data_setup_function);
196         break;
197       default:
198         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
199         break;
200     }
201 }
202 
203 CutTestIterator *
cut_test_iterator_new(const gchar * name,CutIteratedTestFunction test_function,CutDataSetupFunction data_setup_function)204 cut_test_iterator_new (const gchar *name,
205                        CutIteratedTestFunction test_function,
206                        CutDataSetupFunction data_setup_function)
207 {
208     return g_object_new(CUT_TYPE_TEST_ITERATOR,
209                         "element-name", "test-iterator",
210                         "name", name,
211                         "iterated-test-function", test_function,
212                         "data-setup-function", data_setup_function,
213                         NULL);
214 }
215 
216 CutTestIterator *
cut_test_iterator_new_empty(void)217 cut_test_iterator_new_empty (void)
218 {
219     return cut_test_iterator_new(NULL, NULL, NULL);
220 }
221 
222 CutIteratedTest *
cut_test_iterator_create_iterated_test(CutTestIterator * test_iterator,const gchar * name,CutIteratedTestFunction iterated_test_function,CutTestData * test_data)223 cut_test_iterator_create_iterated_test (CutTestIterator *test_iterator,
224                                         const gchar     *name,
225                                         CutIteratedTestFunction iterated_test_function,
226                                         CutTestData     *test_data)
227 {
228     CutTestIteratorClass *klass;
229 
230     klass = CUT_TEST_ITERATOR_GET_CLASS(test_iterator);
231     if (klass->create_iterated_test) {
232         return klass->create_iterated_test(test_iterator,
233                                            name,
234                                            iterated_test_function,
235                                            test_data);
236     } else {
237         return cut_iterated_test_new(name,
238                                      iterated_test_function,
239                                      test_data);
240     }
241 }
242 
243 void
cut_test_iterator_add_test(CutTestIterator * test_iterator,CutIteratedTest * test)244 cut_test_iterator_add_test (CutTestIterator *test_iterator,
245                             CutIteratedTest *test)
246 {
247     cut_test_container_add_test(CUT_TEST_CONTAINER(test_iterator),
248                                 CUT_TEST(test));
249 }
250 
251 typedef struct _RunTestInfo
252 {
253     CutRunContext *run_context;
254     CutTestCase *test_case;
255     CutTestIterator *test_iterator;
256     CutIteratedTest *iterated_test;
257     CutTestContext *test_context;
258     CutTestContext *parent_test_context;
259 } RunTestInfo;
260 
261 static void
run_test_without_thread(gpointer data,gpointer user_data)262 run_test_without_thread (gpointer data, gpointer user_data)
263 {
264     RunTestInfo *info = data;
265     CutRunContext *run_context;
266     CutTestCase *test_case;
267     CutTestIterator *test_iterator;
268     CutIteratedTest *iterated_test;
269     CutTestContext *test_context, *parent_test_context;
270     gint signum;
271     jmp_buf crash_jump_buffer;
272     CutCrashBacktrace *crash_backtrace = NULL;
273     gboolean *success = user_data;
274 
275     run_context = info->run_context;
276     test_case = info->test_case;
277     test_iterator = info->test_iterator;
278     iterated_test = info->iterated_test;
279     test_context = info->test_context;
280     parent_test_context = info->parent_test_context;
281 
282     if (cut_run_context_is_canceled(run_context))
283         return;
284 
285     cut_test_context_current_push(test_context);
286 
287     if (cut_run_context_is_multi_thread(run_context) ||
288         !cut_run_context_get_handle_signals(run_context)) {
289         signum = 0;
290     } else {
291         crash_backtrace = cut_crash_backtrace_new(&crash_jump_buffer);
292         signum = setjmp(crash_jump_buffer);
293     }
294     switch (signum) {
295     case 0:
296         g_signal_emit_by_name(test_iterator, "start-iterated-test",
297                               iterated_test, test_context);
298 
299         cut_test_case_run_setup(test_case, test_context);
300         if (cut_test_context_is_failed(test_context)) {
301             *success = FALSE;
302         } else {
303             if (!cut_test_run(CUT_TEST(iterated_test),
304                               test_context, run_context))
305                 *success = FALSE;
306         }
307 
308         if (crash_backtrace)
309             cut_crash_backtrace_free(crash_backtrace);
310 
311         break;
312 #ifndef G_OS_WIN32
313     case SIGSEGV:
314     case SIGABRT:
315     case SIGTERM:
316     case SIGBUS:
317         success = FALSE;
318         cut_crash_backtrace_emit(cut_run_context_get_test_suite(run_context),
319                                  test_case, CUT_TEST(iterated_test),
320                                  test_iterator,
321                                  cut_iterated_test_get_data(iterated_test),
322                                  test_context);
323         break;
324     case SIGINT:
325         cut_run_context_cancel(run_context);
326         break;
327 #endif
328     default:
329         break;
330     }
331 
332     cut_test_case_run_teardown(test_case, test_context);
333 
334     cut_test_context_set_failed(parent_test_context,
335                                 cut_test_context_is_failed(test_context));
336 
337     g_signal_emit_by_name(test_iterator, "complete-iterated-test",
338                           iterated_test, test_context, *success);
339 
340     cut_test_context_set_test(test_context, NULL);
341     cut_test_context_set_test(parent_test_context, NULL);
342     cut_test_context_current_pop();
343 
344     g_object_unref(run_context);
345     g_object_unref(test_case);
346     g_object_unref(test_iterator);
347     g_object_unref(iterated_test);
348     g_object_unref(test_context);
349     g_object_unref(parent_test_context);
350     g_free(info);
351 }
352 
353 static void
run_test_with_thread_support(CutTestIterator * test_iterator,CutIteratedTest * iterated_test,CutTestContext * test_context,CutRunContext * run_context,GThreadPool * thread_pool,gboolean * success)354 run_test_with_thread_support (CutTestIterator *test_iterator,
355                               CutIteratedTest *iterated_test,
356                               CutTestContext *test_context,
357                               CutRunContext *run_context,
358                               GThreadPool *thread_pool,
359                               gboolean *success)
360 {
361     RunTestInfo *info;
362     CutTest *test;
363     CutTestCase *test_case;
364     CutTestContext *local_test_context;
365     gboolean is_multi_thread;
366     gboolean need_no_thread_run = TRUE;
367     const gchar *multi_thread_attribute;
368 
369     if (cut_run_context_is_canceled(run_context))
370         return;
371 
372     test = CUT_TEST(iterated_test);
373     test_case = cut_test_context_get_test_case(test_context);
374     local_test_context = cut_test_context_new(run_context,
375                                               NULL, test_case, test_iterator,
376                                               test);
377     is_multi_thread = cut_run_context_is_multi_thread(run_context);
378     multi_thread_attribute = cut_test_get_attribute(CUT_TEST(test_iterator),
379                                                     "multi-thread");
380     if (multi_thread_attribute &&
381         g_str_equal(multi_thread_attribute, "false"))
382         is_multi_thread = FALSE;
383     cut_test_context_set_multi_thread(local_test_context, is_multi_thread);
384     cut_test_context_set_data(local_test_context,
385                               cut_iterated_test_get_data(iterated_test));
386 
387     cut_test_context_set_test(test_context, test);
388     cut_test_context_set_test(local_test_context, test);
389 
390     info = g_new0(RunTestInfo, 1);
391     info->run_context = g_object_ref(run_context);
392     info->test_case = g_object_ref(test_case);
393     info->test_iterator = g_object_ref(test_iterator);
394     info->iterated_test = g_object_ref(iterated_test);
395     info->test_context = local_test_context;
396     info->parent_test_context = g_object_ref(test_context);
397     if (is_multi_thread && thread_pool) {
398         GError *error = NULL;
399 
400         g_thread_pool_push(thread_pool, info, &error);
401         if (error) {
402             cut_utils_report_error(error);
403         } else {
404             need_no_thread_run = FALSE;
405         }
406     }
407 
408     if (need_no_thread_run)
409         run_test_without_thread(info, success);
410 }
411 
412 static void
cb_test_status(CutTest * test,CutTestContext * context,CutTestResult * result,gpointer data)413 cb_test_status (CutTest *test, CutTestContext *context, CutTestResult *result,
414                 gpointer data)
415 {
416     CutTestResultStatus *status = data;
417 
418     *status = MAX(*status, cut_test_result_get_status(result));
419 }
420 
421 static void
run_iterated_tests(CutTest * test,CutTestContext * test_context,CutRunContext * run_context,CutTestResultStatus * status,gboolean * all_success)422 run_iterated_tests (CutTest *test, CutTestContext *test_context,
423                     CutRunContext *run_context,
424                     CutTestResultStatus *status, gboolean *all_success)
425 {
426     CutTestIteratorPrivate *priv;
427     CutTestIterator *test_iterator;
428     CutTestContainer *test_container;
429     GError *error = NULL;
430     GList *node, *iterated_tests = NULL, *filtered_tests = NULL;
431     const gchar **test_names;
432     guint n_tests;
433     GThreadPool *thread_pool = NULL;
434 
435     thread_pool = g_thread_pool_new(run_test_without_thread,
436                                     all_success,
437                                     cut_run_context_get_max_threads(run_context),
438                                     FALSE,
439                                     &error);
440     if (error) {
441         cut_utils_report_error(error);
442         return;
443     }
444 
445     priv = CUT_TEST_ITERATOR_GET_PRIVATE(test);
446     test_iterator = CUT_TEST_ITERATOR(test);
447     while (cut_test_context_have_data(test_context)) {
448         CutIteratedTest *iterated_test;
449         CutTestData *test_data;
450 
451         test_data = cut_test_context_get_current_data(test_context);
452         iterated_test =
453             cut_test_iterator_create_iterated_test(test_iterator,
454                                                    cut_test_get_name(test),
455                                                    priv->iterated_test_function,
456                                                    test_data);
457         cut_test_iterator_add_test(test_iterator, iterated_test);
458         g_object_unref(iterated_test);
459 
460         iterated_tests = g_list_prepend(iterated_tests, iterated_test);
461 
462         g_signal_connect(iterated_test, "success",
463                          G_CALLBACK(cb_test_status), status);
464         g_signal_connect(iterated_test, "failure",
465                          G_CALLBACK(cb_test_status), status);
466         g_signal_connect(iterated_test, "error",
467                          G_CALLBACK(cb_test_status), status);
468         g_signal_connect(iterated_test, "pending",
469                          G_CALLBACK(cb_test_status), status);
470         g_signal_connect(iterated_test, "notification",
471                          G_CALLBACK(cb_test_status), status);
472         g_signal_connect(iterated_test, "omission",
473                          G_CALLBACK(cb_test_status), status);
474 
475         cut_test_context_shift_data(test_context);
476     }
477 
478     test_container = CUT_TEST_CONTAINER(test);
479     test_names = cut_run_context_get_target_test_names(run_context);
480     filtered_tests = cut_test_container_filter_children(test_container,
481                                                         test_names);
482 
483     n_tests = cut_test_container_get_n_tests(test_container, run_context);
484     g_signal_emit_by_name(test_iterator, "ready", n_tests);
485     g_signal_emit_by_name(test, "start", NULL);
486 
487     for (node = filtered_tests; node; node = g_list_next(node)) {
488         CutIteratedTest *iterated_test = node->data;
489 
490         run_test_with_thread_support(test_iterator, iterated_test,
491                                      test_context, run_context,
492                                      thread_pool, all_success);
493     }
494     g_list_free(filtered_tests);
495 
496     if (thread_pool)
497         g_thread_pool_free(thread_pool, FALSE, TRUE);
498 
499     for (node = iterated_tests; node; node = g_list_next(node)) {
500         CutIteratedTest *iterated_test = node->data;
501 
502         g_signal_handlers_disconnect_by_func(iterated_test,
503                                              G_CALLBACK(cb_test_status),
504                                              status);
505         cut_iterated_test_clear_data(iterated_test);
506     }
507     g_list_free(iterated_tests);
508 }
509 
510 static gboolean
run(CutTest * test,CutTestContext * test_context,CutRunContext * run_context)511 run (CutTest *test, CutTestContext *test_context, CutRunContext *run_context)
512 {
513     CutTestIterator *test_iterator;
514     CutTestIteratorPrivate *priv;
515     CutTestResult *result;
516     CutTestCase *test_case;
517     CutTestResultStatus status = CUT_TEST_RESULT_SUCCESS;
518     gboolean all_success = TRUE;
519     jmp_buf jump_buffer;
520 
521     test_iterator = CUT_TEST_ITERATOR(test);
522     g_return_val_if_fail(CUT_IS_TEST_ITERATOR(test_iterator), FALSE);
523 
524     priv = CUT_TEST_ITERATOR_GET_PRIVATE(test_iterator);
525     g_return_val_if_fail(priv->data_setup_function != NULL, FALSE);
526 
527     cut_test_context_set_test_iterator(test_context, test_iterator);
528     if (priv->data_setup_function) {
529         cut_test_context_set_jump_buffer(test_context, &jump_buffer);
530         if (setjmp(jump_buffer) == 0) {
531             priv->data_setup_function();
532         }
533     }
534 
535     if (cut_test_context_is_failed(test_context)) {
536         cut_test_context_set_test_iterator(test_context, NULL);
537         return FALSE;
538     }
539 
540     run_iterated_tests(test, test_context, run_context,
541                        &status, &all_success);
542 
543     test_case = cut_test_context_get_test_case(test_context);
544     result = cut_test_result_new(status,
545                                  NULL, test_iterator, test_case, NULL, NULL,
546                                  NULL, NULL, NULL);
547     cut_test_emit_result_signal(CUT_TEST(test_iterator), test_context, result);
548     g_object_unref(result);
549     g_signal_emit_by_name(test, "complete", NULL, all_success);
550 
551     cut_test_context_set_test_iterator(test_context, NULL);
552 
553     return all_success;
554 }
555 
556 
557 /*
558 vi:nowrap:ai:expandtab:sw=4
559 */
560