diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d0666bf..a76ce9e 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -10,15 +10,20 @@ jobs: build: strategy: matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] + os: [ubuntu-latest, windows-latest, macos-11, macos-14] type: [Debug, RelWithDebInfo, MinSizeRel, Release] - compiler: [default, clang, gcc] + compiler: [default, clang, gcc, tcc] exclude: - - {os: "macOS-latest", compiler: "clang"} - - {os: "windows-latest", compiler: "gcc"} - - {os: "macOS-latest", compiler: "gcc"} + - {os: "macos-11", compiler: "clang"} + - {os: "macos-11", compiler: "gcc"} + - {os: "macos-11", compiler: "tcc"} + - {os: "macos-14", compiler: "clang"} + - {os: "macos-14", compiler: "gcc"} + - {os: "macos-14", compiler: "tcc"} - {os: "ubuntu-latest", compiler: "default"} - {os: "ubuntu-latest", compiler: "default"} + - {os: "windows-latest", compiler: "gcc"} + - {os: "windows-latest", compiler: "tcc"} runs-on: ${{ matrix.os }} steps: @@ -29,7 +34,7 @@ jobs: - name: Setup dependencies if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get install -y gcc-10 g++-10 clang-10 + run: sudo apt-get install -y gcc-10 g++-10 clang-10 tcc - name: Configure CMake shell: bash @@ -43,6 +48,12 @@ jobs: working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 + - name: Configure CMake with TCC (Ubuntu) + shell: bash + if: matrix.compiler == 'tcc' && startsWith(matrix.os, 'ubuntu') + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=tcc -DCMAKE_CXX_COMPILER=g++-10 + - name: Configure CMake with Clang (Ubuntu) shell: bash if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'ubuntu') diff --git a/test/utest.h b/test/utest.h index 26bf890..be80738 100644 --- a/test/utest.h +++ b/test/utest.h @@ -36,26 +36,61 @@ #ifdef _MSC_VER /* Disable warning about not inlining 'inline' functions. - TODO: We'll fix this later by not using fprintf within our macros, and - instead use snprintf to a realloc'ed buffer. */ #pragma warning(disable : 4710) /* Disable warning about inlining functions that are not marked 'inline'. - TODO: add a UTEST_NOINLINE onto the macro generated functions to fix this. */ #pragma warning(disable : 4711) + +/* + Disable warning for alignment padding added +*/ +#pragma warning(disable : 4820) + +#if _MSC_VER > 1900 +/* + Disable warning about preprocessor macros not being defined in MSVC headers. +*/ +#pragma warning(disable : 4668) + +/* + Disable warning about no function prototype given in MSVC headers. +*/ +#pragma warning(disable : 4255) + +/* + Disable warning about pointer or reference to potentially throwing function. +*/ +#pragma warning(disable : 5039) + +/* + Disable warning about macro expansion producing 'defined' has undefined + behavior. +*/ +#pragma warning(disable : 5105) +#endif + +#if _MSC_VER > 1930 +/* + Disable warning about 'const' variable is not used. +*/ +#pragma warning(disable : 5264) +#endif + #pragma warning(push, 1) #endif -#if defined(_MSC_VER) +#if defined(_MSC_VER) && (_MSC_VER < 1920) typedef __int64 utest_int64_t; typedef unsigned __int64 utest_uint64_t; +typedef unsigned __int32 utest_uint32_t; #else #include typedef int64_t utest_int64_t; typedef uint64_t utest_uint64_t; +typedef uint32_t utest_uint32_t; #endif #include @@ -64,26 +99,79 @@ typedef uint64_t utest_uint64_t; #include #include +#if defined(__cplusplus) +#if defined(_MSC_VER) && !defined(_CPPUNWIND) +/* We're on MSVC and the compiler is compiling without exception support! */ +#elif !defined(_MSC_VER) && !defined(__EXCEPTIONS) +/* We're on a GCC/Clang compiler that doesn't have exception support! */ +#else +#define UTEST_HAS_EXCEPTIONS 1 +#endif +#endif + +#if defined(UTEST_HAS_EXCEPTIONS) +#include +#endif + #if defined(_MSC_VER) #pragma warning(pop) #endif -#if defined(_MSC_VER) -#if defined(_M_IX86) -#define _X86_ +#if defined(__cplusplus) +#define UTEST_C_FUNC extern "C" +#else +#define UTEST_C_FUNC #endif -#if defined(_M_AMD64) -#define _AMD64_ +#define UTEST_TEST_PASSED (0) +#define UTEST_TEST_FAILURE (1) +#define UTEST_TEST_SKIPPED (2) + +#if defined(__TINYC__) +#define UTEST_ATTRIBUTE(a) __attribute((a)) +#else +#define UTEST_ATTRIBUTE(a) __attribute__((a)) #endif -#pragma warning(push, 1) -#include -#include -#pragma warning(pop) +#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) -#elif defined(__linux__) +#if defined(__MINGW64__) || defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#endif + +#if defined(_WINDOWS_) || defined(_WINDOWS_H) +typedef LARGE_INTEGER utest_large_integer; +#else +// use old QueryPerformanceCounter definitions (not sure is this needed in some +// edge cases or not) on Win7 with VS2015 these extern declaration cause "second +// C linkage of overloaded function not allowed" error +typedef union { + struct { + unsigned long LowPart; + long HighPart; + } DUMMYSTRUCTNAME; + struct { + unsigned long LowPart; + long HighPart; + } u; + utest_int64_t QuadPart; +} utest_large_integer; + +UTEST_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceCounter( + utest_large_integer *); +UTEST_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceFrequency( + utest_large_integer *); + +#if defined(__MINGW64__) || defined(__MINGW32__) +#pragma GCC diagnostic pop +#endif +#endif +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || \ + defined(__HAIKU__) /* slightly obscure include here - we need to include glibc's features.h, but we don't want to just include a header that might not be defined for other @@ -108,32 +196,68 @@ typedef uint64_t utest_uint64_t; #endif #elif defined(__APPLE__) -#include +#include #endif -#if defined(_MSC_VER) +#if defined(_MSC_VER) && (_MSC_VER < 1920) #define UTEST_PRId64 "I64d" #define UTEST_PRIu64 "I64u" -#define UTEST_INLINE __forceinline +#else +#include + +#define UTEST_PRId64 PRId64 +#define UTEST_PRIu64 PRIu64 +#endif #if defined(__cplusplus) -#define UTEST_C_FUNC extern "C" +#define UTEST_INLINE inline + +#if defined(__clang__) +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") + +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") #else -#define UTEST_C_FUNC +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS #endif +#define UTEST_INITIALIZER(f) \ + struct f##_cpp_struct { \ + f##_cpp_struct(); \ + }; \ + UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS static f##_cpp_struct \ + f##_cpp_global UTEST_INITIALIZER_END_DISABLE_WARNINGS; \ + f##_cpp_struct::f##_cpp_struct() +#elif defined(_MSC_VER) +#define UTEST_INLINE __forceinline + #if defined(_WIN64) #define UTEST_SYMBOL_PREFIX #else #define UTEST_SYMBOL_PREFIX "_" #endif +#if defined(__clang__) +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wmissing-variable-declarations\"") + +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") +#else +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS +#endif + #pragma section(".CRT$XCU", read) #define UTEST_INITIALIZER(f) \ static void __cdecl f(void); \ - __pragma(comment(linker, "/include:" UTEST_SYMBOL_PREFIX #f "_")); \ - UTEST_C_FUNC __declspec(allocate(".CRT$XCU")) void(__cdecl * f##_)(void) = \ - f; \ + UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + __pragma(comment(linker, "/include:" UTEST_SYMBOL_PREFIX #f "_")) \ + UTEST_C_FUNC __declspec(allocate(".CRT$XCU")) void(__cdecl * \ + f##_)(void) = f; \ + UTEST_INITIALIZER_END_DISABLE_WARNINGS \ static void __cdecl f(void) #else #if defined(__linux__) @@ -153,14 +277,10 @@ typedef uint64_t utest_uint64_t; #endif #endif -#include - -#define UTEST_PRId64 PRId64 -#define UTEST_PRIu64 PRIu64 #define UTEST_INLINE inline #define UTEST_INITIALIZER(f) \ - static void f(void) __attribute__((constructor)); \ + static void f(void) UTEST_ATTRIBUTE(constructor); \ static void f(void) #endif @@ -170,8 +290,8 @@ typedef uint64_t utest_uint64_t; #define UTEST_EXTERN extern "C" #define UTEST_NULL NULL #else -#define UTEST_CAST(type, x) ((type)x) -#define UTEST_PTR_CAST(type, x) ((type)x) +#define UTEST_CAST(type, x) ((type)(x)) +#define UTEST_PTR_CAST(type, x) ((type)(x)) #define UTEST_EXTERN extern #define UTEST_NULL 0 #endif @@ -188,23 +308,41 @@ typedef uint64_t utest_uint64_t; #pragma warning(pop) #define UTEST_COLOUR_OUTPUT() (_isatty(_fileno(stdout))) #else +#if defined(__EMSCRIPTEN__) +#include +#define UTEST_COLOUR_OUTPUT() false +#else #include #define UTEST_COLOUR_OUTPUT() (isatty(STDOUT_FILENO)) #endif +#endif + +static UTEST_INLINE void *utest_realloc(void *const pointer, size_t new_size) { + void *const new_pointer = realloc(pointer, new_size); + + if (UTEST_NULL == new_pointer) { + free(new_pointer); + } + + return new_pointer; +} static UTEST_INLINE utest_int64_t utest_ns(void) { -#ifdef _MSC_VER - LARGE_INTEGER counter; - LARGE_INTEGER frequency; +#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) + utest_large_integer counter; + utest_large_integer frequency; QueryPerformanceCounter(&counter); QueryPerformanceFrequency(&frequency); return UTEST_CAST(utest_int64_t, (counter.QuadPart * 1000000000) / frequency.QuadPart); -#elif defined(__linux) && defined(__STRICT_ANSI__) +#elif defined(__linux__) && defined(__STRICT_ANSI__) return UTEST_CAST(utest_int64_t, clock()) * 1000000000 / CLOCKS_PER_SEC; -#elif defined(__linux) +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || \ + defined(__HAIKU__) struct timespec ts; -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(__HAIKU__) timespec_get(&ts, TIME_UTC); #else const clockid_t cid = CLOCK_REALTIME; @@ -216,7 +354,11 @@ static UTEST_INLINE utest_int64_t utest_ns(void) { #endif return UTEST_CAST(utest_int64_t, ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec; #elif __APPLE__ - return UTEST_CAST(utest_int64_t, mach_absolute_time()); + return UTEST_CAST(utest_int64_t, clock_gettime_nsec_np(CLOCK_UPTIME_RAW)); +#elif __EMSCRIPTEN__ + return emscripten_performance_now() * 1000000.0; +#else +#error Unsupported platform! #endif } @@ -239,14 +381,18 @@ UTEST_EXTERN struct utest_state_s utest_state; #if defined(_MSC_VER) #define UTEST_WEAK __forceinline +#elif defined(__MINGW32__) || defined(__MINGW64__) +#define UTEST_WEAK static UTEST_ATTRIBUTE(used) +#elif defined(__clang__) || defined(__GNUC__) || defined(__TINYC__) +#define UTEST_WEAK UTEST_ATTRIBUTE(weak) #else -#define UTEST_WEAK __attribute__((weak)) +#error Non clang, non gcc, non MSVC, non tcc compiler found! #endif #if defined(_MSC_VER) #define UTEST_UNUSED #else -#define UTEST_UNUSED __attribute__((unused)) +#define UTEST_UNUSED UTEST_ATTRIBUTE(unused) #endif #ifdef __clang__ @@ -263,29 +409,146 @@ UTEST_EXTERN struct utest_state_s utest_state; #pragma clang diagnostic pop #endif -#ifdef _MSC_VER -#define UTEST_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__) -#else #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wvariadic-macros" #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" #endif + +#ifdef _MSC_VER +#define UTEST_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__) +#else #define UTEST_SNPRINTF(...) snprintf(__VA_ARGS__) +#endif + #ifdef __clang__ #pragma clang diagnostic pop #endif -#endif #if defined(__cplusplus) /* if we are using c++ we can use overloaded methods (its in the language) */ #define UTEST_OVERLOADABLE #elif defined(__clang__) /* otherwise, if we are using clang with c - use the overloadable attribute */ -#define UTEST_OVERLOADABLE __attribute__((overloadable)) +#define UTEST_OVERLOADABLE UTEST_ATTRIBUTE(overloadable) +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +#include + +template ::value> +struct utest_type_deducer final { + static void _(const T t); +}; + +template <> struct utest_type_deducer { + static void _(const signed char c) { + UTEST_PRINTF("%d", static_cast(c)); + } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned char c) { + UTEST_PRINTF("%u", static_cast(c)); + } +}; + +template <> struct utest_type_deducer { + static void _(const short s) { UTEST_PRINTF("%d", static_cast(s)); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned short s) { + UTEST_PRINTF("%u", static_cast(s)); + } +}; + +template <> struct utest_type_deducer { + static void _(const float f) { UTEST_PRINTF("%f", static_cast(f)); } +}; + +template <> struct utest_type_deducer { + static void _(const double d) { UTEST_PRINTF("%f", d); } +}; + +template <> struct utest_type_deducer { + static void _(const long double d) { +#if defined(__MINGW32__) || defined(__MINGW64__) + /* MINGW is weird - doesn't like LF at all?! */ + UTEST_PRINTF("%f", (double)d); +#else + UTEST_PRINTF("%Lf", d); #endif + } +}; + +template <> struct utest_type_deducer { + static void _(const int i) { UTEST_PRINTF("%d", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned int i) { UTEST_PRINTF("%u", i); } +}; + +template <> struct utest_type_deducer { + static void _(const long i) { UTEST_PRINTF("%ld", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned long i) { UTEST_PRINTF("%lu", i); } +}; + +template <> struct utest_type_deducer { + static void _(const long long i) { UTEST_PRINTF("%lld", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned long long i) { UTEST_PRINTF("%llu", i); } +}; + +template struct utest_type_deducer { + static void _(const T *t) { + UTEST_PRINTF("%p", static_cast(const_cast(t))); + } +}; + +template struct utest_type_deducer { + static void _(T *t) { UTEST_PRINTF("%p", static_cast(t)); } +}; + +template struct utest_type_deducer { + static void _(const T t) { + UTEST_PRINTF("%llu", static_cast(t)); + } +}; + +template +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const T t) { + utest_type_deducer::_(t); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#elif defined(UTEST_OVERLOADABLE) + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(signed char c); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(signed char c) { + UTEST_PRINTF("%d", UTEST_CAST(int, c)); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned char c); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned char c) { + UTEST_PRINTF("%u", UTEST_CAST(unsigned int, c)); +} -#if defined(UTEST_OVERLOADABLE) UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f); UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f) { UTEST_PRINTF("%f", UTEST_CAST(double, f)); @@ -298,7 +561,12 @@ UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d) { UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d); UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d) { +#if defined(__MINGW32__) || defined(__MINGW64__) + /* MINGW is weird - doesn't like LF at all?! */ + UTEST_PRINTF("%f", (double)d); +#else UTEST_PRINTF("%Lf", d); +#endif } UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i); @@ -330,7 +598,8 @@ UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const void *p) { long long is a c++11 extension */ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) || \ - defined(__cplusplus) && (__cplusplus >= 201103L) + defined(__cplusplus) && (__cplusplus >= 201103L) || \ + (defined(__MINGW32__) || defined(__MINGW64__)) #ifdef __clang__ #pragma clang diagnostic push @@ -353,7 +622,9 @@ utest_type_printer(long long unsigned int i) { #endif #endif -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !(defined(__MINGW32__) || defined(__MINGW64__)) || \ + defined(__TINYC__) #define utest_type_printer(val) \ UTEST_PRINTF(_Generic((val), signed char \ : "%d", unsigned char \ @@ -381,9 +652,10 @@ utest_type_printer(long long unsigned int i) { #define utest_type_printer(...) UTEST_PRINTF("undef") #endif -#ifdef _MSC_VER +#if defined(_MSC_VER) #define UTEST_SURPRESS_WARNING_BEGIN \ - __pragma(warning(push)) __pragma(warning(disable : 4127)) + __pragma(warning(push)) __pragma(warning(disable : 4127)) \ + __pragma(warning(disable : 4571)) __pragma(warning(disable : 4130)) #define UTEST_SURPRESS_WARNING_END __pragma(warning(pop)) #else #define UTEST_SURPRESS_WARNING_BEGIN @@ -419,8 +691,31 @@ utest_type_printer(long long unsigned int i) { #define UTEST_STRNCMP(x, y, size) strncmp(x, y, size) #endif +#if defined(_MSC_VER) +#define UTEST_STRNCPY(x, y, size) strcpy_s(x, size, y) +#elif !defined(__clang__) && defined(__GNUC__) +static UTEST_INLINE char * +utest_strncpy_gcc(char *const dst, const char *const src, const size_t size) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" + return strncpy(dst, src, size); +#pragma GCC diagnostic pop +} + +#define UTEST_STRNCPY(x, y, size) utest_strncpy_gcc(x, y, size) +#else +#define UTEST_STRNCPY(x, y, size) strncpy(x, y, size) +#endif + +#define UTEST_SKIP(msg) \ + do { \ + UTEST_PRINTF(" Skipped : '%s'\n", (msg)); \ + *utest_result = UTEST_TEST_SKIPPED; \ + return; \ + } while (0) + #if defined(__clang__) -#define UTEST_EXPECT(x, y, cond) \ +#define UTEST_COND(x, y, cond, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"") \ @@ -429,270 +724,408 @@ utest_type_printer(long long unsigned int i) { UTEST_AUTO(x) xEval = (x); \ UTEST_AUTO(y) yEval = (y); \ if (!((xEval)cond(yEval))) { \ + const char *const xAsString = #x; \ + const char *const yAsString = #y; \ _Pragma("clang diagnostic pop") \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : "); \ - utest_type_printer(xEval); \ - UTEST_PRINTF("\n"); \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : ("); \ + UTEST_PRINTF("%s) " #cond " (%s", xAsString, yAsString); \ + UTEST_PRINTF(")\n"); \ UTEST_PRINTF(" Actual : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF(" vs "); \ utest_type_printer(yEval); \ UTEST_PRINTF("\n"); \ - *utest_result = 1; \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#elif defined(__GNUC__) -#define UTEST_EXPECT(x, y, cond) \ +#elif defined(__GNUC__) || defined(__TINYC__) +#define UTEST_COND(x, y, cond, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ UTEST_AUTO(x) xEval = (x); \ UTEST_AUTO(y) yEval = (y); \ if (!((xEval)cond(yEval))) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : "); \ - utest_type_printer(xEval); \ - UTEST_PRINTF("\n"); \ + const char *const xAsString = #x; \ + const char *const yAsString = #y; \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : ("); \ + UTEST_PRINTF("%s) " #cond " (%s", xAsString, yAsString); \ + UTEST_PRINTF(")\n"); \ UTEST_PRINTF(" Actual : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF(" vs "); \ utest_type_printer(yEval); \ UTEST_PRINTF("\n"); \ - *utest_result = 1; \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END #else -#define UTEST_EXPECT(x, y, cond) \ +#define UTEST_COND(x, y, cond, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ if (!((x)cond(y))) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - *utest_result = 1; \ + UTEST_PRINTF("%s:%i: Failure (Expected " #cond " Actual)", __FILE__, \ + __LINE__); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s", msg); \ + } \ + UTEST_PRINTF("\n"); \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END #endif -#define EXPECT_TRUE(x) \ +#define EXPECT_EQ(x, y) UTEST_COND(x, y, ==, "", 0) +#define EXPECT_EQ_MSG(x, y, msg) UTEST_COND(x, y, ==, msg, 0) +#define ASSERT_EQ(x, y) UTEST_COND(x, y, ==, "", 1) +#define ASSERT_EQ_MSG(x, y, msg) UTEST_COND(x, y, ==, msg, 1) + +#define EXPECT_NE(x, y) UTEST_COND(x, y, !=, "", 0) +#define EXPECT_NE_MSG(x, y, msg) UTEST_COND(x, y, !=, msg, 0) +#define ASSERT_NE(x, y) UTEST_COND(x, y, !=, "", 1) +#define ASSERT_NE_MSG(x, y, msg) UTEST_COND(x, y, !=, msg, 1) + +#define EXPECT_LT(x, y) UTEST_COND(x, y, <, "", 0) +#define EXPECT_LT_MSG(x, y, msg) UTEST_COND(x, y, <, msg, 0) +#define ASSERT_LT(x, y) UTEST_COND(x, y, <, "", 1) +#define ASSERT_LT_MSG(x, y, msg) UTEST_COND(x, y, <, msg, 1) + +#define EXPECT_LE(x, y) UTEST_COND(x, y, <=, "", 0) +#define EXPECT_LE_MSG(x, y, msg) UTEST_COND(x, y, <=, msg, 0) +#define ASSERT_LE(x, y) UTEST_COND(x, y, <=, "", 1) +#define ASSERT_LE_MSG(x, y, msg) UTEST_COND(x, y, <=, msg, 1) + +#define EXPECT_GT(x, y) UTEST_COND(x, y, >, "", 0) +#define EXPECT_GT_MSG(x, y, msg) UTEST_COND(x, y, >, msg, 0) +#define ASSERT_GT(x, y) UTEST_COND(x, y, >, "", 1) +#define ASSERT_GT_MSG(x, y, msg) UTEST_COND(x, y, >, msg, 1) + +#define EXPECT_GE(x, y) UTEST_COND(x, y, >=, "", 0) +#define EXPECT_GE_MSG(x, y, msg) UTEST_COND(x, y, >=, msg, 0) +#define ASSERT_GE(x, y) UTEST_COND(x, y, >=, "", 1) +#define ASSERT_GE_MSG(x, y, msg) UTEST_COND(x, y, >=, msg, 1) + +#define UTEST_TRUE(x, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - if (!(x)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + const int xEval = !!(x); \ + if (!(xEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ UTEST_PRINTF(" Expected : true\n"); \ - UTEST_PRINTF(" Actual : %s\n", (x) ? "true" : "false"); \ - *utest_result = 1; \ + UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#define EXPECT_FALSE(x) \ +#define EXPECT_TRUE(x) UTEST_TRUE(x, "", 0) +#define EXPECT_TRUE_MSG(x, msg) UTEST_TRUE(x, msg, 0) +#define ASSERT_TRUE(x) UTEST_TRUE(x, "", 1) +#define ASSERT_TRUE_MSG(x, msg) UTEST_TRUE(x, msg, 1) + +#define UTEST_FALSE(x, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - if (x) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + const int xEval = !!(x); \ + if (xEval) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ UTEST_PRINTF(" Expected : false\n"); \ - UTEST_PRINTF(" Actual : %s\n", (x) ? "true" : "false"); \ - *utest_result = 1; \ + UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#define EXPECT_EQ(x, y) UTEST_EXPECT(x, y, ==) -#define EXPECT_NE(x, y) UTEST_EXPECT(x, y, !=) -#define EXPECT_LT(x, y) UTEST_EXPECT(x, y, <) -#define EXPECT_LE(x, y) UTEST_EXPECT(x, y, <=) -#define EXPECT_GT(x, y) UTEST_EXPECT(x, y, >) -#define EXPECT_GE(x, y) UTEST_EXPECT(x, y, >=) +#define EXPECT_FALSE(x) UTEST_FALSE(x, "", 0) +#define EXPECT_FALSE_MSG(x, msg) UTEST_FALSE(x, msg, 0) +#define ASSERT_FALSE(x) UTEST_FALSE(x, "", 1) +#define ASSERT_FALSE_MSG(x, msg) UTEST_FALSE(x, msg, 1) -#define EXPECT_STREQ(x, y) \ +#define UTEST_STREQ(x, y, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - if (0 != strcmp(x, y)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%s\"\n", x); \ - UTEST_PRINTF(" Actual : \"%s\"\n", y); \ - *utest_result = 1; \ + const char *xEval = (x); \ + const char *yEval = (y); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 != strcmp(xEval, yEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \ + UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#define EXPECT_STRNE(x, y) \ - UTEST_SURPRESS_WARNING_BEGIN do { \ - if (0 == strcmp(x, y)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%s\"\n", x); \ - UTEST_PRINTF(" Actual : \"%s\"\n", y); \ - *utest_result = 1; \ - } \ - } \ - while (0) \ - UTEST_SURPRESS_WARNING_END +#define EXPECT_STREQ(x, y) UTEST_STREQ(x, y, "", 0) +#define EXPECT_STREQ_MSG(x, y, msg) UTEST_STREQ(x, y, msg, 0) +#define ASSERT_STREQ(x, y) UTEST_STREQ(x, y, "", 1) +#define ASSERT_STREQ_MSG(x, y, msg) UTEST_STREQ(x, y, msg, 1) -#define EXPECT_STRNEQ(x, y, n) \ +#define UTEST_STRNE(x, y, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - if (0 != UTEST_STRNCMP(x, y, n)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, n), x); \ - UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, n), y); \ - *utest_result = 1; \ + const char *xEval = (x); \ + const char *yEval = (y); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 == strcmp(xEval, yEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \ + UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#define EXPECT_STRNNE(x, y, n) \ - UTEST_SURPRESS_WARNING_BEGIN do { \ - if (0 == UTEST_STRNCMP(x, y, n)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, n), x); \ - UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, n), y); \ - *utest_result = 1; \ - } \ - } \ - while (0) \ - UTEST_SURPRESS_WARNING_END +#define EXPECT_STRNE(x, y) UTEST_STRNE(x, y, "", 0) +#define EXPECT_STRNE_MSG(x, y, msg) UTEST_STRNE(x, y, msg, 0) +#define ASSERT_STRNE(x, y) UTEST_STRNE(x, y, "", 1) +#define ASSERT_STRNE_MSG(x, y, msg) UTEST_STRNE(x, y, msg, 1) -#if defined(__clang__) -#define UTEST_ASSERT(x, y, cond) \ - UTEST_SURPRESS_WARNING_BEGIN do { \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"") \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat-pedantic\"") \ - _Pragma("clang diagnostic ignored \"-Wfloat-equal\"") \ - UTEST_AUTO(x) xEval = (x); \ - UTEST_AUTO(y) yEval = (y); \ - if (!((xEval)cond(yEval))) { \ - _Pragma("clang diagnostic pop") \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : "); \ - utest_type_printer(xEval); \ - UTEST_PRINTF("\n"); \ - UTEST_PRINTF(" Actual : "); \ - utest_type_printer(yEval); \ - UTEST_PRINTF("\n"); \ - *utest_result = 1; \ - return; \ - } \ - } \ - while (0) \ - UTEST_SURPRESS_WARNING_END -#elif defined(__GNUC__) -#define UTEST_ASSERT(x, y, cond) \ +#define UTEST_STRNEQ(x, y, n, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - UTEST_AUTO(x) xEval = (x); \ - UTEST_AUTO(y) yEval = (y); \ - if (!((xEval)cond(yEval))) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : "); \ - utest_type_printer(xEval); \ - UTEST_PRINTF("\n"); \ - UTEST_PRINTF(" Actual : "); \ - utest_type_printer(yEval); \ - UTEST_PRINTF("\n"); \ - *utest_result = 1; \ - return; \ + const char *xEval = (x); \ + const char *yEval = (y); \ + const size_t nEval = UTEST_CAST(size_t, n); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 != UTEST_STRNCMP(xEval, yEval, nEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \ + UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#else -#define UTEST_ASSERT(x, y, cond) \ - UTEST_SURPRESS_WARNING_BEGIN do { \ - if (!((x)cond(y))) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - *utest_result = 1; \ - return; \ - } \ - } \ - while (0) \ - UTEST_SURPRESS_WARNING_END -#endif -#define ASSERT_TRUE(x) \ - UTEST_SURPRESS_WARNING_BEGIN do { \ - if (!(x)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : true\n"); \ - UTEST_PRINTF(" Actual : %s\n", (x) ? "true" : "false"); \ - *utest_result = 1; \ - return; \ - } \ - } \ - while (0) \ - UTEST_SURPRESS_WARNING_END +#define EXPECT_STRNEQ(x, y, n) UTEST_STRNEQ(x, y, n, "", 0) +#define EXPECT_STRNEQ_MSG(x, y, n, msg) UTEST_STRNEQ(x, y, n, msg, 0) +#define ASSERT_STRNEQ(x, y, n) UTEST_STRNEQ(x, y, n, "", 1) +#define ASSERT_STRNEQ_MSG(x, y, n, msg) UTEST_STRNEQ(x, y, n, msg, 1) -#define ASSERT_FALSE(x) \ +#define UTEST_STRNNE(x, y, n, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - if (x) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : false\n"); \ - UTEST_PRINTF(" Actual : %s\n", (x) ? "true" : "false"); \ - *utest_result = 1; \ - return; \ + const char *xEval = (x); \ + const char *yEval = (y); \ + const size_t nEval = UTEST_CAST(size_t, n); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 == UTEST_STRNCMP(xEval, yEval, nEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \ + UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#define ASSERT_EQ(x, y) UTEST_ASSERT(x, y, ==) -#define ASSERT_NE(x, y) UTEST_ASSERT(x, y, !=) -#define ASSERT_LT(x, y) UTEST_ASSERT(x, y, <) -#define ASSERT_LE(x, y) UTEST_ASSERT(x, y, <=) -#define ASSERT_GT(x, y) UTEST_ASSERT(x, y, >) -#define ASSERT_GE(x, y) UTEST_ASSERT(x, y, >=) +#define EXPECT_STRNNE(x, y, n) UTEST_STRNNE(x, y, n, "", 0) +#define EXPECT_STRNNE_MSG(x, y, n, msg) UTEST_STRNNE(x, y, n, msg, 0) +#define ASSERT_STRNNE(x, y, n) UTEST_STRNNE(x, y, n, "", 1) +#define ASSERT_STRNNE_MSG(x, y, n, msg) UTEST_STRNNE(x, y, n, msg, 1) -#define ASSERT_STREQ(x, y) \ +#define UTEST_NEAR(x, y, epsilon, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - if (0 != strcmp(x, y)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%s\"\n", x); \ - UTEST_PRINTF(" Actual : \"%s\"\n", y); \ - *utest_result = 1; \ - return; \ + const double diff = \ + utest_fabs(UTEST_CAST(double, x) - UTEST_CAST(double, y)); \ + if (diff > UTEST_CAST(double, epsilon) || utest_isnan(diff)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %f\n", UTEST_CAST(double, x)); \ + UTEST_PRINTF(" Actual : %f\n", UTEST_CAST(double, y)); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#define ASSERT_STRNE(x, y) \ - UTEST_SURPRESS_WARNING_BEGIN do { \ - if (0 == strcmp(x, y)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%s\"\n", x); \ - UTEST_PRINTF(" Actual : \"%s\"\n", y); \ - *utest_result = 1; \ - return; \ - } \ - } \ - while (0) \ - UTEST_SURPRESS_WARNING_END +#define EXPECT_NEAR(x, y, epsilon) UTEST_NEAR(x, y, epsilon, "", 0) +#define EXPECT_NEAR_MSG(x, y, epsilon, msg) UTEST_NEAR(x, y, epsilon, msg, 0) +#define ASSERT_NEAR(x, y, epsilon) UTEST_NEAR(x, y, epsilon, "", 1) +#define ASSERT_NEAR_MSG(x, y, epsilon, msg) UTEST_NEAR(x, y, epsilon, msg, 1) -#define ASSERT_STRNEQ(x, y, n) \ +#if defined(UTEST_HAS_EXCEPTIONS) +#define UTEST_EXCEPTION(x, exception_type, msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - if (0 != UTEST_STRNCMP(x, y, n)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, n), x); \ - UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, n), y); \ - *utest_result = 1; \ - return; \ + int exception_caught = 0; \ + try { \ + x; \ + } catch (const exception_type &) { \ + exception_caught = 1; \ + } catch (...) { \ + exception_caught = 2; \ + } \ + if (1 != exception_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception\n", #exception_type); \ + UTEST_PRINTF(" Actual : %s\n", (2 == exception_caught) \ + ? "Unexpected exception" \ + : "No exception"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END -#define ASSERT_STRNNE(x, y, n) \ +#define EXPECT_EXCEPTION(x, exception_type) \ + UTEST_EXCEPTION(x, exception_type, "", 0) +#define EXPECT_EXCEPTION_MSG(x, exception_type, msg) \ + UTEST_EXCEPTION(x, exception_type, msg, 0) +#define ASSERT_EXCEPTION(x, exception_type) \ + UTEST_EXCEPTION(x, exception_type, "", 1) +#define ASSERT_EXCEPTION_MSG(x, exception_type, msg) \ + UTEST_EXCEPTION(x, exception_type, msg, 1) + +#define UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, \ + msg, is_assert) \ UTEST_SURPRESS_WARNING_BEGIN do { \ - if (0 == UTEST_STRNCMP(x, y, n)) { \ - UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, n), x); \ - UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, n), y); \ - *utest_result = 1; \ - return; \ + int exception_caught = 0; \ + char *message_caught = UTEST_NULL; \ + try { \ + x; \ + } catch (const exception_type &e) { \ + const char *const what = e.what(); \ + exception_caught = 1; \ + if (0 != \ + UTEST_STRNCMP(what, exception_message, strlen(exception_message))) { \ + const size_t message_size = strlen(what) + 1; \ + message_caught = UTEST_PTR_CAST(char *, malloc(message_size)); \ + UTEST_STRNCPY(message_caught, what, message_size); \ + } \ + } catch (...) { \ + exception_caught = 2; \ + } \ + if (1 != exception_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception\n", #exception_type); \ + UTEST_PRINTF(" Actual : %s\n", (2 == exception_caught) \ + ? "Unexpected exception" \ + : "No exception"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } else if (UTEST_NULL != message_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception with message %s\n", \ + #exception_type, exception_message); \ + UTEST_PRINTF(" Actual message : %s\n", message_caught); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + free(message_caught); \ + if (is_assert) { \ + return; \ + } \ } \ } \ while (0) \ UTEST_SURPRESS_WARNING_END +#define EXPECT_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, "", 0) +#define EXPECT_EXCEPTION_WITH_MESSAGE_MSG(x, exception_type, \ + exception_message, msg) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, msg, 0) +#define ASSERT_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, "", 1) +#define ASSERT_EXCEPTION_WITH_MESSAGE_MSG(x, exception_type, \ + exception_message, msg) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, msg, 1) +#endif + +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#define UTEST_SURPRESS_WARNINGS_BEGIN \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunsafe-buffer-usage\"") +#define UTEST_SURPRESS_WARNINGS_END _Pragma("clang diagnostic pop") +#else +#define UTEST_SURPRESS_WARNINGS_BEGIN +#define UTEST_SURPRESS_WARNINGS_END +#endif +#elif defined(__GNUC__) && __GNUC__ >= 8 && defined(__cplusplus) +#define UTEST_SURPRESS_WARNINGS_BEGIN \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wclass-memaccess\"") +#define UTEST_SURPRESS_WARNINGS_END _Pragma("GCC diagnostic pop") +#else +#define UTEST_SURPRESS_WARNINGS_BEGIN +#define UTEST_SURPRESS_WARNINGS_END +#endif + #define UTEST(SET, NAME) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ UTEST_EXTERN struct utest_state_s utest_state; \ static void utest_run_##SET##_##NAME(int *utest_result); \ static void utest_##SET##_##NAME(int *utest_result, size_t utest_index) { \ @@ -701,19 +1134,24 @@ utest_type_printer(long long unsigned int i) { } \ UTEST_INITIALIZER(utest_register_##SET##_##NAME) { \ const size_t index = utest_state.tests_length++; \ - const char *name_part = #SET "." #NAME; \ + const char name_part[] = #SET "." #NAME; \ const size_t name_size = strlen(name_part) + 1; \ char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ - utest_state.tests = \ - UTEST_PTR_CAST(struct utest_test_state_s *, \ - realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ - sizeof(struct utest_test_state_s) * \ - utest_state.tests_length)); \ - utest_state.tests[index].func = &utest_##SET##_##NAME; \ - utest_state.tests[index].name = name; \ - utest_state.tests[index].index = 0; \ - UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_##SET##_##NAME; \ + utest_state.tests[index].name = name; \ + utest_state.tests[index].index = 0; \ + UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + } else if (name) { \ + free(name); \ + } \ } \ + UTEST_SURPRESS_WARNINGS_END \ void utest_run_##SET##_##NAME(int *utest_result) #define UTEST_F_SETUP(FIXTURE) \ @@ -725,6 +1163,7 @@ utest_type_printer(long long unsigned int i) { struct FIXTURE *utest_fixture) #define UTEST_F(FIXTURE, NAME) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ UTEST_EXTERN struct utest_state_s utest_state; \ static void utest_f_setup_##FIXTURE(int *, struct FIXTURE *); \ static void utest_f_teardown_##FIXTURE(int *, struct FIXTURE *); \ @@ -735,7 +1174,7 @@ utest_type_printer(long long unsigned int i) { (void)utest_index; \ memset(&fixture, 0, sizeof(fixture)); \ utest_f_setup_##FIXTURE(utest_result, &fixture); \ - if (0 != *utest_result) { \ + if (UTEST_TEST_PASSED != *utest_result) { \ return; \ } \ utest_run_##FIXTURE##_##NAME(utest_result, &fixture); \ @@ -743,18 +1182,23 @@ utest_type_printer(long long unsigned int i) { } \ UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME) { \ const size_t index = utest_state.tests_length++; \ - const char *name_part = #FIXTURE "." #NAME; \ + const char name_part[] = #FIXTURE "." #NAME; \ const size_t name_size = strlen(name_part) + 1; \ char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ - utest_state.tests = \ - UTEST_PTR_CAST(struct utest_test_state_s *, \ - realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ - sizeof(struct utest_test_state_s) * \ - utest_state.tests_length)); \ - utest_state.tests[index].func = &utest_f_##FIXTURE##_##NAME; \ - utest_state.tests[index].name = name; \ - UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_f_##FIXTURE##_##NAME; \ + utest_state.tests[index].name = name; \ + UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + } else if (name) { \ + free(name); \ + } \ } \ + UTEST_SURPRESS_WARNINGS_END \ void utest_run_##FIXTURE##_##NAME(int *utest_result, \ struct FIXTURE *utest_fixture) @@ -767,6 +1211,7 @@ utest_type_printer(long long unsigned int i) { int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index) #define UTEST_I(FIXTURE, NAME, INDEX) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ UTEST_EXTERN struct utest_state_s utest_state; \ static void utest_run_##FIXTURE##_##NAME##_##INDEX(int *, struct FIXTURE *); \ static void utest_i_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \ @@ -774,7 +1219,7 @@ utest_type_printer(long long unsigned int i) { struct FIXTURE fixture; \ memset(&fixture, 0, sizeof(fixture)); \ utest_i_setup_##FIXTURE(utest_result, &fixture, index); \ - if (0 != *utest_result) { \ + if (UTEST_TEST_PASSED != *utest_result) { \ return; \ } \ utest_run_##FIXTURE##_##NAME##_##INDEX(utest_result, &fixture); \ @@ -785,24 +1230,71 @@ utest_type_printer(long long unsigned int i) { utest_uint64_t iUp; \ for (i = 0; i < (INDEX); i++) { \ const size_t index = utest_state.tests_length++; \ - const char *name_part = #FIXTURE "." #NAME; \ + const char name_part[] = #FIXTURE "." #NAME; \ const size_t name_size = strlen(name_part) + 32; \ char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ - utest_state.tests = \ - UTEST_PTR_CAST(struct utest_test_state_s *, \ - realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ - sizeof(struct utest_test_state_s) * \ - utest_state.tests_length)); \ - utest_state.tests[index].func = &utest_i_##FIXTURE##_##NAME##_##INDEX; \ - utest_state.tests[index].index = i; \ - utest_state.tests[index].name = name; \ - iUp = UTEST_CAST(utest_uint64_t, i); \ - UTEST_SNPRINTF(name, name_size, "%s/%" UTEST_PRIu64, name_part, iUp); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_i_##FIXTURE##_##NAME##_##INDEX; \ + utest_state.tests[index].index = i; \ + utest_state.tests[index].name = name; \ + iUp = UTEST_CAST(utest_uint64_t, i); \ + UTEST_SNPRINTF(name, name_size, "%s/%" UTEST_PRIu64, name_part, iUp); \ + } else if (name) { \ + free(name); \ + } \ } \ } \ + UTEST_SURPRESS_WARNINGS_END \ void utest_run_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \ struct FIXTURE *utest_fixture) +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +UTEST_WEAK +double utest_fabs(double d); +UTEST_WEAK +double utest_fabs(double d) { + union { + double d; + utest_uint64_t u; + } both; + both.d = d; + both.u &= 0x7fffffffffffffffu; + return both.d; +} + +UTEST_WEAK +int utest_isnan(double d); +UTEST_WEAK +int utest_isnan(double d) { + union { + double d; + utest_uint64_t u; + } both; + both.d = d; + both.u &= 0x7fffffffffffffffu; + return both.u > 0x7ff0000000000000u; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif +#endif + UTEST_WEAK int utest_should_filter_test(const char *filter, const char *testcase); UTEST_WEAK int utest_should_filter_test(const char *filter, @@ -871,47 +1363,39 @@ UTEST_WEAK int utest_should_filter_test(const char *filter, return 0; } -static UTEST_INLINE int utest_strncmp(const char *a, const char *b, size_t n) { - /* strncmp breaks on Wall / Werror on gcc/clang, so we avoid using it */ - unsigned i; - - for (i = 0; i < n; i++) { - if (a[i] < b[i]) { - return -1; - } else if (a[i] > b[i]) { - return 1; - } - } - - return 0; -} - static UTEST_INLINE FILE *utest_fopen(const char *filename, const char *mode) { #ifdef _MSC_VER FILE *file; if (0 == fopen_s(&file, filename, mode)) { return file; } else { - return 0; + return UTEST_NULL; } #else return fopen(filename, mode); #endif } -UTEST_WEAK int utest_main(int argc, const char *const argv[]); -UTEST_WEAK int utest_main(int argc, const char *const argv[]) { +static UTEST_INLINE int utest_main(int argc, const char *const argv[]); +int utest_main(int argc, const char *const argv[]) { utest_uint64_t failed = 0; + utest_uint64_t skipped = 0; size_t index = 0; size_t *failed_testcases = UTEST_NULL; size_t failed_testcases_length = 0; + size_t *skipped_testcases = UTEST_NULL; + size_t skipped_testcases_length = 0; const char *filter = UTEST_NULL; utest_uint64_t ran_tests = 0; + int enable_mixed_units = 0; + int random_order = 0; + utest_uint32_t seed = 0; - enum colours { RESET, GREEN, RED }; + enum colours { RESET, GREEN, RED, YELLOW }; const int use_colours = UTEST_COLOUR_OUTPUT(); - const char *colours[] = {"\033[0m", "\033[32m", "\033[31m"}; + const char *colours[] = {"\033[0m", "\033[32m", "\033[31m", "\033[33m"}; + if (!use_colours) { for (index = 0; index < sizeof colours / sizeof colours[0]; index++) { colours[index] = ""; @@ -925,31 +1409,81 @@ UTEST_WEAK int utest_main(int argc, const char *const argv[]) { /* Test config switches */ const char filter_str[] = "--filter="; const char output_str[] = "--output="; + const char enable_mixed_units_str[] = "--enable-mixed-units"; + const char random_order_str[] = "--random-order"; + const char random_order_with_seed_str[] = "--random-order="; - if (0 == utest_strncmp(argv[index], help_str, strlen(help_str))) { + if (0 == UTEST_STRNCMP(argv[index], help_str, strlen(help_str))) { printf("utest.h - the single file unit testing solution for C/C++!\n" "Command line Options:\n" - " --help Show this message and exit.\n" - " --filter= Filter the test cases to run (EG. MyTest*.a " - "would run MyTestCase.a but not MyTestCase.b).\n" - " --list-tests List testnames, one per line. Output names " - "can be passed to --filter.\n" - " --output= Output an xunit XML file to the file " - "specified in .\n"); + " --help Show this message and exit.\n" + " --filter= Filter the test cases to run (EG. " + "MyTest*.a would run MyTestCase.a but not MyTestCase.b).\n" + " --list-tests List testnames, one per line. Output " + "names can be passed to --filter.\n"); + printf(" --output= Output an xunit XML file to the file " + "specified in .\n" + " --enable-mixed-units Enable the per-test output to contain " + "mixed units (s/ms/us/ns).\n" + " --random-order[=] Randomize the order that the tests are " + "ran in. If the optional argument is not provided, then a " + "random starting seed is used.\n"); goto cleanup; } else if (0 == - utest_strncmp(argv[index], filter_str, strlen(filter_str))) { + UTEST_STRNCMP(argv[index], filter_str, strlen(filter_str))) { /* user wants to filter what test cases run! */ filter = argv[index] + strlen(filter_str); } else if (0 == - utest_strncmp(argv[index], output_str, strlen(output_str))) { + UTEST_STRNCMP(argv[index], output_str, strlen(output_str))) { utest_state.output = utest_fopen(argv[index] + strlen(output_str), "w+"); - } else if (0 == utest_strncmp(argv[index], list_str, strlen(list_str))) { + } else if (0 == UTEST_STRNCMP(argv[index], list_str, strlen(list_str))) { for (index = 0; index < utest_state.tests_length; index++) { UTEST_PRINTF("%s\n", utest_state.tests[index].name); } /* when printing the test list, don't actually run the tests */ return 0; + } else if (0 == UTEST_STRNCMP(argv[index], enable_mixed_units_str, + strlen(enable_mixed_units_str))) { + enable_mixed_units = 1; + } else if (0 == UTEST_STRNCMP(argv[index], random_order_with_seed_str, + strlen(random_order_with_seed_str))) { + seed = + UTEST_CAST(utest_uint32_t, + strtoul(argv[index] + strlen(random_order_with_seed_str), + UTEST_NULL, 10)); + random_order = 1; + } else if (0 == UTEST_STRNCMP(argv[index], random_order_str, + strlen(random_order_str))) { + const utest_int64_t ns = utest_ns(); + + // Some really poor pseudo-random using the current time. I do this + // because I really want to avoid using C's rand() because that'd mean our + // random would be affected by any srand() usage by the user (which I + // don't want). + seed = UTEST_CAST(utest_uint32_t, ns >> 32) * 31 + + UTEST_CAST(utest_uint32_t, ns & 0xffffffff); + random_order = 1; + } + } + + if (random_order) { + // Use Fisher-Yates with the Durstenfield's version to randomly re-order the + // tests. + for (index = utest_state.tests_length; index > 1; index--) { + // For the random order we'll use PCG. + const utest_uint32_t state = seed; + const utest_uint32_t word = + ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + const utest_uint32_t next = + ((word >> 22u) ^ word) % UTEST_CAST(utest_uint32_t, index); + + // Swap the randomly chosen element into the last location. + const struct utest_test_state_s copy = utest_state.tests[index - 1]; + utest_state.tests[index - 1] = utest_state.tests[next]; + utest_state.tests[next] = copy; + + // Move the seed onwards. + seed = seed * 747796405u + 2891336453u; } } @@ -975,7 +1509,7 @@ UTEST_WEAK int utest_main(int argc, const char *const argv[]) { } for (index = 0; index < utest_state.tests_length; index++) { - int result = 0; + int result = UTEST_TEST_PASSED; utest_int64_t ns = 0; if (utest_should_filter_test(filter, utest_state.tests[index].name)) { @@ -992,32 +1526,92 @@ UTEST_WEAK int utest_main(int argc, const char *const argv[]) { ns = utest_ns(); errno = 0; +#if defined(UTEST_HAS_EXCEPTIONS) + UTEST_SURPRESS_WARNING_BEGIN + try { + utest_state.tests[index].func(&result, utest_state.tests[index].index); + } catch (const std::exception &err) { + printf(" Exception : %s\n", err.what()); + result = UTEST_TEST_FAILURE; + } catch (...) { + printf(" Exception : Unknown\n"); + result = UTEST_TEST_FAILURE; + } + UTEST_SURPRESS_WARNING_END +#else utest_state.tests[index].func(&result, utest_state.tests[index].index); +#endif ns = utest_ns() - ns; if (utest_state.output) { fprintf(utest_state.output, "\n"); } - if (0 != result) { + // Record the failing test. + if (UTEST_TEST_FAILURE == result) { const size_t failed_testcase_index = failed_testcases_length++; failed_testcases = UTEST_PTR_CAST( - size_t *, realloc(UTEST_PTR_CAST(void *, failed_testcases), - sizeof(size_t) * failed_testcases_length)); - failed_testcases[failed_testcase_index] = index; + size_t *, utest_realloc(UTEST_PTR_CAST(void *, failed_testcases), + sizeof(size_t) * failed_testcases_length)); + if (UTEST_NULL != failed_testcases) { + failed_testcases[failed_testcase_index] = index; + } failed++; - printf("%s[ FAILED ]%s %s (%" UTEST_PRId64 "ns)\n", colours[RED], - colours[RESET], utest_state.tests[index].name, ns); - } else { - printf("%s[ OK ]%s %s (%" UTEST_PRId64 "ns)\n", colours[GREEN], - colours[RESET], utest_state.tests[index].name, ns); + } else if (UTEST_TEST_SKIPPED == result) { + const size_t skipped_testcase_index = skipped_testcases_length++; + skipped_testcases = UTEST_PTR_CAST( + size_t *, utest_realloc(UTEST_PTR_CAST(void *, skipped_testcases), + sizeof(size_t) * skipped_testcases_length)); + if (UTEST_NULL != skipped_testcases) { + skipped_testcases[skipped_testcase_index] = index; + } + skipped++; + } + + { + const char *const units[] = {"ns", "us", "ms", "s", UTEST_NULL}; + unsigned int unit_index = 0; + utest_int64_t time = ns; + + if (enable_mixed_units) { + for (unit_index = 0; UTEST_NULL != units[unit_index]; unit_index++) { + if (10000 > time) { + break; + } + + time /= 1000; + } + } + + if (UTEST_TEST_FAILURE == result) { + printf("%s[ FAILED ]%s %s (%" UTEST_PRId64 "%s)\n", colours[RED], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } else if (UTEST_TEST_SKIPPED == result) { + printf("%s[ SKIPPED ]%s %s (%" UTEST_PRId64 "%s)\n", colours[YELLOW], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } else { + printf("%s[ OK ]%s %s (%" UTEST_PRId64 "%s)\n", colours[GREEN], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } } } printf("%s[==========]%s %" UTEST_PRIu64 " test cases ran.\n", colours[GREEN], colours[RESET], ran_tests); printf("%s[ PASSED ]%s %" UTEST_PRIu64 " tests.\n", colours[GREEN], - colours[RESET], ran_tests - failed); + colours[RESET], ran_tests - failed - skipped); + + if (0 != skipped) { + printf("%s[ SKIPPED ]%s %" UTEST_PRIu64 " tests, listed below:\n", + colours[YELLOW], colours[RESET], skipped); + for (index = 0; index < skipped_testcases_length; index++) { + printf("%s[ SKIPPED ]%s %s\n", colours[YELLOW], colours[RESET], + utest_state.tests[skipped_testcases[index]].name); + } + } if (0 != failed) { printf("%s[ FAILED ]%s %" UTEST_PRIu64 " tests, listed below:\n", @@ -1037,6 +1631,7 @@ UTEST_WEAK int utest_main(int argc, const char *const argv[]) { free(UTEST_PTR_CAST(void *, utest_state.tests[index].name)); } + free(UTEST_PTR_CAST(void *, skipped_testcases)); free(UTEST_PTR_CAST(void *, failed_testcases)); free(UTEST_PTR_CAST(void *, utest_state.tests)); @@ -1047,6 +1642,12 @@ UTEST_WEAK int utest_main(int argc, const char *const argv[]) { return UTEST_CAST(int, failed); } +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic pop +#endif +#endif + /* we need, in exactly one source file, define the global struct that will hold the data we need to run utest. This macro allows the user to declare the