diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1a769ac --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/examples/all.c b/examples/all.c new file mode 100644 index 0000000..9d77b1a --- /dev/null +++ b/examples/all.c @@ -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(); +} \ No newline at end of file diff --git a/examples/assertions.c b/examples/assertions.c new file mode 100644 index 0000000..0cc5c9b --- /dev/null +++ b/examples/assertions.c @@ -0,0 +1,106 @@ +#include +#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 \ No newline at end of file diff --git a/examples/expect_assertions.c b/examples/expect_assertions.c new file mode 100644 index 0000000..d435018 --- /dev/null +++ b/examples/expect_assertions.c @@ -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 \ No newline at end of file diff --git a/examples/fail.c b/examples/fail.c new file mode 100644 index 0000000..5d70c02 --- /dev/null +++ b/examples/fail.c @@ -0,0 +1,188 @@ +#include +#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 \ No newline at end of file diff --git a/examples/float.c b/examples/float.c new file mode 100644 index 0000000..9548c05 --- /dev/null +++ b/examples/float.c @@ -0,0 +1,46 @@ +#include +#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 \ No newline at end of file diff --git a/examples/groups.c b/examples/groups.c new file mode 100644 index 0000000..b965ee3 --- /dev/null +++ b/examples/groups.c @@ -0,0 +1,103 @@ +#include +#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 \ No newline at end of file diff --git a/examples/parameterized.c b/examples/parameterized.c new file mode 100644 index 0000000..7289bde --- /dev/null +++ b/examples/parameterized.c @@ -0,0 +1,86 @@ +#include +#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 \ No newline at end of file diff --git a/examples/skip.c b/examples/skip.c new file mode 100644 index 0000000..489f1ca --- /dev/null +++ b/examples/skip.c @@ -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 \ No newline at end of file diff --git a/examples/source.c b/examples/source.c new file mode 100644 index 0000000..7df04f9 --- /dev/null +++ b/examples/source.c @@ -0,0 +1,22 @@ +#include +#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; +} + diff --git a/examples/source.h b/examples/source.h new file mode 100644 index 0000000..2c23264 --- /dev/null +++ b/examples/source.h @@ -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 diff --git a/xassert.h b/xassert.h new file mode 100644 index 0000000..6ce5193 --- /dev/null +++ b/xassert.h @@ -0,0 +1,18 @@ +#include + +#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) diff --git a/xtest.c b/xtest.c new file mode 100644 index 0000000..92842be --- /dev/null +++ b/xtest.c @@ -0,0 +1,312 @@ +#include +#include + +#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 " : "", + "", "", 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; +} \ No newline at end of file diff --git a/xtest.h b/xtest.h new file mode 100644 index 0000000..df3f41c --- /dev/null +++ b/xtest.h @@ -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 +#include +#include + +#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