| /* |
| Check: a unit test framework for C |
| Copyright (C) 2001, Arien Malec |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
| |
| This program 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 General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| */ |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #ifdef HAVE_SYS_TYPES_H |
| # include <sys/types.h> |
| #endif |
| |
| #ifdef HAVE_SYS_WAIT_H |
| # include <sys/wait.h> |
| #endif |
| #ifndef WIFSIGNALED |
| # define WIFSIGNALED(stat_val) \ |
| (((stat_val) & 255) != 255 && \ |
| ((stat_val) & 255) != 0) |
| #endif |
| #ifndef WTERMSIG |
| # define WTERMSIG(stat_val) ((stat_val) & 255) |
| #endif |
| #ifndef WIFEXITED |
| # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) |
| #endif |
| #ifndef WEXITSTATUS |
| # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) |
| #endif |
| |
| #ifdef HAVE_UNISTD_H |
| # include <unistd.h> |
| #endif |
| |
| #include <stdio.h> |
| |
| #ifdef STDC_HEADERS |
| # include <stdlib.h> |
| # include <stdarg.h> |
| #endif |
| |
| #include "error.h" |
| #include "list.h" |
| #include "check.h" |
| #include "check_impl.h" |
| #include "check_msg.h" |
| #include "check_log.h" |
| |
| #ifndef USE_FORKWAITMSG |
| int nofork_exit_status; |
| #endif |
| |
| static void srunner_run_tcase (SRunner *sr, TCase *tc); |
| static void srunner_add_failure (SRunner *sr, TestResult *tf); |
| static TestResult *tfun_run (int msqid, const char *tcname, TF *tf); |
| static TestResult *receive_result_info (int msqid, int status, |
| const char *tcname, |
| const char *tfname); |
| static void receive_last_loc_info (int msqid, TestResult *tr); |
| static void receive_failure_info (int msqid, int status, TestResult *tr); |
| static List *srunner_resultlst (SRunner *sr); |
| |
| |
| #ifdef USE_FORKWAITMSG |
| static char *signal_msg (int sig); |
| #endif |
| static char *exit_msg (int exitstatus); |
| static int non_pass (int val); |
| |
| |
| SRunner *srunner_create (Suite *s) |
| { |
| SRunner *sr = emalloc (sizeof(SRunner)); /* freed in srunner_free */ |
| sr->slst = list_create(); |
| list_add_end(sr->slst, s); |
| sr->stats = emalloc (sizeof(TestStats)); /* freed in srunner_free */ |
| sr->stats->n_checked = sr->stats->n_failed = sr->stats->n_errors = 0; |
| sr->resultlst = list_create(); |
| sr->log_fname = NULL; |
| sr->loglst = NULL; |
| return sr; |
| } |
| |
| void srunner_add_suite (SRunner *sr, Suite *s) |
| { |
| list_add_end(sr->slst, s); |
| } |
| |
| void srunner_free (SRunner *sr) |
| { |
| List *l; |
| TestResult *tr; |
| if (sr == NULL) |
| return; |
| |
| free (sr->stats); |
| list_free(sr->slst); |
| |
| l = sr->resultlst; |
| for (list_front(l); !list_at_end(l); list_advance(l)) { |
| tr = list_val(l); |
| free(tr->file); |
| free(tr->msg); |
| free(tr); |
| } |
| list_free (sr->resultlst); |
| |
| free (sr); |
| } |
| |
| |
| |
| void srunner_run_all (SRunner *sr, int print_mode) |
| { |
| List *slst; |
| List *tcl; |
| TCase *tc; |
| if (sr == NULL) |
| return; |
| if (print_mode < 0 || print_mode >= CRLAST) |
| eprintf("Bad print_mode argument to srunner_run_all: %d", print_mode); |
| |
| srunner_init_logging (sr, print_mode); |
| |
| log_srunner_start (sr); |
| |
| slst = sr->slst; |
| |
| for (list_front(slst); !list_at_end(slst); list_advance(slst)) { |
| Suite *s = list_val(slst); |
| |
| log_suite_start (sr, s); |
| |
| tcl = s->tclst; |
| |
| for (list_front(tcl);!list_at_end (tcl); list_advance (tcl)) { |
| tc = list_val (tcl); |
| srunner_run_tcase (sr, tc); |
| } |
| } |
| |
| log_srunner_end (sr); |
| |
| srunner_end_logging (sr); |
| } |
| |
| static void srunner_add_failure (SRunner *sr, TestResult *tr) |
| { |
| sr->stats->n_checked++; |
| list_add_end (sr->resultlst, tr); |
| switch (tr->rtype) { |
| |
| case CRPASS: |
| return; |
| case CRFAILURE: |
| sr->stats->n_failed++; |
| return; |
| case CRERROR: |
| sr->stats->n_errors++; |
| return; |
| } |
| } |
| |
| |
| static void srunner_run_tcase (SRunner *sr, TCase *tc) |
| { |
| List *tfl; |
| TF *tfun; |
| TestResult *tr; |
| int msqid; |
| |
| if (tc->setup) |
| tc->setup(); |
| msqid = create_msq(); |
| tfl = tc->tflst; |
| |
| for (list_front(tfl); !list_at_end (tfl); list_advance (tfl)) { |
| tfun = list_val (tfl); |
| tr = tfun_run (msqid, tc->name, tfun); |
| srunner_add_failure (sr, tr); |
| log_test_end(sr, tr); |
| } |
| delete_msq(msqid); |
| if (tc->teardown) |
| tc->teardown(); |
| } |
| |
| static void receive_last_loc_info (int msqid, TestResult *tr) |
| { |
| LastLocMsg *lmsg; |
| lmsg = receive_last_loc_msg (msqid); |
| tr->file = last_loc_file (lmsg); |
| tr->line = last_loc_line (lmsg); |
| free (lmsg); |
| } |
| |
| static void receive_failure_info (int msqid, int status, TestResult *tr) |
| { |
| FailureMsg *fmsg; |
| #ifdef USE_FORKWAITMSG |
| if (WIFSIGNALED(status)) { |
| tr->rtype = CRERROR; |
| tr->msg = signal_msg (WTERMSIG(status)); |
| return; |
| } |
| |
| if (WIFEXITED(status)) { |
| |
| if (WEXITSTATUS(status) == 0) { |
| tr->rtype = CRPASS; |
| tr->msg = emalloc(strlen("Test passed") + 1); |
| strcpy (tr->msg, "Test passed"); |
| } |
| else { |
| |
| fmsg = receive_failure_msg (msqid); |
| if (fmsg == NULL) { /* implies early exit */ |
| tr->rtype = CRERROR; |
| tr->msg = exit_msg (WEXITSTATUS(status)); |
| } |
| else { |
| tr->rtype = CRFAILURE; |
| tr->msg = emalloc(strlen(fmsg->msg) + 1); |
| strcpy (tr->msg, fmsg->msg); |
| free (fmsg); |
| } |
| } |
| } else { |
| eprintf ("Bad status from wait() call\n"); |
| } |
| #else |
| if (status == 0) { |
| tr->rtype = CRPASS; |
| tr->msg = emalloc(strlen("Test passed") + 1); |
| strcpy (tr->msg, "Test passed"); |
| } |
| else { |
| fmsg = receive_failure_msg (msqid); |
| if (fmsg == NULL) { /* implies early exit */ |
| tr->rtype = CRERROR; |
| tr->msg = exit_msg (status); |
| } |
| else { |
| tr->rtype = CRFAILURE; |
| tr->msg = emalloc(strlen(fmsg->msg) + 1); |
| strcpy (tr->msg, fmsg->msg); |
| free (fmsg); |
| } |
| } |
| #endif |
| } |
| |
| static TestResult *receive_result_info (int msqid, int status, |
| const char *tcname, const char *tfname) |
| { |
| TestResult *tr = emalloc (sizeof(TestResult)); |
| |
| tr->tcname = tcname; |
| tr->tfname = tfname; |
| receive_last_loc_info (msqid, tr); |
| receive_failure_info (msqid, status, tr); |
| return tr; |
| } |
| |
| static TestResult *tfun_run (int msqid, const char *tcname, TF *tfun) |
| { |
| #ifdef USE_FORKWAITMSG |
| pid_t pid; |
| #endif |
| int status = 0; |
| |
| #ifdef USE_FORKWAITMSG |
| pid = fork(); |
| if (pid == -1) |
| eprintf ("Unable to fork:"); |
| if (pid == 0) { |
| tfun->fn(msqid); |
| _exit(EXIT_SUCCESS); |
| } |
| (void) wait(&status); |
| #else |
| nofork_exit_status = 0; |
| tfun->fn(msqid); |
| status = nofork_exit_status; |
| #endif |
| return receive_result_info(msqid, status, tcname, tfun->name); |
| } |
| |
| |
| |
| |
| int srunner_ntests_failed (SRunner *sr) |
| { |
| return sr->stats->n_failed + sr->stats->n_errors; |
| } |
| |
| int srunner_ntests_run (SRunner *sr) |
| { |
| return sr->stats->n_checked; |
| } |
| |
| TestResult **srunner_failures (SRunner *sr) |
| { |
| int i = 0; |
| TestResult **trarray; |
| List *rlst; |
| trarray = malloc (sizeof(trarray[0]) * srunner_ntests_failed (sr)); |
| |
| rlst = srunner_resultlst (sr); |
| for (list_front(rlst); !list_at_end(rlst); list_advance(rlst)) { |
| TestResult *tr = list_val(rlst); |
| if (non_pass(tr->rtype)) |
| trarray[i++] = tr; |
| |
| } |
| return trarray; |
| } |
| |
| TestResult **srunner_results (SRunner *sr) |
| { |
| int i = 0; |
| TestResult **trarray; |
| List *rlst; |
| |
| trarray = malloc (sizeof(trarray[0]) * srunner_ntests_run (sr)); |
| |
| rlst = srunner_resultlst (sr); |
| for (list_front(rlst); !list_at_end(rlst); list_advance(rlst)) { |
| trarray[i++] = list_val(rlst); |
| } |
| return trarray; |
| } |
| |
| static List *srunner_resultlst (SRunner *sr) |
| { |
| return sr->resultlst; |
| } |
| |
| char *tr_msg (TestResult *tr) |
| { |
| return tr->msg; |
| } |
| |
| int tr_lno (TestResult *tr) |
| { |
| return tr->line; |
| } |
| |
| char *tr_lfile (TestResult *tr) |
| { |
| return tr->file; |
| } |
| |
| int tr_rtype (TestResult *tr) |
| { |
| return tr->rtype; |
| } |
| |
| const char *tr_tcname (TestResult *tr) |
| { |
| return tr->tcname; |
| } |
| |
| #ifdef USE_FORKWAITMSG |
| static char *signal_msg (int signal) |
| { |
| char *msg = emalloc (CMAXMSG); /* free'd by caller */ |
| #ifdef HAVE_SNPRINTF |
| snprintf(msg, CMAXMSG, "Received signal %d", signal); |
| #else |
| sprintf(msg, "Received signal %d", signal); |
| #endif |
| return msg; |
| } |
| #endif |
| |
| static char *exit_msg (int exitval) |
| { |
| char *msg = emalloc(CMAXMSG); /* free'd by caller */ |
| #ifdef HAVE_SNPRINTF |
| snprintf(msg, CMAXMSG, |
| "Early exit with return value %d", exitval); |
| #else |
| sprintf(msg, "Early exit with return value %d", exitval); |
| #endif |
| return msg; |
| } |
| |
| static int non_pass (int val) |
| { |
| return val == CRFAILURE || val == CRERROR; |
| } |
| |