blob: ec4eee99008e96bedbffb0f2c52f5bac3324894b [file] [log] [blame]
/*
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;
}