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