1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  *  Copyright (C) 2007-2014  Kouhei Sutou <kou@clear-code.com>
4  *  Copyright (C) 2014  Kazuhiro Yamato <kz0817@gmail.com>
5  *
6  *  This library is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU Lesser General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif /* HAVE_CONFIG_H */
24 
25 #include <stdlib.h>
26 #include <string.h>
27 #include <signal.h>
28 
29 #include <glib.h>
30 
31 #include "cut-test.h"
32 #include "cut-test-container.h"
33 #include "cut-run-context.h"
34 #include "cut-test-result.h"
35 #include "cut-utils.h"
36 #include "cut-crash-backtrace.h"
37 
38 #include <gcutter/gcut-marshalers.h>
39 
40 #ifdef ERROR
41 #  undef ERROR /* for Windows */
42 #endif
43 
44 #define CUT_TEST_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CUT_TYPE_TEST, CutTestPrivate))
45 
46 typedef struct _CutTestPrivate	CutTestPrivate;
47 struct _CutTestPrivate
48 {
49     gchar *name;
50     gchar *full_name;
51     gchar *element_name;
52     CutTestFunction test_function;
53     GTimer *timer;
54     GTimeVal start_time;
55     gdouble elapsed;
56     GHashTable *attributes;
57     gchar *base_directory;
58     jmp_buf *jump_buffer;
59 };
60 
61 enum
62 {
63     PROP_0,
64     PROP_NAME,
65     PROP_ELEMENT_NAME,
66     PROP_TEST_FUNCTION,
67     PROP_BASE_DIRECTORY
68 };
69 
70 enum
71 {
72     START,
73     PASS_ASSERTION,
74     SUCCESS,
75     FAILURE,
76     ERROR,
77     PENDING,
78     NOTIFICATION,
79     OMISSION,
80     CRASH,
81     COMPLETE,
82     LAST_SIGNAL
83 };
84 
85 static gint cut_test_signals[LAST_SIGNAL] = {0};
86 
87 G_DEFINE_TYPE (CutTest, cut_test, G_TYPE_OBJECT)
88 
89 static void dispose        (GObject         *object);
90 static void set_property   (GObject         *object,
91                             guint            prop_id,
92                             const GValue    *value,
93                             GParamSpec      *pspec);
94 static void get_property   (GObject         *object,
95                             guint            prop_id,
96                             GValue          *value,
97                             GParamSpec      *pspec);
98 
99 static void         start        (CutTest  *test,
100                                   CutTestContext *test_context);
101 static gdouble      get_elapsed  (CutTest  *test);
102 static void         set_elapsed  (CutTest  *test, gdouble elapsed);
103 static gboolean     run          (CutTest        *test,
104                                   CutTestContext *test_context,
105                                   CutRunContext  *run_context);
106 static void         long_jump    (CutTest        *test,
107                                   jmp_buf        *jump_buffer,
108                                   gint            value);
109 static gboolean     is_available (CutTest        *test,
110                                   CutTestContext *test_context,
111                                   CutRunContext  *run_context);
112 static void         invoke       (CutTest        *test,
113                                   CutTestContext *test_context,
114                                   CutRunContext  *run_context);
115 static void         emit_result_signal
116                                  (CutTest        *test,
117                                   CutTestContext *test_context,
118                                   CutTestResult  *result);
119 
120 static void
cut_test_class_init(CutTestClass * klass)121 cut_test_class_init (CutTestClass *klass)
122 {
123     GObjectClass *gobject_class;
124     GParamSpec *spec;
125 
126     gobject_class = G_OBJECT_CLASS(klass);
127 
128     gobject_class->dispose      = dispose;
129     gobject_class->set_property = set_property;
130     gobject_class->get_property = get_property;
131 
132     klass->start = start;
133     klass->get_elapsed = get_elapsed;
134     klass->set_elapsed = set_elapsed;
135     klass->run = run;
136     klass->long_jump = long_jump;
137     klass->is_available = is_available;
138     klass->invoke = invoke;
139     klass->emit_result_signal = emit_result_signal;
140 
141     spec = g_param_spec_string("name",
142                                "Test name",
143                                "The name of the test",
144                                NULL,
145                                G_PARAM_READWRITE);
146     g_object_class_install_property(gobject_class, PROP_NAME, spec);
147 
148     spec = g_param_spec_string("element-name",
149                                "Element name",
150                                "The element name of the test",
151                                NULL,
152                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
153     g_object_class_install_property(gobject_class, PROP_ELEMENT_NAME, spec);
154 
155     spec = g_param_spec_pointer("test-function",
156                                 "Test Function",
157                                 "The function for test",
158                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
159     g_object_class_install_property(gobject_class, PROP_TEST_FUNCTION, spec);
160 
161     spec = g_param_spec_string("base-directory",
162                                "Base directory",
163                                "The base directory of the test",
164                                NULL,
165                                G_PARAM_READWRITE);
166     g_object_class_install_property(gobject_class, PROP_BASE_DIRECTORY, spec);
167 
168     cut_test_signals[START]
169         = g_signal_new ("start",
170                         G_TYPE_FROM_CLASS (klass),
171                         G_SIGNAL_RUN_FIRST,
172                         G_STRUCT_OFFSET (CutTestClass, start),
173                         NULL, NULL,
174                         g_cclosure_marshal_VOID__OBJECT,
175                         G_TYPE_NONE, 1, CUT_TYPE_TEST_CONTEXT);
176 
177     cut_test_signals[PASS_ASSERTION]
178         = g_signal_new ("pass-assertion",
179                         G_TYPE_FROM_CLASS (klass),
180                         G_SIGNAL_RUN_LAST,
181                         G_STRUCT_OFFSET (CutTestClass, pass_assertion),
182                         NULL, NULL,
183                         g_cclosure_marshal_VOID__OBJECT,
184                         G_TYPE_NONE, 1, CUT_TYPE_TEST_CONTEXT);
185 
186     cut_test_signals[SUCCESS]
187         = g_signal_new ("success",
188                         G_TYPE_FROM_CLASS (klass),
189                         G_SIGNAL_RUN_LAST,
190                         G_STRUCT_OFFSET (CutTestClass, success),
191                         NULL, NULL,
192                         _gcut_marshal_VOID__OBJECT_OBJECT,
193                         G_TYPE_NONE, 2,
194                         CUT_TYPE_TEST_CONTEXT, CUT_TYPE_TEST_RESULT);
195 
196     cut_test_signals[FAILURE]
197         = g_signal_new ("failure",
198                         G_TYPE_FROM_CLASS (klass),
199                         G_SIGNAL_RUN_LAST,
200                         G_STRUCT_OFFSET (CutTestClass, failure),
201                         NULL, NULL,
202                         _gcut_marshal_VOID__OBJECT_OBJECT,
203                         G_TYPE_NONE, 2,
204                         CUT_TYPE_TEST_CONTEXT, CUT_TYPE_TEST_RESULT);
205 
206     cut_test_signals[ERROR]
207         = g_signal_new ("error",
208                         G_TYPE_FROM_CLASS (klass),
209                         G_SIGNAL_RUN_LAST,
210                         G_STRUCT_OFFSET (CutTestClass, error),
211                         NULL, NULL,
212                         _gcut_marshal_VOID__OBJECT_OBJECT,
213                         G_TYPE_NONE, 2,
214                         CUT_TYPE_TEST_CONTEXT, CUT_TYPE_TEST_RESULT);
215 
216     cut_test_signals[PENDING]
217         = g_signal_new ("pending",
218                         G_TYPE_FROM_CLASS (klass),
219                         G_SIGNAL_RUN_LAST,
220                         G_STRUCT_OFFSET (CutTestClass, pending),
221                         NULL, NULL,
222                         _gcut_marshal_VOID__OBJECT_OBJECT,
223                         G_TYPE_NONE, 2,
224                         CUT_TYPE_TEST_CONTEXT, CUT_TYPE_TEST_RESULT);
225 
226     cut_test_signals[NOTIFICATION]
227         = g_signal_new ("notification",
228                         G_TYPE_FROM_CLASS (klass),
229                         G_SIGNAL_RUN_LAST,
230                         G_STRUCT_OFFSET (CutTestClass, notification),
231                         NULL, NULL,
232                         _gcut_marshal_VOID__OBJECT_OBJECT,
233                         G_TYPE_NONE, 2,
234                         CUT_TYPE_TEST_CONTEXT, CUT_TYPE_TEST_RESULT);
235 
236     cut_test_signals[OMISSION]
237         = g_signal_new("omission",
238                         G_TYPE_FROM_CLASS(klass),
239                         G_SIGNAL_RUN_LAST,
240                         G_STRUCT_OFFSET(CutTestClass, omission),
241                         NULL, NULL,
242                         _gcut_marshal_VOID__OBJECT_OBJECT,
243                         G_TYPE_NONE, 2,
244                         CUT_TYPE_TEST_CONTEXT, CUT_TYPE_TEST_RESULT);
245 
246     cut_test_signals[CRASH]
247         = g_signal_new("crash",
248                         G_TYPE_FROM_CLASS(klass),
249                         G_SIGNAL_RUN_LAST,
250                         G_STRUCT_OFFSET(CutTestClass, crash),
251                         NULL, NULL,
252                         _gcut_marshal_VOID__OBJECT_OBJECT,
253                         G_TYPE_NONE, 2,
254                         CUT_TYPE_TEST_CONTEXT, CUT_TYPE_TEST_RESULT);
255 
256     cut_test_signals[COMPLETE]
257         = g_signal_new("complete",
258                        G_TYPE_FROM_CLASS (klass),
259                        G_SIGNAL_RUN_LAST,
260                        G_STRUCT_OFFSET (CutTestClass, complete),
261                        NULL, NULL,
262                        _gcut_marshal_VOID__OBJECT_BOOLEAN,
263                        G_TYPE_NONE, 2, CUT_TYPE_TEST_CONTEXT, G_TYPE_BOOLEAN);
264 
265 
266     g_type_class_add_private(gobject_class, sizeof(CutTestPrivate));
267 }
268 
269 static void
cut_test_init(CutTest * test)270 cut_test_init (CutTest *test)
271 {
272     CutTestPrivate *priv = CUT_TEST_GET_PRIVATE(test);
273 
274     priv->full_name = NULL;
275 
276     priv->test_function = NULL;
277     priv->timer = NULL;
278     priv->start_time.tv_sec = 0;
279     priv->start_time.tv_usec = 0;
280     priv->elapsed = -1.0;
281     priv->attributes = g_hash_table_new_full(g_str_hash, g_str_equal,
282                                              g_free, g_free);
283     priv->jump_buffer = NULL;
284 }
285 
286 static void
free_full_name(CutTestPrivate * priv)287 free_full_name (CutTestPrivate *priv)
288 {
289     if (priv->full_name) {
290         g_free(priv->full_name);
291         priv->full_name = NULL;
292     }
293 }
294 
295 static void
dispose(GObject * object)296 dispose (GObject *object)
297 {
298     CutTestPrivate *priv = CUT_TEST_GET_PRIVATE(object);
299 
300     if (priv->name) {
301         g_free(priv->name);
302         priv->name = NULL;
303     }
304 
305     if (priv->element_name) {
306         g_free(priv->element_name);
307         priv->element_name = NULL;
308     }
309 
310     priv->test_function = NULL;
311 
312     if (priv->timer) {
313         g_timer_destroy(priv->timer);
314         priv->timer = NULL;
315     }
316 
317     if (priv->attributes) {
318         g_hash_table_unref(priv->attributes);
319         priv->attributes = NULL;
320     }
321 
322     free_full_name(priv);
323 
324     if (priv->base_directory) {
325         g_free(priv->base_directory);
326         priv->base_directory = NULL;
327     }
328 
329     G_OBJECT_CLASS(cut_test_parent_class)->dispose(object);
330 }
331 
332 static void
set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)333 set_property (GObject      *object,
334               guint         prop_id,
335               const GValue *value,
336               GParamSpec   *pspec)
337 {
338     CutTestPrivate *priv = CUT_TEST_GET_PRIVATE(object);
339 
340     switch (prop_id) {
341       case PROP_NAME:
342         cut_test_set_name(CUT_TEST(object), g_value_get_string(value));
343         break;
344       case PROP_ELEMENT_NAME:
345         if (priv->element_name) {
346             g_free(priv->element_name);
347             priv->element_name = NULL;
348         }
349         priv->element_name = g_value_dup_string(value);
350         break;
351       case PROP_TEST_FUNCTION:
352         priv->test_function = g_value_get_pointer(value);
353         break;
354       case PROP_BASE_DIRECTORY:
355         cut_test_set_base_directory(CUT_TEST(object), g_value_get_string(value));
356         break;
357       default:
358         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
359         break;
360     }
361 }
362 
363 static void
get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)364 get_property (GObject    *object,
365               guint       prop_id,
366               GValue     *value,
367               GParamSpec *pspec)
368 {
369     CutTestPrivate *priv = CUT_TEST_GET_PRIVATE(object);
370 
371     switch (prop_id) {
372       case PROP_NAME:
373         g_value_set_string(value, priv->name);
374         break;
375       case PROP_ELEMENT_NAME:
376         g_value_set_string(value, priv->element_name);
377         break;
378       case PROP_TEST_FUNCTION:
379         g_value_set_pointer(value, priv->test_function);
380         break;
381       case PROP_BASE_DIRECTORY:
382         g_value_set_string(value, priv->base_directory);
383         break;
384       default:
385         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
386         break;
387     }
388 }
389 
390 CutTest *
cut_test_new(const gchar * name,CutTestFunction function)391 cut_test_new (const gchar *name, CutTestFunction function)
392 {
393     return g_object_new(CUT_TYPE_TEST,
394                         "element-name", "test",
395                         "name", name,
396                         "test-function", function,
397                         NULL);
398 }
399 
400 CutTest *
cut_test_new_empty(void)401 cut_test_new_empty (void)
402 {
403     return cut_test_new(NULL, NULL);
404 }
405 
406 static void
start(CutTest * test,CutTestContext * test_context)407 start (CutTest *test, CutTestContext *test_context)
408 {
409     GTimeVal value;
410     g_get_current_time(&value);
411     cut_test_set_start_time(test, &value);
412 }
413 
414 static gboolean
is_available(CutTest * test,CutTestContext * test_context,CutRunContext * run_context)415 is_available (CutTest *test, CutTestContext *test_context,
416               CutRunContext *run_context)
417 {
418     return CUT_TEST_GET_PRIVATE(test)->test_function != NULL;
419 }
420 
421 static void
invoke(CutTest * test,CutTestContext * test_context,CutRunContext * run_context)422 invoke (CutTest *test, CutTestContext *test_context, CutRunContext *run_context)
423 {
424     CutTestPrivate *priv;
425 
426     priv = CUT_TEST_GET_PRIVATE(test);
427     if (cut_run_context_get_stop_before_test(run_context))
428         G_BREAKPOINT();
429     priv->test_function();
430 }
431 
432 static gboolean
run(CutTest * test,CutTestContext * test_context,CutRunContext * run_context)433 run (CutTest *test, CutTestContext *test_context, CutRunContext *run_context)
434 {
435     CutTestClass *klass;
436     CutTestPrivate *priv;
437     gboolean success = TRUE;
438     jmp_buf jump_buffer;
439     CutTestCase *test_case;
440     CutTestIterator *test_iterator;
441     CutTestResult *result;
442     CutTestData *data = NULL;
443     gint signum;
444     jmp_buf crash_jump_buffer;
445     CutCrashBacktrace *crash_backtrace = NULL;
446 
447     priv = CUT_TEST_GET_PRIVATE(test);
448     klass = CUT_TEST_GET_CLASS(test);
449 
450     priv->jump_buffer = &jump_buffer;
451 
452     if (!klass->is_available(test, test_context, run_context)) {
453         priv->jump_buffer = NULL;
454         return FALSE;
455     }
456 
457     test_case = cut_test_context_get_test_case(test_context);
458     test_iterator = cut_test_context_get_test_iterator(test_context);
459     if (CUT_IS_ITERATED_TEST(test))
460         data = cut_iterated_test_get_data(CUT_ITERATED_TEST(test));
461 
462     if (cut_run_context_is_multi_thread(run_context) ||
463         !cut_run_context_get_handle_signals(run_context)) {
464         signum = 0;
465     } else {
466         crash_backtrace = cut_crash_backtrace_new(&crash_jump_buffer);
467         signum = setjmp(crash_jump_buffer);
468     }
469     switch (signum) {
470     case 0:
471         g_signal_emit_by_name(test, "start", test_context);
472 
473         cut_test_context_set_jump_buffer(test_context, &jump_buffer);
474         if (setjmp(jump_buffer) == 0) {
475             if (priv->timer) {
476                 g_timer_start(priv->timer);
477             } else {
478                 priv->timer = g_timer_new();
479             }
480             klass->invoke(test, test_context, run_context);
481         }
482         g_timer_stop(priv->timer);
483 
484         success = !cut_test_context_is_failed(test_context);
485 
486         if (crash_backtrace)
487             cut_crash_backtrace_free(crash_backtrace);
488         break;
489 #ifndef G_OS_WIN32
490     case SIGSEGV:
491     case SIGABRT:
492     case SIGTERM:
493     case SIGBUS:
494         success = FALSE;
495         {
496             CutTestSuite *test_suite;
497             test_suite = cut_test_context_get_test_suite(test_context);
498             cut_crash_backtrace_emit(test_suite, test_case,
499                                      test, test_iterator, data,
500                                      test_context);
501         }
502         break;
503     case SIGINT:
504         cut_run_context_cancel(run_context);
505         break;
506 #endif
507     default:
508         break;
509     }
510 
511     if (success) {
512         result = cut_test_result_new(CUT_TEST_RESULT_SUCCESS,
513                                      test, test_iterator, test_case,
514                                      NULL, data,
515                                      NULL, NULL, NULL);
516         cut_test_emit_result_signal(test, test_context, result);
517         g_object_unref(result);
518     }
519 
520     g_signal_emit_by_name(test, "complete", test_context, success);
521 
522     priv->jump_buffer = NULL;
523 
524     return success;
525 }
526 
527 static void
long_jump(CutTest * test,jmp_buf * jump_buffer,gint value)528 long_jump (CutTest *test, jmp_buf *jump_buffer, gint value)
529 {
530     longjmp(*jump_buffer, value);
531 }
532 
533 gboolean
cut_test_run(CutTest * test,CutTestContext * test_context,CutRunContext * run_context)534 cut_test_run (CutTest *test, CutTestContext *test_context,
535               CutRunContext *run_context)
536 {
537     return CUT_TEST_GET_CLASS(test)->run(test, test_context, run_context);
538 }
539 
540 void
cut_test_long_jump(CutTest * test,jmp_buf * jump_buffer,gint value)541 cut_test_long_jump (CutTest *test, jmp_buf *jump_buffer, gint value)
542 {
543     return CUT_TEST_GET_CLASS(test)->long_jump(test, jump_buffer, value);
544 }
545 
546 gboolean
cut_test_is_own_jump_buffer(CutTest * test,jmp_buf * jump_buffer)547 cut_test_is_own_jump_buffer (CutTest *test, jmp_buf *jump_buffer)
548 {
549     CutTestPrivate *priv = CUT_TEST_GET_PRIVATE(test);
550 
551     return priv->jump_buffer == jump_buffer;
552 }
553 
554 const gchar *
cut_test_get_name(CutTest * test)555 cut_test_get_name (CutTest *test)
556 {
557     return CUT_TEST_GET_PRIVATE(test)->name;
558 }
559 
560 void
cut_test_set_name(CutTest * test,const gchar * name)561 cut_test_set_name (CutTest *test, const gchar *name)
562 {
563     CutTestPrivate *priv = CUT_TEST_GET_PRIVATE(test);
564 
565     if (priv->name) {
566         g_free(priv->name);
567         priv->name = NULL;
568     }
569 
570     free_full_name(priv);
571 
572     if (name)
573         priv->name = g_strdup(name);
574 }
575 
576 const gchar *
cut_test_get_full_name(CutTest * test)577 cut_test_get_full_name (CutTest *test)
578 {
579     CutTestPrivate *priv;
580     CutTestData *data = NULL;
581 
582     priv = CUT_TEST_GET_PRIVATE(test);
583     if (priv->full_name)
584         return priv->full_name;
585 
586     if (CUT_IS_ITERATED_TEST(test))
587         data = cut_iterated_test_get_data(CUT_ITERATED_TEST(test));
588 
589     if (priv->name && data) {
590         priv->full_name = g_strconcat(priv->name,
591                                       " (",
592                                       cut_test_data_get_name(data),
593                                       ")",
594                                       NULL);
595     } else if (priv->name) {
596         priv->full_name = g_strdup(priv->name);
597     } else if (data) {
598         priv->full_name = g_strconcat(" (",
599                                       cut_test_data_get_name(data),
600                                       ")",
601                                       NULL);
602     }
603 
604     return priv->full_name;
605 }
606 
607 const gchar *
cut_test_get_description(CutTest * test)608 cut_test_get_description (CutTest *test)
609 {
610     return cut_test_get_attribute(test, "description");
611 }
612 
613 void
cut_test_get_start_time(CutTest * test,GTimeVal * start_time)614 cut_test_get_start_time (CutTest *test, GTimeVal *start_time)
615 {
616     memcpy(start_time, &(CUT_TEST_GET_PRIVATE(test)->start_time),
617            sizeof(GTimeVal));
618 }
619 
620 void
cut_test_set_start_time(CutTest * test,GTimeVal * start_time)621 cut_test_set_start_time (CutTest *test, GTimeVal *start_time)
622 {
623     memcpy(&(CUT_TEST_GET_PRIVATE(test)->start_time), start_time,
624            sizeof(GTimeVal));
625 }
626 
627 static gdouble
get_elapsed(CutTest * test)628 get_elapsed (CutTest *test)
629 {
630     CutTestPrivate *priv = CUT_TEST_GET_PRIVATE(test);
631 
632     if (!(priv->elapsed < 0.0))
633         return priv->elapsed;
634 
635     if (priv->timer)
636         return g_timer_elapsed(priv->timer, NULL);
637     else
638         return 0.0;
639 }
640 
641 gdouble
cut_test_get_elapsed(CutTest * test)642 cut_test_get_elapsed (CutTest *test)
643 {
644     return CUT_TEST_GET_CLASS(test)->get_elapsed(test);
645 }
646 
647 static void
set_elapsed(CutTest * test,gdouble elapsed)648 set_elapsed (CutTest *test, gdouble elapsed)
649 {
650     CUT_TEST_GET_PRIVATE(test)->elapsed = elapsed;
651 }
652 
653 void
cut_test_set_elapsed(CutTest * test,gdouble elapsed)654 cut_test_set_elapsed (CutTest *test, gdouble elapsed)
655 {
656     CUT_TEST_GET_CLASS(test)->set_elapsed(test, elapsed);
657 }
658 
659 const gchar *
cut_test_get_attribute(CutTest * test,const gchar * name)660 cut_test_get_attribute (CutTest *test, const gchar *name)
661 {
662     return g_hash_table_lookup(CUT_TEST_GET_PRIVATE(test)->attributes, name);
663 }
664 
665 void
cut_test_set_attribute(CutTest * test,const gchar * name,const gchar * value)666 cut_test_set_attribute (CutTest *test, const gchar *name, const gchar *value)
667 {
668     g_hash_table_replace(CUT_TEST_GET_PRIVATE(test)->attributes,
669                          g_strdup(name),
670                          g_strdup(value));
671 }
672 
673 GHashTable *
cut_test_get_attributes(CutTest * test)674 cut_test_get_attributes (CutTest *test)
675 {
676     return CUT_TEST_GET_PRIVATE(test)->attributes;
677 }
678 
679 const gchar *
cut_test_get_base_directory(CutTest * test)680 cut_test_get_base_directory (CutTest *test)
681 {
682     return CUT_TEST_GET_PRIVATE(test)->base_directory;
683 }
684 
685 void
cut_test_set_base_directory(CutTest * test,const gchar * base_directory)686 cut_test_set_base_directory (CutTest *test, const gchar *base_directory)
687 {
688     CutTestPrivate *priv = CUT_TEST_GET_PRIVATE(test);
689 
690     if (priv->base_directory) {
691         g_free(priv->base_directory);
692         priv->base_directory = NULL;
693     }
694     priv->base_directory = g_strdup(base_directory);
695 }
696 
697 gchar *
cut_test_to_xml(CutTest * test)698 cut_test_to_xml (CutTest *test)
699 {
700     GString *string;
701 
702     string = g_string_new(NULL);
703     cut_test_to_xml_string(test, string, 0);
704     return g_string_free(string, FALSE);
705 }
706 
707 typedef struct _AppendAttributeInfo {
708     GString *string;
709     guint indent;
710 } AppendAttributeInfo;
711 
712 static void
append_attribute(const gchar * key,const gchar * value,AppendAttributeInfo * info)713 append_attribute (const gchar *key, const gchar *value,
714                   AppendAttributeInfo *info)
715 {
716     if (strcmp(key, "description") == 0)
717         return;
718 
719     cut_utils_append_indent(info->string, info->indent);
720     g_string_append(info->string, "<option>\n");
721 
722     cut_utils_append_xml_element_with_value(info->string, info->indent + 2,
723                                             "name", key);
724     cut_utils_append_xml_element_with_value(info->string, info->indent + 2,
725                                             "value", value);
726 
727     cut_utils_append_indent(info->string, info->indent);
728     g_string_append(info->string, "</option>\n");
729 }
730 
731 void
cut_test_to_xml_string(CutTest * test,GString * string,guint indent)732 cut_test_to_xml_string (CutTest *test, GString *string, guint indent)
733 {
734     CutTestPrivate *priv;
735     gchar *escaped, *start_time, *elapsed;
736     const gchar *description, *name;
737     GHashTable *attributes;
738 
739     priv = CUT_TEST_GET_PRIVATE(test);
740 
741     escaped = g_markup_escape_text(priv->element_name, -1);
742     cut_utils_append_indent(string, indent);
743     g_string_append_printf(string, "<%s>\n", escaped);
744 
745     name = cut_test_get_name(test);
746     if (name)
747         cut_utils_append_xml_element_with_value(string, indent + 2,
748                                                 "name", name);
749 
750     description = cut_test_get_description(test);
751     if (description)
752         cut_utils_append_xml_element_with_value(string, indent + 2,
753                                                 "description", description);
754 
755     start_time = g_time_val_to_iso8601(&(priv->start_time));
756     cut_utils_append_xml_element_with_value(string, indent + 2,
757                                             "start-time", start_time);
758     g_free(start_time);
759 
760     elapsed = cut_utils_double_to_string(cut_test_get_elapsed(test));
761     cut_utils_append_xml_element_with_value(string, indent + 2,
762                                             "elapsed", elapsed);
763     g_free(elapsed);
764 
765     attributes = cut_test_get_attributes(test);
766     if (attributes) {
767         AppendAttributeInfo info;
768 
769         info.string = string;
770         info.indent = indent + 2;
771         g_hash_table_foreach(attributes, (GHFunc)append_attribute, &info);
772     }
773 
774     cut_utils_append_indent(string, indent);
775     g_string_append_printf(string, "</%s>\n", escaped);
776     g_free(escaped);
777 }
778 
779 void
cut_test_set_result_elapsed(CutTest * test,CutTestResult * result)780 cut_test_set_result_elapsed (CutTest *test, CutTestResult *result)
781 {
782     CutTestPrivate *priv;
783 
784     priv = CUT_TEST_GET_PRIVATE(test);
785     cut_test_result_set_start_time(result, &(priv->start_time));
786     cut_test_result_set_elapsed(result, cut_test_get_elapsed(test));
787 }
788 
789 static void
emit_result_signal(CutTest * test,CutTestContext * test_context,CutTestResult * result)790 emit_result_signal (CutTest *test,
791                     CutTestContext *test_context,
792                     CutTestResult *result)
793 {
794     const gchar *status_signal_name = NULL;
795     CutTestResultStatus status;
796 
797     status = cut_test_result_get_status(result);
798     status_signal_name = cut_test_result_status_to_signal_name(status);
799     g_signal_emit_by_name(test, status_signal_name, test_context, result);
800 }
801 
802 void
cut_test_emit_result_signal(CutTest * test,CutTestContext * test_context,CutTestResult * result)803 cut_test_emit_result_signal (CutTest *test,
804                              CutTestContext *test_context,
805                              CutTestResult *result)
806 {
807     cut_test_set_result_elapsed(test, result);
808 
809     CUT_TEST_GET_CLASS(test)->emit_result_signal(test, test_context, result);
810 }
811 
812 /*
813 vi:ts=4:nowrap:ai:expandtab:sw=4
814 */
815