| /* |
| * Check: a unit test framework for C |
| * Copyright (C) 2001, 2002 Arien Malec |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "libcompat.h" |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <internal-check.h> |
| #if ENABLE_SUBUNIT |
| #include <subunit/child.h> |
| #endif |
| |
| #include "check_error.h" |
| #include "check_list.h" |
| #include "check_impl.h" |
| #include "check_log.h" |
| #include "check_print.h" |
| #include "check_str.h" |
| |
| /* |
| * If a log file is specified to be "-", then instead of |
| * opening a file the log output is printed to stdout. |
| */ |
| #define STDOUT_OVERRIDE_LOG_FILE_NAME "-" |
| |
| static void srunner_send_evt (SRunner * sr, void *obj, enum cl_event evt); |
| |
| void |
| srunner_set_log (SRunner * sr, const char *fname) |
| { |
| if (sr->log_fname) |
| return; |
| sr->log_fname = fname; |
| } |
| |
| int |
| srunner_has_log (SRunner * sr) |
| { |
| return srunner_log_fname (sr) != NULL; |
| } |
| |
| const char * |
| srunner_log_fname (SRunner * sr) |
| { |
| /* check if log filename have been set explicitly */ |
| if (sr->log_fname != NULL) |
| return sr->log_fname; |
| |
| return getenv ("CK_LOG_FILE_NAME"); |
| } |
| |
| |
| void |
| srunner_set_xml (SRunner * sr, const char *fname) |
| { |
| if (sr->xml_fname) |
| return; |
| sr->xml_fname = fname; |
| } |
| |
| int |
| srunner_has_xml (SRunner * sr) |
| { |
| return srunner_xml_fname (sr) != NULL; |
| } |
| |
| const char * |
| srunner_xml_fname (SRunner * sr) |
| { |
| /* check if XML log filename have been set explicitly */ |
| if (sr->xml_fname != NULL) { |
| return sr->xml_fname; |
| } |
| |
| return getenv ("CK_XML_LOG_FILE_NAME"); |
| } |
| |
| void |
| srunner_set_tap (SRunner * sr, const char *fname) |
| { |
| if (sr->tap_fname) |
| return; |
| sr->tap_fname = fname; |
| } |
| |
| int |
| srunner_has_tap (SRunner * sr) |
| { |
| return srunner_tap_fname (sr) != NULL; |
| } |
| |
| const char * |
| srunner_tap_fname (SRunner * sr) |
| { |
| /* check if tap log filename have been set explicitly */ |
| if (sr->tap_fname != NULL) { |
| return sr->tap_fname; |
| } |
| |
| return getenv ("CK_TAP_LOG_FILE_NAME"); |
| } |
| |
| void |
| srunner_register_lfun (SRunner * sr, FILE * lfile, int close, |
| LFun lfun, enum print_output printmode) |
| { |
| Log *l = (Log *) emalloc (sizeof (Log)); |
| |
| if (printmode == CK_ENV) { |
| printmode = get_env_printmode (); |
| } |
| |
| l->lfile = lfile; |
| l->lfun = lfun; |
| l->close = close; |
| l->mode = printmode; |
| check_list_add_end (sr->loglst, l); |
| return; |
| } |
| |
| void |
| log_srunner_start (SRunner * sr) |
| { |
| srunner_send_evt (sr, NULL, CLSTART_SR); |
| } |
| |
| void |
| log_srunner_end (SRunner * sr) |
| { |
| srunner_send_evt (sr, NULL, CLEND_SR); |
| } |
| |
| void |
| log_suite_start (SRunner * sr, Suite * s) |
| { |
| srunner_send_evt (sr, s, CLSTART_S); |
| } |
| |
| void |
| log_suite_end (SRunner * sr, Suite * s) |
| { |
| srunner_send_evt (sr, s, CLEND_S); |
| } |
| |
| void |
| log_test_start (SRunner * sr, TCase * tc, TF * tfun) |
| { |
| char buffer[100]; |
| |
| snprintf (buffer, 99, "%s:%s", tc->name, tfun->name); |
| srunner_send_evt (sr, buffer, CLSTART_T); |
| } |
| |
| void |
| log_test_end (SRunner * sr, TestResult * tr) |
| { |
| srunner_send_evt (sr, tr, CLEND_T); |
| } |
| |
| static void |
| srunner_send_evt (SRunner * sr, void *obj, enum cl_event evt) |
| { |
| List *l; |
| Log *lg; |
| |
| l = sr->loglst; |
| for (check_list_front (l); !check_list_at_end (l); check_list_advance (l)) { |
| lg = (Log *) check_list_val (l); |
| fflush (lg->lfile); |
| lg->lfun (sr, lg->lfile, lg->mode, obj, evt); |
| fflush (lg->lfile); |
| } |
| } |
| |
| void |
| stdout_lfun (SRunner * sr, FILE * file, enum print_output printmode, |
| void *obj, enum cl_event evt) |
| { |
| Suite *s; |
| |
| switch (evt) { |
| case CLINITLOG_SR: |
| break; |
| case CLENDLOG_SR: |
| break; |
| case CLSTART_SR: |
| if (printmode > CK_SILENT) { |
| fprintf (file, "Running suite(s):"); |
| } |
| break; |
| case CLSTART_S: |
| s = (Suite *) obj; |
| if (printmode > CK_SILENT) { |
| fprintf (file, " %s\n", s->name); |
| } |
| break; |
| case CLEND_SR: |
| if (printmode > CK_SILENT) { |
| /* we don't want a newline before printing here, newlines should |
| come after printing a string, not before. it's better to add |
| the newline above in CLSTART_S. |
| */ |
| srunner_fprint (file, sr, printmode); |
| } |
| break; |
| case CLEND_S: |
| break; |
| case CLSTART_T: |
| break; |
| case CLEND_T: |
| break; |
| default: |
| eprintf ("Bad event type received in stdout_lfun", __FILE__, __LINE__); |
| } |
| |
| |
| } |
| |
| void |
| lfile_lfun (SRunner * sr, FILE * file, |
| enum print_output printmode CK_ATTRIBUTE_UNUSED, void *obj, |
| enum cl_event evt) |
| { |
| TestResult *tr; |
| Suite *s; |
| |
| switch (evt) { |
| case CLINITLOG_SR: |
| break; |
| case CLENDLOG_SR: |
| break; |
| case CLSTART_SR: |
| break; |
| case CLSTART_S: |
| s = (Suite *) obj; |
| fprintf (file, "Running suite %s\n", s->name); |
| break; |
| case CLEND_SR: |
| fprintf (file, "Results for all suites run:\n"); |
| srunner_fprint (file, sr, CK_MINIMAL); |
| break; |
| case CLEND_S: |
| break; |
| case CLSTART_T: |
| break; |
| case CLEND_T: |
| tr = (TestResult *) obj; |
| tr_fprint (file, tr, CK_VERBOSE); |
| break; |
| default: |
| eprintf ("Bad event type received in lfile_lfun", __FILE__, __LINE__); |
| } |
| |
| |
| } |
| |
| void |
| xml_lfun (SRunner * sr CK_ATTRIBUTE_UNUSED, FILE * file, |
| enum print_output printmode CK_ATTRIBUTE_UNUSED, void *obj, |
| enum cl_event evt) |
| { |
| TestResult *tr; |
| Suite *s; |
| static struct timespec ts_start = { 0, 0 }; |
| static char t[sizeof "yyyy-mm-dd hh:mm:ss"] = { 0 }; |
| |
| if (t[0] == 0) { |
| struct timeval inittv; |
| struct tm now; |
| |
| gettimeofday (&inittv, NULL); |
| clock_gettime (check_get_clockid (), &ts_start); |
| if (localtime_r ((const time_t *) &(inittv.tv_sec), &now) != NULL) { |
| strftime (t, sizeof ("yyyy-mm-dd hh:mm:ss"), "%Y-%m-%d %H:%M:%S", &now); |
| } |
| } |
| |
| switch (evt) { |
| case CLINITLOG_SR: |
| fprintf (file, "<?xml version=\"1.0\"?>\n"); |
| fprintf (file, |
| "<?xml-stylesheet type=\"text/xsl\" href=\"http://check.sourceforge.net/xml/check_unittest.xslt\"?>\n"); |
| fprintf (file, |
| "<testsuites xmlns=\"http://check.sourceforge.net/ns\">\n"); |
| fprintf (file, " <datetime>%s</datetime>\n", t); |
| break; |
| case CLENDLOG_SR: |
| { |
| struct timespec ts_end = { 0, 0 }; |
| unsigned long duration; |
| |
| /* calculate time the test were running */ |
| clock_gettime (check_get_clockid (), &ts_end); |
| duration = (unsigned long) DIFF_IN_USEC (ts_start, ts_end); |
| fprintf (file, " <duration>%lu.%06lu</duration>\n", |
| duration / US_PER_SEC, duration % US_PER_SEC); |
| fprintf (file, "</testsuites>\n"); |
| } |
| break; |
| case CLSTART_SR: |
| break; |
| case CLSTART_S: |
| s = (Suite *) obj; |
| fprintf (file, " <suite>\n"); |
| fprintf (file, " <title>"); |
| fprint_xml_esc (file, s->name); |
| fprintf (file, "</title>\n"); |
| break; |
| case CLEND_SR: |
| break; |
| case CLEND_S: |
| fprintf (file, " </suite>\n"); |
| break; |
| case CLSTART_T: |
| break; |
| case CLEND_T: |
| tr = (TestResult *) obj; |
| tr_xmlprint (file, tr, CK_VERBOSE); |
| break; |
| default: |
| eprintf ("Bad event type received in xml_lfun", __FILE__, __LINE__); |
| } |
| |
| } |
| |
| void |
| tap_lfun (SRunner * sr CK_ATTRIBUTE_UNUSED, FILE * file, |
| enum print_output printmode CK_ATTRIBUTE_UNUSED, void *obj, |
| enum cl_event evt) |
| { |
| TestResult *tr; |
| |
| static int num_tests_run = 0; |
| |
| switch (evt) { |
| case CLINITLOG_SR: |
| /* As this is a new log file, reset the number of tests executed */ |
| num_tests_run = 0; |
| break; |
| case CLENDLOG_SR: |
| /* Output the test plan as the last line */ |
| fprintf (file, "1..%d\n", num_tests_run); |
| fflush (file); |
| break; |
| case CLSTART_SR: |
| break; |
| case CLSTART_S: |
| break; |
| case CLEND_SR: |
| break; |
| case CLEND_S: |
| break; |
| case CLSTART_T: |
| break; |
| case CLEND_T: |
| /* Print the test result to the tap file */ |
| num_tests_run += 1; |
| tr = (TestResult *) obj; |
| fprintf (file, "%s %d - %s:%s:%s: %s\n", |
| tr->rtype == CK_PASS ? "ok" : "not ok", num_tests_run, |
| tr->file, tr->tcname, tr->tname, tr->msg); |
| fflush (file); |
| break; |
| default: |
| eprintf ("Bad event type received in tap_lfun", __FILE__, __LINE__); |
| } |
| } |
| |
| #if ENABLE_SUBUNIT |
| void |
| subunit_lfun (SRunner * sr, FILE * file, enum print_output printmode, |
| void *obj, enum cl_event evt) |
| { |
| TestResult *tr; |
| char const *name; |
| |
| /* assert(printmode == CK_SUBUNIT); */ |
| |
| switch (evt) { |
| case CLINITLOG_SR: |
| break; |
| case CLENDLOG_SR: |
| break; |
| case CLSTART_SR: |
| break; |
| case CLSTART_S: |
| break; |
| case CLEND_SR: |
| if (printmode > CK_SILENT) { |
| fprintf (file, "\n"); |
| srunner_fprint (file, sr, printmode); |
| } |
| break; |
| case CLEND_S: |
| break; |
| case CLSTART_T: |
| name = (const char *) obj; |
| subunit_test_start (name); |
| break; |
| case CLEND_T: |
| tr = (TestResult *) obj; |
| { |
| char *name = ck_strdup_printf ("%s:%s", tr->tcname, tr->tname); |
| char *msg = tr_short_str (tr); |
| |
| switch (tr->rtype) { |
| case CK_PASS: |
| subunit_test_pass (name); |
| break; |
| case CK_FAILURE: |
| subunit_test_fail (name, msg); |
| break; |
| case CK_ERROR: |
| subunit_test_error (name, msg); |
| break; |
| case CK_TEST_RESULT_INVALID: |
| default: |
| eprintf ("Bad result type in subunit_lfun", __FILE__, __LINE__); |
| free (name); |
| free (msg); |
| } |
| } |
| break; |
| default: |
| eprintf ("Bad event type received in subunit_lfun", __FILE__, __LINE__); |
| } |
| } |
| #endif |
| |
| static FILE * |
| srunner_open_file (const char *filename) |
| { |
| FILE *f = NULL; |
| |
| if (strcmp (filename, STDOUT_OVERRIDE_LOG_FILE_NAME) == 0) { |
| f = stdout; |
| } else { |
| f = fopen (filename, "w"); |
| if (f == NULL) { |
| eprintf ("Error in call to fopen while opening file %s:", __FILE__, |
| __LINE__ - 2, filename); |
| } |
| } |
| return f; |
| } |
| |
| FILE * |
| srunner_open_lfile (SRunner * sr) |
| { |
| FILE *f = NULL; |
| |
| if (srunner_has_log (sr)) { |
| f = srunner_open_file (srunner_log_fname (sr)); |
| } |
| return f; |
| } |
| |
| FILE * |
| srunner_open_xmlfile (SRunner * sr) |
| { |
| FILE *f = NULL; |
| |
| if (srunner_has_xml (sr)) { |
| f = srunner_open_file (srunner_xml_fname (sr)); |
| } |
| return f; |
| } |
| |
| FILE * |
| srunner_open_tapfile (SRunner * sr) |
| { |
| FILE *f = NULL; |
| |
| if (srunner_has_tap (sr)) { |
| f = srunner_open_file (srunner_tap_fname (sr)); |
| } |
| return f; |
| } |
| |
| void |
| srunner_init_logging (SRunner * sr, enum print_output print_mode) |
| { |
| FILE *f; |
| |
| sr->loglst = check_list_create (); |
| #if ENABLE_SUBUNIT |
| if (print_mode != CK_SUBUNIT) |
| #endif |
| srunner_register_lfun (sr, stdout, 0, stdout_lfun, print_mode); |
| #if ENABLE_SUBUNIT |
| else |
| srunner_register_lfun (sr, stdout, 0, subunit_lfun, print_mode); |
| #endif |
| f = srunner_open_lfile (sr); |
| if (f) { |
| srunner_register_lfun (sr, f, f != stdout, lfile_lfun, print_mode); |
| } |
| f = srunner_open_xmlfile (sr); |
| if (f) { |
| srunner_register_lfun (sr, f, f != stdout, xml_lfun, print_mode); |
| } |
| f = srunner_open_tapfile (sr); |
| if (f) { |
| srunner_register_lfun (sr, f, f != stdout, tap_lfun, print_mode); |
| } |
| srunner_send_evt (sr, NULL, CLINITLOG_SR); |
| } |
| |
| void |
| srunner_end_logging (SRunner * sr) |
| { |
| List *l; |
| int rval; |
| |
| srunner_send_evt (sr, NULL, CLENDLOG_SR); |
| |
| l = sr->loglst; |
| for (check_list_front (l); !check_list_at_end (l); check_list_advance (l)) { |
| Log *lg = (Log *) check_list_val (l); |
| |
| if (lg->close) { |
| rval = fclose (lg->lfile); |
| if (rval != 0) |
| eprintf ("Error in call to fclose while closing log file:", |
| __FILE__, __LINE__ - 2); |
| } |
| free (lg); |
| } |
| check_list_free (l); |
| sr->loglst = NULL; |
| } |