From 35e8eb93a0fcbc396fc58dfee3ff05db6c5dbd79 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 14 Oct 2013 20:29:06 +0200 Subject: [PATCH] unit-tests: Implement testing framework without "check" --- src/libstrongswan/tests/Makefile.am | 8 +- src/libstrongswan/tests/test_runner.c | 474 +++++++++++++++++++++++--- src/libstrongswan/tests/test_runner.h | 2 +- src/libstrongswan/tests/test_suite.c | 240 +++++++++++++ src/libstrongswan/tests/test_suite.h | 351 +++++++++++++++---- 5 files changed, 959 insertions(+), 116 deletions(-) create mode 100644 src/libstrongswan/tests/test_suite.c diff --git a/src/libstrongswan/tests/Makefile.am b/src/libstrongswan/tests/Makefile.am index 54ceaf53b..c9d10e9fd 100644 --- a/src/libstrongswan/tests/Makefile.am +++ b/src/libstrongswan/tests/Makefile.am @@ -3,7 +3,7 @@ TESTS = test_runner check_PROGRAMS = $(TESTS) test_runner_SOURCES = \ - test_runner.c test_runner.h test_suite.h \ + test_runner.c test_runner.h test_suite.c test_suite.h \ suites/test_linked_list.c \ suites/test_enumerator.c \ suites/test_linked_list_enumerator.c \ @@ -28,11 +28,9 @@ test_runner_CFLAGS = \ -I$(top_srcdir)/src/libstrongswan \ -DPLUGINDIR=\""$(top_builddir)/src/libstrongswan/plugins\"" \ -DPLUGINS=\""${s_plugins}\"" \ - @COVERAGE_CFLAGS@ \ - @CHECK_CFLAGS@ + @COVERAGE_CFLAGS@ test_runner_LDFLAGS = @COVERAGE_LDFLAGS@ test_runner_LDADD = \ $(top_builddir)/src/libstrongswan/libstrongswan.la \ - $(PTHREADLIB) \ - @CHECK_LIBS@ + $(PTHREADLIB) diff --git a/src/libstrongswan/tests/test_runner.c b/src/libstrongswan/tests/test_runner.c index e0e7e2b81..a46007a3a 100644 --- a/src/libstrongswan/tests/test_runner.c +++ b/src/libstrongswan/tests/test_runner.c @@ -1,6 +1,8 @@ /* * Copyright (C) 2013 Tobias Brunner * Hochschule fuer Technik Rapperswil + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec AG * * 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 @@ -13,14 +15,20 @@ * for more details. */ -#include - #include "test_runner.h" #include #include +#include #include +#include +#include + +/** + * Get a tty color escape character for stderr + */ +#define TTY(color) tty_escape_get(2, TTY_FG_##color) /** * Load plugins from builddir @@ -43,17 +51,147 @@ static bool load_plugins() return lib->plugins->load(lib->plugins, PLUGINS); } -int main() +/** + * Check if a specific feature is available, return falg if so + */ +static int check_feature(plugin_feature_t feature, int flag) { - SRunner *sr; - int nf; + if (lib->plugins->has_feature(lib->plugins, feature)) + { + return flag; + } + return 0; +} - /* test cases are forked and there is no cleanup, so disable leak detective. - * if test_suite.h is included leak detective is enabled in test cases */ - setenv("LEAK_DETECTIVE_DISABLE", "1", 1); - /* redirect all output to stderr (to redirect make's stdout to /dev/null) */ - dup2(2, 1); +/** + * Load all available test suites + */ +static array_t *load_suites() +{ + array_t *suites; + enum { + OTEST_RSA = (1<<0), + OTEST_ECDSA = (1<<1), + } otest = 0; + library_init(NULL); + if (!load_plugins()) + { + library_deinit(); + return NULL; + } + lib->plugins->status(lib->plugins, LEVEL_CTRL); + + /* we have to build the test suite array without leak detective, so + * separate plugin checks and suite creation */ + otest |= check_feature(PLUGIN_DEPENDS(PRIVKEY_GEN, KEY_RSA), OTEST_RSA); + otest |= check_feature(PLUGIN_DEPENDS(PRIVKEY_GEN, KEY_ECDSA), OTEST_ECDSA); + + library_deinit(); + + suites = array_create(0, 0); + + array_insert(suites, -1, bio_reader_suite_create()); + array_insert(suites, -1, bio_writer_suite_create()); + array_insert(suites, -1, chunk_suite_create()); + array_insert(suites, -1, enum_suite_create()); + array_insert(suites, -1, enumerator_suite_create()); + array_insert(suites, -1, linked_list_suite_create()); + array_insert(suites, -1, linked_list_enumerator_suite_create()); + array_insert(suites, -1, hashtable_suite_create()); + array_insert(suites, -1, array_suite_create()); + array_insert(suites, -1, identification_suite_create()); + array_insert(suites, -1, threading_suite_create()); + array_insert(suites, -1, utils_suite_create()); + array_insert(suites, -1, host_suite_create()); + array_insert(suites, -1, vectors_suite_create()); + array_insert(suites, -1, pen_suite_create()); + array_insert(suites, -1, asn1_suite_create()); + array_insert(suites, -1, printf_suite_create()); + if (otest & OTEST_RSA) + { + array_insert(suites, -1, rsa_suite_create()); + } + if (otest & OTEST_ECDSA) + { + array_insert(suites, -1, ecdsa_suite_create()); + } + + return suites; +} + +/** + * Unload and destroy test suites and associated data + */ +static void unload_suites(array_t *suites) +{ + test_suite_t *suite; + test_case_t *tcase; + + while (array_remove(suites, 0, &suite)) + { + while (array_remove(suite->tcases, 0, &tcase)) + { + array_destroy(tcase->functions); + array_destroy(tcase->fixtures); + } + free(suite); + } + array_destroy(suites); +} + +/** + * Run a single test function, return FALSE on failure + */ +static bool run_test(test_function_t *tfun, int i) +{ + if (test_restore_point()) + { + tfun->cb(i); + return TRUE; + } + return FALSE; +} + +/** + * Invoke fixture setup/teardown + */ +static bool call_fixture(test_case_t *tcase, bool up) +{ + enumerator_t *enumerator; + test_fixture_t *fixture; + bool failure = FALSE; + + enumerator = array_create_enumerator(tcase->fixtures); + while (enumerator->enumerate(enumerator, &fixture)) + { + if (test_restore_point()) + { + if (up) + { + fixture->setup(); + } + else + { + fixture->teardown(); + } + } + else + { + failure = TRUE; + break; + } + } + enumerator->destroy(enumerator); + + return !failure; +} + +/** + * Test initialization, initializes libstrongswan for the next run + */ +static bool pre_test() +{ library_init(NULL); /* use non-blocking RNG to generate keys fast */ @@ -62,47 +200,295 @@ int main() lib->settings->get_str(lib->settings, "libstrongswan.plugins.random.urandom", "/dev/urandom")); + if (lib->leak_detective) + { + /* disable leak reports during testing */ + lib->leak_detective->set_report_cb(lib->leak_detective, + NULL, NULL, NULL); + } if (!load_plugins()) { library_deinit(); - return EXIT_FAILURE; + return FALSE; } - lib->plugins->status(lib->plugins, LEVEL_CTRL); - sr = srunner_create(NULL); - srunner_add_suite(sr, bio_reader_suite_create()); - srunner_add_suite(sr, bio_writer_suite_create()); - srunner_add_suite(sr, chunk_suite_create()); - srunner_add_suite(sr, enum_suite_create()); - srunner_add_suite(sr, enumerator_suite_create()); - srunner_add_suite(sr, linked_list_suite_create()); - srunner_add_suite(sr, linked_list_enumerator_suite_create()); - srunner_add_suite(sr, hashtable_suite_create()); - srunner_add_suite(sr, array_suite_create()); - srunner_add_suite(sr, identification_suite_create()); - srunner_add_suite(sr, threading_suite_create()); - srunner_add_suite(sr, utils_suite_create()); - srunner_add_suite(sr, host_suite_create()); - srunner_add_suite(sr, vectors_suite_create()); - srunner_add_suite(sr, printf_suite_create()); - srunner_add_suite(sr, pen_suite_create()); - srunner_add_suite(sr, asn1_suite_create()); - if (lib->plugins->has_feature(lib->plugins, - PLUGIN_DEPENDS(PRIVKEY_GEN, KEY_RSA))) + dbg_default_set_level(LEVEL_SILENT); + return TRUE; +} + +/** + * Failure description + */ +typedef struct { + char *name; + char msg[512 - sizeof(char*) - 2 * sizeof(int)]; + const char *file; + int line; + int i; + backtrace_t *bt; +} failure_t; + +/** + * Data passed to leak report callbacks + */ +typedef struct { + array_t *failures; + char *name; + int i; + int leaks; +} report_data_t; + +/** + * Leak report callback, build failures from leaks + */ +static void report_leaks(report_data_t *data, int count, size_t bytes, + backtrace_t *bt, bool detailed) +{ + failure_t failure = { + .name = data->name, + .i = data->i, + .bt = bt->clone(bt), + }; + + snprintf(failure.msg, sizeof(failure.msg), + "Leak detected: %d allocations using %zu bytes", count, bytes); + + array_insert(data->failures, -1, &failure); +} + +/** + * Leak summary callback, check if any leaks found + */ +static void sum_leaks(report_data_t *data, int count, size_t bytes, + int whitelisted) +{ + data->leaks = count; +} + +/** + * Do library cleanup and optionally check for memory leaks + */ +static bool post_test(bool check_leaks, array_t *failures, char *name, int i) +{ + report_data_t data = { + .failures = failures, + .name = name, + .i = i, + }; + + if (check_leaks && lib->leak_detective) { - srunner_add_suite(sr, rsa_suite_create()); + lib->leak_detective->set_report_cb(lib->leak_detective, + (leak_detective_report_cb_t)report_leaks, + (leak_detective_summary_cb_t)sum_leaks, &data); } - if (lib->plugins->has_feature(lib->plugins, - PLUGIN_DEPENDS(PRIVKEY_GEN, KEY_ECDSA))) - { - srunner_add_suite(sr, ecdsa_suite_create()); - } - - srunner_run_all(sr, CK_NORMAL); - nf = srunner_ntests_failed(sr); - - srunner_free(sr); library_deinit(); - return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; + return data.leaks != 0; +} + +/** + * Collect failure information, add failure_t to array + */ +static void collect_failure_info(array_t *failures, char *name, int i) +{ + failure_t failure = { + .name = name, + .i = i, + .bt = test_failure_backtrace(), + }; + + failure.line = test_failure_get(failure.msg, sizeof(failure.msg), + &failure.file); + + array_insert(failures, -1, &failure); +} + +/** + * Print array of collected failure_t to stderr + */ +static void print_failures(array_t *failures) +{ + failure_t failure; + + while (array_remove(failures, 0, &failure)) + { + fprintf(stderr, " %sFailure in '%s': %s (", + TTY(RED), failure.name, failure.msg); + if (failure.line) + { + fprintf(stderr, "%s:%d, ", failure.file, failure.line); + } + fprintf(stderr, "i = %d)%s\n", failure.i, TTY(DEF)); + if (failure.bt) + { + failure.bt->log(failure.bt, stderr, TRUE); + failure.bt->destroy(failure.bt); + } + } +} + +/** + * Run a single test case with fixtures + */ +static bool run_case(test_case_t *tcase) +{ + enumerator_t *enumerator; + test_function_t *tfun; + int passed = 0; + array_t *failures; + + failures = array_create(sizeof(failure_t), 0); + + fprintf(stderr, " Running case '%s': ", tcase->name); + fflush(stderr); + + enumerator = array_create_enumerator(tcase->functions); + while (enumerator->enumerate(enumerator, &tfun)) + { + int i, rounds = 0; + + for (i = tfun->start; i < tfun->end; i++) + { + if (pre_test()) + { + bool ok = FALSE, leaks = FALSE; + + test_setup_timeout(tcase->timeout); + + if (call_fixture(tcase, TRUE)) + { + if (run_test(tfun, i)) + { + if (call_fixture(tcase, FALSE)) + { + ok = TRUE; + } + } + else + { + call_fixture(tcase, FALSE); + } + + } + leaks = post_test(ok, failures, tfun->name, i); + + test_setup_timeout(0); + + if (ok) + { + if (!leaks) + { + rounds++; + fprintf(stderr, "%s+%s", TTY(GREEN), TTY(DEF)); + } + } + else + { + collect_failure_info(failures, tfun->name, i); + } + if (!ok || leaks) + { + fprintf(stderr, "%s-%s", TTY(RED), TTY(DEF)); + } + } + else + { + fprintf(stderr, "!"); + } + } + fflush(stderr); + if (rounds == tfun->end - tfun->start) + { + passed++; + } + } + enumerator->destroy(enumerator); + + fprintf(stderr, "\n"); + + print_failures(failures); + array_destroy(failures); + + return passed == array_count(tcase->functions); +} + +/** + * Run a single test suite + */ +static bool run_suite(test_suite_t *suite) +{ + enumerator_t *enumerator; + test_case_t *tcase; + int passed = 0; + + fprintf(stderr, " Running suite '%s':\n", suite->name); + + enumerator = array_create_enumerator(suite->tcases); + while (enumerator->enumerate(enumerator, &tcase)) + { + if (run_case(tcase)) + { + passed++; + } + } + enumerator->destroy(enumerator); + + if (passed == array_count(suite->tcases)) + { + fprintf(stderr, " %sPassed all %u '%s' test cases%s\n", + TTY(GREEN), array_count(suite->tcases), suite->name, TTY(DEF)); + return TRUE; + } + fprintf(stderr, " %sPassed %u/%u '%s' test cases%s\n", + TTY(RED), passed, array_count(suite->tcases), suite->name, TTY(DEF)); + return FALSE; +} + +int main(int argc, char *argv[]) +{ + array_t *suites; + test_suite_t *suite; + enumerator_t *enumerator; + int passed = 0, result; + + /* redirect all output to stderr (to redirect make's stdout to /dev/null) */ + dup2(2, 1); + + test_setup_handler(); + + suites = load_suites(); + if (!suites) + { + return EXIT_FAILURE; + } + + fprintf(stderr, "Running %u test suites:\n", array_count(suites)); + + enumerator = array_create_enumerator(suites); + while (enumerator->enumerate(enumerator, &suite)) + { + if (run_suite(suite)) + { + passed++; + } + } + enumerator->destroy(enumerator); + + if (passed == array_count(suites)) + { + fprintf(stderr, "%sPassed all %u suites%s\n", + TTY(GREEN), array_count(suites), TTY(DEF)); + result = EXIT_SUCCESS; + } + else + { + fprintf(stderr, "%sPassed %u of %u suites%s\n", + TTY(RED), passed, array_count(suites), TTY(DEF)); + result = EXIT_FAILURE; + } + + unload_suites(suites); + + return result; } diff --git a/src/libstrongswan/tests/test_runner.h b/src/libstrongswan/tests/test_runner.h index 63b71f724..a35c01241 100644 --- a/src/libstrongswan/tests/test_runner.h +++ b/src/libstrongswan/tests/test_runner.h @@ -16,7 +16,7 @@ #ifndef TEST_RUNNER_H_ #define TEST_RUNNER_H_ -#include +#include Suite *bio_reader_suite_create(); Suite *bio_writer_suite_create(); diff --git a/src/libstrongswan/tests/test_suite.c b/src/libstrongswan/tests/test_suite.c new file mode 100644 index 000000000..29221eb68 --- /dev/null +++ b/src/libstrongswan/tests/test_suite.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec AG + * + * 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. See . + * + * 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. + */ + +#include "test_suite.h" + +#include +#include + +/** + * Failure message buf + */ +static char failure_buf[512]; + +/** + * Source file failure occured + */ +static const char *failure_file; + +/** + * Line of source file failure occured + */ +static int failure_line; + +/** + * Backtrace of failure, if any + */ +static backtrace_t *failure_backtrace; + +/** + * Longjump restore point when failing + */ +sigjmp_buf test_restore_point_env; + +/** + * See header. + */ +test_suite_t* test_suite_create(const char *name) +{ + test_suite_t *suite; + + INIT(suite, + .name = name, + .tcases = array_create(0, 0), + ); + return suite; +} + +/** + * See header. + */ +test_case_t* test_case_create(const char *name) +{ + test_case_t *tcase; + + INIT(tcase, + .name = name, + .functions = array_create(sizeof(test_function_t), 0), + .fixtures = array_create(sizeof(test_fixture_t), 0), + .timeout = TEST_FUNCTION_DEFAULT_TIMEOUT, + ); + return tcase; +} + +/** + * See header. + */ +void test_case_add_checked_fixture(test_case_t *tcase, test_fixture_cb_t setup, + test_fixture_cb_t teardown) +{ + test_fixture_t fixture = { + .setup = setup, + .teardown = teardown, + }; + array_insert(tcase->fixtures, -1, &fixture); +} + +/** + * See header. + */ +void test_case_add_test_name(test_case_t *tcase, char *name, + test_function_cb_t cb, int start, int end) +{ + test_function_t fun = { + .name = name, + .cb = cb, + .start = start, + .end = end, + }; + array_insert(tcase->functions, -1, &fun); +} + +/** + * See header. + */ +void test_case_set_timeout(test_case_t *tcase, int s) +{ + tcase->timeout = s; +} + +/** + * See header. + */ +void test_suite_add_case(test_suite_t *suite, test_case_t *tcase) +{ + array_insert(suite->tcases, -1, tcase); +} + +/** + * Let test case fail + */ +static inline void test_failure() +{ + siglongjmp(test_restore_point_env, 1); +} + +/** + * See header. + */ +void test_fail_vmsg(const char *file, int line, char *fmt, va_list args) +{ + vsnprintf(failure_buf, sizeof(failure_buf), fmt, args); + failure_line = line; + failure_file = file; + + test_failure(); +} + +/** + * See header. + */ +void test_fail_msg(const char *file, int line, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vsnprintf(failure_buf, sizeof(failure_buf), fmt, args); + failure_line = line; + failure_file = file; + va_end(args); + + test_failure(); +} + +/** + * Signal handler catching critical and alarm signals + */ +static void test_sighandler(int signal) +{ + char *signame; + bool old = FALSE; + + switch (signal) + { + case SIGSEGV: + signame = "SIGSEGV"; + break; + case SIGILL: + signame = "SIGILL"; + break; + case SIGBUS: + signame = "SIGBUS"; + break; + case SIGALRM: + signame = "timeout"; + break; + default: + signame = "SIG"; + break; + } + if (lib->leak_detective) + { + old = lib->leak_detective->set_state(lib->leak_detective, FALSE); + } + failure_backtrace = backtrace_create(3); + if (lib->leak_detective) + { + lib->leak_detective->set_state(lib->leak_detective, old); + } + test_fail_msg(NULL, 0, "%s(%d)", signame, signal); +} + +/** + * See header. + */ +void test_setup_handler() +{ + struct sigaction action; + + action.sa_handler = test_sighandler; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGILL, &action, NULL); + sigaction(SIGBUS, &action, NULL); + sigaction(SIGALRM, &action, NULL); +} + +/** + * See header. + */ +void test_setup_timeout(int s) +{ + alarm(s); +} + +/** + * See header. + */ +int test_failure_get(char *msg, int len, const char **file) +{ + strncpy(msg, failure_buf, len - 1); + msg[len - 1] = 0; + *file = failure_file; + return failure_line; +} + +/** + * See header. + */ +backtrace_t *test_failure_backtrace() +{ + backtrace_t *bt; + + bt = failure_backtrace; + failure_backtrace = NULL; + + return bt; +} diff --git a/src/libstrongswan/tests/test_suite.h b/src/libstrongswan/tests/test_suite.h index 2a2861323..11aca3b5a 100644 --- a/src/libstrongswan/tests/test_suite.h +++ b/src/libstrongswan/tests/test_suite.h @@ -1,6 +1,8 @@ /* * Copyright (C) 2013 Tobias Brunner * Hochschule fuer Technik Rapperswil + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec AG * * 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 @@ -16,95 +18,312 @@ #ifndef TEST_UTILS_H_ #define TEST_UTILS_H_ -#include +#define _GNU_SOURCE +#include + #include #include +#include +#include + +typedef struct test_suite_t test_suite_t; +typedef struct test_case_t test_case_t; +typedef struct test_function_t test_function_t; +typedef struct test_fixture_t test_fixture_t; /** - * Used to mark test cases that use test fixtures. + * Default timeout for a single test function */ -#define UNIT_TEST_FIXTURE_USED "UNIT_TEST_FIXTURE_USED" +#define TEST_FUNCTION_DEFAULT_TIMEOUT 2 /** - * Check for memory leaks and fail if any are encountered. + * Test function implementation */ -#define CHECK_FOR_LEAKS() do \ -{ \ - if (lib->leak_detective) \ - { \ - if (lib->leak_detective->leaks(lib->leak_detective)) { \ - lib->leak_detective->report(lib->leak_detective, TRUE); \ - } \ - ck_assert_int_eq(lib->leak_detective->leaks(lib->leak_detective), 0); \ - } \ -} \ -while(0) +typedef void (*test_function_cb_t)(int); /** - * Extended versions of the START|END_TEST macros that use leak detective. + * Fixture for a test case. + */ +typedef void (*test_fixture_cb_t)(void); + +/** + * A test suite; a collection of test cases with fixtures + */ +struct test_suite_t { + /** name of the test suite */ + const char *name; + /** test cases registered, as test_case_t* */ + array_t *tcases; +}; + +/** + * A test case; multiple test functions using the same fixtures + */ +struct test_case_t { + /** name of the test case */ + const char *name; + /** tests registered, as test_function_t */ + array_t *functions; + /** fixture for tests, as test_fixture_t */ + array_t *fixtures; + /** timeout for each function, in s */ + int timeout; +}; + +/** + * A test function, with optional loop setup + */ +struct test_function_t { + /** name of test function */ + char *name; + /** tests function registered, test_function_t* */ + test_function_cb_t cb; + /** start for loop test */ + int start; + /** end for loop test */ + int end; +}; + +/** + * Registered fixture for a test case + */ +struct test_fixture_t { + test_fixture_cb_t setup; + test_fixture_cb_t teardown; +}; + +/** + * Create a new test suite * - * Since each test case runs in its own fork of the test runner the stuff - * allocated before the test starts is not freed, so leak detective is disabled - * by default to prevent false positives. By enabling it right when the test - * starts we at least capture leaks created by the tested objects/functions and - * the test case itself. This allows writing test cases for cleanup functions. + * @param name name of the test suite + * @return test suite + */ +test_suite_t* test_suite_create(const char *name); + +/** + * Create a new test case * - * To define test fixture with possibly allocated/destroyed memory that is - * allocated/freed in a test case use the START|END_SETUP|TEARDOWN macros. + * @param name name of test case + * @return test case */ -#undef START_TEST -#define START_TEST(name) \ -static void name (int _i CK_ATTRIBUTE_UNUSED) \ -{ \ - tcase_fn_start(""#name, __FILE__, __LINE__); \ - dbg_default_set_level(LEVEL_SILENT); \ - if (lib->leak_detective) \ - { \ - lib->leak_detective->set_state(lib->leak_detective, TRUE); \ - } +test_case_t* test_case_create(const char *name); -#undef END_TEST -#define END_TEST \ - if (!lib->get(lib, UNIT_TEST_FIXTURE_USED)) \ +/** + * Add a setup/teardown function to the test case + * + * @param tcase test case to add a fixture to + * @param setup setup function called before each test + * @param teardown cleanup function called after each test + */ +void test_case_add_checked_fixture(test_case_t *tcase, test_fixture_cb_t setup, + test_fixture_cb_t teardown); + +/** + * Add a test function to a test case, with a name, looped several times + * + * @param name name of the test case + * @param tcase test case to add test function to + * @param cb callback function to invoke for test + * @param start start of loop counter + * @param end end of loop counter + */ +void test_case_add_test_name(test_case_t *tcase, char *name, + test_function_cb_t cb, int start, int end); + +/** + * Add a test function to a test case + * + * @param tcase test case to add test function to + * @param cb callback function to invoke for test + */ +#define test_case_add_test(tcase, cb) \ + test_case_add_test_name(tcase, #cb, cb, 0, 1) + +/** + * Add a test function to a test case, looped several times + * + * @param tcase test case to add test function to + * @param cb callback function to invoke for test + * @param start start of loop counter + * @param end end of loop counter + */ +#define test_case_add_loop_test(tcase, cb, start, end) \ + test_case_add_test_name(tcase, #cb, cb, start, end) + +/** + * Set a custom timeout for test functions in a test case + * + * @param tcase test case to set timeout for + * @param s test timeout in s + */ +void test_case_set_timeout(test_case_t *tcase, int s); + +/** + * Add a test function to a test case, looped several times + * + * @param tcase test case to add test function to + * @param cb callback function to invoke for test + * @param start start of loop counter + * @param end end of loop counter + */ +void test_suite_add_case(test_suite_t *suite, test_case_t *tcase); + +/** + * sigjmp restore point used by test_restore_point + */ +extern sigjmp_buf test_restore_point_env; + +/** + * Set or return from an execution restore point + * + * This call sets a restore execution point and returns TRUE after it has + * been set up. On test failure, the execution is returned to the restore point + * and FALSE is returned to indicate test failure. + * + * @return TRUE if restore point set, FALSE when restored + */ +#define test_restore_point() (sigsetjmp(test_restore_point_env, 1) == 0) + +/** + * Set up signal handlers for test cases + */ +void test_setup_handler(); + +/** + * Set up a timeout to let a test fail + * + * @param s timeout, 0 to disable timeout + */ +void test_setup_timeout(int s); + +/** + * Get info about a test failure + * + * @param msg buffer receiving failure info + * @param len size of msg buffer + * @param file pointer receiving source code file + * @return source code line number + */ +int test_failure_get(char *msg, int len, const char **file); + +/** + * Get a backtrace for a failure. + * + * @return allocated backtrace of test failure, if any + */ +backtrace_t *test_failure_backtrace(); + +/** + * Let a test fail and set a message using vprintf style arguments. + * + * @param file source code file name + * @param line source code line number + * @param fmt printf format string + * @param args argument list for fmt + */ +void test_fail_vmsg(const char *file, int line, char *fmt, va_list args); + +/** + * Let a test fail and set a message using printf style arguments. + * + * @param file source code file name + * @param line source code line number + * @param fmt printf format string + * @param ... arguments for fmt + */ +void test_fail_msg(const char *file, int line, char *fmt, ...); + +/** + * Check if two integers equal, fail test if not + * + * @param a first integer + * @param b second integer + */ +#define test_int_eq(a, b) \ +({ \ + typeof(a) _a = a; \ + typeof(b) _b = b; \ + if (_a != _b) \ { \ - CHECK_FOR_LEAKS(); \ + test_fail_msg(__FILE__, __LINE__, #a " != " #b " (%d != %d)", _a, _b); \ } \ -} +}) /** - * Define a function to setup a test fixture that can be used with the above - * macros. + * Check if two strings equal, fail test if not + * + * @param a first string + * @param b second string */ -#define START_SETUP(name) \ -static void name() \ -{ \ - lib->set(lib, UNIT_TEST_FIXTURE_USED, (void*)TRUE); \ - if (lib->leak_detective) \ +#define test_str_eq(a, b) \ +({ \ + char* _a = (char*)a; \ + char* _b = (char*)b; \ + if (!_a || !_b || !streq(_a, _b)) \ { \ - lib->leak_detective->set_state(lib->leak_detective, TRUE); \ - } + test_fail_msg(__FILE__, __LINE__, \ + #a " != " #b " (\"%s\" != \"%s\")", _a, _b); \ + } \ +}) /** - * End a setup function + * Check if a statement evaluates to TRUE, fail test if not + * + * @param x statement to evaluate */ +#define test_assert(x) \ +({ \ + if (!(x)) \ + { \ + test_fail_msg(__FILE__, __LINE__, #x); \ + } \ +}) + +/** + * Check if a statement evaluates to TRUE, fail and print a message if not + * + * @param x statement to evaluate + * @param fmt message format string + * @param ... fmt printf arguments + */ +#define test_assert_msg(x, fmt, ...) \ +({ \ + if (!(x)) \ + { \ + test_fail_msg(__FILE__, __LINE__, #x ": " fmt, ##__VA_ARGS__); \ + } \ +}) + + + +/* "check unit testing" compatibility */ +#define Suite test_suite_t +#define TCase test_case_t +#define ck_assert_int_eq test_int_eq +#define ck_assert test_assert +#define ck_assert_msg test_assert_msg +#define ck_assert_str_eq test_str_eq +#define fail(fmt, ...) test_fail_msg(__FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define fail_if(x, fmt, ...) \ +({ \ + if (x) \ + { \ + test_fail_msg(__FILE__, __LINE__, #x ": " fmt, ##__VA_ARGS__); \ + } \ +}) +#define fail_unless test_assert_msg +#define suite_create test_suite_create +#define tcase_create test_case_create +#define tcase_add_checked_fixture test_case_add_checked_fixture +#define tcase_add_test test_case_add_test +#define tcase_add_loop_test test_case_add_loop_test +#define tcase_set_timeout test_case_set_timeout +#define suite_add_tcase test_suite_add_case +#define START_TEST(name) static void name (int _i) { +#define END_TEST } +#define START_SETUP(name) static void name() { #define END_SETUP } - -/** - * Define a function to teardown a test fixture that can be used with the above - * macros. - */ -#define START_TEARDOWN(name) \ -static void name() \ -{ - -/** - * End a teardown function - */ -#define END_TEARDOWN \ - if (lib->get(lib, UNIT_TEST_FIXTURE_USED)) \ - { \ - CHECK_FOR_LEAKS(); \ - } \ -} +#define START_TEARDOWN(name) static void name() { +#define END_TEARDOWN } #endif /** TEST_UTILS_H_ */