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