first version, with some examples

This commit is contained in:
Gwendolyn 2022-01-02 15:00:54 +01:00
parent 0c291b3e03
commit e20003c073
14 changed files with 1177 additions and 0 deletions

21
CMakeLists.txt Normal file
View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.21)
project(xtest C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -pedantic")
add_compile_definitions(XTEST)
add_executable(example-all xtest.c examples/all.c examples/parameterized.c examples/assertions.c examples/expect_assertions.c examples/fail.c examples/groups.c examples/float.c examples/source.c examples/source.h examples/skip.c)
target_compile_definitions(example-all PRIVATE XTEST_ALL_EXAMPLES)
add_executable(example-assertions xtest.c examples/assertions.c examples/source.c examples/source.h)
add_executable(example-expect_assertions xtest.c examples/expect_assertions.c examples/source.c examples/source.h)
add_executable(example-fail xtest.c examples/fail.c examples/source.c examples/source.h)
add_executable(example-float xtest.c examples/float.c examples/source.c examples/source.h)
add_executable(example-groups xtest.c examples/groups.c examples/source.c examples/source.h)
add_executable(example-parameterized xtest.c examples/parameterized.c examples/source.c examples/source.h)
add_executable(example-skip xtest.c examples/skip.c examples/source.c examples/source.h)

22
examples/all.c Normal file
View file

@ -0,0 +1,22 @@
#include "../xtest.h"
void example_assertions();
void example_expect_assertions();
void example_fail();
void example_float();
void example_groups();
void example_parameterized();
void example_skip();
int main(int argc, char ** argv) {
xtest_run_group(example_assertions);
xtest_run_group(example_expect_assertions);
xtest_run_group(example_fail);
xtest_run_group(example_float);
xtest_run_group(example_groups);
xtest_run_group(example_parameterized);
xtest_run_group(example_skip);
return xtest_complete();
}

106
examples/assertions.c Normal file
View file

@ -0,0 +1,106 @@
#include <stddef.h>
#include "../xtest.h"
#include "source.h"
void test_assert(void *fixture, void **params) {
int i1 = 1;
int i2 = 1;
int i3 = 2;
double d1 = 1.0;
double d2 = 1.0;
double d3 = 2.0;
char c1 = 'a';
char c2 = 'a';
char c3 = 'b';
unsigned int u1 = 1;
unsigned int u2 = 1;
unsigned int u3 = 2;
xtest_assert(i1 == i2);
xtest_assert(i1 != i3);
xtest_assert(d1 == d2);
xtest_assert(d1 != d3);
xtest_assert(c1 == c2);
xtest_assert(c1 != c3);
xtest_assert(u1 == u2);
xtest_assert(u1 != u3);
xtest_assert(1);
xtest_assert(ret_1());
}
void test_assert_is(void *fixture, void **params) {
int i1 = 1;
int i2 = 1;
int i3 = 2;
double d1 = 1.0;
double d2 = 1.0;
double d3 = 2.0;
char c1 = 'a';
char c2 = 'a';
char c3 = 'b';
unsigned int u1 = 1;
unsigned int u2 = 1;
unsigned int u3 = 2;
_Bool b1 = 0;
_Bool b2 = 0;
_Bool b3 = 1;
xtest_assert_is(i1, i2);
xtest_assert_is_not(i1, i3);
xtest_assert_float_is(d1, d2, 4);
xtest_assert_float_is_not(d1, d3, 4);
xtest_assert_is(c1, c2);
xtest_assert_is_not(c1, c3);
xtest_assert_is(u1, u2);
xtest_assert_is_not(u1, u3);
xtest_assert_is(b1, b2);
xtest_assert_is_not(b1, b3);
}
void test_assert_str_is(void *fixture, void **params) {
const char *s1 = "ret_1";
const char *s2 = "ret_1";
const char *s3 = "bar";
xtest_assert_str_is(s1, s2);
xtest_assert_str_is_not(s1, s3);
}
void test_assert_mem_is(void *fixture, void **params) {
char m1[100];
char m2[100];
for (int i = 0; i < 100; ++i) {
m1[i] = m2[i] = 'a';
}
for (int i = 50; i < 100; ++i) {
m2[i] = 'b';
}
xtest_assert_mem_is(m1, m2, 10);
xtest_assert_mem_is(m1, m2, 50);
xtest_assert_mem_is_not(m1, m2, 60);
xtest_assert_mem_is_not(m1, m2, 100);
}
void example_assertions() {
xtest_run(test_assert);
xtest_run(test_assert_is);
xtest_run(test_assert_str_is);
xtest_run(test_assert_mem_is);
}
#ifndef XTEST_ALL_EXAMPLES
int main(int argc, char **argv) {
example_assertions();
return xtest_complete();
}
#endif

View file

@ -0,0 +1,46 @@
#include "../xtest.h"
#include "source.h"
void test_simple() {
xtest_expect_assertion_failure();
div(10, 0);
}
void test_no_failure() {
div(10,1);
}
void test_no_failure_2() {
div(10,2);
}
void test_will_fail() {
xtest_expect_assertion_failure();
div(10,1);
}
void test_will_fail_2() {
div(10,0);
}
void test_other_failure() {
xtest_expect_assertion_failure();
xtest_assert(0);
div(10, 0);
}
void example_expect_assertions() {
xtest_run(test_simple);
xtest_run(test_no_failure);
xtest_run(test_other_failure);
xtest_run(test_will_fail);
xtest_run(test_will_fail_2);
xtest_run(test_no_failure_2);
}
#ifndef XTEST_ALL_EXAMPLES
int main(int argc, char **argv) {
example_expect_assertions();
return xtest_complete();
}
#endif

188
examples/fail.c Normal file
View file

@ -0,0 +1,188 @@
#include <stddef.h>
#include "../xtest.h"
void fail_assert() {
xtest_assert(0);
}
void fail_assert_is_char() {
xtest_assert_is((char)'a', (char)'b');
}
void fail_assert_is_not_char() {
xtest_assert_is_not((char)'a', (char)'a');
}
void fail_assert_is_signed_char() {
xtest_assert_is((signed char)'a', (signed char)'b');
}
void fail_assert_is_not_signed_char() {
xtest_assert_is_not((signed char)'a', (signed char)'a');
}
void fail_assert_is_unsigned_char() {
xtest_assert_is((unsigned char)'a', (unsigned char)'b');
}
void fail_assert_is_not_unsigned_char() {
xtest_assert_is_not((unsigned char)'a', (unsigned char)'a');
}
void fail_assert_is_short() {
xtest_assert_is((short) 1, (short) 2);
}
void fail_assert_is_not_short() {
xtest_assert_is_not((short) 1, (short) 1);
}
void fail_assert_is_ushort() {
xtest_assert_is((unsigned short) 1, (unsigned short) 2);
}
void fail_assert_is_not_ushort() {
xtest_assert_is_not((unsigned short) 1, (unsigned short) 1);
}
void fail_assert_is_int() {
xtest_assert_is((int) 1, (int) 2);
}
void fail_assert_is_not_int() {
xtest_assert_is_not((int) 1, (int) 1);
}
void fail_assert_is_uint() {
xtest_assert_is((unsigned int) 1, (unsigned int) 2);
}
void fail_assert_is_not_uint() {
xtest_assert_is_not((unsigned int) 1, (unsigned int) 1);
}
void fail_assert_is_long() {
xtest_assert_is((short) 1, (short) 2);
}
void fail_assert_is_not_long() {
xtest_assert_is_not((long) 1, (long) 1);
}
void fail_assert_is_ulong() {
xtest_assert_is((unsigned long) 1, (unsigned long) 2);
}
void fail_assert_is_not_ulong() {
xtest_assert_is_not((unsigned long) 1, (unsigned long) 1);
}
void fail_assert_is_longlong() {
xtest_assert_is((long long) 1, (long long) 2);
}
void fail_assert_is_not_longlong() {
xtest_assert_is_not((long long) 1, (long long) 1);
}
void fail_assert_is_ulonglong() {
xtest_assert_is((unsigned long long) 1, (unsigned long long) 2);
}
void fail_assert_is_not_ulonglong() {
xtest_assert_is_not((unsigned long long) 1, (unsigned long long) 1);
}
void fail_assert_is_ptr() {
int i = 1;
int j = 1;
xtest_assert_is((void*) &i, (void*) &j);
}
void fail_assert_is_not_ptr() {
int i = 1;
xtest_assert_is_not((void*) &i, (void*) &i);
}
void fail_assert_str_is() {
xtest_assert_str_is("foo", "bar");
}
void fail_assert_str_is_not() {
xtest_assert_str_is_not("foo", "foo");
}
void fail_assert_mem_is() {
xtest_assert_mem_is("aaaaaaaaaaaaa", "aaaaaaaaaaaab", 13);
}
void fail_assert_mem_is_not() {
xtest_assert_mem_is_not("aaaaaaaaaaaaa", "aaaaaaaaaaaab", 12);
}
void fail_assert_float_is() {
xtest_assert_float_is(1.2356, 1.2346, 3);
}
void fail_assert_float_is_not() {
xtest_assert_float_is_not(1.2356, 1.2346, 2);
}
void fail_assert_is() {
xtest_run(fail_assert_is_char);
xtest_run(fail_assert_is_not_char);
xtest_run(fail_assert_is_signed_char);
xtest_run(fail_assert_is_not_signed_char);
xtest_run(fail_assert_is_unsigned_char);
xtest_run(fail_assert_is_not_unsigned_char);
xtest_run(fail_assert_is_short);
xtest_run(fail_assert_is_not_short);
xtest_run(fail_assert_is_ushort);
xtest_run(fail_assert_is_not_ushort);
xtest_run(fail_assert_is_int);
xtest_run(fail_assert_is_not_int);
xtest_run(fail_assert_is_uint);
xtest_run(fail_assert_is_not_uint);
xtest_run(fail_assert_is_long);
xtest_run(fail_assert_is_not_long);
xtest_run(fail_assert_is_ulong);
xtest_run(fail_assert_is_not_ulong);
xtest_run(fail_assert_is_longlong);
xtest_run(fail_assert_is_not_longlong);
xtest_run(fail_assert_is_ulonglong);
xtest_run(fail_assert_is_not_ulonglong);
xtest_run(fail_assert_is_ptr);
xtest_run(fail_assert_is_not_ptr);
}
void fail_assert_str() {
xtest_run(fail_assert_str_is);
xtest_run(fail_assert_str_is_not);
}
void fail_assert_mem() {
xtest_run(fail_assert_mem_is);
xtest_run(fail_assert_mem_is_not);
}
void fail_assert_float() {
xtest_run(fail_assert_float_is);
xtest_run(fail_assert_float_is_not);
}
void example_fail() {
xtest_run(fail_assert);
xtest_run_group(fail_assert_is);
xtest_run_group(fail_assert_str);
xtest_run_group(fail_assert_mem);
xtest_run_group(fail_assert_float);
}
#ifndef XTEST_ALL_EXAMPLES
int main(int argc, char **argv) {
example_fail();
return xtest_complete();
}
#endif

46
examples/float.c Normal file
View file

@ -0,0 +1,46 @@
#include <stddef.h>
#include "../xtest.h"
void test_float() {
xtest_assert_float_is(1.0, 1.0, 0);
xtest_assert_float_is(1.0, 1.0, 1);
xtest_assert_float_is(1.0, 1.0, 2);
xtest_assert_float_is(1.0, 1.0, 3);
xtest_assert_float_is(1.0, 1.0, 4);
xtest_assert_float_is(1.0, 1.0, 5);
xtest_assert_float_is_not(1.0, 2.0, 0);
xtest_assert_float_is_not(1.0, 2.0, 1);
xtest_assert_float_is_not(1.0, 2.0, 2);
xtest_assert_float_is_not(1.0, 2.0, 3);
xtest_assert_float_is_not(1.0, 2.0, 4);
xtest_assert_float_is_not(1.0, 2.0, 5);
xtest_assert_float_is(0.001, 0.00100000000001, 0);
xtest_assert_float_is(0.001, 0.00100000000001, 1);
xtest_assert_float_is(0.001, 0.00100000000001, 2);
xtest_assert_float_is(0.001, 0.00100000000001, 3);
xtest_assert_float_is(0.001, 0.00100000000001, 4);
xtest_assert_float_is(0.001, 0.00100000000001, 5);
xtest_assert_float_is(0.001, 0.00100000000001, 6);
xtest_assert_float_is(0.001, 0.00100000000001, 7);
xtest_assert_float_is(0.001, 0.00100000000001, 8);
xtest_assert_float_is(0.001, 0.00100000000001, 9);
xtest_assert_float_is(0.001, 0.00100000000001, 10);
xtest_assert_float_is(0.001, 0.00100000000001, 11);
xtest_assert_float_is(0.001, 0.00100000000001, 12);
xtest_assert_float_is(0.001, 0.00100000000001, 13);
xtest_assert_float_is_not(0.001, 0.00100000000001, 14);
xtest_assert_float_is_not(0.001, 0.00100000000001, 15);
xtest_assert_float_is_not(0.001, 0.00100000000001, 16);
}
void example_float() {
xtest_run(test_float);
}
#ifndef XTEST_ALL_EXAMPLES
int main(int argc, char **argv) {
example_float();
return xtest_complete();
}
#endif

103
examples/groups.c Normal file
View file

@ -0,0 +1,103 @@
#include <stddef.h>
#include "../xtest.h"
void test_1() {}
void test_2() {}
void test_3() {}
void test_4() {}
void test_5() {}
void test_6() {}
void test_7() {}
void test_8() {}
void test_9() {}
void groups_manual() {
xtest_group(A);
xtest_group(foo);
xtest_run(test_1);
xtest_group(bar);
xtest_run(test_2);
xtest_run(test_3);
xtest_end_group();
xtest_end_group();
xtest_run(test_4);
xtest_end_group();
xtest_group(B);
xtest_run(test_5);
xtest_run(test_6);
xtest_end_group();
xtest_group(C);
xtest_group(X);
xtest_group(Y);
xtest_group(Z);
xtest_run(test_7);
xtest_end_group();
xtest_end_group();
xtest_end_group();
xtest_end_group();
xtest_group(D);
xtest_run(test_8);
xtest_run(test_9);
xtest_end_group();
}
void bar() {
xtest_run(test_2);
xtest_run(test_3);
}
void foo() {
xtest_run(test_1);
xtest_run_group(bar);
}
void A() {
xtest_run_group(foo);
xtest_run(test_4);
}
void B() {
xtest_run(test_5);
xtest_run(test_6);
}
void Z() {
xtest_run(test_7);
}
void Y() {
xtest_run_group(Z);
}
void X() {
xtest_run_group(Y);
}
void C() {
xtest_run_group(X);
}
void D() {
xtest_run(test_8);
xtest_run(test_9);
}
void groups_convenient() {
xtest_run_group(A);
xtest_run_group(B);
xtest_run_group(C);
xtest_run_group(D);
}
void example_groups() {
groups_manual();
groups_convenient();
}
#ifndef XTEST_ALL_EXAMPLES
int main(int argc, char **argv) {
example_groups();
return xtest_complete();
}
#endif

86
examples/parameterized.c Normal file
View file

@ -0,0 +1,86 @@
#include <stddef.h>
#include "../xtest.h"
#include "source.h"
void test_add(void *fixture, void **params) {
int a = xtest_get_param(int, 0, params);
int b = xtest_get_param(int, 1, params);
int result = a + b;
xtest_assert_is(add(a, b), result);
}
void test_str_equals(void *fixture, void **params) {
const char* s1 = xtest_get_param_ptr(const char*, 0, params);
const char* s2 = xtest_get_param_ptr(const char*, 1, params);
if (str_equals(s1, s2)) {
xtest_assert_str_is(s1, s2);
} else {
xtest_assert_str_is_not(s1, s2);
}
}
xtest_param add_params[] = {
{
.name = "A",
.values = &(xtest_param_values) {
&(int) {1},
&(int) {10},
&(int) {100},
NULL
}
},
{
.name = "B",
.values = &(xtest_param_values) {
&(int) {2},
&(int) {22},
&(int) {222},
NULL
}
},
{
.name = NULL,
.values = NULL,
}
};
xtest_param str_params[] = {
{
.name = "A",
.values = &(xtest_param_values) {
"foo",
"bar",
"hello",
"world",
NULL
}
},
{
.name = "B",
.values = &(xtest_param_values) {
"foo",
"bar",
"xyz",
NULL
}
},
{
.name = NULL,
.values = NULL,
}
};
void example_parameterized() {
xtest_run_parameterized(test_add, add_params);
xtest_run_parameterized(test_str_equals, str_params);
}
#ifndef XTEST_ALL_EXAMPLES
int main(int argc, char **argv) {
example_parameterized();
return xtest_complete();
}
#endif

61
examples/skip.c Normal file
View file

@ -0,0 +1,61 @@
#include "../xtest.h"
#include "source.h"
void no_skip_1() {}
void no_skip_2() {}
void no_skip_3() {}
void no_skip_4() {}
void no_skip_5() {}
void no_skip_6() {}
void skip_1() {
xtest_skip("foo");
}
void skip_2() {
xtest_skip("bar");
}
void skip_3() {
xtest_skip(NULL);
}
void skip_4() {
xtest_skip("some reason");
}
void skip_5() {
xtest_skip("other reason");
}
void skip_6() {
xtest_skip(NULL);
}
void example_skip() {
xtest_run(no_skip_1);
xtest_run(skip_1);
xtest_run(no_skip_2);
xtest_run(no_skip_3);
xtest_run(skip_2);
xtest_run(skip_3);
xtest_run(skip_4);
xtest_run(no_skip_4);
xtest_run(skip_5);
xtest_run(no_skip_5);
xtest_run(no_skip_6);
xtest_run(skip_6);
}
#ifndef XTEST_ALL_EXAMPLES
int main(int argc, char **argv) {
example_skip();
return xtest_complete();
}
#endif

22
examples/source.c Normal file
View file

@ -0,0 +1,22 @@
#include <string.h>
#include "source.h"
#include "../xassert.h"
int ret_1() {
return 1;
}
double div(double a, double b) {
assert(b != 0.0);
return a/b;
}
int add(int a, int b) {
return a + b;
}
int str_equals(const char *s1, const char *s2) {
return strcmp(s1, s2) == 0;
}

13
examples/source.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef XTEST_SOURCE_H
#define XTEST_SOURCE_H
int ret_1();
double div(double a, double b);
int add(int a, int b);
int str_equals(const char * s1, const char * s2);
#endif //XTEST_SOURCE_H

18
xassert.h Normal file
View file

@ -0,0 +1,18 @@
#include <assert.h>
#define XTEST_XASSERT
#if defined(XTEST_XTEST_H) && !defined(XTEST_SOURCE)
#error "do not include xtest.h and xassert.h at the same time. xassert.h should only be included from your source files and xtest.h should only be included from your test files."
#endif
#if defined(XTEST) && (!defined(XTEST_XTEST_H) || defined(XTEST_SOURCE))
void xtest_internal_assert(const char* file, int line, const char* func, const char* expr);
#undef assert
#define assert(__e) ((__e) ? (void)0 : xtest_internal_assert(__FILE__, __LINE__, \
__ASSERT_FUNC, #__e))
#endif //defined(XTEST) && !defined(XTEST_XTEST_H)

312
xtest.c Normal file
View file

@ -0,0 +1,312 @@
#include <setjmp.h>
#include <math.h>
#define XTEST_SOURCE
#include "xtest.h"
#include "xassert.h"
struct xtest_assert_info {
const char *file;
int line;
const char *func;
const char *expr;
};
struct xtest_assert_info assertion_info;
#ifndef XTEST_MAX_GROUP_NESTING
#define XTEST_MAX_GROUP_NESTING 16
#endif
#ifndef XTEST_TEST_NAME_MAX_LENGTH
#define XTEST_TEST_NAME_MAX_LENGTH 1023
#endif
const char * group_nesting[XTEST_MAX_GROUP_NESTING];
int group_nesting_pos = 0;
jmp_buf xtest_jmp;
#ifndef XTEST_ASSERT_MESSAGE_MAX_LEN
#define XTEST_ASSERT_MESSAGE_MAX_LEN 1023
#endif
char assert_message[XTEST_ASSERT_MESSAGE_MAX_LEN + 1];
const char *skip_reason;
int xtest_indent = 0;
int expecting_assertion = 0;
int num_tests = 0;
int successful_tests = 0;
int failed_tests = 0;
int skipped_tests = 0;
int xtest_complete() {
#ifndef XTEST_ENABLE_SUBUNIT
printf("============\nTotal: %d, Failed: %d, Skipped: %d\n", num_tests, failed_tests, skipped_tests);
#endif //XTEST_ENABLE_SUBUNIT
fflush(stdout);
return failed_tests != 0 ? 1 : 0;
}
void subunit_message(const char * name, const char * message, const char * details) {
printf("%s: %s", message, name);
if (details == NULL) {
printf("\n");
} else {
printf(" [\n%s%s]\n", details, details[strlen(details)] != '\n' ? "\n" : "");
}
}
void xtest_internal_run(xtest_test_fn fn, const char *name, xtest_param *params, xtest_setup_fn setup,
xtest_teardown_fn teardown) {
num_tests += 1;
expecting_assertion = 0;
char full_name[XTEST_TEST_NAME_MAX_LENGTH + 1] = "";
size_t name_len = 0;
for (int i = 0; i < group_nesting_pos; ++i) {
name_len += snprintf(full_name + name_len, XTEST_TEST_NAME_MAX_LENGTH - name_len, "%s/", group_nesting[i]);
assert(name_len <= XTEST_TEST_NAME_MAX_LENGTH);
}
name_len += snprintf(full_name + name_len, XTEST_TEST_NAME_MAX_LENGTH - name_len, "%s", name);
assert(name_len <= XTEST_TEST_NAME_MAX_LENGTH);
void *fixture = NULL;
#ifndef XTEST_ENABLE_SUBUNIT
printf("%*s%s: ", xtest_indent, "", name);
#else
subunit_message(full_name, "test", NULL);
#endif //XTEST_ENABLE_SUBUNIT
if (setup != NULL) {
int jmpres = setjmp(xtest_jmp);
if (jmpres == 0) {
setup(&fixture);
} else if (jmpres == 3) {
goto skipped;
} else {
snprintf(assert_message, 1024, "assertion failure in setup function: %s in %s (%s:%d)", assertion_info.expr,
assertion_info.func, assertion_info.file, assertion_info.line);
goto failed;
}
}
int jmpres = setjmp(xtest_jmp);
if (jmpres == 0) {
if (params == NULL) {
expecting_assertion = 0;
fn(fixture, NULL);
} else {
int num_params = 0;
while (params[num_params].name != NULL) num_params++;
int validx[num_params];
int valmax[num_params];
void *current_vals[num_params];
for (int i = 0; i < num_params; ++i) {
validx[i] = 0;
valmax[i] = 0;
while ((*params[i].values)[valmax[i]] != NULL) valmax[i]++;
}
while (1) {
for (int i = 0; i < num_params; ++i) {
current_vals[i] = (*params[i].values)[validx[i]];
}
expecting_assertion = 0;
fn(fixture, current_vals);
int carry = 1;
for (int i = num_params - 1; i >= 0; --i) {
validx[i] += 1;
if (validx[i] >= valmax[i]) {
validx[i] = 0;
} else {
carry = 0;
break;
}
}
if (carry == 1) break;
}
}
if (expecting_assertion) { // an assertion failure in the tested code was expected but didn't occur
snprintf(assert_message, 1024, "expected an assertion failure in the tested code");
goto failed;
} else {
goto success;
}
} else if (jmpres == 1) { // a testing assertion failed
goto failed;
} else if (jmpres == 2) { // an assertion in the tested code failed
if (expecting_assertion) {
goto success;
} else {
snprintf(assert_message, 1024, "unexpected assertion failure in tested code: %s in %s (%s:%d)",
assertion_info.expr, assertion_info.func, assertion_info.file, assertion_info.line);
goto failed;
}
} else if (jmpres == 3) {
goto skipped;
}
success:
#ifndef XTEST_ENABLE_SUBUNIT
printf("SUCCESS\n");
#else
subunit_message(full_name, "success", NULL);
#endif //XTEST_ENABLE_SUBUNIT
successful_tests += 1;
goto cleanup;
failed:
#ifndef XTEST_ENABLE_SUBUNIT
printf("FAILED:\n%*s %s\n", xtest_indent, "", assert_message);
#else
subunit_message(full_name, "failure", assert_message);
#endif //XTEST_ENABLE_SUBUNIT
failed_tests += 1;
goto cleanup;
skipped:
skipped_tests += 1;
#ifndef XTEST_ENABLE_SUBUNIT
if (skip_reason != NULL) {
printf("SKIPPED: %s\n", skip_reason);
} else {
printf("SKIPPED\n");
}
#else
subunit_message(full_name, "skipped", skip_reason);
#endif //XTEST_ENABLE_SUBUNIT
goto cleanup;
cleanup:
if (teardown != NULL && fixture != NULL) {
teardown(fixture);
}
}
/// this function replaces the standard library assert and jumps back to the xtest_internal_run invocation that is running the test
/// \param file file in which the assertion happened
/// \param line line on which the assertion happened
/// \param func function in which the assertion happened
/// \param expr stringified expression of the assertion
void xtest_internal_assert(const char *file, int line, const char *func, const char *expr) {
assertion_info = (struct xtest_assert_info) {
.file = file, .line = line, .func = func, .expr = expr
};
longjmp(xtest_jmp, 2);
}
#define FAIL_ASSERT_FORMAT_1 "assertion failed: %s, expected %s"
#define FAIL_ASSERT_FORMAT_2 " but got "
#define FAIL_ASSERT_FORMAT_3 " (%s:%d)"
#define FAIL_ASSERT_FORMAT(fmt) FAIL_ASSERT_FORMAT_1 fmt FAIL_ASSERT_FORMAT_2 fmt FAIL_ASSERT_FORMAT_3
#define CAST_PTR(type, ptr) *(type *)(ptr)
#define PRINT_ASSERT_MESSAGE(fmt, type) snprintf(assert_message, XTEST_ASSERT_MESSAGE_MAX_LEN, FAIL_ASSERT_FORMAT(fmt), \
expression, invert ? "not " : "", CAST_PTR(type, expected), CAST_PTR(type, actual), file, line)
void xtest_fail_assert(const char *expression, const char *file, int line, void *expected,
void *actual, int invert, enum xtest_type type) {
switch (type) {
case xtest_type_bool:
snprintf(assert_message, XTEST_ASSERT_MESSAGE_MAX_LEN, FAIL_ASSERT_FORMAT("%s"), expression, invert ? "not " : "",
CAST_PTR(_Bool, expected) ? "true" : "false", CAST_PTR(_Bool, actual) ? "true" : "false", file,
line);
break;
case xtest_type_char:
PRINT_ASSERT_MESSAGE("'%c'", char);
break;
case xtest_type_short:
PRINT_ASSERT_MESSAGE("%i", short);
break;
case xtest_type_unsigned_short:
PRINT_ASSERT_MESSAGE("%u", unsigned short);
break;
case xtest_type_int:
PRINT_ASSERT_MESSAGE("%i", int);
break;
case xtest_type_unsigned_int:
PRINT_ASSERT_MESSAGE("%u", unsigned int);
break;
case xtest_type_long:
PRINT_ASSERT_MESSAGE("%li", long);
break;
case xtest_type_unsigned_long:
PRINT_ASSERT_MESSAGE("%lu", unsigned long);
break;
case xtest_type_long_long:
PRINT_ASSERT_MESSAGE("%lli", long long);
break;
case xtest_type_unsigned_long_long:
PRINT_ASSERT_MESSAGE("%llu", unsigned long long);
break;
case xtest_type_float:
PRINT_ASSERT_MESSAGE("%f", float);
break;
case xtest_type_double:
PRINT_ASSERT_MESSAGE("%f", double);
break;
case xtest_type_long_double:
PRINT_ASSERT_MESSAGE("%Lf", long double);
break;
case xtest_type_string:
PRINT_ASSERT_MESSAGE("\"%s\"", char*);
break;
case xtest_type_void_pointer:
PRINT_ASSERT_MESSAGE("%p", void*);
break;
case xtest_type_other:
default:
snprintf(assert_message, XTEST_ASSERT_MESSAGE_MAX_LEN, FAIL_ASSERT_FORMAT("%s"), expression, invert ? "not " : "",
"<value of unknown type>", "<value of unknown type>", file, line);
break;
}
longjmp(xtest_jmp, 1);
}
void
xtest_assert_float(double expected, double actual, int precision, int invert, const char *expression, const char *file,
int line) {
double epsilon = pow(10, -(precision));
double diff = fabs(expected - actual);
int equals = diff < epsilon;
int failed = invert == equals; // invert = true: fail if equals, invert = false: fail if not equals
if (!failed) return;
snprintf(assert_message, 1024, "assertion failed: %s, expected %s%g but got %g (difference %g, precision %d) (%s:%d)",
expression, invert ? "not " : "", expected, actual, diff, precision, file, line);
longjmp(xtest_jmp, 1);
}
void xtest_skip(const char *reason) {
skip_reason = reason;
longjmp(xtest_jmp, 3);
}
void xtest_internal_start_group(const char *name) {
#ifndef XTEST_ENABLE_SUBUNIT
printf("%*s%s:\n", xtest_indent, "", name);
#endif
group_nesting[group_nesting_pos] = name;
group_nesting_pos += 1;
xtest_indent += XTEST_INDENT;
}
void xtest_internal_end_group() {
assert(group_nesting_pos > 0);
group_nesting_pos -= 1;
xtest_indent -= XTEST_INDENT;
}
void xtest_expect_assertion_failure() {
expecting_assertion = 1;
}

133
xtest.h Normal file
View file

@ -0,0 +1,133 @@
#ifndef XTEST_XTEST_H
#define XTEST_XTEST_H
#if defined(XTEST_XASSERT) && !defined(XTEST_SOURCE)
#error "do not include xtest.h and xassert.h at the same time. xassert.h should only be included from your source files and xtest.h should only be included from your test files."
#endif
#include <stdio.h>
#include <string.h>
#include <assert.h>
#ifndef XTEST_INDENT
#define XTEST_INDENT 2
#endif
typedef void (*xtest_test_fn)(void *, void **);
typedef void (*xtest_setup_fn)(void **);
typedef void (*xtest_teardown_fn)(void *);
typedef struct xtest_param_s xtest_param;
typedef void *xtest_param_values[];
struct xtest_param_s {
const char *name;
xtest_param_values *values;
};
enum xtest_type {
xtest_type_bool,
xtest_type_char,
xtest_type_short,
xtest_type_unsigned_short,
xtest_type_int,
xtest_type_unsigned_int,
xtest_type_long,
xtest_type_unsigned_long,
xtest_type_long_long,
xtest_type_unsigned_long_long,
xtest_type_float,
xtest_type_double,
xtest_type_long_double,
xtest_type_string,
xtest_type_void_pointer,
xtest_type_other,
};
void xtest_fail_assert(const char *expression, const char *file, int line, void *expected,
void *actual, int invert, enum xtest_type type);
void
xtest_assert_float(double expected, double actual, int precision, int invert, const char *expression, const char *file,
int line);
void xtest_internal_run(xtest_test_fn fn, const char *name, xtest_param *params, xtest_setup_fn setup,
xtest_teardown_fn teardown);
void xtest_internal_start_group(const char *name);
void xtest_internal_end_group();
void xtest_skip(const char *reason);
void xtest_expect_assertion_failure();
int xtest_complete();
#define xtest_run(fn) xtest_internal_run(fn, #fn, (void*)0, (void*)0, (void*)0)
#define xtest_run_parameterized(fn, params) xtest_internal_run(fn, #fn, params, (void*)0, (void*)0)
#define xtest_run_with_fixture(fn, setup, teardown) xtest_internal_run(fn, #fn, (void*)0, setup, teardown)
#define xtest_run_parameterized_with_fixture(fn, params, setup, teardown) xtest_internal_run(fn, #fn, params, setup, teardown)
#define xtest_group(name) xtest_internal_start_group(#name)
#define xtest_end_group() xtest_internal_end_group()
#define xtest_run_group(fn) do { \
xtest_group(fn); \
fn(); \
xtest_end_group(); \
} while (0)
#define xtest_get_param(type, idx, params) *((type*)(params)[idx])
#define xtest_get_param_ptr(type, idx, params) (type)(params)[idx]
#define xtest_get_type(val) _Generic(val, \
_Bool: xtest_type_bool, \
char: xtest_type_char, \
signed char: xtest_type_char, \
unsigned char: xtest_type_char, \
short: xtest_type_short, \
unsigned short: xtest_type_unsigned_short,\
int: xtest_type_int,\
unsigned int: xtest_type_unsigned_int,\
long: xtest_type_long,\
unsigned long: xtest_type_unsigned_long,\
long long: xtest_type_long_long,\
unsigned long long: xtest_type_unsigned_long_long,\
float: xtest_type_float,\
double: xtest_type_double,\
long double: xtest_type_long_double,\
const char *: xtest_type_string,\
void*:xtest_type_void_pointer,\
default: xtest_type_other)
#define force_types_equal(a, b) (void) _Generic(a, \
typeof(b): (void*)0 \
)
#define xtest_assert_(expr, s, actual, expected, invert) do { \
force_types_equal(expected, actual); \
int _xtest_res = (expr); \
if (!(_xtest_res)) { \
typeof(actual) _xtest_a = actual; \
typeof(expected) _xtest_e = expected; \
enum xtest_type _xtest_type = xtest_get_type(actual); \
xtest_fail_assert(s, __FILE__, __LINE__, &_xtest_e, &_xtest_a, invert, _xtest_type); \
} \
} while(0)
#define xtest_assert(e) xtest_assert_(!!(e), #e, (_Bool)(e), (_Bool)1, 0)
#define xtest_assert_is(actual, expected) xtest_assert_((actual) == (expected), #actual " is " #expected, actual, expected, 0)
#define xtest_assert_is_not(actual, expected) xtest_assert_((actual) != (expected), #actual " is not " #expected, actual, expected, 1)
#define xtest_assert_float_is(actual, expected, precision) xtest_assert_float(expected, actual, precision, 0, #actual " is " #expected, __FILE__, __LINE__)
#define xtest_assert_float_is_not(actual, expected, precision) xtest_assert_float(expected, actual, precision, 1, #actual " is not " #expected, __FILE__, __LINE__)
#define xtest_assert_str_is(actual, expected) xtest_assert_(strcmp(actual, expected) == 0, #actual " is " #expected, (const char*)(actual), (const char*)(expected), 0)
#define xtest_assert_str_is_not(actual, expected) xtest_assert_(strcmp(actual, expected) != 0, #actual " is not " #expected, (const char*)(actual), (const char*)(expected), 1)
#define xtest_assert_mem_is(actual, expected, len) xtest_assert_(memcmp(actual, expected, len) == 0, #actual " is " #expected " (" #len " bytes)", NULL, NULL, 0)
#define xtest_assert_mem_is_not(actual, expected, len) xtest_assert_(memcmp(actual, expected, len) != 0, #actual " is not " #expected " (" #len " bytes)", NULL, NULL, 1)
#endif //XTEST_XTEST_H