| /* |
| * 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/libcompat.h" |
| |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <math.h> |
| |
| #include "internal-check.h" |
| #include "check_error.h" |
| #include "check_list.h" |
| #include "check_impl.h" |
| #include "check_msg.h" |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> /* for _POSIX_VERSION */ |
| #endif |
| |
| #ifndef DEFAULT_TIMEOUT |
| #define DEFAULT_TIMEOUT 4 |
| #endif |
| |
| /* |
| * When a process exits either normally, with exit(), or |
| * by an uncaught signal, The lower 0x377 bits are passed |
| * to the parent. Of those, only the lower 8 bits are |
| * returned by the WEXITSTATUS() macro. |
| */ |
| #define WEXITSTATUS_MASK 0xFF |
| |
| int check_major_version = CHECK_MAJOR_VERSION; |
| int check_minor_version = CHECK_MINOR_VERSION; |
| int check_micro_version = CHECK_MICRO_VERSION; |
| |
| const char* current_test_name = NULL; |
| |
| static int non_pass (int val); |
| static Fixture *fixture_create (SFun fun, int ischecked); |
| static void tcase_add_fixture (TCase * tc, SFun setup, SFun teardown, |
| int ischecked); |
| static void tr_init (TestResult * tr); |
| static void suite_free (Suite * s); |
| static void tcase_free (TCase * tc); |
| |
| Suite * |
| suite_create (const char *name) |
| { |
| Suite *s; |
| |
| s = (Suite *) emalloc (sizeof (Suite)); /* freed in suite_free */ |
| if (name == NULL) |
| s->name = ""; |
| else |
| s->name = name; |
| s->tclst = check_list_create (); |
| return s; |
| } |
| |
| int |
| suite_tcase (Suite * s, const char *tcname) |
| { |
| List *l; |
| TCase *tc; |
| |
| if (s == NULL) |
| return 0; |
| |
| l = s->tclst; |
| for (check_list_front (l); !check_list_at_end (l); check_list_advance (l)) { |
| tc = (TCase *) check_list_val (l); |
| if (strcmp (tcname, tc->name) == 0) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| suite_free (Suite * s) |
| { |
| List *l; |
| |
| if (s == NULL) |
| return; |
| l = s->tclst; |
| for (check_list_front (l); !check_list_at_end (l); check_list_advance (l)) { |
| tcase_free ((TCase *) check_list_val (l)); |
| } |
| check_list_free (s->tclst); |
| free (s); |
| } |
| |
| |
| TCase * |
| tcase_create (const char *name) |
| { |
| char *env; |
| double timeout_sec = DEFAULT_TIMEOUT; |
| |
| TCase *tc = (TCase *) emalloc (sizeof (TCase)); /*freed in tcase_free */ |
| |
| if (name == NULL) |
| tc->name = ""; |
| else |
| tc->name = name; |
| |
| env = getenv ("CK_DEFAULT_TIMEOUT"); |
| if (env != NULL) { |
| char *endptr = NULL; |
| double tmp = strtod (env, &endptr); |
| |
| if (tmp >= 0 && endptr != env && (*endptr) == '\0') { |
| timeout_sec = tmp; |
| } |
| } |
| |
| env = getenv ("CK_TIMEOUT_MULTIPLIER"); |
| if (env != NULL) { |
| char *endptr = NULL; |
| double tmp = strtod (env, &endptr); |
| |
| if (tmp >= 0 && endptr != env && (*endptr) == '\0') { |
| timeout_sec = timeout_sec * tmp; |
| } |
| } |
| |
| tc->timeout.tv_sec = (time_t) floor (timeout_sec); |
| tc->timeout.tv_nsec = |
| (long) ((timeout_sec - floor (timeout_sec)) * (double) NANOS_PER_SECONDS); |
| |
| tc->tflst = check_list_create (); |
| tc->unch_sflst = check_list_create (); |
| tc->ch_sflst = check_list_create (); |
| tc->unch_tflst = check_list_create (); |
| tc->ch_tflst = check_list_create (); |
| tc->tags = check_list_create (); |
| |
| return tc; |
| } |
| |
| /* |
| * Helper function to create a list of tags from |
| * a space separated string. |
| */ |
| List * |
| tag_string_to_list (const char *tags_string) |
| { |
| List *list; |
| char *tags; |
| char *tag; |
| |
| list = check_list_create (); |
| |
| if (NULL == tags_string) { |
| return list; |
| } |
| |
| tags = strdup (tags_string); |
| tag = strtok (tags, " "); |
| while (tag) { |
| check_list_add_end (list, strdup (tag)); |
| tag = strtok (NULL, " "); |
| } |
| free (tags); |
| return list; |
| } |
| |
| void |
| tcase_set_tags (TCase * tc, const char *tags_orig) |
| { |
| /* replace any pre-existing list */ |
| if (tc->tags) { |
| check_list_apply (tc->tags, free); |
| check_list_free (tc->tags); |
| } |
| tc->tags = tag_string_to_list (tags_orig); |
| } |
| |
| static void |
| tcase_free (TCase * tc) |
| { |
| check_list_apply (tc->tflst, free); |
| check_list_apply (tc->unch_sflst, free); |
| check_list_apply (tc->ch_sflst, free); |
| check_list_apply (tc->unch_tflst, free); |
| check_list_apply (tc->ch_tflst, free); |
| check_list_apply (tc->tags, free); |
| check_list_free (tc->tflst); |
| check_list_free (tc->unch_sflst); |
| check_list_free (tc->ch_sflst); |
| check_list_free (tc->unch_tflst); |
| check_list_free (tc->ch_tflst); |
| check_list_free (tc->tags); |
| free (tc); |
| } |
| |
| unsigned int |
| tcase_matching_tag (TCase * tc, List * check_for) |
| { |
| |
| if (NULL == check_for) { |
| return 0; |
| } |
| |
| for (check_list_front (check_for); !check_list_at_end (check_for); |
| check_list_advance (check_for)) { |
| for (check_list_front (tc->tags); !check_list_at_end (tc->tags); |
| check_list_advance (tc->tags)) { |
| if (0 == strcmp ((const char *) check_list_val (tc->tags), |
| (const char *) check_list_val (check_for))) { |
| return 1; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| void |
| suite_add_tcase (Suite * s, TCase * tc) |
| { |
| if (s == NULL || tc == NULL || check_list_contains (s->tclst, tc)) { |
| return; |
| } |
| |
| check_list_add_end (s->tclst, tc); |
| } |
| |
| void |
| _tcase_add_test (TCase * tc, TFun fn, const char *name, int _signal, |
| int allowed_exit_value, int start, int end) |
| { |
| TF *tf; |
| |
| if (tc == NULL || fn == NULL || name == NULL) |
| return; |
| tf = (TF *) emalloc (sizeof (TF)); /* freed in tcase_free */ |
| tf->fn = fn; |
| tf->loop_start = start; |
| tf->loop_end = end; |
| tf->signal = _signal; /* 0 means no signal expected */ |
| tf->allowed_exit_value = (WEXITSTATUS_MASK & allowed_exit_value); /* 0 is default successful exit */ |
| tf->name = name; |
| check_list_add_end (tc->tflst, tf); |
| } |
| |
| static Fixture * |
| fixture_create (SFun fun, int ischecked) |
| { |
| Fixture *f; |
| |
| f = (Fixture *) emalloc (sizeof (Fixture)); |
| f->fun = fun; |
| f->ischecked = ischecked; |
| |
| return f; |
| } |
| |
| void |
| tcase_add_unchecked_fixture (TCase * tc, SFun setup, SFun teardown) |
| { |
| tcase_add_fixture (tc, setup, teardown, 0); |
| } |
| |
| void |
| tcase_add_checked_fixture (TCase * tc, SFun setup, SFun teardown) |
| { |
| tcase_add_fixture (tc, setup, teardown, 1); |
| } |
| |
| static void |
| tcase_add_fixture (TCase * tc, SFun setup, SFun teardown, int ischecked) |
| { |
| if (setup) { |
| if (ischecked) |
| check_list_add_end (tc->ch_sflst, fixture_create (setup, ischecked)); |
| else |
| check_list_add_end (tc->unch_sflst, fixture_create (setup, ischecked)); |
| } |
| |
| /* Add teardowns at front so they are run in reverse order. */ |
| if (teardown) { |
| if (ischecked) |
| check_list_add_front (tc->ch_tflst, fixture_create (teardown, ischecked)); |
| else |
| check_list_add_front (tc->unch_tflst, |
| fixture_create (teardown, ischecked)); |
| } |
| } |
| |
| void |
| tcase_set_timeout (TCase * tc, double timeout) |
| { |
| #if defined(HAVE_FORK) |
| if (timeout >= 0) { |
| char *env = getenv ("CK_TIMEOUT_MULTIPLIER"); |
| |
| if (env != NULL) { |
| char *endptr = NULL; |
| double tmp = strtod (env, &endptr); |
| |
| if (tmp >= 0 && endptr != env && (*endptr) == '\0') { |
| timeout = timeout * tmp; |
| } |
| } |
| |
| tc->timeout.tv_sec = (time_t) floor (timeout); |
| tc->timeout.tv_nsec = |
| (long) ((timeout - floor (timeout)) * (double) NANOS_PER_SECONDS); |
| } |
| #else |
| (void) tc; |
| (void) timeout; |
| eprintf |
| ("This version does not support timeouts, as fork is not supported", |
| __FILE__, __LINE__); |
| /* Ignoring, as Check is not compiled with fork support. */ |
| #endif /* HAVE_FORK */ |
| } |
| |
| void |
| tcase_fn_start (const char *fname, const char *file, int line) |
| { |
| send_ctx_info (CK_CTX_TEST); |
| send_loc_info (file, line); |
| |
| current_test_name = fname; |
| } |
| |
| const char * |
| tcase_name () |
| { |
| return current_test_name; |
| } |
| |
| void |
| _mark_point (const char *file, int line) |
| { |
| send_loc_info (file, line); |
| } |
| |
| void |
| _ck_assert_failed (const char *file, int line, const char *expr, ...) |
| { |
| const char *msg; |
| va_list ap; |
| char buf[BUFSIZ]; |
| const char *to_send; |
| |
| send_loc_info (file, line); |
| |
| va_start (ap, expr); |
| msg = (const char *) va_arg (ap, char *); |
| |
| /* |
| * If a message was passed, format it with vsnprintf. |
| * Otherwise, print the expression as is. |
| */ |
| if (msg != NULL) { |
| vsnprintf (buf, BUFSIZ, msg, ap); |
| to_send = buf; |
| } else { |
| to_send = expr; |
| } |
| |
| va_end (ap); |
| send_failure_info (to_send); |
| if (cur_fork_status () == CK_FORK) { |
| #if defined(HAVE_FORK) && HAVE_FORK==1 |
| _exit (1); |
| #endif /* HAVE_FORK */ |
| } else { |
| longjmp (error_jmp_buffer, 1); |
| } |
| } |
| |
| SRunner * |
| srunner_create (Suite * s) |
| { |
| SRunner *sr = (SRunner *) emalloc (sizeof (SRunner)); /* freed in srunner_free */ |
| |
| sr->slst = check_list_create (); |
| if (s != NULL) |
| check_list_add_end (sr->slst, s); |
| sr->stats = (TestStats *) emalloc (sizeof (TestStats)); /* freed in srunner_free */ |
| sr->stats->n_checked = sr->stats->n_failed = sr->stats->n_errors = 0; |
| sr->resultlst = check_list_create (); |
| sr->log_fname = NULL; |
| sr->xml_fname = NULL; |
| sr->tap_fname = NULL; |
| sr->loglst = NULL; |
| |
| #if defined(HAVE_FORK) |
| sr->fstat = CK_FORK_GETENV; |
| #else |
| /* |
| * Overriding the default of running tests in fork mode, |
| * as this system does not have fork() |
| */ |
| sr->fstat = CK_NOFORK; |
| #endif /* HAVE_FORK */ |
| |
| return sr; |
| } |
| |
| void |
| srunner_add_suite (SRunner * sr, Suite * s) |
| { |
| if (s == NULL) |
| return; |
| |
| check_list_add_end (sr->slst, s); |
| } |
| |
| void |
| srunner_free (SRunner * sr) |
| { |
| List *l; |
| TestResult *tr; |
| |
| if (sr == NULL) |
| return; |
| |
| free (sr->stats); |
| l = sr->slst; |
| for (check_list_front (l); !check_list_at_end (l); check_list_advance (l)) { |
| suite_free ((Suite *) check_list_val (l)); |
| } |
| check_list_free (sr->slst); |
| |
| l = sr->resultlst; |
| for (check_list_front (l); !check_list_at_end (l); check_list_advance (l)) { |
| tr = (TestResult *) check_list_val (l); |
| tr_free (tr); |
| } |
| check_list_free (sr->resultlst); |
| |
| free (sr); |
| } |
| |
| 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 = |
| (TestResult **) emalloc (sizeof (trarray[0]) * |
| srunner_ntests_failed (sr)); |
| |
| rlst = sr->resultlst; |
| for (check_list_front (rlst); !check_list_at_end (rlst); |
| check_list_advance (rlst)) { |
| TestResult *tr = (TestResult *) check_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 = |
| (TestResult **) emalloc (sizeof (trarray[0]) * srunner_ntests_run (sr)); |
| |
| rlst = sr->resultlst; |
| for (check_list_front (rlst); !check_list_at_end (rlst); |
| check_list_advance (rlst)) { |
| trarray[i++] = (TestResult *) check_list_val (rlst); |
| } |
| return trarray; |
| } |
| |
| static int |
| non_pass (int val) |
| { |
| return val != CK_PASS; |
| } |
| |
| TestResult * |
| tr_create (void) |
| { |
| TestResult *tr; |
| |
| tr = (TestResult *) emalloc (sizeof (TestResult)); |
| tr_init (tr); |
| return tr; |
| } |
| |
| static void |
| tr_init (TestResult * tr) |
| { |
| tr->ctx = CK_CTX_INVALID; |
| tr->line = -1; |
| tr->rtype = CK_TEST_RESULT_INVALID; |
| tr->msg = NULL; |
| tr->file = NULL; |
| tr->tcname = NULL; |
| tr->tname = NULL; |
| tr->duration = -1; |
| } |
| |
| void |
| tr_free (TestResult * tr) |
| { |
| free (tr->file); |
| free (tr->msg); |
| free (tr); |
| } |
| |
| |
| const char * |
| tr_msg (TestResult * tr) |
| { |
| return tr->msg; |
| } |
| |
| int |
| tr_lno (TestResult * tr) |
| { |
| return tr->line; |
| } |
| |
| const char * |
| tr_lfile (TestResult * tr) |
| { |
| return tr->file; |
| } |
| |
| int |
| tr_rtype (TestResult * tr) |
| { |
| return tr->rtype; |
| } |
| |
| enum ck_result_ctx |
| tr_ctx (TestResult * tr) |
| { |
| return tr->ctx; |
| } |
| |
| const char * |
| tr_tcname (TestResult * tr) |
| { |
| return tr->tcname; |
| } |
| |
| static enum fork_status _fstat = CK_FORK; |
| |
| void |
| set_fork_status (enum fork_status fstat) |
| { |
| if (fstat == CK_FORK || fstat == CK_NOFORK || fstat == CK_FORK_GETENV) |
| _fstat = fstat; |
| else |
| eprintf ("Bad status in set_fork_status", __FILE__, __LINE__); |
| } |
| |
| enum fork_status |
| cur_fork_status (void) |
| { |
| return _fstat; |
| } |
| |
| /** |
| * Not all systems support the same clockid_t's. This call checks |
| * if the CLOCK_MONOTONIC clockid_t is valid. If so, that is returned, |
| * otherwise, CLOCK_REALTIME is returned. |
| * |
| * The clockid_t that was found to work on the first call is |
| * cached for subsequent calls. |
| */ |
| clockid_t |
| check_get_clockid () |
| { |
| static clockid_t clockid = -1; |
| |
| if (clockid == -1) { |
| /* |
| * Only check if we have librt available. Otherwise, the clockid |
| * will be ignored anyway, as the clock_gettime() and |
| * timer_create() functions will be re-implemented in libcompat. |
| * Worse, if librt and alarm() are unavailable, this check |
| * will result in an assert(0). |
| */ |
| #if defined(HAVE_POSIX_TIMERS) && defined(HAVE_MONOTONIC_CLOCK) |
| timer_t timerid; |
| |
| if (timer_create (CLOCK_MONOTONIC, NULL, &timerid) == 0) { |
| timer_delete (timerid); |
| clockid = CLOCK_MONOTONIC; |
| } else { |
| clockid = CLOCK_REALTIME; |
| } |
| #else |
| clockid = CLOCK_MONOTONIC; |
| #endif |
| } |
| |
| return clockid; |
| } |