| /* |
| * 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 <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * 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 <signal.h> |
| #include <unistd.h> |
| |
| #ifndef WIN32 |
| #include <pthread.h> |
| #endif |
| |
| #include <threading/thread.h> |
| |
| /** |
| * Failure message buf |
| */ |
| static char failure_buf[4096]; |
| |
| /** |
| * Source file failure occurred |
| */ |
| static const char *failure_file; |
| |
| /** |
| * Line of source file failure occurred |
| */ |
| static int failure_line; |
| |
| /** |
| * Backtrace of failure, if any |
| */ |
| static backtrace_t *failure_backtrace; |
| |
| /** |
| * Flag to indicate if a worker thread failed |
| */ |
| static bool worker_failed; |
| |
| /** |
| * Warning message buf |
| */ |
| static char warning_buf[4096]; |
| |
| /** |
| * Source file warning was issued |
| */ |
| static const char *warning_file; |
| |
| /** |
| * Line of source file warning was issued |
| */ |
| static int warning_line; |
| |
| /** |
| * 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); |
| } |
| |
| #ifdef WIN32 |
| |
| /** |
| * Longjump restore point when failing |
| */ |
| jmp_buf test_restore_point_env; |
| |
| /** |
| * Thread ID of main thread |
| */ |
| static DWORD main_thread; |
| |
| /** |
| * APC routine invoked by main thread on worker failure |
| */ |
| static void WINAPI set_worker_failure(ULONG_PTR dwParam) |
| { |
| worker_failed = TRUE; |
| } |
| |
| /** |
| * Let test case fail |
| */ |
| static void test_failure() |
| { |
| if (GetCurrentThreadId() == main_thread) |
| { |
| longjmp(test_restore_point_env, 1); |
| } |
| else |
| { |
| HANDLE *thread; |
| |
| thread = OpenThread(THREAD_SET_CONTEXT, FALSE, main_thread); |
| if (thread) |
| { |
| QueueUserAPC(set_worker_failure, thread, (uintptr_t)NULL); |
| CloseHandle(thread); |
| } |
| thread_exit(NULL); |
| } |
| } |
| |
| /** |
| * See header. |
| */ |
| void test_fail_if_worker_failed() |
| { |
| if (GetCurrentThreadId() == main_thread && worker_failed) |
| { |
| test_failure(); |
| } |
| } |
| |
| /** |
| * Vectored exception handler |
| */ |
| static long WINAPI eh_handler(PEXCEPTION_POINTERS ei) |
| { |
| char *ename; |
| bool old = FALSE; |
| |
| switch (ei->ExceptionRecord->ExceptionCode) |
| { |
| case EXCEPTION_ACCESS_VIOLATION: |
| ename = "ACCESS_VIOLATION"; |
| break; |
| case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: |
| ename = "ARRAY_BOUNDS_EXCEEDED"; |
| break; |
| case EXCEPTION_DATATYPE_MISALIGNMENT: |
| ename = "DATATYPE_MISALIGNMENT"; |
| break; |
| case EXCEPTION_FLT_DENORMAL_OPERAND: |
| ename = "FLT_DENORMAL_OPERAND"; |
| break; |
| case EXCEPTION_FLT_DIVIDE_BY_ZERO: |
| ename = "FLT_DIVIDE_BY_ZERO"; |
| break; |
| case EXCEPTION_FLT_INEXACT_RESULT: |
| ename = "FLT_INEXACT_RESULT"; |
| break; |
| case EXCEPTION_FLT_INVALID_OPERATION: |
| ename = "FLT_INVALID_OPERATION"; |
| break; |
| case EXCEPTION_FLT_OVERFLOW: |
| ename = "FLT_OVERFLOW"; |
| break; |
| case EXCEPTION_FLT_STACK_CHECK: |
| ename = "FLT_STACK_CHECK"; |
| break; |
| case EXCEPTION_FLT_UNDERFLOW: |
| ename = "FLT_UNDERFLOW"; |
| break; |
| case EXCEPTION_ILLEGAL_INSTRUCTION: |
| ename = "ILLEGAL_INSTRUCTION"; |
| break; |
| case EXCEPTION_IN_PAGE_ERROR: |
| ename = "IN_PAGE_ERROR"; |
| break; |
| case EXCEPTION_INT_DIVIDE_BY_ZERO: |
| ename = "INT_DIVIDE_BY_ZERO"; |
| break; |
| case EXCEPTION_INT_OVERFLOW: |
| ename = "INT_OVERFLOW"; |
| break; |
| case EXCEPTION_INVALID_DISPOSITION: |
| ename = "INVALID_DISPOSITION"; |
| break; |
| case EXCEPTION_NONCONTINUABLE_EXCEPTION: |
| ename = "NONCONTINUABLE_EXCEPTION"; |
| break; |
| case EXCEPTION_PRIV_INSTRUCTION: |
| ename = "PRIV_INSTRUCTION"; |
| break; |
| case EXCEPTION_STACK_OVERFLOW: |
| ename = "STACK_OVERFLOW"; |
| break; |
| default: |
| return EXCEPTION_CONTINUE_EXECUTION; |
| } |
| |
| if (lib->leak_detective) |
| { |
| old = lib->leak_detective->set_state(lib->leak_detective, FALSE); |
| } |
| failure_backtrace = backtrace_create(5); |
| if (lib->leak_detective) |
| { |
| lib->leak_detective->set_state(lib->leak_detective, old); |
| } |
| failure_line = 0; |
| test_fail_msg(NULL, 0, "%s exception", ename); |
| /* not reached */ |
| return EXCEPTION_CONTINUE_EXECUTION; |
| } |
| |
| /** |
| * See header. |
| */ |
| void test_setup_handler() |
| { |
| main_thread = GetCurrentThreadId(); |
| AddVectoredExceptionHandler(0, eh_handler); |
| } |
| |
| /** |
| * See header. |
| */ |
| void test_setup_timeout(int s) |
| { |
| /* TODO: currently not supported. SetTimer()? */ |
| |
| worker_failed = FALSE; |
| } |
| |
| #else /* !WIN32 */ |
| |
| /** |
| * Longjump restore point when failing |
| */ |
| sigjmp_buf test_restore_point_env; |
| |
| /** |
| * Main thread performing tests |
| */ |
| static pthread_t main_thread; |
| |
| /** |
| * Let test case fail |
| */ |
| static inline void test_failure() |
| { |
| if (pthread_self() == main_thread) |
| { |
| siglongjmp(test_restore_point_env, 1); |
| } |
| else |
| { |
| pthread_kill(main_thread, SIGUSR1); |
| /* terminate thread to prevent it from going wild */ |
| pthread_exit(NULL); |
| } |
| } |
| |
| /** |
| * See header. |
| */ |
| void test_fail_if_worker_failed() |
| { |
| if (pthread_self() == main_thread && worker_failed) |
| { |
| test_failure(); |
| } |
| } |
| |
| /** |
| * Signal handler catching critical and alarm signals |
| */ |
| static void test_sighandler(int signal) |
| { |
| char *signame; |
| bool old = FALSE; |
| |
| switch (signal) |
| { |
| case SIGUSR1: |
| /* a different thread failed, abort test at the next opportunity */ |
| worker_failed = TRUE; |
| return; |
| 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); |
| /* unable to restore a valid context for that thread, terminate */ |
| fprintf(stderr, "\n%s(%d) outside of main thread:\n", signame, signal); |
| failure_backtrace->log(failure_backtrace, stderr, TRUE); |
| fprintf(stderr, "terminating...\n"); |
| abort(); |
| } |
| |
| /** |
| * See header. |
| */ |
| void test_setup_handler() |
| { |
| struct sigaction action = { |
| .sa_handler = test_sighandler, |
| }; |
| |
| main_thread = pthread_self(); |
| |
| /* signal handler inherited by all threads */ |
| sigaction(SIGSEGV, &action, NULL); |
| sigaction(SIGILL, &action, NULL); |
| sigaction(SIGBUS, &action, NULL); |
| /* ignore ALRM/USR1, these are caught by main thread only */ |
| action.sa_handler = SIG_IGN; |
| sigaction(SIGALRM, &action, NULL); |
| sigaction(SIGUSR1, &action, NULL); |
| } |
| |
| /** |
| * See header. |
| */ |
| void test_setup_timeout(int s) |
| { |
| struct sigaction action = { |
| .sa_handler = test_sighandler, |
| }; |
| |
| /* This called by main thread only. Setup handler for timeout and |
| * failure cross-thread signaling. */ |
| sigaction(SIGALRM, &action, NULL); |
| sigaction(SIGUSR1, &action, NULL); |
| |
| alarm(s); |
| |
| worker_failed = FALSE; |
| } |
| |
| #endif /* !WIN32 */ |
| |
| /** |
| * 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_warn_msg(const char *file, int line, char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| vsnprintf(warning_buf, sizeof(warning_buf), fmt, args); |
| warning_line = line; |
| warning_file = file; |
| va_end(args); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| /** |
| * 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. |
| */ |
| int test_warning_get(char *msg, int len, const char **file) |
| { |
| int line = warning_line; |
| |
| if (!line) |
| { |
| return 0; |
| } |
| strncpy(msg, warning_buf, len - 1); |
| msg[len - 1] = 0; |
| *file = warning_file; |
| /* reset state */ |
| warning_line = 0; |
| return line; |
| } |
| |
| /** |
| * See header. |
| */ |
| backtrace_t *test_failure_backtrace() |
| { |
| backtrace_t *bt; |
| |
| bt = failure_backtrace; |
| failure_backtrace = NULL; |
| |
| return bt; |
| } |