1 /*
2  * Check: a unit test framework for C
3  * Copyright (C) 2001, 2002 Arien Malec
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (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 GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
18  * MA 02110-1301, USA.
19  */
20 
21 #include "../lib/libcompat.h"
22 
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <check.h>
26 #if ENABLE_SUBUNIT
27 #include <subunit/child.h>
28 #endif
29 
30 #include "check_error.h"
31 #include "check_list.h"
32 #include "check_impl.h"
33 #include "check_log.h"
34 #include "check_print.h"
35 #include "check_str.h"
36 
37 /*
38  * If a log file is specified to be "-", then instead of
39  * opening a file the log output is printed to stdout.
40  */
41 #define STDOUT_OVERRIDE_LOG_FILE_NAME "-"
42 
43 static void srunner_send_evt(SRunner * sr, void *obj, enum cl_event evt);
44 
srunner_set_log(SRunner * sr,const char * fname)45 void srunner_set_log(SRunner * sr, const char *fname)
46 {
47     if(sr->log_fname)
48         return;
49     sr->log_fname = fname;
50 }
51 
srunner_has_log(SRunner * sr)52 int srunner_has_log(SRunner * sr)
53 {
54     return srunner_log_fname(sr) != NULL;
55 }
56 
srunner_log_fname(SRunner * sr)57 const char *srunner_log_fname(SRunner * sr)
58 {
59     /* check if log filename have been set explicitly */
60     if(sr->log_fname != NULL)
61         return sr->log_fname;
62 
63     return getenv("CK_LOG_FILE_NAME");
64 }
65 
66 
srunner_set_xml(SRunner * sr,const char * fname)67 void srunner_set_xml(SRunner * sr, const char *fname)
68 {
69     if(sr->xml_fname)
70         return;
71     sr->xml_fname = fname;
72 }
73 
srunner_has_xml(SRunner * sr)74 int srunner_has_xml(SRunner * sr)
75 {
76     return srunner_xml_fname(sr) != NULL;
77 }
78 
srunner_xml_fname(SRunner * sr)79 const char *srunner_xml_fname(SRunner * sr)
80 {
81     /* check if XML log filename have been set explicitly */
82     if(sr->xml_fname != NULL)
83     {
84         return sr->xml_fname;
85     }
86 
87     return getenv("CK_XML_LOG_FILE_NAME");
88 }
89 
srunner_set_tap(SRunner * sr,const char * fname)90 void srunner_set_tap(SRunner * sr, const char *fname)
91 {
92     if(sr->tap_fname)
93         return;
94     sr->tap_fname = fname;
95 }
96 
srunner_has_tap(SRunner * sr)97 int srunner_has_tap(SRunner * sr)
98 {
99     return srunner_tap_fname(sr) != NULL;
100 }
101 
srunner_tap_fname(SRunner * sr)102 const char *srunner_tap_fname(SRunner * sr)
103 {
104     /* check if tap log filename have been set explicitly */
105     if(sr->tap_fname != NULL)
106     {
107         return sr->tap_fname;
108     }
109 
110     return getenv("CK_TAP_LOG_FILE_NAME");
111 }
112 
srunner_register_lfun(SRunner * sr,FILE * lfile,int close,LFun lfun,enum print_output printmode)113 void srunner_register_lfun(SRunner * sr, FILE * lfile, int close,
114                            LFun lfun, enum print_output printmode)
115 {
116     Log *l = (Log *)emalloc(sizeof(Log));
117 
118     if(printmode == CK_ENV)
119     {
120         printmode = get_env_printmode();
121     }
122 
123     l->lfile = lfile;
124     l->lfun = lfun;
125     l->close = close;
126     l->mode = printmode;
127     check_list_add_end(sr->loglst, l);
128     return;
129 }
130 
log_srunner_start(SRunner * sr)131 void log_srunner_start(SRunner * sr)
132 {
133     srunner_send_evt(sr, NULL, CLSTART_SR);
134 }
135 
log_srunner_end(SRunner * sr)136 void log_srunner_end(SRunner * sr)
137 {
138     srunner_send_evt(sr, NULL, CLEND_SR);
139 }
140 
log_suite_start(SRunner * sr,Suite * s)141 void log_suite_start(SRunner * sr, Suite * s)
142 {
143     srunner_send_evt(sr, s, CLSTART_S);
144 }
145 
log_suite_end(SRunner * sr,Suite * s)146 void log_suite_end(SRunner * sr, Suite * s)
147 {
148     srunner_send_evt(sr, s, CLEND_S);
149 }
150 
log_test_start(SRunner * sr,TCase * tc,TF * tfun)151 void log_test_start(SRunner * sr, TCase * tc, TF * tfun)
152 {
153     char buffer[100];
154 
155     snprintf(buffer, 99, "%s:%s", tc->name, tfun->ttest->name);
156     srunner_send_evt(sr, buffer, CLSTART_T);
157 }
158 
log_test_end(SRunner * sr,TestResult * tr)159 void log_test_end(SRunner * sr, TestResult * tr)
160 {
161     srunner_send_evt(sr, tr, CLEND_T);
162 }
163 
srunner_send_evt(SRunner * sr,void * obj,enum cl_event evt)164 static void srunner_send_evt(SRunner * sr, void *obj, enum cl_event evt)
165 {
166     List *l;
167 
168     l = sr->loglst;
169     for(check_list_front(l); !check_list_at_end(l); check_list_advance(l))
170     {
171         Log *lg = (Log *)check_list_val(l);
172         fflush(lg->lfile);
173         lg->lfun(sr, lg->lfile, lg->mode, obj, evt);
174         fflush(lg->lfile);
175     }
176 }
177 
stdout_lfun(SRunner * sr,FILE * file,enum print_output printmode,void * obj,enum cl_event evt)178 void stdout_lfun(SRunner * sr, FILE * file, enum print_output printmode,
179                  void *obj, enum cl_event evt)
180 {
181     Suite *s;
182 
183     switch (evt)
184     {
185         case CLINITLOG_SR:
186             break;
187         case CLENDLOG_SR:
188             break;
189         case CLSTART_SR:
190             if(printmode > CK_SILENT)
191             {
192                 fprintf(file, "Running suite(s):");
193             }
194             break;
195         case CLSTART_S:
196             s = (Suite *)obj;
197             if(printmode > CK_SILENT)
198             {
199                 fprintf(file, " %s\n", s->name);
200             }
201             break;
202         case CLEND_SR:
203             if(printmode > CK_SILENT)
204             {
205                 /* we don't want a newline before printing here, newlines should
206                    come after printing a string, not before.  it's better to add
207                    the newline above in CLSTART_S.
208                  */
209                 srunner_fprint(file, sr, printmode);
210             }
211             break;
212         case CLEND_S:
213             break;
214         case CLSTART_T:
215             break;
216         case CLEND_T:
217             break;
218         default:
219             eprintf("Bad event type received in stdout_lfun", __FILE__,
220                     __LINE__);
221     }
222 
223 
224 }
225 
lfile_lfun(SRunner * sr,FILE * file,enum print_output printmode CK_ATTRIBUTE_UNUSED,void * obj,enum cl_event evt)226 void lfile_lfun(SRunner * sr, FILE * file,
227                 enum print_output printmode CK_ATTRIBUTE_UNUSED, void *obj,
228                 enum cl_event evt)
229 {
230     TestResult *tr;
231     Suite *s;
232 
233     switch (evt)
234     {
235         case CLINITLOG_SR:
236             break;
237         case CLENDLOG_SR:
238             break;
239         case CLSTART_SR:
240             break;
241         case CLSTART_S:
242             s = (Suite *)obj;
243             fprintf(file, "Running suite %s\n", s->name);
244             break;
245         case CLEND_SR:
246             fprintf(file, "Results for all suites run:\n");
247             srunner_fprint(file, sr, CK_MINIMAL);
248             break;
249         case CLEND_S:
250             break;
251         case CLSTART_T:
252             break;
253         case CLEND_T:
254             tr = (TestResult *)obj;
255             tr_fprint(file, tr, CK_VERBOSE);
256             break;
257         default:
258             eprintf("Bad event type received in lfile_lfun", __FILE__,
259                     __LINE__);
260     }
261 
262 
263 }
264 
xml_lfun(SRunner * sr CK_ATTRIBUTE_UNUSED,FILE * file,enum print_output printmode CK_ATTRIBUTE_UNUSED,void * obj,enum cl_event evt)265 void xml_lfun(SRunner * sr CK_ATTRIBUTE_UNUSED, FILE * file,
266               enum print_output printmode CK_ATTRIBUTE_UNUSED, void *obj,
267               enum cl_event evt)
268 {
269     TestResult *tr;
270     Suite *s;
271     static struct timespec ts_start = { 0, 0 };
272     static char t[sizeof "yyyy-mm-dd hh:mm:ss"] = { 0 };
273 
274     if(t[0] == 0)
275     {
276         struct timeval inittv;
277         struct tm now;
278 
279         gettimeofday(&inittv, NULL);
280         clock_gettime(check_get_clockid(), &ts_start);
281         if(localtime_r((const time_t *)&(inittv.tv_sec), &now) != NULL)
282         {
283             strftime(t, sizeof("yyyy-mm-dd hh:mm:ss"), "%Y-%m-%d %H:%M:%S",
284                      &now);
285         }
286     }
287 
288     switch (evt)
289     {
290         case CLINITLOG_SR:
291             fprintf(file,
292             "<?xml version=\"1.0\"?>\n"
293             "<?xml-stylesheet type=\"text/xsl\" "
294             "href=\"http://check.sourceforge.net/xml/check_unittest.xslt\"?>\n"
295             "<testsuites xmlns=\"http://check.sourceforge.net/ns\">\n"
296             "  <datetime>%s</datetime>\n", t);
297             break;
298         case CLENDLOG_SR:
299         {
300             struct timespec ts_end = { 0, 0 };
301             unsigned long duration;
302 
303             /* calculate time the test were running */
304             clock_gettime(check_get_clockid(), &ts_end);
305             duration = (unsigned long)DIFF_IN_USEC(ts_start, ts_end);
306             fprintf(file,
307             "  <duration>%lu.%06lu</duration>\n"
308             "</testsuites>\n",
309                     duration / US_PER_SEC, duration % US_PER_SEC);
310         }
311             break;
312         case CLSTART_SR:
313             break;
314         case CLSTART_S:
315             s = (Suite *)obj;
316             fprintf(file,
317             "  <suite>\n"
318             "    <title>");
319             fprint_xml_esc(file, s->name);
320             fprintf(file, "</title>\n");
321             break;
322         case CLEND_SR:
323             break;
324         case CLEND_S:
325             fprintf(file, "  </suite>\n");
326             break;
327         case CLSTART_T:
328             break;
329         case CLEND_T:
330             tr = (TestResult *)obj;
331             tr_xmlprint(file, tr, CK_VERBOSE);
332             break;
333         default:
334             eprintf("Bad event type received in xml_lfun", __FILE__,
335                     __LINE__);
336     }
337 
338 }
339 
tap_lfun(SRunner * sr CK_ATTRIBUTE_UNUSED,FILE * file,enum print_output printmode CK_ATTRIBUTE_UNUSED,void * obj,enum cl_event evt)340 void tap_lfun(SRunner * sr CK_ATTRIBUTE_UNUSED, FILE * file,
341               enum print_output printmode CK_ATTRIBUTE_UNUSED, void *obj,
342               enum cl_event evt)
343 {
344     TestResult *tr;
345 
346     static int num_tests_run = 0;
347 
348     switch (evt)
349     {
350         case CLINITLOG_SR:
351             /* As this is a new log file, reset the number of tests executed */
352             num_tests_run = 0;
353             break;
354         case CLENDLOG_SR:
355             /* Output the test plan as the last line */
356             fprintf(file, "1..%d\n", num_tests_run);
357             fflush(file);
358             break;
359         case CLSTART_SR:
360             break;
361         case CLSTART_S:
362             break;
363         case CLEND_SR:
364             break;
365         case CLEND_S:
366             break;
367         case CLSTART_T:
368             break;
369         case CLEND_T:
370             /* Print the test result to the tap file */
371             num_tests_run += 1;
372             tr = (TestResult *)obj;
373             fprintf(file, "%s %d - %s:%s:%s: %s\n",
374                     tr->rtype == CK_PASS ? "ok" : "not ok", num_tests_run,
375                     tr->file, tr->tcname, tr->tname, tr->msg);
376             fflush(file);
377             break;
378         default:
379             eprintf("Bad event type received in tap_lfun", __FILE__,
380                     __LINE__);
381     }
382 }
383 
384 #if ENABLE_SUBUNIT
subunit_lfun(SRunner * sr,FILE * file,enum print_output printmode,void * obj,enum cl_event evt)385 void subunit_lfun(SRunner * sr, FILE * file, enum print_output printmode,
386                   void *obj, enum cl_event evt)
387 {
388     TestResult *tr;
389     char const *name;
390 
391     /* assert(printmode == CK_SUBUNIT); */
392 
393     switch (evt)
394     {
395         case CLINITLOG_SR:
396             break;
397         case CLENDLOG_SR:
398             break;
399         case CLSTART_SR:
400             break;
401         case CLSTART_S:
402             break;
403         case CLEND_SR:
404             if(printmode > CK_SILENT)
405             {
406                 fprintf(file, "\n");
407                 srunner_fprint(file, sr, printmode);
408             }
409             break;
410         case CLEND_S:
411             break;
412         case CLSTART_T:
413             name = (const char *)obj;
414             subunit_test_start(name);
415             break;
416         case CLEND_T:
417             tr = (TestResult *)obj;
418             {
419                 char *name = ck_strdup_printf("%s:%s", tr->tcname, tr->tname);
420                 char *msg = tr_short_str(tr);
421 
422                 switch (tr->rtype)
423                 {
424                     case CK_PASS:
425                         subunit_test_pass(name);
426                         break;
427                     case CK_FAILURE:
428                         subunit_test_fail(name, msg);
429                         break;
430                     case CK_ERROR:
431                         subunit_test_error(name, msg);
432                         break;
433                     case CK_TEST_RESULT_INVALID:
434                     default:
435                         eprintf("Bad result type in subunit_lfun", __FILE__,
436                                 __LINE__);
437                         free(name);
438                         free(msg);
439                 }
440             }
441             break;
442         default:
443             eprintf("Bad event type received in subunit_lfun", __FILE__,
444                     __LINE__);
445     }
446 }
447 #endif
448 
srunner_open_file(const char * filename)449 static FILE *srunner_open_file(const char *filename)
450 {
451     FILE *f = NULL;
452 
453     if(strcmp(filename, STDOUT_OVERRIDE_LOG_FILE_NAME) == 0)
454     {
455         f = stdout;
456     }
457     else
458     {
459         f = fopen(filename, "w");
460         if(f == NULL)
461         {
462             eprintf("Error in call to fopen while opening file %s:", __FILE__,
463                     __LINE__ - 2, filename);
464         }
465     }
466     return f;
467 }
468 
srunner_open_lfile(SRunner * sr)469 FILE *srunner_open_lfile(SRunner * sr)
470 {
471     FILE *f = NULL;
472 
473     if(srunner_has_log(sr))
474     {
475         f = srunner_open_file(srunner_log_fname(sr));
476     }
477     return f;
478 }
479 
srunner_open_xmlfile(SRunner * sr)480 FILE *srunner_open_xmlfile(SRunner * sr)
481 {
482     FILE *f = NULL;
483 
484     if(srunner_has_xml(sr))
485     {
486         f = srunner_open_file(srunner_xml_fname(sr));
487     }
488     return f;
489 }
490 
srunner_open_tapfile(SRunner * sr)491 FILE *srunner_open_tapfile(SRunner * sr)
492 {
493     FILE *f = NULL;
494 
495     if(srunner_has_tap(sr))
496     {
497         f = srunner_open_file(srunner_tap_fname(sr));
498     }
499     return f;
500 }
501 
srunner_init_logging(SRunner * sr,enum print_output print_mode)502 void srunner_init_logging(SRunner * sr, enum print_output print_mode)
503 {
504     FILE *f;
505 
506     sr->loglst = check_list_create();
507 #if ENABLE_SUBUNIT
508     if(print_mode != CK_SUBUNIT)
509 #endif
510         srunner_register_lfun(sr, stdout, 0, stdout_lfun, print_mode);
511 #if ENABLE_SUBUNIT
512     else
513         srunner_register_lfun(sr, stdout, 0, subunit_lfun, print_mode);
514 #endif
515     f = srunner_open_lfile(sr);
516     if(f)
517     {
518         srunner_register_lfun(sr, f, f != stdout, lfile_lfun, print_mode);
519     }
520     f = srunner_open_xmlfile(sr);
521     if(f)
522     {
523         srunner_register_lfun(sr, f, f != stdout, xml_lfun, print_mode);
524     }
525     f = srunner_open_tapfile(sr);
526     if(f)
527     {
528         srunner_register_lfun(sr, f, f != stdout, tap_lfun, print_mode);
529     }
530     srunner_send_evt(sr, NULL, CLINITLOG_SR);
531 }
532 
srunner_end_logging(SRunner * sr)533 void srunner_end_logging(SRunner * sr)
534 {
535     List *l;
536     int rval;
537 
538     srunner_send_evt(sr, NULL, CLENDLOG_SR);
539 
540     l = sr->loglst;
541     for(check_list_front(l); !check_list_at_end(l); check_list_advance(l))
542     {
543         Log *lg = (Log *)check_list_val(l);
544 
545         if(lg->close)
546         {
547             rval = fclose(lg->lfile);
548             if(rval != 0)
549                 eprintf("Error in call to fclose while closing log file:",
550                         __FILE__, __LINE__ - 2);
551         }
552         free(lg);
553     }
554     check_list_free(l);
555     sr->loglst = NULL;
556 }
557