diff --git a/check/NEWS b/check/NEWS new file mode 100644 index 00000000..bb1f1b22 --- /dev/null +++ b/check/NEWS @@ -0,0 +1,77 @@ +Thu Aug 23, 2001: +Released Check 0.7.3 + +Fixed the Autoconf Lyx check in acinclude.m4 so that configure works +on Solaris systems (and hopefully others), and cleaned up a minor +problem in Debian packaging. + +Fri Aug 17, 2001: +Released Check 0.7.2 + +Automated RPM packaging, and included debian packaging. The makefiles +now has an rpm target (the RPMFLAGS variable can be set to add +additional flags to RPM). Debian packages are built the ordinary way +(dpkg-buildpackage). + +Moved the example*.* files to tutorial*.*, since the docs really are +tutorials. Beefed up the tutorial docs to add clarity to the behavior +of fixture setup/teardown (based on a helpful critique by Fred Drake), +and to document the static nature of unit tests demanded by the bug +fix below. + +Many bugfixes: added -Wall to the CCFLAGS for gcc, and fixed a mess of +warnings that resulted. Changed a bizarre naming mismatch in +tcase_set_fixture (masked by the lack of compile warnings), and made +unit tests static (both bugfixes suggested by Fred Drake). Also added +a more sophisticated test of Lyx to (hopefully) ensure that Lyx +supports linuxdoc (but it's not clear to me how to test that for +sure). + + +Wed Jul 30, 2001: +Released Check 0.7.1 + +Reorganized printing and logging functions to allow for a less +primitive logging function. Logging is now documented in the tutorial +documentation. + +Wed Jul 11, 2001: +Released Check 0.7.0 + +Included a primitive logging function (at the moment, it only prints a +copy of the CRVERBOSE output to the log file), added the ability for +an SRunner to run multiple suites (and reorganized the Check tests to +take advantage of that), and added the magic to allow Check to be used +with C++. + +Also added Doxygen markup to the header file, but I'm not terribly +satisfied withe clarity of the output. I may switch to CWEB... Next +release should include API docs and improved logging, if nothing else +comes up... + + +Wed Jun 27, 2001: + +Released Check 0.6.1 + +Bug fix for srunner_failures (bad version actually returned all +results), added srunner_results to do what srunner_failures used to +do, and added corrected unit tests for both. + +Also changed the API for reporting the number of failed tests from +srunner_nfailed to srunner_ntests_failed, to harmonized better with +new function srunner_ntests_run. This unfortunately may break some +unit tests slightly -- that's why the major release number is 0 :-) + +Thu Jun 21, 2001: +Released Check 0.6.0 + +Features improved unit test reporting options, more complete unit +tests, and end-to-end test, and a full API into TestResults + +Check 0.5.2 +Minor edits +Check 0.5.1 +GPL compliance release +Check 0.5.0 +Initial public release diff --git a/check/README b/check/README new file mode 100644 index 00000000..aaf8e695 --- /dev/null +++ b/check/README @@ -0,0 +1,9 @@ +Check is a unit test framework for C. It features a simple interface +for defining unit tests, putting little in the way of the +developer. Tests are run in a separate address space, so Check can +catch both assertion failures and code errors that cause segmentation +faults or other signals. The output from unit tests can be used within +source code editors and IDEs. + +See http://check.sourceforge.net for more information, including a +tutorial. diff --git a/check/check.c b/check/check.c new file mode 100644 index 00000000..79607d2c --- /dev/null +++ b/check/check.c @@ -0,0 +1,123 @@ +/* + 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. +*/ +#include +#include +#include +#include "error.h" +#include "list.h" +#include "check.h" +#include "check_impl.h" +#include "check_msg.h" + + +Suite *suite_create (char *name) +{ + Suite *s; + s = emalloc (sizeof(Suite)); /* freed in suite_free */ + if (name == NULL) + s->name = ""; + else + s->name = name; + s->tclst = list_create(); + return s; +} + +void suite_free (Suite *s) +{ + List *l; + if (s == NULL) + return; + for (l = s->tclst; !list_at_end(l); list_advance (l)) { + tcase_free (list_val(l)); + } + list_free (s->tclst); + free(s); +} + +TCase *tcase_create (char *name) +{ + TCase *tc = emalloc (sizeof(TCase)); /*freed in tcase_free */ + if (name == NULL) + tc->name = ""; + else + tc->name = name; + tc->tflst = list_create(); + tc->setup = tc->teardown = NULL; + + return tc; +} + + +void tcase_free (TCase *tc) +{ + List *l; + l = tc->tflst; + for (list_front(l); !list_at_end(l); list_advance(l)) { + free (list_val(l)); + } + list_free(tc->tflst); + free(tc); +} + +void suite_add_tcase (Suite *s, TCase *tc) +{ + if (s == NULL || tc == NULL) + return; + list_add_end (s->tclst, tc); +} + +void _tcase_add_test (TCase *tc, TFun fn, char *name) +{ + TF * tf; + if (tc == NULL || fn == NULL || name == NULL) + return; + tf = emalloc (sizeof(TF)); /* freed in tcase_free */ + tf->fn = fn; + tf->name = name; + list_add_end (tc->tflst, tf); +} + +void tcase_set_fixture (TCase *tc, SFun setup, SFun teardown) +{ + tc->setup = setup; + tc->teardown = teardown; +} + + +void tcase_fn_start (int msqid, char *fname, char *file, int line) +{ + send_last_loc_msg (msqid, file, line); +} + +void _mark_point (int msqid, char *file, int line) +{ + send_last_loc_msg (msqid, file, line); +} + +void _fail_unless (int msqid, int result, char *file, int line, char * msg) +{ + if (line > MAXLINE) + eprintf ("Line number %d too large to use", line); + + send_last_loc_msg (msqid, file, line); + if (!result) { + send_failure_msg (msqid, msg); + exit(1); + } +} diff --git a/check/check.h b/check/check.h new file mode 100644 index 00000000..208c8ab8 --- /dev/null +++ b/check/check.h @@ -0,0 +1,295 @@ +#ifndef CHECK_H +#define CHECK_H + +/* + 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. +*/ + +/* Comments are in Doxygen format: see http://www.stack.nl/~dimitri/doxygen/ */ + +/*! \mainpage Check: a unit test framework for C + +\section overview + +Overview Check is a unit test framework for C. It features a simple +interface for defining unit tests, putting little in the way of the +developer. Tests are run in a separate address space, so Check can +catch both assertion failures and code errors that cause segmentation +faults or other signals. The output from unit tests can be used within +source code editors and IDEs. + +\section quickref Quick Reference + +\subsection creating Creating + +\par + +Unit tests are created with the #START_TEST/#END_TEST macro pair. The +#fail_unless and #fail macros are used for creating checks within unit +tests; the #mark_point macro is useful for trapping the location of +signals and/or early exits. + +\subsection managing Managing test cases and suites + +\par + +Test cases are created with #tcase_create, unit tests are added +with #tcase_add_test + +\par + +Suites are created with #suite_create, freed with #suite_free; test +cases are added with #suite_add_tcase + +\subsection running Running suites + +\par + +Suites are run through an SRunner, which is created with +#srunner_create, freed with #srunner_free. Additional suites can be +added to an SRunner with #srunner_add_suite. + +\par + +Use #srunner_run_all to run a suite and print results. + +*/ + +/*! \file check.h */ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! Magic values */ +enum { + CMAXMSG = 100 /*!< maximum length of a message, including terminating nul */ +}; + +/*! \defgroup check_core Check Core + Core suite/test case types and functions + @{ +*/ + +/*! \brief opaque type for a test suite */ +typedef struct Suite Suite; + +/*! \brief opaque type for a test case + +A TCase represents a test case. Create with #tcase_create, free with +#tcase_free. For the moment, test cases can only be run through a +suite +*/ + +typedef struct TCase TCase; + +/*! type for a test function */ +typedef void (*TFun) (int); + +/*! type for a setup/teardown function */ +typedef void (*SFun) (void); + +/*! Create a test suite */ +Suite *suite_create (char *name); + +/*! Free a test suite + (For the moment, this also frees all contained test cases) */ +void suite_free (Suite *s); + +/*! Create a test case */ +TCase *tcase_create (char *name); + +/*! Free a test case + (Note that as it stands, one will normally free the contaning suite) */ +void tcase_free (TCase *tc); + +/*! Add a test case to a suite */ +void suite_add_tcase (Suite *s, TCase *tc); + +/*! Add a test function to a test case + (macro version) */ +#define tcase_add_test(tc,tf) _tcase_add_test(tc,tf,"" # tf "") +/*! Add a test function to a test case + (function version -- use this when the macro won't work */ +void _tcase_add_test (TCase *tc, TFun tf, char *fname); + +/*! + +Add fixture setup/teardown functions to a test case Note that +setup/teardown functions are not run in a separate address space, like +test functions, and so must not exit or signal (e.g., segfault) + +*/ +void tcase_set_fixture(TCase *tc, SFun setup, SFun teardown); + +/*! Internal function to mark the start of a test function */ +void tcase_fn_start (int msqid, char *fname, char *file, int line); + +/*! Start a unit test with START_TEST(unit_name), end with END_TEST + One must use braces within a START_/END_ pair to declare new variables */ +#define START_TEST(__testname)\ +static void __testname (int __msqid)\ +{\ + tcase_fn_start (__msqid,""# __testname, __FILE__, __LINE__); + +/*! End a unit test */ +#define END_TEST } + + +/*! Fail the test case unless result is true */ +#define fail_unless(result,msg) _fail_unless(__msqid,result,__FILE__,__LINE__,msg) + +/*! Non macro version of #fail_unless, with more complicated interface */ +void _fail_unless (int msqid, int result, char *file, int line, char *msg); + +/*! Always fail */ +#define fail(msg) _fail_unless(__msqid,0,__FILE__,__LINE__,msg) + +/*! Mark the last point reached in a unit test + (useful for tracking down where a segfault, etc. occurs */ +#define mark_point() _mark_point(__msqid,__FILE__,__LINE__) +/*! Non macro version of #mark_point */ +void _mark_point (int msqid, char *file, int line); + +/*! @} */ + +/*! \defgroup check_run Suite running functions + @{ +*/ + + +/*! Result of a test */ +enum test_result { + CRPASS, /*!< Test passed*/ + CRFAILURE, /*!< Test completed but failed */ + CRERROR /*!< Test failed to complete (signal or non-zero early exit) */ +}; + +/*! Specifies the verbosity of srunner printing */ +enum print_verbosity { + CRSILENT, /*!< No output */ + CRMINIMAL, /*!< Only summary output */ + CRNORMAL, /*!< All failed tests */ + CRVERBOSE, /*!< All tests */ + CRLAST +}; + + +/*! Holds state for a running of a test suite */ +typedef struct SRunner SRunner; + +/*! Opaque type for a test failure */ +typedef struct TestResult TestResult; + +/* accessors for tr fields */ + +/*! Type of result */ +int tr_rtype (TestResult *tr); +/*! Failure message */ +char *tr_msg (TestResult *tr); +/*! Line number at which failure occured */ +int tr_lno (TestResult *tr); +/*! File name at which failure occured */ +char *tr_lfile (TestResult *tr); +/*! Test case in which unit test was run */ +char *tr_tcname (TestResult *tr); + +/*! Creates an SRunner for the given suite */ +SRunner *srunner_create (Suite *s); + +/*! Adds a Suite to an SRunner */ +void srunner_add_suite (SRunner *sr, Suite *s); + +/*! Frees an SRunner */ +void srunner_free (SRunner *sr); + +/* Test running */ + +/*! Runs an SRunner, printing results as specified + (see enum #print_verbosity)*/ +void srunner_run_all (SRunner *sr, int print_mode); + +/* Next functions are valid only after the suite has been + completely run, of course */ + +/*! Number of failed tests in a run suite + Includes failures + errors */ +int srunner_ntests_failed (SRunner *sr); + +/*! Total number of tests run in a run suite */ +int srunner_ntests_run (SRunner *sr); + +/*! \brief Return an array of results for all failures + + Number of failures is equal to #srunner_nfailed_tests. Memory is + alloc'ed and must be freed, but individual TestResults must not */ + +TestResult **srunner_failures (SRunner *sr); + +/*! \brief Return an array of results for all run tests + +Number of failrues is equal to #srunner_ntests_run Memory is alloc'ed +and must be freed, but individual TestResults must not */ + +TestResult **srunner_results (SRunner *sr); + /* Printing */ + +/*! Print the results contained in an SRunner + +\param sr SRunner for which results are printed + +\param print_mode Specification of print verbosity, constrainted to +enum #print_verbosity +*/ + void srunner_print (SRunner *sr, int print_mode); + +/*! @} */ + + +/*! \defgroup check_log Logging functions + @{ +*/ + +/*! Set a log file to which to write during test running. + Log file setting is an initialize only operation -- it should be done + immediatly after SRunner creation, and the log file can't be changed + after being set. + \param sr The SRunner for which to enable logging + \param fname The file name to which to write the log + */ +void srunner_set_log (SRunner *sr, char *fname); + +/*! Does the SRunner have a log file? + \param sr The SRunner to test + \return True if logging, False otherwise +*/ +int srunner_has_log (SRunner *sr); + +/*! Return the name of the log file, or NULL if none + \param sr The SRunner to query + \return The current log file, or NULL if not logging +*/ +char *srunner_log_fname (SRunner *sr); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* CHECK_H */ diff --git a/check/check_impl.h b/check/check_impl.h new file mode 100644 index 00000000..bca52c61 --- /dev/null +++ b/check/check_impl.h @@ -0,0 +1,92 @@ +#ifndef CHECK_IMPL_H +#define CHECK_IMPL_H + +/* + 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. +*/ + +/* This header should be included by any module that needs + to know the implementation details of the check structures + Include stdio.h & list.h before this header +*/ + +/* magic values */ + +enum { + MAXLINE = 9999 /* maximum line no */ +}; + +typedef struct TF { + TFun fn; + char *name; +} TF; + +struct Suite { + char *name; + List *tclst; /* List of test cases */ +}; + +struct TCase { + char *name; + List *tflst; /* list of test functions */ + SFun setup; + SFun teardown; +}; + +typedef struct TestStats { + int n_checked; + int n_failed; + int n_errors; +} TestStats; + +struct TestResult { + int rtype; /* Type of result */ + char *file; /* File where the test occured */ + int line; /* Line number where the test occurred */ + char *tcname; /* Test case that generated the result */ + char *msg; /* Failure message */ +}; + +enum cl_event { + CLSTART_SR, + CLSTART_S, + CLEND_SR, + CLEND_S, + CLEND_T +}; + +typedef void (*LFun) (SRunner *, FILE*, enum print_verbosity, + void *, enum cl_event); + +typedef struct Log { + FILE *lfile; + LFun lfun; + int close; + enum print_verbosity mode; +} Log; + +struct SRunner { + List *slst; + TestStats *stats; + List *resultlst; + char *log_fname; + List *loglst; +}; + + +#endif /* CHECK_IMPL_H */ diff --git a/check/check_log.c b/check/check_log.c new file mode 100644 index 00000000..501c68cb --- /dev/null +++ b/check/check_log.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include "list.h" +#include "error.h" +#include "check_impl.h" +#include "check_log.h" +#include "check_print.h" + +/* + 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. +*/ + +static void srunner_send_evt (SRunner *sr, void *obj, enum cl_event evt); + +void srunner_set_log (SRunner *sr, char *fname) +{ + if (sr->log_fname) + return; + sr->log_fname = fname; +} + +int srunner_has_log (SRunner *sr) +{ + return sr->log_fname != NULL; +} + +char *srunner_log_fname (SRunner *sr) +{ + return sr->log_fname; +} + +void srunner_register_lfun (SRunner *sr, FILE *lfile, int close, + LFun lfun, enum print_verbosity printmode) +{ + Log *l = emalloc (sizeof(Log)); + l->lfile = lfile; + l->lfun = lfun; + l->close = close; + l->mode = printmode; + 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_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 (list_front(l); !list_at_end(l); list_advance(l)) { + lg = 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_verbosity printmode, + void *obj, enum cl_event evt) +{ + TestResult *tr; + Suite *s; + + switch (evt) { + case CLSTART_SR: + if (printmode > CRSILENT) { + fprintf(file, "Running suite(s):"); + } + break; + case CLSTART_S: + s = obj; + if (printmode > CRSILENT) { + fprintf(file, " %s", s->name); + } + break; + case CLEND_SR: + if (printmode > CRSILENT) { + fprintf (file, "\n"); + srunner_fprint (file, sr, printmode); + } + break; + case CLEND_S: + s = obj; + break; + case CLEND_T: + tr = obj; + break; + default: + eprintf("Bad event type received in stdout_lfun"); + } + + +} + +void lfile_lfun (SRunner *sr, FILE *file, enum print_verbosity printmode, + void *obj, enum cl_event evt) +{ + TestResult *tr; + Suite *s; + + switch (evt) { + case CLSTART_SR: + break; + case CLSTART_S: + s = 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, CRMINIMAL); + break; + case CLEND_S: + s = obj; + break; + case CLEND_T: + tr = obj; + tr_fprint(file, tr, CRVERBOSE); + break; + default: + eprintf("Bad event type received in stdout_lfun"); + } + + +} + +FILE *srunner_open_lfile (SRunner *sr) +{ + FILE *f = NULL; + if (srunner_has_log (sr)) { + f = fopen(sr->log_fname, "w"); + if (f == NULL) + eprintf ("Could not open log file %s:", sr->log_fname); + } + return f; +} + +void srunner_init_logging (SRunner *sr, enum print_verbosity print_mode) +{ + FILE *f; + sr->loglst = list_create(); + srunner_register_lfun (sr, stdout, 0, stdout_lfun, print_mode); + f = srunner_open_lfile (sr); + if (f) { + srunner_register_lfun (sr, f, 1, lfile_lfun, print_mode); + } +} + +void srunner_end_logging (SRunner *sr) +{ + List *l; + int rval; + + l = sr->loglst; + for (list_front(l); !list_at_end(l); list_advance(l)) { + Log *lg = list_val(l); + if (lg->close) { + rval = fclose (lg->lfile); + if (rval != 0) + eprintf ("Error closing log file:"); + } + free (lg); + } + list_free(l); + sr->loglst = NULL; +} diff --git a/check/check_log.h b/check/check_log.h new file mode 100644 index 00000000..a7205d7d --- /dev/null +++ b/check/check_log.h @@ -0,0 +1,23 @@ +#ifndef CHECK_LOG_H +#define CHECK_LOG_H + +void log_srunner_start (SRunner *sr); +void log_srunner_end (SRunner *sr); +void log_suite_start (SRunner *sr, Suite *s); +void log_suite_end (SRunner *sr, Suite *s); +void log_test_end (SRunner *sr, TestResult *tr); + +void stdout_lfun (SRunner *sr, FILE *file, enum print_verbosity, + void *obj, enum cl_event evt); + +void lfile_lfun (SRunner *sr, FILE *file, enum print_verbosity, + void *obj, enum cl_event evt); + +void srunner_register_lfun (SRunner *sr, FILE *lfile, int close, + LFun lfun, enum print_verbosity); + +FILE *srunner_open_lfile (SRunner *sr); +void srunner_init_logging (SRunner *sr, enum print_verbosity print_mode); +void srunner_end_logging (SRunner *sr); + +#endif /* CHECK_LOG_H */ diff --git a/check/check_msg.c b/check/check_msg.c new file mode 100644 index 00000000..b87d1870 --- /dev/null +++ b/check/check_msg.c @@ -0,0 +1,155 @@ +/* + 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "list.h" +#include "error.h" +#include "check.h" +#include "check_impl.h" +#include "check_msg.h" + +enum { + LASTLOCMSG = 1, + FAILUREMSG = 2 +}; +static LastLocMsg *create_last_loc_msg (char *file, int line); +static FailureMsg *create_failure_msg (char *msg); + +static FailureMsg *create_failure_msg (char *msg) +{ + FailureMsg *m = emalloc (sizeof(FailureMsg)); + m->message_type = (long int) FAILUREMSG; + strncpy(m->msg, msg, CMAXMSG); + return m; +} + + +static LastLocMsg *create_last_loc_msg (char *file, int line) +{ + LastLocMsg *m = emalloc (sizeof(LastLocMsg)); + m->message_type = (long int) LASTLOCMSG; + snprintf(m->msg, CMAXMSG, "%s\n%d", file, line); + + return m; +} + +char *last_loc_file (LastLocMsg *msg) +{ + int i; + char *rmsg = emalloc (CMAXMSG); /* caller responsible for freeing */ + char *mmsg = msg->msg; + if (msg == NULL) + return NULL; + for (i = 0; mmsg[i] != '\n'; i++) { + if (mmsg[i] == '\0') + eprintf ("Badly formated last loc message"); + rmsg[i] = mmsg[i]; + } + rmsg[i] = '\0'; + return rmsg; +} + +int last_loc_line (LastLocMsg *msg) +{ + char *rmsg; + if (msg == NULL) + return -1; + rmsg = msg->msg; + while (*rmsg != '\n') { + if (*rmsg == '\0') + eprintf ("Badly formated last loc message"); + rmsg++; + } + rmsg++; /*advance past \n */ + return atoi (rmsg); +} + + +void send_last_loc_msg (int msqid, char * file, int line) +{ + int rval; + LastLocMsg *rmsg = create_last_loc_msg(file, line); + rval = msgsnd(msqid, (void *) rmsg, CMAXMSG, IPC_NOWAIT); + if (rval == -1) { + eprintf ("send_last_loc_msg:Failed to send message, msqid = %d:",msqid); + } + free(rmsg); +} + +int create_msq (void) { + int msqid; + msqid = msgget((key_t) 1, 0666 | IPC_CREAT); + if (msqid == -1) + eprintf ("Unable to create message queue:"); + return msqid; +} + +void delete_msq (int msqid) +{ + if (msgctl (msqid, IPC_RMID, NULL) == -1) + eprintf ("Failed to free message queue:"); +} + + +void send_failure_msg (int msqid, char *msg) +{ + int rval; + + FailureMsg *rmsg = create_failure_msg(msg); + + rval = msgsnd(msqid, (void *) rmsg, CMAXMSG, IPC_NOWAIT); + if (rval == -1) + eprintf ("send_failure_msg:Failed to send message:"); + free(rmsg); +} + +LastLocMsg *receive_last_loc_msg (int msqid) +{ + LastLocMsg *rmsg = emalloc(sizeof(LastLocMsg)); /* caller responsible for freeing */ + while (1) { + int rval; + rval = msgrcv(msqid, (void *) rmsg, CMAXMSG, LASTLOCMSG, IPC_NOWAIT); + if (rval == -1) { + if (errno == ENOMSG) + break; + eprintf ("receive_last_loc_msg:Failed to receive message:"); + } + } + return rmsg; +} + +FailureMsg *receive_failure_msg (int msqid) +{ + FailureMsg *rmsg = emalloc(sizeof(FailureMsg)); + int rval; + rval = msgrcv(msqid, (void *) rmsg, CMAXMSG, FAILUREMSG, IPC_NOWAIT); + if (rval == -1) { + if (errno == ENOMSG) + return NULL; + eprintf ("receive_failure_msg:Failed to receive message:"); + } + return rmsg; +} + diff --git a/check/check_msg.h b/check/check_msg.h new file mode 100644 index 00000000..76ab94ad --- /dev/null +++ b/check/check_msg.h @@ -0,0 +1,53 @@ +#ifndef CHECK_MSG_H +#define CHECK_MSG_H + +/* + 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. +*/ + +/* Functions implementing messaging during test runs */ +/* check.h must be included before this header */ + + +typedef struct LastLocMsg { + long int message_type; + char msg[CMAXMSG]; /* Format: filename\nlineno\0 */ +} LastLocMsg; + +typedef struct FailureMsg { + long int message_type; + char msg[CMAXMSG]; +} FailureMsg; + +int create_msq (void); +void delete_msq (int msqid); + +void send_failure_msg (int msqid, char *msg); +void send_last_loc_msg (int msqid, char * file, int line); + +/* malloc'd return value which caller is responsible for + freeing in each of the next two functions */ +FailureMsg *receive_failure_msg (int msqid); +LastLocMsg *receive_last_loc_msg (int msqid); + +/* file name contained in the LastLocMsg */ +/* return value is malloc'd, caller responsible for freeing */ +char *last_loc_file(LastLocMsg *msg); +int last_loc_line(LastLocMsg *msg); + +#endif /*CHECK_MSG_H */ diff --git a/check/check_print.c b/check/check_print.c new file mode 100644 index 00000000..31de5b9d --- /dev/null +++ b/check/check_print.c @@ -0,0 +1,106 @@ +/* + 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. +*/ + +#include +#include +#include "list.h" +#include "check_impl.h" +#include "check_print.h" +#include "error.h" + +static void srunner_fprint_summary (FILE *file, SRunner *sr, int print_mode); +static void srunner_fprint_results (FILE *file, SRunner *sr, int print_mode); + +static int percent_passed (TestStats *t); +static char *rtype_to_string (int rtype); + +void srunner_print (SRunner *sr, int print_mode) +{ + srunner_fprint (stdout, sr, print_mode); +} + +void srunner_fprint (FILE *file, SRunner *sr, int print_mode) +{ + srunner_fprint_summary (file, sr, print_mode); + srunner_fprint_results (file, sr, print_mode); +} + +static void srunner_fprint_summary (FILE *file, SRunner *sr, int print_mode) +{ + TestStats *ts = sr->stats; + if (print_mode >= CRMINIMAL) { + fprintf (file, "%d%%: Checks: %d, Failures: %d, Errors: %d\n", + percent_passed (ts), ts->n_checked, ts->n_failed, + ts->n_errors); + } + return; +} + +static void srunner_fprint_results (FILE *file, SRunner *sr, int print_mode) +{ + List *resultlst; + + resultlst = sr->resultlst; + + for (list_front(resultlst); !list_at_end(resultlst); list_advance(resultlst)) { + TestResult *tr = list_val(resultlst); + tr_fprint (file, tr, print_mode); + } + return; +} + +void tr_fprint (FILE *file, TestResult *tr, int print_mode) +{ + char *exact_msg; + exact_msg = (tr->rtype == CRERROR) ? "(after this point) ": ""; + if ((print_mode >= CRVERBOSE && tr->rtype == CRPASS) || + (tr->rtype != CRPASS && print_mode >= CRNORMAL)) { + fprintf (file, "%s:%d:%s:%s: %s%s\n", + tr->file, tr->line, + rtype_to_string(tr->rtype), tr->tcname, + exact_msg, tr->msg); + } +} + +static int percent_passed (TestStats *t) +{ + if (t->n_failed == 0 && t->n_errors == 0) + return 100; + else + return (int) ( (float) (t->n_checked - (t->n_failed + t->n_errors)) / + (float) t->n_checked * 100); +} + +static char *rtype_to_string (int rtype) +{ + switch (rtype) { + case CRPASS: + return "P"; + break; + case CRFAILURE: + return "F"; + break; + case CRERROR: + return "E"; + break; + default: + eprintf("Bad argument %d to rtype_to_string", rtype); + return NULL; + } +} diff --git a/check/check_print.h b/check/check_print.h new file mode 100644 index 00000000..ee90905b --- /dev/null +++ b/check/check_print.h @@ -0,0 +1,27 @@ +/* + 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. +*/ + +#ifndef CHECK_PRINT_H +#define CHECK_PRINT_H + +void tr_fprint (FILE *file, TestResult *tr, int print_mode); +void srunner_fprint (FILE *file, SRunner *sr, int print_mode); + + +#endif /* CHECK_PRINT_H */ diff --git a/check/check_run.c b/check/check_run.c new file mode 100644 index 00000000..6a2462f2 --- /dev/null +++ b/check/check_run.c @@ -0,0 +1,333 @@ +/* + 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. +*/ + +#include +#include +#include +#include +#include +#include +#include "error.h" +#include "list.h" +#include "check.h" +#include "check_impl.h" +#include "check_msg.h" +#include "check_log.h" + + +static void srunner_run_tcase (SRunner *sr, TCase *tc); +static void srunner_add_failure (SRunner *sr, TestResult *tf); +static TestResult *tfun_run (int msqid, char *tcname, TF *tf); +static TestResult *receive_result_info (int msqid, int status, char *tcname); +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); + + +static char *signal_msg (int sig); +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); + if (tr->rtype == CRFAILURE || tr->rtype == CRERROR) + 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; + + if (WIFSIGNALED(status)) { + tr->rtype = CRERROR; + tr->msg = signal_msg (WTERMSIG(status)); + return; + } + + if (WIFEXITED(status)) { + + if (WEXITSTATUS(status) == 0) { + tr->rtype = CRPASS; + /* TODO: It would be cleaner to strdup this & + not special case the free...*/ + 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"); + } +} + +static TestResult *receive_result_info (int msqid, int status, char *tcname) +{ + TestResult *tr = emalloc (sizeof(TestResult)); + + tr->tcname = tcname; + receive_last_loc_info (msqid, tr); + receive_failure_info (msqid, status, tr); + return tr; +} + +static TestResult *tfun_run (int msqid, char *tcname, TF *tfun) +{ + pid_t pid; + int status = 0; + + pid = fork(); + if (pid == -1) + eprintf ("Unable to fork:"); + if (pid == 0) { + tfun->fn(msqid); + _exit(EXIT_SUCCESS); + } + (void) wait(&status); + return receive_result_info(msqid, status, tcname); +} + + + + +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; +} + +char *tr_tcname (TestResult *tr) +{ + return tr->tcname; +} + + +static char *signal_msg (int signal) +{ + char *msg = emalloc (CMAXMSG); /* free'd by caller */ + snprintf(msg, CMAXMSG, "Received signal %d", signal); + return msg; +} + +static char *exit_msg (int exitval) +{ + char *msg = emalloc(CMAXMSG); /* free'd by caller */ + snprintf(msg, CMAXMSG, + "Early exit with return value %d", exitval); + return msg; +} + +static int non_pass (int val) +{ + return val == CRFAILURE || val == CRERROR; +} + diff --git a/check/error.c b/check/error.c new file mode 100644 index 00000000..f2227a2b --- /dev/null +++ b/check/error.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include "error.h" + +/* + 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. +*/ + +void eprintf (char *fmt, ...) +{ + va_list args; + fflush(stdout); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + /*include system error information if format ends in colon */ + if (fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') + fprintf(stderr, " %s", strerror(errno)); + fprintf(stderr, "\n"); + + exit(2); +} + +void *emalloc (size_t n) +{ + void *p; + p = malloc(n); + if (p == NULL) + eprintf("malloc of %u bytes failed:", n); + return p; +} + +void *erealloc (void * ptr, size_t n) +{ + void *p; + p = realloc (ptr, n); + if (p == NULL) + eprintf("realloc of %u bytes failed:", n); + return p; +} diff --git a/check/error.h b/check/error.h new file mode 100644 index 00000000..01c2e136 --- /dev/null +++ b/check/error.h @@ -0,0 +1,32 @@ +#ifndef ERROR_H +#define ERROR_H + +/* + 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. +*/ + +/* Include stdlib.h beforehand */ + +/* Print error message and die + If fmt ends in colon, include system error information */ +void eprintf (char *fmt, ...); +/* malloc or die */ +void *emalloc(size_t n); +void *erealloc(void *, size_t n); + +#endif /*ERROR_H*/ diff --git a/check/list.c b/check/list.c new file mode 100644 index 00000000..5e2d04a1 --- /dev/null +++ b/check/list.c @@ -0,0 +1,113 @@ +#include +#include "list.h" +#include "error.h" + +/* + 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. +*/ + +enum { + LINIT = 1, + LGROW = 2 +}; + +struct List { + int n_elts; + int max_elts; + int current; /* pointer to the current node */ + int last; /* pointer to the node before END */ + void **data; +}; + +static void maybe_grow (List *lp) +{ + if (lp->n_elts >= lp->max_elts) { + lp->max_elts *= LGROW; + lp->data = erealloc (lp->data, lp->max_elts * sizeof(lp->data[0])); + } +} + +List *list_create (void) +{ + List *lp; + lp = emalloc (sizeof(List)); + lp->n_elts = 0; + lp->max_elts = LINIT; + lp->data = emalloc(sizeof(lp->data[0]) * LINIT); + lp->current = lp->last = -1; + return lp; +} + +void list_add_end (List *lp, void *val) +{ + if (lp == NULL) + return; + maybe_grow(lp); + lp->last++; + lp->n_elts++; + lp->current = lp->last; + lp->data[lp->current] = val; +} + +int list_at_end (List *lp) +{ + if (lp->current == -1) + return 1; + else + return (lp->current > lp->last); +} + +void list_front (List *lp) +{ + if (lp->current == -1) + return; + lp->current = 0; +} + + +void list_free (List *lp) +{ + if (lp == NULL) + return; + + free(lp->data); + free (lp); +} + +void *list_val (List *lp) +{ + if (lp == NULL) + return NULL; + if (lp->current == -1 || lp->current > lp->last) + return NULL; + + return lp->data[lp->current]; +} + +void list_advance (List *lp) +{ + if (lp == NULL) + return; + if (list_at_end(lp)) + return; + lp->current++; +} + + + + diff --git a/check/list.h b/check/list.h new file mode 100644 index 00000000..17215a2c --- /dev/null +++ b/check/list.h @@ -0,0 +1,51 @@ +#ifndef LIST_H +#define LIST_H + +/* + 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. +*/ + +typedef struct List List; + +/* Create an empty list */ +List * list_create (void); + +/* Is list at end? */ +int list_at_end (List * lp); + +/* Position list at front */ +void list_front(List *lp); + + +/* Add a value to the end of the list, + positioning newly added value as current value */ +void list_add_end (List *lp, void *val); + +/* Give the value of the current node */ +void *list_val (List * lp); + +/* Position the list at the next node */ +void list_advance (List * lp); + +/* Free a list, but don't free values */ +void list_free (List * lp); + +/* Free a list, freeing values using a freeing function */ +/* void list_vfree (List * lp, void (*fp) (void *)); */ + +#endif /*LIST_H*/