Files
rtems/cpukit/libtest/t-test.c
Sebastian Huber a0304e8d62 libtest: Output basename of source files
Output only the basename of source files to be independent of the build
system source paths.

In the future it may be better to use the GCC -fmacro-prefix-map option.
This option is available in GCC 8 and later.  It is not yet available in
clang.

Update #3818.
2019-11-25 11:32:19 +01:00

997 lines
19 KiB
C

/*
* 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 128
#define T_SCOPE_SIZE 5
typedef struct {
pthread_spinlock_t lock;
char *buf;
unsigned int buf_mask;
atomic_uint buf_head;
atomic_uint buf_tail;
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;
typedef struct {
char *s;
size_t n;
} T_putchar_string_context;
static void
T_putchar_string(int c, void *arg)
{
T_putchar_string_context *sctx;
char *s;
size_t n;
sctx = arg;
s = sctx->s;
n = sctx->n;
if (n == 1) {
c = '\0';
}
if (n > 1) {
*s = (char)c;
++s;
--n;
}
sctx->s = s;
sctx->n = n;
}
int
T_snprintf(char *s, size_t n, char const *fmt, ...)
{
va_list ap;
int len;
T_putchar_string_context sctx = {
.s = s,
.n = n
};
va_start(ap, fmt);
len = _IO_Vprintf(T_putchar_string, &sctx, fmt, ap);
va_end(ap);
if (sctx.n > 0) {
*sctx.s = '\0';
}
return len;
}
static int
T_vprintf_direct(char const *fmt, va_list ap)
{
T_context *ctx;
unsigned int head;
unsigned int tail;
ctx = &T_instance;
head = atomic_load_explicit(&ctx->buf_head, memory_order_acquire);
tail = atomic_load_explicit(&ctx->buf_tail, memory_order_relaxed);
while (head != tail) {
(*ctx->putchar)(ctx->buf[tail], ctx->putchar_arg);
tail = (tail + 1) & ctx->buf_mask;
}
atomic_store_explicit(&ctx->buf_tail, tail, memory_order_relaxed);
return _IO_Vprintf(ctx->putchar, ctx->putchar_arg, fmt, ap);
}
static int
T_vprintf_buffered(char const *fmt, va_list ap)
{
unsigned int len;
T_context *ctx;
char buf[T_LINE_SIZE];
T_putchar_string_context sctx = {
.s = buf,
.n = sizeof(buf)
};
unsigned int head;
unsigned int tail;
unsigned int mask;
unsigned int capacity;
len = (unsigned int)_IO_Vprintf(T_putchar_string, &sctx, fmt, ap);
if (len >= sizeof(buf)) {
len = sizeof(buf) - 1;
}
ctx = &T_instance;
pthread_spin_lock(&ctx->lock);
head = atomic_load_explicit(&ctx->buf_head, memory_order_relaxed);
tail = atomic_load_explicit(&ctx->buf_tail, memory_order_relaxed);
mask = ctx->buf_mask;
capacity = (tail - head - 1) & mask;
if (len <= capacity) {
unsigned int todo;
char *c;
todo = len;
c = buf;
while (todo > 0) {
ctx->buf[head] = *c;
head = (head + 1) & mask;
--todo;
++c;
}
atomic_store_explicit(&ctx->buf_head, head,
memory_order_release);
} else {
/* If it does not fit into the buffer, discard everything */
len = 0;
}
pthread_spin_unlock(&ctx->lock);
return (int)len;
}
int
T_vprintf(char const *fmt, va_list ap)
{
int len;
if (T_is_runner()) {
len = T_vprintf_direct(fmt, ap);
} else {
len = T_vprintf_buffered(fmt, ap);
}
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 "?";
}
}
static const char *
T_file(const T_check_context *t)
{
const char *file;
file = strrchr(t->file, '/');
if (file == NULL) {
return t->file;
}
return file + 1;
}
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), 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), t->line);
} else {
T_printf("F:*:%i:%s:%s:%i:", T_cpu(),
T_scope(scope), T_file(t), 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), 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_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_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->buf = config->buf;
if (config->buf_size > 0 &&
(config->buf_size & (config->buf_size - 1)) == 0) {
ctx->buf_mask = config->buf_size - 1;
} else {
ctx->buf_mask = 0;
}
atomic_store_explicit(&ctx->buf_head, 0, memory_order_relaxed);
ctx->buf_tail = 0;
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_actions_forward(config, T_EVENT_RUN_INITIALIZE_EARLY, config->name);
T_do_log(ctx, T_QUIET, "A:%s\n", config->name);
T_system(ctx);
ctx->run_begin_time = (*config->now)();
T_actions_forward(config, T_EVENT_RUN_INITIALIZE_LATE, config->name);
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_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_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_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_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;
T_actions_backward(config, T_EVENT_RUN_FINALIZE_EARLY, config->name);
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_actions_backward(config, T_EVENT_RUN_FINALIZE_LATE, 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;
}