diff options
Diffstat (limited to 'cpukit/libtest/t-test.c')
-rw-r--r-- | cpukit/libtest/t-test.c | 888 |
1 files changed, 888 insertions, 0 deletions
diff --git a/cpukit/libtest/t-test.c b/cpukit/libtest/t-test.c new file mode 100644 index 0000000000..cf3bcd6a99 --- /dev/null +++ b/cpukit/libtest/t-test.c @@ -0,0 +1,888 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (C) 2018, 2019 embedded brains GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define _GNU_SOURCE + +#include <t.h> + +#include <sys/queue.h> +#include <limits.h> +#include <pthread.h> +#include <sched.h> +#include <setjmp.h> +#include <stdatomic.h> + +#ifdef __rtems__ +#include <rtems/score/io.h> +#include <rtems/score/percpu.h> +#include <rtems/score/smp.h> +#include <rtems/score/threadimpl.h> +#include <rtems/linkersets.h> +#include <rtems/version.h> +#else +#include "t-test-printf.h" +#endif /* __rtems__ */ + +#define T_LINE_SIZE 120 + +#define T_SCOPE_SIZE 5 + +typedef struct { + pthread_spinlock_t lock; + void (*putchar)(int, void *); + void *putchar_arg; + T_verbosity verbosity; + const T_case_context *registered_cases; + const T_case_context *current_case; + void *fixture_context; + LIST_HEAD(, T_destructor) destructors; + T_time case_begin_time; + atomic_uint planned_steps; + atomic_uint steps; + atomic_uint failures; + jmp_buf case_begin_context; + unsigned int overall_cases; + unsigned int overall_steps; + unsigned int overall_failures; + T_time run_begin_time; +#ifdef __rtems__ + Thread_Control *runner_thread; + const Per_CPU_Control *runner_cpu; +#else + bool runner_valid; + pthread_t runner_thread; +#endif + const T_config *config; +} T_context; + +static T_context T_instance; + +static int +T_do_vprintf(T_context *ctx, char const *fmt, va_list ap) +{ + return _IO_Vprintf(ctx->putchar, ctx->putchar_arg, fmt, ap); +} + +int +T_vprintf(char const *fmt, va_list ap) +{ + return T_do_vprintf(&T_instance, fmt, ap); +} + +typedef struct { + char *s; + size_t n; +} T_putchar_string_context; + +static void +T_putchar_string(int c, void *arg) +{ + T_putchar_string_context *ctx; + char *s; + size_t n; + + ctx = arg; + s = ctx->s; + n = ctx->n; + + if (n == 1) { + c = '\0'; + } + + if (n > 1) { + *s = (char)c; + ++s; + --n; + } + + ctx->s = s; + ctx->n = n; +} + +int +T_snprintf(char *s, size_t n, char const *fmt, ...) +{ + va_list ap; + int len; + T_putchar_string_context ctx = { + .s = s, + .n = n + }; + + va_start(ap, fmt); + len = _IO_Vprintf(T_putchar_string, &ctx, fmt, ap); + va_end(ap); + + if (ctx.n > 0) { + *ctx.s = '\0'; + } + + return len; +} + +static int +T_cpu(void) +{ +#if defined(__rtems__) + return (int)_SMP_Get_current_processor(); +#elif defined(__linux__) + return sched_getcpu(); +#else + return 0; +#endif +} + +#if defined(__rtems__) +static const char * +T_object_name_to_string(Objects_Name name, char *buf) +{ + uint32_t on; + size_t i; + int s; + + on = name.name_u32; + i = 0; + + for (s = 24; s >= 0; s -= 8) { + unsigned char c; + + c = (unsigned char)(on >> s); + + if (c >= '!' && c <= '~') { + buf[i] = (char)c; + ++i; + } + } + + buf[i] = '\0'; + return buf; +} + +static const char * +T_thread_name(const Thread_Control *th, char *buf) +{ + if (th != NULL) { + const char *name; + + name = th->Join_queue.Queue.name; + + if (name != NULL && name[0] != '\0') { + return name; + } else { + return T_object_name_to_string(th->Object.name, buf); + } + } else { + buf[0] = '?'; + buf[1] = '\0'; + return buf; + } +} +#endif + +static const char * +T_scope(char *buf) +{ + const char *r; + +#if defined(__rtems__) + ISR_Level level; + const Per_CPU_Control *cpu_self; + + _ISR_Local_disable(level); + cpu_self = _Per_CPU_Get(); + + if (cpu_self->isr_nest_level == 0) { + Thread_Control *executing; + + executing = _Per_CPU_Get_executing(cpu_self); + _ISR_Local_enable(level); + r = T_thread_name(executing, buf); + } else { + _ISR_Local_enable(level); + buf[0] = 'I'; + buf[1] = 'S'; + buf[2] = 'R'; + buf[3] = '\0'; + r = buf; + } +#elif defined(__linux__) + static __thread char name[128]; + + (void)buf; + + if (name[0] == '\0') { + pthread_getname_np(pthread_self(), name, sizeof(name)); + } + + r = &name[0]; +#else + buf[0] = '?'; + buf[1] = '\0'; + r = buf; +#endif + + return r; +} + +static void +T_set_runner(T_context *ctx) +{ +#ifdef __rtems__ + ISR_Level level; + const Per_CPU_Control *cpu_self; + + _ISR_Local_disable(level); + cpu_self = _Per_CPU_Get(); + ctx->runner_cpu = cpu_self; + + if (cpu_self->isr_nest_level == 0) { + ctx->runner_thread = _Per_CPU_Get_executing(cpu_self); + } else { + ctx->runner_thread = NULL; + } + + _ISR_Local_enable(level); +#else + ctx->runner_valid = true; + ctx->runner_thread = pthread_self(); +#endif +} + +int +T_printf(char const *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = T_vprintf(fmt, ap); + va_end(ap); + + return len; +} + +void +T_log(T_verbosity verbosity, char const *fmt, ...) +{ + T_context *ctx; + + ctx = &T_instance; + + if (ctx->verbosity >= verbosity) { + va_list ap; + + T_printf("L:"); + va_start(ap, fmt); + T_vprintf(fmt, ap); + va_end(ap); + T_printf("\n"); + } +} + +static unsigned int +T_fetch_add_step(T_context *ctx) +{ + return atomic_fetch_add_explicit(&ctx->steps, 1, memory_order_relaxed); +} + +static unsigned int +T_add_failure(T_context *ctx) +{ + return atomic_fetch_add_explicit(&ctx->failures, 1, + memory_order_relaxed); +} + +static void +T_stop(T_context *ctx) +{ + const T_case_context *tc; + + tc = ctx->current_case; + + if (tc != NULL) { + const T_fixture *fixture; + + fixture = tc->fixture; + + if (fixture != NULL && fixture->stop != NULL) { + (*fixture->stop)(ctx->fixture_context); + } + } + + longjmp(ctx->case_begin_context, 1); +} + +void T_plan(unsigned int planned_steps) +{ + T_context *ctx; + unsigned int expected; + bool success; + + ctx = &T_instance; + expected = UINT_MAX; + success = atomic_compare_exchange_strong_explicit(&ctx->planned_steps, + &expected, planned_steps, memory_order_relaxed, + memory_order_relaxed); + T_check_true(success, NULL, "planned steps (%u) already set", expected); +} + +void +T_case_register(T_case_context *tc) +{ + T_context *ctx; + + ctx = &T_instance; + tc->next = ctx->registered_cases; + ctx->registered_cases = tc; +} + +T_verbosity +T_set_verbosity(T_verbosity verbosity) +{ + T_context *ctx; + T_verbosity previous; + + ctx = &T_instance; + previous = ctx->verbosity; + ctx->verbosity = verbosity; + + return previous; +} + +void * +T_fixture_context(void) +{ + return T_instance.fixture_context; +} + +void +T_set_fixture_context(void *context) +{ + T_instance.fixture_context = context; +} + +const char * +T_case_name(void) +{ + const T_case_context *tc; + + tc = T_instance.current_case; + + if (tc != NULL) { + return tc->name; + } else { + return "?"; + } +} + +void +T_check_true(bool ok, const T_check_context *t, const char *fmt, ...) +{ + T_context *ctx; + va_list ap; + char scope[T_SCOPE_SIZE]; + + ctx = &T_instance; + + if (t != NULL) { + unsigned int step; + + if ((t->flags & T_CHECK_QUIET) == 0) { + step = T_fetch_add_step(ctx); + } else { + step = UINT_MAX; + } + + if ((t->flags & T_CHECK_STEP_FLAG) != 0 && + step != T_CHECK_STEP_FROM_FLAGS(t->flags)) { + T_add_failure(ctx); + T_printf("F:%u:%i:%s:%s:%i:planned step (%u)\n", step, + T_cpu(), T_scope(scope), t->file, t->line, + T_CHECK_STEP_FROM_FLAGS(t->flags)); + } else if (!ok) { + T_add_failure(ctx); + + if (ctx->verbosity >= T_NORMAL) { + if ((t->flags & T_CHECK_QUIET) == 0) { + T_printf("F:%u:%i:%s:%s:%i:", + step, T_cpu(), T_scope(scope), + t->file, t->line); + } else { + T_printf("F:*:%i:%s:%s:%i:", T_cpu(), + T_scope(scope), t->file, t->line); + } + + va_start(ap, fmt); + T_vprintf(fmt, ap); + va_end(ap); + + T_printf("\n"); + } + + if ((t->flags & T_CHECK_STOP) != 0) { + T_stop(ctx); + } + } else if ((t->flags & T_CHECK_QUIET) == 0 && + ctx->verbosity >= T_VERBOSE) { + T_printf("P:%u:%i:%s:%s:%i\n", step, T_cpu(), + T_scope(scope), t->file, t->line); + } + } else if (!ok) { + T_add_failure(ctx); + + T_printf("F:*:%i:%s:*:*:", T_cpu(), T_scope(scope)); + + va_start(ap, fmt); + T_vprintf(fmt, ap); + va_end(ap); + + T_printf("\n"); + } +} + +static void +T_do_log(T_context *ctx, T_verbosity verbosity, char const *fmt, ...) +{ + if (ctx->verbosity >= verbosity) { + va_list ap; + + va_start(ap, fmt); + T_vprintf(fmt, ap); + va_end(ap); + } +} + +static void +T_system(T_context *ctx) +{ +#if defined(__rtems__) + T_do_log(ctx, T_QUIET, "S:Platform:RTEMS\n"); + T_do_log(ctx, T_QUIET, "S:Compiler:" __VERSION__ "\n"); + T_do_log(ctx, T_QUIET, "S:Version:%s\n", rtems_version()); + T_do_log(ctx, T_QUIET, "S:BSP:%s\n", rtems_board_support_package()); +#if RTEMS_DEBUG + T_do_log(ctx, T_QUIET, "S:RTEMS_DEBUG:1\n"); +#else + T_do_log(ctx, T_QUIET, "S:RTEMS_DEBUG:0\n"); +#endif +#if RTEMS_MULTIPROCESSING + T_do_log(ctx, T_QUIET, "S:RTEMS_MULTIPROCESSING:1\n"); +#else + T_do_log(ctx, T_QUIET, "S:RTEMS_MULTIPROCESSING:0\n"); +#endif +#if RTEMS_POSIX_API + T_do_log(ctx, T_QUIET, "S:RTEMS_POSIX_API:1\n"); +#else + T_do_log(ctx, T_QUIET, "S:RTEMS_POSIX_API:0\n"); +#endif +#if RTEMS_PROFILING + T_do_log(ctx, T_QUIET, "S:RTEMS_PROFILING:1\n"); +#else + T_do_log(ctx, T_QUIET, "S:RTEMS_PROFILING:0\n"); +#endif +#if RTEMS_SMP + T_do_log(ctx, T_QUIET, "S:RTEMS_SMP:1\n"); +#else + T_do_log(ctx, T_QUIET, "S:RTEMS_SMP:0\n"); +#endif +#elif defined(__linux__) + T_do_log(ctx, T_QUIET, "S:Platform:Linux\n"); + T_do_log(ctx, T_QUIET, "S:Compiler:" __VERSION__ "\n"); +#else + T_do_log(ctx, T_QUIET, "S:Platform:POSIX\n"); +#ifdef __VERSION__ + T_do_log(ctx, T_QUIET, "S:Compiler:" __VERSION__ "\n"); +#endif +#endif +} + +void +T_add_destructor(T_destructor *dtor, void (*destroy)(T_destructor *)) +{ + T_context *ctx; + + dtor->destroy = destroy; + ctx = &T_instance; + pthread_spin_lock(&ctx->lock); + LIST_INSERT_HEAD(&ctx->destructors, dtor, node); + pthread_spin_unlock(&ctx->lock); +} + +void +T_remove_destructor(T_destructor *dtor) +{ + T_context *ctx; + + ctx = &T_instance; + pthread_spin_lock(&ctx->lock); + LIST_REMOVE(dtor, node); + pthread_spin_unlock(&ctx->lock); +} + +static void +T_call_destructors(const T_context *ctx) +{ + T_destructor *dtor; + +#ifdef __linux__ + while (!LIST_EMPTY(&ctx->destructors)) { + dtor = LIST_FIRST(&ctx->destructors); + LIST_REMOVE(dtor, node); + (*dtor->destroy)(dtor); + } +#else + T_destructor *tmp; + + LIST_FOREACH_SAFE(dtor, &ctx->destructors, node, tmp) { + (*dtor->destroy)(dtor); + } +#endif +} + +static void +T_call_actions_forward(const T_config *config, T_event event, const char *name) +{ + const T_action *actions; + size_t n; + size_t i; + + actions = config->actions; + n = config->action_count; + + for (i = 0; i < n; ++i) { + (*actions[i])(event, name); + } +} + +static void +T_call_actions_backward(const T_config *config, T_event event, + const char *name) +{ + const T_action *actions; + size_t n; + size_t i; + + actions = config->actions; + n = config->action_count; + + for (i = 0; i < n; ++i) { + (*actions[n - i - 1])(event, name); + } +} + +static T_context * +T_do_run_initialize(const T_config *config) +{ + T_context *ctx; + + ctx = &T_instance; + + pthread_spin_init(&ctx->lock, PTHREAD_PROCESS_PRIVATE); + + ctx->config = config; + ctx->putchar = config->putchar; + ctx->putchar_arg = config->putchar_arg; + ctx->verbosity = config->verbosity; + atomic_store_explicit(&ctx->steps, 0, memory_order_relaxed); + atomic_store_explicit(&ctx->failures, 0, memory_order_relaxed); + ctx->overall_cases = 0; + ctx->overall_steps = 0; + ctx->overall_failures = 0; + + T_set_runner(ctx); + T_call_actions_forward(config, T_EVENT_RUN_INITIALIZE, config->name); + T_do_log(ctx, T_QUIET, "A:%s\n", config->name); + T_system(ctx); + ctx->run_begin_time = (*config->now)(); + + return ctx; +} + +static void +T_do_case_begin(T_context *ctx, const T_case_context *tc) +{ + const T_config *config; + const T_fixture *fixture; + + config = ctx->config; + fixture = tc->fixture; + ctx->verbosity = config->verbosity; + ctx->current_case = tc; + LIST_INIT(&ctx->destructors); + atomic_store_explicit(&ctx->planned_steps, UINT_MAX, + memory_order_relaxed); + atomic_store_explicit(&ctx->steps, 0, memory_order_relaxed); + atomic_store_explicit(&ctx->failures, 0, memory_order_relaxed); + + T_call_actions_forward(config, T_EVENT_CASE_EARLY, tc->name); + T_do_log(ctx, T_NORMAL, "B:%s\n", tc->name); + ctx->case_begin_time = (*config->now)(); + T_call_actions_forward(config, T_EVENT_CASE_BEGIN, tc->name); + + if (fixture != NULL) { + ctx->fixture_context = fixture->initial_context; + + if (fixture->setup != NULL) { + (*fixture->setup)(ctx->fixture_context); + } + } +} + +static void +T_do_case_end(T_context *ctx, const T_case_context *tc) +{ + const T_config *config; + const T_fixture *fixture; + unsigned int planned_steps; + unsigned int steps; + unsigned int failures; + T_time delta; + T_time_string ts; + + config = ctx->config; + fixture = tc->fixture; + + if (fixture != NULL && fixture->teardown != NULL) { + (*fixture->teardown)(ctx->fixture_context); + } + + T_call_destructors(ctx); + T_call_actions_backward(config, T_EVENT_CASE_END, tc->name); + + planned_steps = atomic_fetch_add_explicit(&ctx->planned_steps, + 0, memory_order_relaxed); + steps = atomic_fetch_add_explicit(&ctx->steps, 0, + memory_order_relaxed); + failures = atomic_fetch_add_explicit(&ctx->failures, 0, + memory_order_relaxed); + + if (planned_steps != UINT_MAX && planned_steps != steps && + failures == 0) { + ++failures; + + if (ctx->verbosity >= T_NORMAL) { + char scope[T_SCOPE_SIZE]; + + T_printf("F:*:%i:%s:*:*:actual steps (%u), " + "planned steps (%u)\n", T_cpu(), + T_scope(scope), steps, planned_steps); + } + } + + delta = (*config->now)() - ctx->case_begin_time; + T_do_log(ctx, T_QUIET, "E:%s:N:%u:F:%u:D:%s\n", + tc->name, steps, failures, T_time_to_string_us(delta, ts)); + + ++ctx->overall_cases; + ctx->overall_steps += steps; + ctx->overall_failures += failures; + + T_call_actions_backward(config, T_EVENT_CASE_LATE, tc->name); +} + +static void +T_run_case(T_context *ctx, const T_case_context *tc) +{ + T_do_case_begin(ctx, tc); + + if (setjmp(ctx->case_begin_context) == 0) { + (*tc->body)(); + } + + T_do_case_end(ctx, tc); +} + +static void +T_do_run_all(T_context *ctx) +{ + const T_case_context *tc; + + tc = ctx->registered_cases; + + while (tc != NULL) { + T_run_case(ctx, tc); + tc = tc->next; + } +} + +static bool +T_do_run_finalize(T_context *ctx) +{ + const T_config *config; + T_time delta; + T_time_string ts; + + config = ctx->config; + delta = (*config->now)() - ctx->run_begin_time; + T_do_log(ctx, T_QUIET, "Z:%s:C:%u:N:%u:F:%u:D:%s\n", config->name, + ctx->overall_cases, ctx->overall_steps, ctx->overall_failures, + T_time_to_string_us(delta, ts)); + T_call_actions_backward(config, T_EVENT_RUN_FINALIZE, config->name); +#ifdef __rtems__ + ctx->runner_thread = NULL; + ctx->runner_cpu = NULL; +#else + ctx->runner_valid = false; +#endif + pthread_spin_destroy(&ctx->lock); + + return ctx->overall_failures == 0; +} + +int +T_main(const T_config *config) +{ + T_context *ctx; + + ctx = T_do_run_initialize(config); + T_do_run_all(ctx); + + return T_do_run_finalize(ctx) ? 0 : 1; +} + +bool T_is_runner(void) +{ + T_context *ctx; + bool is_runner; +#ifdef __rtems__ + ISR_Level level; + const Per_CPU_Control *cpu_self; +#endif + + ctx = &T_instance; +#ifdef __rtems__ + _ISR_Local_disable(level); + cpu_self = _Per_CPU_Get(); + + if (ctx->runner_thread != NULL) { + is_runner = cpu_self->isr_nest_level == 0 && + _Per_CPU_Get_executing(cpu_self) == ctx->runner_thread; + } else { + is_runner = cpu_self == ctx->runner_cpu; + } + + _ISR_Local_enable(level); +#else + is_runner = ctx->runner_valid && + pthread_equal(pthread_self(), ctx->runner_thread) != 0; +#endif + + return is_runner; +} + +#ifdef __rtems__ +RTEMS_LINKER_ROSET(_T, T_case_context *); +#endif /* __rtems__ */ + +void T_register(void) +{ +#ifdef __rtems__ + T_case_context * const *tc; + + RTEMS_LINKER_SET_FOREACH(_T, tc) { + T_case_register(*tc); + } +#endif /* __rtems__ */ +} + +void +T_run_initialize(const T_config *config) +{ + (void)T_do_run_initialize(config); +} + +void +T_run_all(void) +{ + T_do_run_all(&T_instance); +} + +void +T_run_by_name(const char *name) +{ + T_context *ctx; + const T_case_context *tc; + + ctx = &T_instance; + tc = ctx->registered_cases; + + while (tc != NULL) { + if (strcmp(tc->name, name) == 0) { + T_run_case(ctx, tc); + break; + } + + tc = tc->next; + } +} + +static T_case_context default_case; + +void +T_case_begin(const char *name, const T_fixture *fixture) +{ + T_case_context *tc; + + tc = &default_case; + tc->name = name; + tc->fixture = fixture; + T_do_case_begin(&T_instance, tc); +} + +void +T_case_end(void) +{ + T_case_context *tc; + + tc = &default_case; + T_do_case_end(&T_instance, tc); +} + +bool +T_run_finalize(void) +{ + return T_do_run_finalize(&T_instance); +} + +T_time +T_case_begin_time(void) +{ + return T_instance.case_begin_time; +} + +void +T_set_putchar(T_putchar new_putchar, void *new_arg, T_putchar *old_putchar, + void **old_arg) +{ + T_context *ctx; + + ctx = &T_instance; + *old_putchar = ctx->putchar; + *old_arg = ctx->putchar_arg; + ctx->putchar = new_putchar; + ctx->putchar_arg = new_arg; +} |