ctest/ctest.h
Mike Gelfand 26ab614fac Declare test case functions static
Provide prototypes for setup and teardown test case functions as making
them static is not possible due to weak linking.

Provide prototype for ctest_main function as making it static might break
existing code if someone does `#define ctest_main main`.

This eliminates "no previous prototype for function '...'
[-Wmissing-prototypes]" Clang warning.
2017-03-04 21:40:07 +03:00

539 lines
17 KiB
C

/* Copyright 2011-2016 Bas van den Berg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CTEST_H
#define CTEST_H
#if defined _WIN32 || defined __CYGWIN__
#ifndef WIN32
#define WIN32
#endif
#endif
#ifndef WIN32
#define CTEST_IMPL_WEAK __attribute__ ((weak))
#else
#define CTEST_IMPL_WEAK
#endif
#include <inttypes.h> /* intmax_t, uintmax_t, PRI* */
#include <stddef.h> /* size_t */
typedef void (*ctest_setup_func)(void*);
typedef void (*ctest_teardown_func)(void*);
struct ctest {
const char* ssname; // suite name
const char* ttname; // test name
void (*run)();
int skip;
void* data;
ctest_setup_func setup;
ctest_teardown_func teardown;
unsigned int magic;
};
#define CTEST_IMPL_NAME(name) ctest_##name
#define CTEST_IMPL_FNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_run)
#define CTEST_IMPL_TNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname)
#define CTEST_IMPL_MAGIC (0xdeadbeef)
#ifdef __APPLE__
#define CTEST_IMPL_SECTION __attribute__ ((used, section ("__DATA, .ctest")))
#else
#define CTEST_IMPL_SECTION __attribute__ ((used, section (".ctest")))
#endif
#define CTEST_IMPL_STRUCT(sname, tname, tskip, tdata, tsetup, tteardown) \
static struct ctest CTEST_IMPL_TNAME(sname, tname) CTEST_IMPL_SECTION = { \
.ssname=#sname, \
.ttname=#tname, \
.run = CTEST_IMPL_FNAME(sname, tname), \
.skip = tskip, \
.data = tdata, \
.setup = (ctest_setup_func) tsetup, \
.teardown = (ctest_teardown_func) tteardown, \
.magic = CTEST_IMPL_MAGIC }
#define CTEST_DATA(sname) struct CTEST_IMPL_NAME(sname##_data)
#define CTEST_SETUP(sname) \
void CTEST_IMPL_WEAK CTEST_IMPL_NAME(sname##_setup)(struct CTEST_IMPL_NAME(sname##_data)* data); \
void CTEST_IMPL_WEAK CTEST_IMPL_NAME(sname##_setup)(struct CTEST_IMPL_NAME(sname##_data)* data)
#define CTEST_TEARDOWN(sname) \
void CTEST_IMPL_WEAK CTEST_IMPL_NAME(sname##_teardown)(struct CTEST_IMPL_NAME(sname##_data)* data); \
void CTEST_IMPL_WEAK CTEST_IMPL_NAME(sname##_teardown)(struct CTEST_IMPL_NAME(sname##_data)* data)
#define CTEST_IMPL_CTEST(sname, tname, tskip) \
static void CTEST_IMPL_FNAME(sname, tname)(void); \
CTEST_IMPL_STRUCT(sname, tname, tskip, NULL, NULL, NULL); \
static void CTEST_IMPL_FNAME(sname, tname)(void)
#ifdef __APPLE__
#define CTEST_IMPL_SETUP_FNAME(sname) NULL
#define CTEST_IMPL_TEARDOWN_FNAME(sname) NULL
#else
#define CTEST_IMPL_SETUP_FNAME(sname) CTEST_IMPL_NAME(sname##_setup)
#define CTEST_IMPL_TEARDOWN_FNAME(sname) CTEST_IMPL_NAME(sname##_teardown)
#endif
#define CTEST_IMPL_CTEST2(sname, tname, tskip) \
static struct CTEST_IMPL_NAME(sname##_data) CTEST_IMPL_NAME(sname##_data); \
CTEST_SETUP(sname); \
CTEST_TEARDOWN(sname); \
static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_NAME(sname##_data)* data); \
CTEST_IMPL_STRUCT(sname, tname, tskip, &CTEST_IMPL_NAME(sname##_data), CTEST_IMPL_SETUP_FNAME(sname), CTEST_IMPL_TEARDOWN_FNAME(sname)); \
static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_NAME(sname##_data)* data)
void CTEST_LOG(const char* fmt, ...);
void CTEST_ERR(const char* fmt, ...); // doesn't return
#define CTEST(sname, tname) CTEST_IMPL_CTEST(sname, tname, 0)
#define CTEST_SKIP(sname, tname) CTEST_IMPL_CTEST(sname, tname, 1)
#define CTEST2(sname, tname) CTEST_IMPL_CTEST2(sname, tname, 0)
#define CTEST2_SKIP(sname, tname) CTEST_IMPL_CTEST2(sname, tname, 1)
void assert_str(const char* exp, const char* real, const char* caller, int line);
#define ASSERT_STR(exp, real) assert_str(exp, real, __FILE__, __LINE__)
void assert_data(const unsigned char* exp, size_t expsize,
const unsigned char* real, size_t realsize,
const char* caller, int line);
#define ASSERT_DATA(exp, expsize, real, realsize) \
assert_data(exp, expsize, real, realsize, __FILE__, __LINE__)
void assert_equal(intmax_t exp, intmax_t real, const char* caller, int line);
#define ASSERT_EQUAL(exp, real) assert_equal(exp, real, __FILE__, __LINE__)
void assert_equal_u(uintmax_t exp, uintmax_t real, const char* caller, int line);
#define ASSERT_EQUAL_U(exp, real) assert_equal_u(exp, real, __FILE__, __LINE__)
void assert_not_equal(intmax_t exp, intmax_t real, const char* caller, int line);
#define ASSERT_NOT_EQUAL(exp, real) assert_not_equal(exp, real, __FILE__, __LINE__)
void assert_not_equal_u(uintmax_t exp, uintmax_t real, const char* caller, int line);
#define ASSERT_NOT_EQUAL_U(exp, real) assert_not_equal_u(exp, real, __FILE__, __LINE__)
void assert_interval(intmax_t exp1, intmax_t exp2, intmax_t real, const char* caller, int line);
#define ASSERT_INTERVAL(exp1, exp2, real) assert_interval(exp1, exp2, real, __FILE__, __LINE__)
void assert_null(void* real, const char* caller, int line);
#define ASSERT_NULL(real) assert_null((void*)real, __FILE__, __LINE__)
void assert_not_null(const void* real, const char* caller, int line);
#define ASSERT_NOT_NULL(real) assert_not_null(real, __FILE__, __LINE__)
void assert_true(int real, const char* caller, int line);
#define ASSERT_TRUE(real) assert_true(real, __FILE__, __LINE__)
void assert_false(int real, const char* caller, int line);
#define ASSERT_FALSE(real) assert_false(real, __FILE__, __LINE__)
void assert_fail(const char* caller, int line);
#define ASSERT_FAIL() assert_fail(__FILE__, __LINE__)
void assert_dbl_near(double exp, double real, double tol, const char* caller, int line);
#define ASSERT_DBL_NEAR(exp, real) assert_dbl_near(exp, real, 1e-4, __FILE__, __LINE__)
#define ASSERT_DBL_NEAR_TOL(exp, real, tol) assert_dbl_near(exp, real, tol, __FILE__, __LINE__)
void assert_dbl_far(double exp, double real, double tol, const char* caller, int line);
#define ASSERT_DBL_FAR(exp, real) assert_dbl_far(exp, real, 1e-4, __FILE__, __LINE__)
#define ASSERT_DBL_FAR_TOL(exp, real, tol) assert_dbl_far(exp, real, tol, __FILE__, __LINE__)
#ifdef CTEST_MAIN
#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef __APPLE__
#include <dlfcn.h>
#endif
static size_t ctest_errorsize;
static char* ctest_errormsg;
#define MSG_SIZE 4096
static char ctest_errorbuffer[MSG_SIZE];
static jmp_buf ctest_err;
static int color_output = 1;
static const char* suite_name;
typedef int (*ctest_filter_func)(struct ctest*);
#define ANSI_BLACK "\033[0;30m"
#define ANSI_RED "\033[0;31m"
#define ANSI_GREEN "\033[0;32m"
#define ANSI_YELLOW "\033[0;33m"
#define ANSI_BLUE "\033[0;34m"
#define ANSI_MAGENTA "\033[0;35m"
#define ANSI_CYAN "\033[0;36m"
#define ANSI_GREY "\033[0;37m"
#define ANSI_DARKGREY "\033[01;30m"
#define ANSI_BRED "\033[01;31m"
#define ANSI_BGREEN "\033[01;32m"
#define ANSI_BYELLOW "\033[01;33m"
#define ANSI_BBLUE "\033[01;34m"
#define ANSI_BMAGENTA "\033[01;35m"
#define ANSI_BCYAN "\033[01;36m"
#define ANSI_WHITE "\033[01;37m"
#define ANSI_NORMAL "\033[0m"
CTEST(suite, test) { }
inline static void vprint_errormsg(const char* const fmt, va_list ap) {
// (v)snprintf returns the number that would have been written
const int ret = vsnprintf(ctest_errormsg, ctest_errorsize, fmt, ap);
if (ret < 0) {
ctest_errormsg[0] = 0x00;
} else {
const size_t size = (size_t) ret;
const size_t s = (ctest_errorsize <= size ? size -ctest_errorsize : size);
// ctest_errorsize may overflow at this point
ctest_errorsize -= s;
ctest_errormsg += s;
}
}
inline static void print_errormsg(const char* const fmt, ...) {
va_list argp;
va_start(argp, fmt);
vprint_errormsg(fmt, argp);
va_end(argp);
}
static void msg_start(const char* color, const char* title) {
if (color_output) {
print_errormsg("%s", color);
}
print_errormsg(" %s: ", title);
}
static void msg_end() {
if (color_output) {
print_errormsg(ANSI_NORMAL);
}
print_errormsg("\n");
}
void CTEST_LOG(const char* fmt, ...)
{
va_list argp;
msg_start(ANSI_BLUE, "LOG");
va_start(argp, fmt);
vprint_errormsg(fmt, argp);
va_end(argp);
msg_end();
}
void CTEST_ERR(const char* fmt, ...)
{
va_list argp;
msg_start(ANSI_YELLOW, "ERR");
va_start(argp, fmt);
vprint_errormsg(fmt, argp);
va_end(argp);
msg_end();
longjmp(ctest_err, 1);
}
void assert_str(const char* exp, const char* real, const char* caller, int line) {
if ((exp == NULL && real != NULL) ||
(exp != NULL && real == NULL) ||
(exp && real && strcmp(exp, real) != 0)) {
CTEST_ERR("%s:%d expected '%s', got '%s'", caller, line, exp, real);
}
}
void assert_data(const unsigned char* exp, size_t expsize,
const unsigned char* real, size_t realsize,
const char* caller, int line) {
size_t i;
if (expsize != realsize) {
CTEST_ERR("%s:%d expected %" PRIuMAX " bytes, got %" PRIuMAX, caller, line, (uintmax_t) expsize, (uintmax_t) realsize);
}
for (i=0; i<expsize; i++) {
if (exp[i] != real[i]) {
CTEST_ERR("%s:%d expected 0x%02x at offset %" PRIuMAX " got 0x%02x",
caller, line, exp[i], (uintmax_t) i, real[i]);
}
}
}
void assert_equal(intmax_t exp, intmax_t real, const char* caller, int line) {
if (exp != real) {
CTEST_ERR("%s:%d expected %" PRIdMAX ", got %" PRIdMAX, caller, line, exp, real);
}
}
void assert_equal_u(uintmax_t exp, uintmax_t real, const char* caller, int line) {
if (exp != real) {
CTEST_ERR("%s:%d expected %" PRIuMAX ", got %" PRIuMAX, caller, line, exp, real);
}
}
void assert_not_equal(intmax_t exp, intmax_t real, const char* caller, int line) {
if ((exp) == (real)) {
CTEST_ERR("%s:%d should not be %" PRIdMAX, caller, line, real);
}
}
void assert_not_equal_u(uintmax_t exp, uintmax_t real, const char* caller, int line) {
if ((exp) == (real)) {
CTEST_ERR("%s:%d should not be %" PRIuMAX, caller, line, real);
}
}
void assert_interval(intmax_t exp1, intmax_t exp2, intmax_t real, const char* caller, int line) {
if (real < exp1 || real > exp2) {
CTEST_ERR("%s:%d expected %" PRIdMAX "-%" PRIdMAX ", got %" PRIdMAX, caller, line, exp1, exp2, real);
}
}
void assert_dbl_near(double exp, double real, double tol, const char* caller, int line) {
double diff = exp - real;
double absdiff = diff;
/* avoid using fabs and linking with a math lib */
if(diff < 0) {
absdiff *= -1;
}
if (absdiff > tol) {
CTEST_ERR("%s:%d expected %0.3e, got %0.3e (diff %0.3e, tol %0.3e)", caller, line, exp, real, diff, tol);
}
}
void assert_dbl_far(double exp, double real, double tol, const char* caller, int line) {
double diff = exp - real;
double absdiff = diff;
/* avoid using fabs and linking with a math lib */
if(diff < 0) {
absdiff *= -1;
}
if (absdiff <= tol) {
CTEST_ERR("%s:%d expected %0.3e, got %0.3e (diff %0.3e, tol %0.3e)", caller, line, exp, real, diff, tol);
}
}
void assert_null(void* real, const char* caller, int line) {
if ((real) != NULL) {
CTEST_ERR("%s:%d should be NULL", caller, line);
}
}
void assert_not_null(const void* real, const char* caller, int line) {
if (real == NULL) {
CTEST_ERR("%s:%d should not be NULL", caller, line);
}
}
void assert_true(int real, const char* caller, int line) {
if ((real) == 0) {
CTEST_ERR("%s:%d should be true", caller, line);
}
}
void assert_false(int real, const char* caller, int line) {
if ((real) != 0) {
CTEST_ERR("%s:%d should be false", caller, line);
}
}
void assert_fail(const char* caller, int line) {
CTEST_ERR("%s:%d shouldn't come here", caller, line);
}
static int suite_all(struct ctest* t) {
(void) t; // fix unused parameter warning
return 1;
}
static int suite_filter(struct ctest* t) {
return strncmp(suite_name, t->ssname, strlen(suite_name)) == 0;
}
static uint64_t getCurrentTime() {
struct timeval now;
gettimeofday(&now, NULL);
uint64_t now64 = (uint64_t) now.tv_sec;
now64 *= 1000000;
now64 += ((uint64_t) now.tv_usec);
return now64;
}
static void color_print(const char* color, const char* text) {
if (color_output)
printf("%s%s"ANSI_NORMAL"\n", color, text);
else
printf("%s\n", text);
}
#ifdef __APPLE__
static void *find_symbol(struct ctest *test, const char *fname)
{
const char* const symbol_prefix = "ctest_";
size_t len = strlen(symbol_prefix) + strlen(test->ssname) + 1 + strlen(fname);
char *symbol_name = (char *) malloc(len + 1);
memset(symbol_name, 0, len + 1);
snprintf(symbol_name, len + 1, "%s%s_%s", symbol_prefix, test->ssname, fname);
//fprintf(stderr, ">>>> dlsym: loading %s\n", symbol_name);
void *symbol = dlsym(RTLD_DEFAULT, symbol_name);
if (!symbol) {
//fprintf(stderr, ">>>> ERROR: %s\n", dlerror());
}
// returns NULL on error
free(symbol_name);
return symbol;
}
#endif
#ifdef CTEST_SEGFAULT
#include <signal.h>
static void sighandler(int signum)
{
char msg[128];
sprintf(msg, "[SIGNAL %d: %s]", signum, sys_siglist[signum]);
color_print(ANSI_BRED, msg);
fflush(stdout);
/* "Unregister" the signal handler and send the signal back to the process
* so it can terminate as expected */
signal(signum, SIG_DFL);
kill(getpid(), signum);
}
#endif
int ctest_main(int argc, const char *argv[]);
int ctest_main(int argc, const char *argv[])
{
static int total = 0;
static int num_ok = 0;
static int num_fail = 0;
static int num_skip = 0;
static int index = 1;
static ctest_filter_func filter = suite_all;
#ifdef CTEST_SEGFAULT
signal(SIGSEGV, sighandler);
#endif
if (argc == 2) {
suite_name = argv[1];
filter = suite_filter;
}
#ifdef CTEST_NO_COLORS
color_output = 0;
#else
color_output = isatty(1);
#endif
uint64_t t1 = getCurrentTime();
struct ctest* ctest_begin = &CTEST_IMPL_TNAME(suite, test);
struct ctest* ctest_end = &CTEST_IMPL_TNAME(suite, test);
// find begin and end of section by comparing magics
while (1) {
struct ctest* t = ctest_begin-1;
if (t->magic != CTEST_IMPL_MAGIC) break;
ctest_begin--;
}
while (1) {
struct ctest* t = ctest_end+1;
if (t->magic != CTEST_IMPL_MAGIC) break;
ctest_end++;
}
ctest_end++; // end after last one
static struct ctest* test;
for (test = ctest_begin; test != ctest_end; test++) {
if (test == &CTEST_IMPL_TNAME(suite, test)) continue;
if (filter(test)) total++;
}
for (test = ctest_begin; test != ctest_end; test++) {
if (test == &CTEST_IMPL_TNAME(suite, test)) continue;
if (filter(test)) {
ctest_errorbuffer[0] = 0;
ctest_errorsize = MSG_SIZE-1;
ctest_errormsg = ctest_errorbuffer;
printf("TEST %d/%d %s:%s ", index, total, test->ssname, test->ttname);
fflush(stdout);
if (test->skip) {
color_print(ANSI_BYELLOW, "[SKIPPED]");
num_skip++;
} else {
int result = setjmp(ctest_err);
if (result == 0) {
#ifdef __APPLE__
if (!test->setup) {
test->setup = (ctest_setup_func) find_symbol(test, "setup");
}
if (!test->teardown) {
test->teardown = (ctest_teardown_func) find_symbol(test, "teardown");
}
#endif
if (test->setup) test->setup(test->data);
if (test->data)
test->run(test->data);
else
test->run();
if (test->teardown) test->teardown(test->data);
// if we got here it's ok
#ifdef CTEST_COLOR_OK
color_print(ANSI_BGREEN, "[OK]");
#else
printf("[OK]\n");
#endif
num_ok++;
} else {
color_print(ANSI_BRED, "[FAIL]");
num_fail++;
}
if (ctest_errorsize != MSG_SIZE-1) printf("%s", ctest_errorbuffer);
}
index++;
}
}
uint64_t t2 = getCurrentTime();
const char* color = (num_fail) ? ANSI_BRED : ANSI_GREEN;
char results[80];
sprintf(results, "RESULTS: %d tests (%d ok, %d failed, %d skipped) ran in %" PRIu64 " ms", total, num_ok, num_fail, num_skip, (t2 - t1)/1000);
color_print(color, results);
return num_fail;
}
#endif
#endif