You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1299 lines
65 KiB
C++
1299 lines
65 KiB
C++
/*
|
|
_______ _ _
|
|
|__ __| /\ | | | |
|
|
| | / \ | | | | Tau - The Micro Testing Framework for C/C++
|
|
| | / /\ \ | | | | Language: C
|
|
| | / ____ \ | |__| | https://github.com/jasmcaus/tau
|
|
|_| /_/ \_\ \____/
|
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>
|
|
SPDX-License-Identifier: MIT
|
|
Copyright (c) 2021 Jason Dsouza <http://github.com/jasmcaus>
|
|
*/
|
|
|
|
#ifndef TAU_H_
|
|
#define TAU_H_
|
|
|
|
#include <tau/types.h>
|
|
#include <tau/misc.h>
|
|
|
|
TAU_DISABLE_DEBUG_WARNINGS
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__)
|
|
#define TAU_UNIX_ 1
|
|
#include <errno.h>
|
|
#include <libgen.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
|
|
#if defined(CLOCK_PROCESS_CPUTIME_ID) && defined(CLOCK_MONOTONIC)
|
|
#define TAU_HAS_POSIX_TIMER_ 1
|
|
#endif // CLOCK_PROCESS_CPUTIME_ID
|
|
#endif // unix
|
|
|
|
#if defined(_gnu_linux_) || defined(__linux__)
|
|
#define TAU_LINUX_ 1
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#endif // _gnu_linux_
|
|
|
|
#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
|
|
#define TAU_WIN_ 1
|
|
#pragma warning(push, 0)
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#pragma warning(pop)
|
|
#endif // _WIN32
|
|
|
|
#ifdef __has_include
|
|
#if __has_include(<valgrind.h>)
|
|
#include <valgrind.h>
|
|
#endif // __has_include(<valgrind.h>)
|
|
#endif // __has_include
|
|
|
|
#ifdef __cplusplus
|
|
#define TAU_C_FUNC extern "C"
|
|
#define TAU_EXTERN extern "C"
|
|
#else
|
|
#define TAU_C_FUNC
|
|
#define TAU_EXTERN extern
|
|
#endif // __cplusplus
|
|
|
|
#if defined(__cplusplus)
|
|
#include <exception>
|
|
#endif //__cplusplus
|
|
|
|
#if defined(__cplusplus)
|
|
#define TAU_ABORT std::abort()
|
|
#else
|
|
#define TAU_ABORT exit(1)
|
|
#endif //__cplusplus
|
|
|
|
// Enable the use of the non-standard keyword __attribute__ to silence warnings under some compilers
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#define TAU_ATTRIBUTE_(attr) __attribute__((attr))
|
|
#else
|
|
#define TAU_ATTRIBUTE_(attr)
|
|
#endif // __GNUC__
|
|
|
|
#ifdef __cplusplus
|
|
// On C++, default to its polymorphism capabilities
|
|
#define TAU_OVERLOADABLE
|
|
#elif defined(__clang__)
|
|
// If we're still in C, use the __attribute__ keyword for Clang
|
|
#define TAU_OVERLOADABLE __attribute__((overloadable))
|
|
#endif // __cplusplus
|
|
|
|
#if defined(_MSC_VER) || defined(__cplusplus)
|
|
#define TAU_WEAK inline
|
|
#define TAU_UNUSED
|
|
#else
|
|
#define TAU_WEAK __attribute__((weak))
|
|
#define TAU_UNUSED __attribute__((unused))
|
|
#endif // _MSC_VER
|
|
|
|
#ifndef TAU_NO_TESTING
|
|
|
|
typedef void (*tau_testsuite_t)();
|
|
typedef struct tauTestSuiteStruct {
|
|
tau_testsuite_t func;
|
|
char* name;
|
|
} tauTestSuiteStruct;
|
|
|
|
typedef struct tauTestStateStruct {
|
|
tauTestSuiteStruct* tests;
|
|
tau_ull numTestSuites;
|
|
FILE* foutput;
|
|
} tauTestStateStruct;
|
|
|
|
static tau_u64 tauStatsTotalTestSuites = 0;
|
|
static tau_u64 tauStatsTestsRan = 0;
|
|
static tau_u64 tauStatsNumTestsFailed = 0;
|
|
static tau_u64 tauStatsSkippedTests = 0;
|
|
static tau_ull* tauStatsFailedTestSuites = TAU_NULL;
|
|
static tau_ull tauStatsNumFailedTestSuites = 0;
|
|
extern tau_u64 tauStatsNumWarnings;
|
|
|
|
// Overridden in `tau_main` if the cmdline option `--no-color` is passed
|
|
static int tauShouldColourizeOutput = 1;
|
|
static int tauDisableSummary = 0;
|
|
static int tauDisplayOnlyFailedOutput = 0;
|
|
static int tauDisplayTests = 0;
|
|
|
|
static const char* tau_argv0_ = TAU_NULL;
|
|
static const char* cmd_filter = TAU_NULL;
|
|
#endif // TAU_NO_TESTING
|
|
|
|
/**
|
|
This helps us determine whether a CHECK or a REQUIRE are being called from within (or outside)
|
|
a Test Suite. Tau supports both - so we need to handle this.
|
|
|
|
We could have somehow determined this from within a function, but this is a cleaner approach
|
|
(which is the Tau's aim).
|
|
|
|
Inside the TEST() initializer, this is set to `true` (because we are inside a Test Suite), so the
|
|
`CHECK`s and `REQUIRE`s will do their thing and return the appropriate result.
|
|
If the assertion macro is not within the `TEST()` scope, it simply does not return anything - it only
|
|
resets it back to false so that this same process occurs for the rest of the checks.
|
|
*/
|
|
extern volatile int checkIsInsideTestSuite;
|
|
extern volatile int hasCurrentTestFailed;
|
|
|
|
#ifndef TAU_NO_TESTING
|
|
extern volatile int shouldFailTest;
|
|
extern volatile int shouldAbortTest;
|
|
|
|
/**
|
|
This function is called from within a macro in the format {CHECK|REQUIRE)_*
|
|
If we are inside a test suite _and_ a check fails, we need to be able to signal to Tau to handle this
|
|
appropriately - fail the current test suite and carry on with the other checks (or move on to the next
|
|
suite in the case of a REQUIRE)
|
|
*/
|
|
static void failIfInsideTestSuite__();
|
|
static void abortIfInsideTestSuite__();
|
|
|
|
static void failIfInsideTestSuite__() {
|
|
if(checkIsInsideTestSuite == 1) {
|
|
hasCurrentTestFailed = 1;
|
|
shouldFailTest = 1;
|
|
}
|
|
}
|
|
|
|
static void abortIfInsideTestSuite__() {
|
|
if(checkIsInsideTestSuite == 1) {
|
|
hasCurrentTestFailed = 1;
|
|
shouldAbortTest = 1;
|
|
}
|
|
}
|
|
|
|
#endif // TAU_NO_TESTING
|
|
|
|
static void incrementWarnings() {
|
|
#ifndef TAU_NO_TESTING
|
|
tauStatsNumWarnings++;
|
|
#endif // TAU_NO_TESTING
|
|
}
|
|
|
|
#ifndef TAU_NO_TESTING
|
|
// extern to the global state tau needs to execute
|
|
TAU_EXTERN tauTestStateStruct tauTestContext;
|
|
|
|
#if defined(_MSC_VER)
|
|
#ifndef TAU_USE_OLD_QPC
|
|
typedef LARGE_INTEGER TAU_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;
|
|
} s;
|
|
struct {
|
|
unsigned long LowPart;
|
|
long HighPart;
|
|
} u;
|
|
Int64 QuadPart;
|
|
} TAU_LARGE_INTEGER;
|
|
|
|
TAU_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceCounter(TAU_LARGE_INTEGER*);
|
|
TAU_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceFrequency(TAU_LARGE_INTEGER*);
|
|
#endif // TAU_USE_OLD_QPC
|
|
|
|
#elif defined(__linux__)
|
|
// 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 C libraries like musl.
|
|
// Instead we include limits.h, which I know all glibc distributions include features.h
|
|
#include <limits.h>
|
|
|
|
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
|
|
#include <time.h>
|
|
|
|
#if((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 17)))
|
|
// glibc is version 2.17 or above, so we can just use clock_gettime
|
|
#define TAU_USE_CLOCKGETTIME
|
|
#else
|
|
#include <sys/syscall.h>
|
|
#include <unistd.h>
|
|
#endif // __GLIBC__
|
|
|
|
#else // Other libc implementations
|
|
#include <time.h>
|
|
#define TAU_USE_CLOCKGETTIME
|
|
#endif // __GLIBC__
|
|
|
|
#elif defined(__APPLE__)
|
|
#include <mach/mach_time.h>
|
|
#endif // _MSC_VER
|
|
|
|
/**
|
|
Tau Timer
|
|
This method is useful in timing the execution of an Tau Test Suite
|
|
To use this, simply call this function before and after the particular code block you want to time,
|
|
and their difference will give you the time (in seconds).
|
|
NOTE: This method has been edited to return the time (in nanoseconds). Depending on how large this value
|
|
(e.g: 54890938849ns), we appropriately convert it to milliseconds/seconds before displaying it to stdout.
|
|
*/
|
|
static inline double tauClock() {
|
|
#ifdef TAU_WIN_
|
|
LARGE_INTEGER counter;
|
|
LARGE_INTEGER frequency;
|
|
QueryPerformanceCounter(&counter);
|
|
QueryPerformanceFrequency(&frequency);
|
|
return TAU_CAST(double, (counter.QuadPart * 1000 * 1000 * 1000) / frequency.QuadPart); // in nanoseconds
|
|
|
|
#elif defined(__linux) && defined(__STRICT_ANSI__)
|
|
return TAU_CAST(double, clock()) * 1000000000 / CLOCKS_PER_SEC; // in nanoseconds
|
|
|
|
#elif defined(__linux)
|
|
struct timespec ts;
|
|
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
|
|
timespec_get(&ts, TIME_UTC);
|
|
#else
|
|
const clockid_t cid = CLOCK_REALTIME;
|
|
#if defined(TAU_USE_CLOCKGETTIME)
|
|
clock_gettime(cid, &ts);
|
|
#else
|
|
syscall(SYS_clock_gettime, cid, &ts);
|
|
#endif
|
|
#endif
|
|
return TAU_CAST(double, ts.tv_sec) * 1000 * 1000 * 1000 + TAU_CAST(double, ts.tv_nsec); // in nanoseconds
|
|
|
|
#elif __APPLE__
|
|
return TAU_CAST(double, mach_absolute_time());
|
|
#endif // TAU_WIN_
|
|
}
|
|
|
|
static void tauClockPrintDuration(const double nanoseconds_duration) {
|
|
tau_u64 n;
|
|
int num_digits = 0;
|
|
n = TAU_CAST(tau_u64, nanoseconds_duration);
|
|
while(n!=0) {
|
|
n/=10;
|
|
++num_digits;
|
|
}
|
|
|
|
// Stick with nanoseconds (no need for decimal points here)
|
|
switch(num_digits) {
|
|
case 1: case 2:
|
|
printf("%.0lfns", nanoseconds_duration); break;
|
|
case 3: case 4: case 5:
|
|
printf("%.2lfus", nanoseconds_duration/1000); break;
|
|
case 6: case 7: case 8:
|
|
printf("%.2lfms", nanoseconds_duration/1000000); break;
|
|
default:
|
|
printf("%.2lfs", nanoseconds_duration/1000000000); break;
|
|
}
|
|
}
|
|
|
|
// TAU_TEST_INITIALIZER
|
|
#if defined(_MSC_VER)
|
|
#if defined(_WIN64)
|
|
#define TAU_SYMBOL_PREFIX
|
|
#else
|
|
#define TAU_SYMBOL_PREFIX "_"
|
|
#endif // _WIN64
|
|
|
|
#pragma section(".CRT$XCU", read)
|
|
#define TAU_TEST_INITIALIZER(f) \
|
|
static void __cdecl f(void); \
|
|
__pragma(comment(linker, "/include:" TAU_SYMBOL_PREFIX #f "_")) \
|
|
TAU_C_FUNC __declspec(allocate(".CRT$XCU")) void(__cdecl * f##_)(void) = f; \
|
|
static void __cdecl f(void)
|
|
#else
|
|
#define TAU_TEST_INITIALIZER(f) \
|
|
static void f(void) __attribute__((constructor)); \
|
|
static void f(void)
|
|
#endif // _MSC_VER
|
|
|
|
|
|
static inline void* tau_realloc(void* const ptr, const tau_ull new_size) {
|
|
void* const new_ptr = realloc(ptr, new_size);
|
|
|
|
if(TAU_NONE(new_ptr))
|
|
free(new_ptr);
|
|
|
|
return new_ptr;
|
|
}
|
|
#endif // TAU_NO_TESTING
|
|
|
|
#define TAU_COLOUR_DEFAULT_ 0
|
|
#define TAU_COLOUR_RED_ 1
|
|
#define TAU_COLOUR_GREEN_ 2
|
|
#define TAU_COLOUR_YELLOW_ 4
|
|
#define TAU_COLOUR_BLUE_ 5
|
|
#define TAU_COLOUR_CYAN_ 6
|
|
#define TAU_COLOUR_BRIGHTRED_ 7
|
|
#define TAU_COLOUR_BRIGHTGREEN_ 8
|
|
#define TAU_COLOUR_BRIGHTYELLOW_ 9
|
|
#define TAU_COLOUR_BRIGHTBLUE_ 10
|
|
#define TAU_COLOUR_BRIGHTCYAN_ 11
|
|
#define TAU_COLOUR_BOLD_ 12
|
|
|
|
static inline int TAU_ATTRIBUTE_(format (printf, 2, 3))
|
|
tauColouredPrintf(const int colour, const char* const fmt, ...);
|
|
static inline int TAU_ATTRIBUTE_(format (printf, 2, 3))
|
|
tauColouredPrintf(const int colour, const char* const fmt, ...) {
|
|
va_list args;
|
|
char buffer[256];
|
|
int n;
|
|
|
|
va_start(args, fmt);
|
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
|
va_end(args);
|
|
buffer[sizeof(buffer)-1] = '\0';
|
|
|
|
#ifndef TAU_NO_TESTING
|
|
if(!tauShouldColourizeOutput) {
|
|
return printf("%s", buffer);
|
|
}
|
|
#endif // TAU_NO_TESTING
|
|
|
|
#ifdef TAU_UNIX_
|
|
{
|
|
const char* str;
|
|
switch(colour) {
|
|
case TAU_COLOUR_RED_: str = "\033[0;31m"; break;
|
|
case TAU_COLOUR_GREEN_: str = "\033[0;32m"; break;
|
|
case TAU_COLOUR_YELLOW_: str = "\033[0;33m"; break;
|
|
case TAU_COLOUR_BLUE_: str = "\033[0;34m"; break;
|
|
case TAU_COLOUR_CYAN_: str = "\033[0;36m"; break;
|
|
case TAU_COLOUR_BRIGHTRED_: str = "\033[1;31m"; break;
|
|
case TAU_COLOUR_BRIGHTGREEN_: str = "\033[1;32m"; break;
|
|
case TAU_COLOUR_BRIGHTYELLOW_: str = "\033[1;33m"; break;
|
|
case TAU_COLOUR_BRIGHTBLUE_: str = "\033[1;34m"; break;
|
|
case TAU_COLOUR_BRIGHTCYAN_: str = "\033[1;36m"; break;
|
|
case TAU_COLOUR_BOLD_: str = "\033[1m"; break;
|
|
default: str = "\033[0m"; break;
|
|
}
|
|
printf("%s", str);
|
|
n = printf("%s", buffer);
|
|
printf("\033[0m"); // Reset the colour
|
|
return n;
|
|
}
|
|
#elif defined(TAU_WIN_)
|
|
{
|
|
HANDLE h;
|
|
CONSOLE_SCREEN_BUFFER_INFO info;
|
|
WORD attr;
|
|
|
|
h = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
GetConsoleScreenBufferInfo(h, &info);
|
|
|
|
switch(colour) {
|
|
case TAU_COLOUR_RED_: attr = FOREGROUND_RED; break;
|
|
case TAU_COLOUR_GREEN_: attr = FOREGROUND_GREEN; break;
|
|
case TAU_COLOUR_BLUE_: attr = FOREGROUND_BLUE; break;
|
|
case TAU_COLOUR_CYAN_: attr = FOREGROUND_BLUE | FOREGROUND_GREEN; break;
|
|
case TAU_COLOUR_YELLOW_: attr = FOREGROUND_RED | FOREGROUND_GREEN; break;
|
|
case TAU_COLOUR_BRIGHTRED_: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
|
|
case TAU_COLOUR_BRIGHTGREEN_: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
|
|
case TAU_COLOUR_BRIGHTCYAN_: attr = FOREGROUND_BLUE | FOREGROUND_GREEN |
|
|
FOREGROUND_INTENSITY; break;
|
|
case TAU_COLOUR_BRIGHTYELLOW_: attr = FOREGROUND_RED | FOREGROUND_GREEN |
|
|
FOREGROUND_INTENSITY; break;
|
|
case TAU_COLOUR_BOLD_: attr = FOREGROUND_BLUE | FOREGROUND_GREEN |
|
|
FOREGROUND_RED | FOREGROUND_INTENSITY; break;
|
|
default: attr = 0; break;
|
|
}
|
|
if(attr != 0)
|
|
SetConsoleTextAttribute(h, attr);
|
|
n = printf("%s", buffer);
|
|
SetConsoleTextAttribute(h, info.wAttributes);
|
|
return n;
|
|
}
|
|
#else
|
|
n = printf("%s", buffer);
|
|
return n;
|
|
#endif // TAU_UNIX_
|
|
}
|
|
|
|
#ifndef TAU_NO_TESTING
|
|
#define tauPrintf(...) { \
|
|
if(tauTestContext.foutput) \
|
|
fprintf(tauTestContext.foutput, __VA_ARGS__); \
|
|
printf(__VA_ARGS__); \
|
|
}
|
|
#else
|
|
#define tauPrintf(...) \
|
|
printf(__VA_ARGS__)
|
|
#endif // TAU_NO_TESTING
|
|
|
|
|
|
static inline int tauIsDigit(const char c) { return c >= '0' && c <= '9'; }
|
|
// If the macro arguments can be decomposed further, we need to print the `In macro ..., so and so failed`.
|
|
// This method signals whether this message should be printed.
|
|
//
|
|
// Note: the arguments are of type `char const*` as opposed to `char*`.
|
|
// This helps mitigate the ``warning: ISO C++ forbids converting a string constant to 'char*'`.
|
|
// See: https://stackoverflow.com/questions/20944784/why-is-conversion-from-string-constant-to-char-valid-in-c-but-invalid-in-c/20944858
|
|
static inline int tauShouldDecomposeMacro(const char* const actual, const char* const expected, const int isStringCmp) {
|
|
// Signal that the macro can be further decomposed if either of the following symbols are present
|
|
int dots = 0;
|
|
int numActualDigits = 0;
|
|
int numExpectedDigits = 0;
|
|
|
|
// If not inside a string comparison, we will return `1` only if we determine that `actual` is a variable
|
|
// name/expression (i.e for a value, we search through each character verifying that each is a digit
|
|
// - for floats, we allow a maximum of 1 '.' char)
|
|
if(!isStringCmp) {
|
|
for(int i = 0; i < strlen(actual); i++) {
|
|
if(tauIsDigit(actual[i])) {
|
|
numActualDigits++;
|
|
} else if(actual[i] == '.') {
|
|
dots++;
|
|
if(dots > 1) { return 1; }
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
// Do the same for `expected`
|
|
dots = 0;
|
|
for(int i=0; i < strlen(expected); i++) {
|
|
if(tauIsDigit(expected[i])) {
|
|
numExpectedDigits++;
|
|
} else if(expected[i] == '.') {
|
|
dots++;
|
|
if(dots > 1) { return 1; }
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
// Inside a string comparison, we search for common expression tokens like the following:
|
|
// '(', ')', '-'
|
|
else {
|
|
if(strchr(actual, '(') != NULL || strchr(expected, '(') != NULL ||
|
|
actual[0] != '"' || expected[0] != '"' ) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef TAU_OVERLOADABLE
|
|
#ifndef TAU_CAN_USE_OVERLOADABLES
|
|
#define TAU_CAN_USE_OVERLOADABLES
|
|
#endif // TAU_CAN_USE_OVERLOADABLES
|
|
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const float f);
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const double d);
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long double d);
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const int i);
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const unsigned int i);
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long int i);
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long unsigned int i);
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const void* const p);
|
|
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const float f) { tauPrintf("%f", TAU_CAST(double, f)); }
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const double d) { tauPrintf("%f", d); }
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long double d) { tauPrintf("%Lf", d); }
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const int i) { tauPrintf("%d", i); }
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const unsigned int i) { tauPrintf("%u", i); }
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long int i) { tauPrintf("%ld", i); }
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long unsigned int i) { tauPrintf("%lu", i); }
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const void* const p) { tauPrintf("%p", p); }
|
|
|
|
// long long is in C++ only
|
|
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) || defined(__cplusplus) && (__cplusplus >= 201103L)
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long long int i);
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long long unsigned int i);
|
|
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long long int i) { tauPrintf("%lld", i); }
|
|
TAU_WEAK TAU_OVERLOADABLE void TAU_OVERLOAD_PRINTER(const long long unsigned int i) { tauPrintf("%llu", i); }
|
|
#endif // __STDC_VERSION__
|
|
|
|
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
|
|
#ifndef TAU_CAN_USE_OVERLOADABLES
|
|
#define TAU_CAN_USE_OVERLOADABLES
|
|
#endif // TAU_CAN_USE_OVERLOADABLES
|
|
|
|
#define TAU_OVERLOAD_PRINTER(val) \
|
|
tauPrintf( \
|
|
_Generic((val), \
|
|
char : "'%c'", \
|
|
char* : "%s", \
|
|
unsigned char : "%hhu", \
|
|
short : "%hd", \
|
|
unsigned short : "%hu", \
|
|
int : "%d", \
|
|
unsigned int : "%u", \
|
|
long : "%ld", \
|
|
long long : "%lld", \
|
|
unsigned long : "%lu", \
|
|
unsigned long long : "%"TAU_PRIu64, \
|
|
float : "%f", \
|
|
double : "%f", \
|
|
long double : "%Lf", \
|
|
void* : "%p"), \
|
|
(val))
|
|
|
|
#else
|
|
// If we're here, this means that the Compiler does not support overloadable methods
|
|
#define TAU_OVERLOAD_PRINTER(...) \
|
|
tauPrintf("Error: Your compiler does not support overloadable methods."); \
|
|
tauPrintf("If you think this was an error, please file an issue on Tau's Github repo.")
|
|
#endif // TAU_OVERLOADABLE
|
|
|
|
#ifndef TAU_NO_TESTING
|
|
#define TAU_FAIL_IF_INSIDE_TESTSUITE failIfInsideTestSuite__()
|
|
#define TAU_ABORT_IF_INSIDE_TESTSUITE abortIfInsideTestSuite__()
|
|
#else
|
|
#define TAU_FAIL_IF_INSIDE_TESTSUITE TAU_ABORT
|
|
#define TAU_ABORT_IF_INSIDE_TESTSUITE TAU_ABORT
|
|
#endif // TAU_NO_TESTING
|
|
|
|
// ifCondFailsThenPrint is the string representation of the opposite of the truthy value of `cond`
|
|
// For example, if `cond` is "!=", then `ifCondFailsThenPrint` will be `==`
|
|
#if defined(TAU_CAN_USE_OVERLOADABLES)
|
|
#define __TAUCMP__(actual, expected, cond, space, macroName, failOrAbort) \
|
|
do { \
|
|
if(!((actual)cond(expected))) { \
|
|
tauPrintf("%s:%u: ", __FILE__, __LINE__); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "FAILED\n"); \
|
|
if(tauShouldDecomposeMacro(#actual, #expected, 0)) { \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, " In macro : "); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, "%s( %s, %s )\n", \
|
|
#macroName, \
|
|
#actual, #expected); \
|
|
} \
|
|
tauPrintf(" Expected : %s", #actual); \
|
|
printf(" %s ", #cond space); \
|
|
TAU_OVERLOAD_PRINTER(expected); \
|
|
tauPrintf("\n"); \
|
|
\
|
|
tauPrintf(" Actual : %s", #actual); \
|
|
printf(" == "); \
|
|
TAU_OVERLOAD_PRINTER(actual); \
|
|
tauPrintf("\n"); \
|
|
failOrAbort; \
|
|
if(shouldAbortTest) { \
|
|
return; \
|
|
} \
|
|
} \
|
|
} \
|
|
while(0)
|
|
|
|
// TAU_OVERLOAD_PRINTER does not work on some compilers
|
|
#else
|
|
#define __TAUCMP__(actual, expected, cond, space, macroName, failOrAbort) \
|
|
do { \
|
|
if(!((actual)cond(expected))) { \
|
|
tauPrintf("%s:%u: ", __FILE__, __LINE__); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "FAILED\n"); \
|
|
if(tauShouldDecomposeMacro(#actual, #expected, 0)) { \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, " In macro : "); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, "%s( %s, %s )\n", \
|
|
#macroName, \
|
|
#actual, #expected); \
|
|
} \
|
|
tauPrintf(" Expected : %s", #actual); \
|
|
printf(" %s ", #cond space); \
|
|
printf(#expected); \
|
|
tauPrintf("\n"); \
|
|
\
|
|
tauPrintf(" Actual : %s", #actual); \
|
|
printf(" == "); \
|
|
printf(#actual); \
|
|
tauPrintf("\n"); \
|
|
failOrAbort; \
|
|
if(shouldAbortTest) { \
|
|
return; \
|
|
} \
|
|
} \
|
|
} \
|
|
while(0)
|
|
#endif // TAU_CAN_USE_OVERLOADABLES
|
|
|
|
#define __TAUCMP_STR__(actual, expected, cond, ifCondFailsThenPrint, actualPrint, macroName, failOrAbort) \
|
|
do { \
|
|
if(strcmp(actual, expected) cond 0) { \
|
|
tauPrintf("%s:%u: ", __FILE__, __LINE__); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "FAILED\n"); \
|
|
if(tauShouldDecomposeMacro(#actual, #expected, 1)) { \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, " In macro : "); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, "%s( %s, %s )\n", \
|
|
#macroName, \
|
|
#actual, #expected); \
|
|
} \
|
|
tauPrintf(" Expected : \"%s\" %s \"%s\"\n", actual, #ifCondFailsThenPrint, expected); \
|
|
tauPrintf(" Actual : %s\n", #actualPrint); \
|
|
failOrAbort; \
|
|
if(shouldAbortTest) { \
|
|
return; \
|
|
} \
|
|
} \
|
|
} \
|
|
while(0)
|
|
|
|
|
|
static void tauPrintColouredIfDifferent(const tau_u8 ch, const tau_u8 ref) {
|
|
if(ch == ref) {
|
|
tauPrintf("%02X", ch);
|
|
} else {
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTYELLOW_, "%02X", ch);
|
|
}
|
|
}
|
|
|
|
|
|
static void tauPrintHexBufCmp(const void* const buff, const void* const ref, const int size) {
|
|
const tau_u8* const test_buff = TAU_CAST(const tau_u8* const, buff);
|
|
const tau_u8* const ref_buff = TAU_CAST(const tau_u8* const, ref);
|
|
|
|
tauColouredPrintf(TAU_COLOUR_CYAN_,"<");
|
|
if(size != 0)
|
|
tauPrintColouredIfDifferent(test_buff[0], ref_buff[0]);
|
|
|
|
for(int i = 1; i < size; ++i) {
|
|
printf(" ");
|
|
tauPrintColouredIfDifferent(test_buff[i], ref_buff[i]);
|
|
}
|
|
tauColouredPrintf(TAU_COLOUR_CYAN_,">");
|
|
}
|
|
|
|
|
|
#define __TAUCMP_BUF__(actual, expected, len, cond, ifCondFailsThenPrint, actualPrint, macroName, failOrAbort) \
|
|
do { \
|
|
if(memcmp(actual, expected, len) cond 0) { \
|
|
tauPrintf("%s:%u: ", __FILE__, __LINE__); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "FAILED\n"); \
|
|
if(tauShouldDecomposeMacro(#actual, #expected, 1)) { \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, " In macro : "); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, "%s( %s, %s, %s )\n", \
|
|
#macroName, \
|
|
#actual, #expected, #len); \
|
|
} \
|
|
tauPrintf(" Expected : "); tauPrintHexBufCmp(actual, expected, len); \
|
|
tauPrintf(" %s ", #ifCondFailsThenPrint); \
|
|
tauPrintHexBufCmp(expected, actual, len); \
|
|
tauPrintf("\n Actual : %s\n", #actualPrint); \
|
|
failOrAbort; \
|
|
if(shouldAbortTest) { \
|
|
return; \
|
|
} \
|
|
} \
|
|
} \
|
|
while(0)
|
|
|
|
#define __TAUCMP_STRN__(actual, expected, n, cond, ifCondFailsThenPrint, actualPrint, macroName, failOrAbort) \
|
|
do { \
|
|
if(TAU_CAST(int, n) < 0) { \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "`n` cannot be negative\n"); \
|
|
TAU_ABORT; \
|
|
} \
|
|
if(strncmp(actual, expected, n) cond 0) { \
|
|
tauPrintf("%s:%u: ", __FILE__, __LINE__); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "FAILED\n"); \
|
|
if(tauShouldDecomposeMacro(#actual, #expected, 1)) { \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, " In macro : "); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, "%s( %s, %s, %s)\n", \
|
|
#macroName, \
|
|
#actual, #expected, #n); \
|
|
} \
|
|
tauPrintf(" Expected : \"%.*s\" %s \"%.*s\"\n", TAU_CAST(int, n), actual, \
|
|
#ifCondFailsThenPrint, \
|
|
TAU_CAST(int, n), expected); \
|
|
tauPrintf(" Actual : %s\n", #actualPrint); \
|
|
failOrAbort; \
|
|
if(shouldAbortTest) { \
|
|
return; \
|
|
} \
|
|
} \
|
|
} \
|
|
while(0)
|
|
|
|
|
|
#define __TAUCMP_TF(cond, actual, expected, negateSign, macroName, failOrAbort) \
|
|
do { \
|
|
if(negateSign(cond)) { \
|
|
tauPrintf("%s:%u: ", __FILE__, __LINE__); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "FAILED\n"); \
|
|
if(tauShouldDecomposeMacro(#actual, TAU_NULL, 0)) { \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, " In macro : "); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, "%s( %s )\n", \
|
|
#macroName, \
|
|
#cond); \
|
|
} \
|
|
tauPrintf(" Expected : %s\n", #expected); \
|
|
tauPrintf(" Actual : %s\n", #actual); \
|
|
failOrAbort; \
|
|
if(shouldAbortTest) { \
|
|
return; \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
/**
|
|
############################################
|
|
{CHECK|REQUIRE} Macros
|
|
############################################
|
|
*/
|
|
#define CHECK_EQ(actual, expected) __TAUCMP__(actual, expected, ==, "", CHECK_EQ, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_NE(actual, expected) __TAUCMP__(actual, expected, !=, "", CHECK_NE, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_LT(actual, expected) __TAUCMP__(actual, expected, < , " ", CHECK_LT, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_LE(actual, expected) __TAUCMP__(actual, expected, <=, "", CHECK_LE, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_GT(actual, expected) __TAUCMP__(actual, expected, > , " ", CHECK_GT, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_GE(actual, expected) __TAUCMP__(actual, expected, >=, "", CHECK_GE, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
|
|
#define REQUIRE_EQ(actual, expected) __TAUCMP__(actual, expected, ==, "", REQUIRE_EQ, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_NE(actual, expected) __TAUCMP__(actual, expected, !=, "", REQUIRE_NE, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_LT(actual, expected) __TAUCMP__(actual, expected, < , " ", REQUIRE_LT, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_LE(actual, expected) __TAUCMP__(actual, expected, <=, "", REQUIRE_LE, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_GT(actual, expected) __TAUCMP__(actual, expected, > , " ", REQUIRE_GT, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_GE(actual, expected) __TAUCMP__(actual, expected, >=, "", REQUIRE_GE, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
|
|
// Whole-string checks
|
|
#define CHECK_STREQ(actual, expected) __TAUCMP_STR__(actual, expected, !=, ==, not equal, CHECK_STREQ, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_STRNE(actual, expected) __TAUCMP_STR__(actual, expected, ==, !=, equal, CHECK_STRNE, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_STREQ(actual, expected) __TAUCMP_STR__(actual, expected, !=, ==, not equal, REQUIRE_STREQ, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_STRNE(actual, expected) __TAUCMP_STR__(actual, expected, ==, !=, equal, REQUIRE_STRNE, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
|
|
// Substring Checks
|
|
#define CHECK_SUBSTREQ(actual, expected, n) __TAUCMP_STRN__(actual, expected, n, !=, ==, unequal substrings, CHECK_SUBSTREQ, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_SUBSTRNE(actual, expected, n) __TAUCMP_STRN__(actual, expected, n, ==, !=, equal substrings, CHECK_SUBSTRNE, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_SUBSTREQ(actual, expected, n) __TAUCMP_STRN__(actual, expected, n, !=, ==, unequal substrings, REQUIRE_SUBSTREQ, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_SUBSTRNE(actual, expected, n) __TAUCMP_STRN__(actual, expected, n, ==, !=, equal substrings, REQUIRE_SUBSTRNE, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
|
|
// Buffer Checks
|
|
#define CHECK_BUF_EQ(actual, expected, n) __TAUCMP_BUF__(actual, expected, n, !=, ==, not equal, CHECK_BUF_EQ, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_BUF_NE(actual, expected, n) __TAUCMP_BUF__(actual, expected, n, ==, !=, equal, CHECK_BUF_NE, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_BUF_EQ(actual, expected, n) __TAUCMP_BUF__(actual, expected, n, !=, ==, not equal, REQUIRE_BUF_EQ, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_BUF_NE(actual, expected, n) __TAUCMP_BUF__(actual, expected, n, ==, !=, equal, REQUIRE_BUF_NE, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
|
|
// Note: The negate sign `!` must be there for {CHECK|REQUIRE}_TRUE
|
|
// Do not remove it
|
|
#define CHECK_TRUE(cond) __TAUCMP_TF(cond, false, true, !, CHECK_TRUE, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
#define CHECK_FALSE(cond) __TAUCMP_TF(cond, true, false, , CHECK_FALSE, TAU_FAIL_IF_INSIDE_TESTSUITE)
|
|
|
|
#define REQUIRE_TRUE(cond) __TAUCMP_TF(cond, false, true, !, REQUIRE_TRUE, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
#define REQUIRE_FALSE(cond) __TAUCMP_TF(cond, true, false, , REQUIRE_FALSE, TAU_ABORT_IF_INSIDE_TESTSUITE)
|
|
|
|
#define __TAUCHECKREQUIRE__(cond, failOrAbort, macroName, ...) \
|
|
do { \
|
|
if(!(cond)) { \
|
|
tauPrintf("%s:%u: ", __FILE__, __LINE__); \
|
|
if((sizeof(char[]){__VA_ARGS__}) <= 1) \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "FAILED"); \
|
|
else \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, __VA_ARGS__); \
|
|
printf("\n"); \
|
|
printf("The following assertion failed: \n"); \
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTCYAN_, " %s( %s )\n", #macroName, #cond); \
|
|
failOrAbort; \
|
|
if(shouldAbortTest) { \
|
|
return; \
|
|
} \
|
|
} \
|
|
} \
|
|
while(0)
|
|
|
|
// This is a little hack that allows a form of "polymorphism" to a macro - it allows a user to optionally pass
|
|
// an extra argument to a macro in {CHECK|REQUIRE}.
|
|
// The first argument is always the condition to check/assert. If this condition fails, by default a `FAILED`
|
|
// message is sent to STDOUT. If a second argument is passed to the macro (a string), this will be outputted
|
|
// instead.
|
|
//
|
|
// The MACRO_CHOOSER uses the list of arguments twice, first to form the name of the helper macro, and then to
|
|
// pass the arguments to that helper macro. It uses a standard trick to count the number of arguments to a macro.
|
|
//
|
|
// Neat hack from:
|
|
// https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros, and
|
|
// https://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments
|
|
#define GET_3RD_ARG(arg1, arg2, arg3, ...) arg3
|
|
|
|
#define CHECK_1_ARGS(cond) __TAUCHECKREQUIRE__(cond, TAU_FAIL_IF_INSIDE_TESTSUITE, CHECK, "FAILED")
|
|
#define CHECK_2_ARGS(cond, message) __TAUCHECKREQUIRE__(cond, TAU_FAIL_IF_INSIDE_TESTSUITE, CHECK, message)
|
|
#define CHECK_MACRO_CHOOSER(...) GET_3RD_ARG(__VA_ARGS__, CHECK_2_ARGS, CHECK_1_ARGS, )
|
|
|
|
#define REQUIRE_1_ARGS(cond) __TAUCHECKREQUIRE__(cond, TAU_ABORT_IF_INSIDE_TESTSUITE, REQUIRE, "FAILED")
|
|
#define REQUIRE_2_ARGS(cond, message) __TAUCHECKREQUIRE__(cond, TAU_ABORT_IF_INSIDE_TESTSUITE, REQUIRE, message)
|
|
#define REQUIRE_MACRO_CHOOSER(...) GET_3RD_ARG(__VA_ARGS__, REQUIRE_2_ARGS, REQUIRE_1_ARGS, )
|
|
|
|
#define CHECK(...) CHECK_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
|
|
#define REQUIRE(...) REQUIRE_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
|
|
|
|
#define CHECK_NULL(val) CHECK(val == TAU_NULL)
|
|
#define CHECK_NOT_NULL(val) CHECK(val != TAU_NULL)
|
|
|
|
#define WARN(msg) \
|
|
incrementWarnings(); \
|
|
tauColouredPrintf(TAU_COLOUR_YELLOW_, "%s:%u:\nWARNING: %s\n", __FILE__, __LINE__, #msg)
|
|
|
|
#ifdef __cplusplus
|
|
#define SECTION(...) \
|
|
if(1)
|
|
#define STATIC_REQUIRE(...) \
|
|
static_assert(__VA_ARGS__, #__VA_ARGS__)
|
|
#define STATIC_REQUIRE_FALSE(...) \
|
|
static_assert(!(__VA_ARGS__), "!(" #__VA_ARGS__ ")")
|
|
#else
|
|
#define SECTION(...) \
|
|
WARN(Using "SECTION" in C results in limited diagnostics.); \
|
|
if(1)
|
|
#define STATIC_REQUIRE(...) \
|
|
WARN(Usage of "STATIC_REQUIRE" is not supported in C source files.)
|
|
#define STATIC_REQUIRE_FALSE(...) \
|
|
WARN(Usage of "STATIC_REQUIRE_FALSE" is not supported in C source files.)
|
|
#endif // __cplusplus
|
|
|
|
/**
|
|
#########################################
|
|
Implementation
|
|
#########################################
|
|
*/
|
|
|
|
#ifndef TAU_NO_TESTING
|
|
#ifdef _MSC_VER
|
|
#define TAU_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__)
|
|
#else
|
|
#define TAU_SNPRINTF(...) snprintf(__VA_ARGS__)
|
|
#endif // _MSC_VER
|
|
|
|
#define TEST(TESTSUITE, TESTNAME) \
|
|
TAU_EXTERN tauTestStateStruct tauTestContext; \
|
|
static void _TAU_TEST_FUNC_##TESTSUITE##_##TESTNAME(void); \
|
|
TAU_TEST_INITIALIZER(tau_register_##TESTSUITE##_##TESTNAME) { \
|
|
const tau_ull index = tauTestContext.numTestSuites++; \
|
|
const char* const namePart = #TESTSUITE "." #TESTNAME; \
|
|
const tau_ull nameSize = strlen(namePart) + 1; \
|
|
char* const name = TAU_PTRCAST(char* , malloc(nameSize)); \
|
|
tauTestContext.tests = TAU_PTRCAST( \
|
|
tauTestSuiteStruct*, \
|
|
tau_realloc(TAU_PTRCAST(void* , tauTestContext.tests), \
|
|
sizeof(tauTestSuiteStruct) * \
|
|
tauTestContext.numTestSuites)); \
|
|
tauTestContext.tests[index].func = &_TAU_TEST_FUNC_##TESTSUITE##_##TESTNAME; \
|
|
tauTestContext.tests[index].name = name; \
|
|
TAU_SNPRINTF(name, nameSize, "%s", namePart); \
|
|
} \
|
|
void _TAU_TEST_FUNC_##TESTSUITE##_##TESTNAME(void)
|
|
|
|
|
|
#define TEST_F_SETUP(FIXTURE) \
|
|
static void __TAU_TEST_FIXTURE_SETUP_##FIXTURE(struct FIXTURE* const tau)
|
|
|
|
#define TEST_F_TEARDOWN(FIXTURE) \
|
|
static void __TAU_TEST_FIXTURE_TEARDOWN_##FIXTURE(struct FIXTURE* const tau)
|
|
|
|
#define TEST_F(FIXTURE, NAME) \
|
|
TAU_EXTERN tauTestStateStruct tauTestContext; \
|
|
static void __TAU_TEST_FIXTURE_SETUP_##FIXTURE(struct FIXTURE* const); \
|
|
static void __TAU_TEST_FIXTURE_TEARDOWN_##FIXTURE(struct FIXTURE* const); \
|
|
static void __TAU_TEST_FIXTURE_RUN_##FIXTURE##_##NAME(struct FIXTURE* const); \
|
|
\
|
|
static void __TAU_TEST_FIXTURE_##FIXTURE##_##NAME() { \
|
|
struct FIXTURE fixture; \
|
|
memset(&fixture, 0, sizeof(fixture)); \
|
|
__TAU_TEST_FIXTURE_SETUP_##FIXTURE(&fixture); \
|
|
if(hasCurrentTestFailed == 1) { \
|
|
return; \
|
|
} \
|
|
\
|
|
__TAU_TEST_FIXTURE_RUN_##FIXTURE##_##NAME(&fixture); \
|
|
__TAU_TEST_FIXTURE_TEARDOWN_##FIXTURE(&fixture); \
|
|
} \
|
|
\
|
|
TAU_TEST_INITIALIZER(tau_register_##FIXTURE##_##NAME) { \
|
|
const tau_ull index = tauTestContext.numTestSuites++; \
|
|
const char* const namePart = #FIXTURE "." #NAME; \
|
|
const tau_ull nameSize = strlen(namePart) + 1; \
|
|
char* name = TAU_PTRCAST(char* , malloc(nameSize)); \
|
|
tauTestContext.tests = TAU_PTRCAST( \
|
|
tauTestSuiteStruct*, \
|
|
tau_realloc(TAU_PTRCAST(void*, tauTestContext.tests), \
|
|
sizeof(tauTestSuiteStruct) * \
|
|
tauTestContext.numTestSuites)); \
|
|
tauTestContext.tests[index].func = &__TAU_TEST_FIXTURE_##FIXTURE##_##NAME; \
|
|
tauTestContext.tests[index].name = name; \
|
|
TAU_SNPRINTF(name, nameSize, "%s", namePart); \
|
|
} \
|
|
static void __TAU_TEST_FIXTURE_RUN_##FIXTURE##_##NAME(struct FIXTURE* const tau)
|
|
|
|
|
|
static int tauShouldFilterTest(const char* const filter, const char* const testcase);
|
|
static int tauShouldFilterTest(const char* const filter, const char* const testcase) {
|
|
if(TAU_SOME(filter)) {
|
|
const char* filter_curr = filter;
|
|
const char* testcase_curr = testcase;
|
|
const char* filter_wildcard = TAU_NULL;
|
|
|
|
while ((*filter_curr != TAU_NULLCHAR) && (*testcase_curr != TAU_NULLCHAR)) {
|
|
if(*filter_curr == '*') {
|
|
// store the position of the wildcard
|
|
filter_wildcard = filter_curr;
|
|
|
|
// skip the wildcard character
|
|
filter_curr++;
|
|
|
|
while ((*filter_curr != TAU_NULLCHAR) && (*testcase_curr != TAU_NULLCHAR)) {
|
|
if(*filter_curr == '*') {
|
|
// Found another wildcard (filter is something like *foo*), so exit the current loop,
|
|
// and return to the parent loop to handle the wildcard case
|
|
break;
|
|
} else if(*filter_curr != *testcase_curr) {
|
|
// otherwise our filter didn't match, so reset it
|
|
filter_curr = filter_wildcard;
|
|
}
|
|
|
|
// move testcase along
|
|
testcase_curr++;
|
|
|
|
// move filter along
|
|
filter_curr++;
|
|
}
|
|
|
|
if((*filter_curr == TAU_NULLCHAR) && (*testcase_curr == TAU_NULLCHAR))
|
|
return 0;
|
|
|
|
// if the testcase has been exhausted, we don't have a match!
|
|
if(*testcase_curr == TAU_NULLCHAR)
|
|
return 1;
|
|
} else {
|
|
if(*testcase_curr != *filter_curr) {
|
|
// test case doesn't match filter
|
|
return 1;
|
|
} else {
|
|
// move our filter and testcase forward
|
|
testcase_curr++;
|
|
filter_curr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if((*filter_curr != TAU_NULLCHAR) || ((*testcase_curr == TAU_NULLCHAR) && ((filter == filter_curr) ||
|
|
(filter_curr[-1] != '*')))) {
|
|
// We have a mismatch
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline FILE* tau_fopen(const char* const filename, const char* const mode) {
|
|
#ifdef _MSC_VER
|
|
FILE* file;
|
|
if(fopen_s(&file, filename, mode) == 0) {
|
|
return file;
|
|
} else {
|
|
return TAU_NULL;
|
|
}
|
|
#else
|
|
return fopen(filename, mode);
|
|
#endif // _MSC_VER
|
|
}
|
|
|
|
|
|
static void tau_help_() {
|
|
printf("Usage: %s [options] [test...]\n", tau_argv0_);
|
|
printf("\n");
|
|
printf("Run the specified unit tests; or if the option '--skip' is used, run all\n");
|
|
printf("tests in the suite but those listed. By default, if no tests are specified\n");
|
|
printf("on the command line, all unit tests in the suite are run.\n");
|
|
printf("\n");
|
|
printf("Options:\n");
|
|
printf(" --failed-output-only Output only failed Test Suites");
|
|
printf(" --filter=<filter> Filter the test suites to run (e.g: Suite1*.a\n");
|
|
printf(" would run Suite1Case.a but not Suite1Case.b}\n");
|
|
#if defined(TAU_WIN_)
|
|
printf(" --time Measure test duration\n");
|
|
#elif defined(TAU_HAS_POSIX_TIMER_)
|
|
printf(" --time Measure test duration (real time)\n");
|
|
printf(" --time=TIMER Measure test duration, using given timer\n");
|
|
printf(" (TIMER is one of 'real', 'cpu')\n");
|
|
#endif // TAU_WIN_
|
|
printf(" --no-summary Suppress printing of test results summary\n");
|
|
printf(" --output=<FILE> Write an XUnit XML file to Enable XUnit output\n");
|
|
printf(" to the given file\n");
|
|
printf(" --list List unit tests in the suite and exit\n");
|
|
printf(" --no-color Disable coloured output\n");
|
|
printf(" --help Display this help and exit\n");
|
|
}
|
|
|
|
|
|
static tau_bool tauCmdLineRead(const int argc, const char* const * const argv) {
|
|
// Coloured output
|
|
#ifdef TAU_UNIX_
|
|
tauShouldColourizeOutput = isatty(STDOUT_FILENO);
|
|
#elif defined TAU_WIN_
|
|
#ifdef _BORLANDC_
|
|
tauShouldColourizeOutput = isatty(_fileno(stdout));
|
|
#else
|
|
tauShouldColourizeOutput = _isatty(_fileno(stdout));
|
|
#endif // _BORLANDC_
|
|
#else
|
|
tauShouldColourizeOutput = isatty(STDOUT_FILENO);
|
|
#endif // TAU_UNIX_
|
|
|
|
// loop through all arguments looking for our options
|
|
for(tau_ull i = 1; i < TAU_CAST(tau_ull, argc); i++) {
|
|
/* Informational switches */
|
|
const char* const helpStr = "--help";
|
|
const char* const listStr = "--list";
|
|
const char* const colourStr = "--no-color";
|
|
const char* const summaryStr = "--no-summary";
|
|
const char* const onlyFailedOutput = "--failed-output-only";
|
|
/* Test config switches */
|
|
const char* const filterStr = "--filter=";
|
|
const char* const XUnitOutput = "--output=";
|
|
|
|
// Help
|
|
if(strncmp(argv[i], helpStr, strlen(helpStr)) == 0) {
|
|
tau_help_();
|
|
return tau_false;
|
|
}
|
|
|
|
// Only failed output
|
|
else if(strncmp(argv[i], onlyFailedOutput, strlen(onlyFailedOutput)) == 0) {
|
|
tauDisplayOnlyFailedOutput = 1;
|
|
}
|
|
|
|
// Filter tests
|
|
else if(strncmp(argv[i], filterStr, strlen(filterStr)) == 0)
|
|
// user wants to filter what test suites run!
|
|
cmd_filter = argv[i] + strlen(filterStr);
|
|
|
|
// Write XUnit XML file
|
|
else if(strncmp(argv[i], XUnitOutput, strlen(XUnitOutput)) == 0)
|
|
tauTestContext.foutput = tau_fopen(argv[i] + strlen(XUnitOutput), "w+");
|
|
|
|
// List tests
|
|
else if(strncmp(argv[i], listStr, strlen(listStr)) == 0) {
|
|
for (i = 0; i < tauTestContext.numTestSuites; i++)
|
|
tauPrintf("%s\n", tauTestContext.tests[i].name);
|
|
tauDisplayTests = 1;
|
|
}
|
|
|
|
// Disable colouring
|
|
else if(strncmp(argv[i], colourStr, strlen(colourStr)) == 0) {
|
|
tauShouldColourizeOutput = 0;
|
|
}
|
|
|
|
// Disable Summary
|
|
else if(strncmp(argv[i], summaryStr, strlen(summaryStr))) {
|
|
tauDisableSummary = 1;
|
|
}
|
|
|
|
else {
|
|
printf("ERROR: Unrecognized option: %s", argv[i]);
|
|
return tau_false;
|
|
}
|
|
}
|
|
|
|
return tau_true;
|
|
}
|
|
|
|
static int tauCleanup() {
|
|
for (tau_ull i = 0; i < tauTestContext.numTestSuites; i++)
|
|
free(TAU_PTRCAST(void* , tauTestContext.tests[i].name));
|
|
|
|
free(TAU_PTRCAST(void* , tauStatsFailedTestSuites));
|
|
free(TAU_PTRCAST(void* , tauTestContext.tests));
|
|
|
|
if(tauTestContext.foutput)
|
|
fclose(tauTestContext.foutput);
|
|
|
|
return TAU_CAST(int, tauStatsNumTestsFailed);
|
|
}
|
|
|
|
// Triggers and runs all unit tests
|
|
static void tauRunTests() {
|
|
// Run tests
|
|
for(tau_ull i = 0; i < tauTestContext.numTestSuites; i++) {
|
|
checkIsInsideTestSuite = 1;
|
|
hasCurrentTestFailed = 0;
|
|
|
|
if(tauShouldFilterTest(cmd_filter, tauTestContext.tests[i].name))
|
|
continue;
|
|
|
|
if(!tauDisplayOnlyFailedOutput) {
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTGREEN_, "[ RUN ] ");
|
|
tauColouredPrintf(TAU_COLOUR_DEFAULT_, "%s\n", tauTestContext.tests[i].name);
|
|
}
|
|
|
|
if(tauTestContext.foutput)
|
|
fprintf(tauTestContext.foutput, "<testcase name=\"%s\">", tauTestContext.tests[i].name);
|
|
|
|
// Start the timer
|
|
const double start = tauClock();
|
|
|
|
// The actual test
|
|
tauTestContext.tests[i].func();
|
|
|
|
// Stop the timer
|
|
const double duration = tauClock() - start;
|
|
|
|
if(tauTestContext.foutput)
|
|
fprintf(tauTestContext.foutput, "</testcase>\n");
|
|
|
|
if(hasCurrentTestFailed == 1) {
|
|
const tau_ull failed_testcase_index = tauStatsNumFailedTestSuites++;
|
|
tauStatsFailedTestSuites = TAU_PTRCAST(tau_ull*,
|
|
tau_realloc(TAU_PTRCAST(void*, tauStatsFailedTestSuites),
|
|
sizeof(tau_ull) * tauStatsNumFailedTestSuites));
|
|
tauStatsFailedTestSuites[failed_testcase_index] = i;
|
|
tauStatsNumTestsFailed++;
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "[ FAILED ] ");
|
|
tauColouredPrintf(TAU_COLOUR_DEFAULT_, "%s (", tauTestContext.tests[i].name);
|
|
tauClockPrintDuration(duration);
|
|
printf(")\n");
|
|
} else {
|
|
if(!tauDisplayOnlyFailedOutput) {
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTGREEN_, "[ OK ] ");
|
|
tauColouredPrintf(TAU_COLOUR_DEFAULT_, "%s (", tauTestContext.tests[i].name);
|
|
tauClockPrintDuration(duration);
|
|
printf(")\n");
|
|
}
|
|
}
|
|
}
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTGREEN_, "[==========] ");
|
|
tauColouredPrintf(TAU_COLOUR_DEFAULT_, "%" TAU_PRIu64 " test suites ran\n", tauStatsTestsRan);
|
|
}
|
|
|
|
|
|
static inline int tau_main(const int argc, const char* const * const argv);
|
|
inline int tau_main(const int argc, const char* const * const argv) {
|
|
tauStatsTotalTestSuites = TAU_CAST(tau_u64, tauTestContext.numTestSuites);
|
|
tau_argv0_ = argv[0];
|
|
|
|
// Start the entire Test Session timer
|
|
const double start = tauClock();
|
|
|
|
const tau_bool wasCmdLineReadSuccessful = tauCmdLineRead(argc, argv);
|
|
if (tauDisplayTests)
|
|
return tauCleanup();
|
|
|
|
if(!wasCmdLineReadSuccessful)
|
|
return tauCleanup();
|
|
|
|
for (tau_ull i = 0; i < tauTestContext.numTestSuites; i++) {
|
|
if(tauShouldFilterTest(cmd_filter, tauTestContext.tests[i].name))
|
|
tauStatsSkippedTests++;
|
|
}
|
|
|
|
tauStatsTestsRan = tauStatsTotalTestSuites - tauStatsSkippedTests;
|
|
|
|
// Begin tests`
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTGREEN_, "[==========] ");
|
|
tauColouredPrintf(TAU_COLOUR_BOLD_, "Running %" TAU_PRIu64 " test suites.\n", TAU_CAST(tau_u64, tauStatsTestsRan));
|
|
|
|
if(tauTestContext.foutput) {
|
|
fprintf(tauTestContext.foutput, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
|
fprintf(tauTestContext.foutput,
|
|
"<testsuites tests=\"%" TAU_PRIu64 "\" name=\"All\">\n",
|
|
TAU_CAST(tau_u64, tauStatsTestsRan));
|
|
fprintf(tauTestContext.foutput,
|
|
"<testsuite name=\"Tests\" tests=\"%" TAU_PRIu64 "\">\n",
|
|
TAU_CAST(tau_u64, tauStatsTestsRan));
|
|
}
|
|
|
|
// Run tests
|
|
tauRunTests();
|
|
|
|
// End the entire Test Session timer
|
|
const double duration = tauClock() - start;
|
|
|
|
// Write a Summary
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTGREEN_, "[ PASSED ] %" TAU_PRIu64 " %s\n",
|
|
tauStatsTestsRan - tauStatsNumTestsFailed,
|
|
tauStatsTestsRan - tauStatsNumTestsFailed == 1 ? "suite" : "suites");
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "[ FAILED ] %" TAU_PRIu64 " %s\n",
|
|
tauStatsNumTestsFailed,
|
|
tauStatsNumTestsFailed == 1 ? "suite" : "suites");
|
|
|
|
if(!tauDisableSummary) {
|
|
tauColouredPrintf(TAU_COLOUR_BOLD_, "\nSummary:\n");
|
|
|
|
printf(" Total test suites: %" TAU_PRIu64 "\n", tauStatsTotalTestSuites);
|
|
printf(" Total suites run: %" TAU_PRIu64 "\n", tauStatsTestsRan);
|
|
printf(" Total warnings generated: %" TAU_PRIu64 "\n", tauStatsNumWarnings);
|
|
printf(" Total suites skipped: %" TAU_PRIu64 "\n", tauStatsSkippedTests);
|
|
printf(" Total suites failed: %" TAU_PRIu64 "\n", tauStatsNumTestsFailed);
|
|
}
|
|
|
|
if(tauStatsNumTestsFailed > 0) {
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, "FAILED: ");
|
|
printf("%" TAU_PRIu64 " failed, %" TAU_PRIu64 " passed in ",
|
|
tauStatsNumTestsFailed,
|
|
tauStatsTestsRan - tauStatsNumTestsFailed);
|
|
tauClockPrintDuration(duration);
|
|
printf("\n");
|
|
|
|
for (tau_ull i = 0; i < tauStatsNumFailedTestSuites; i++) {
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTRED_, " [ FAILED ] %s\n",
|
|
tauTestContext.tests[tauStatsFailedTestSuites[i]].name);
|
|
}
|
|
} else if(tauStatsNumTestsFailed == 0 && tauStatsTotalTestSuites > 0) {
|
|
const tau_u64 total_tests_passed = tauStatsTestsRan - tauStatsNumTestsFailed;
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTGREEN_, "SUCCESS: ");
|
|
printf("%" TAU_PRIu64 " test suites passed in ", total_tests_passed);
|
|
tauClockPrintDuration(duration);
|
|
printf("\n");
|
|
} else {
|
|
tauColouredPrintf(TAU_COLOUR_BRIGHTYELLOW_, "WARNING: ");
|
|
printf("No test suites were found. If you think this was an error, please file an issue on Tau's Github repo.");
|
|
printf("\n");
|
|
}
|
|
|
|
if(tauTestContext.foutput)
|
|
fprintf(tauTestContext.foutput, "</testsuite>\n</testsuites>\n");
|
|
|
|
return tauCleanup();
|
|
}
|
|
|
|
/**
|
|
We need to declare these variables here because they're used in multiple translation units (if compiled
|
|
as so).
|
|
For example, if test1.c(pp) #includes `tau/tau.h` and test2.c(pp) does the same, declaring the variables
|
|
static will only make them exist in the first translation unit the compiler sees.
|
|
We can get around this by compiling only one file (eg. main.c(pp)) and including the other source files
|
|
using `#include`, but this would be counter-productive.
|
|
|
|
If we used C++ as the base language for this project, we could have got around this problem using OOP
|
|
constructs, but C++ is not (and should not) be the language this project is written in.
|
|
With C, the best (and closest) thing we have are global variables that persists through the _entire_
|
|
compilation project (all testing source files).
|
|
See: https://stackoverflow.com/questions/1856599/when-to-use-static-keyword-before-global-variables
|
|
*/
|
|
#define TAU_ONLY_GLOBALS() \
|
|
volatile int checkIsInsideTestSuite = 0; \
|
|
volatile int hasCurrentTestFailed = 0; \
|
|
volatile int shouldFailTest = 0; \
|
|
volatile int shouldAbortTest = 0; \
|
|
tau_u64 tauStatsNumWarnings = 0;
|
|
|
|
// If a user wants to define their own `main()` function, this _must_ be at the very end of the functtion
|
|
#define TAU_NO_MAIN() \
|
|
tauTestStateStruct tauTestContext = {0, 0, 0}; \
|
|
TAU_ONLY_GLOBALS()
|
|
|
|
// Define a main() function to call into tau.h and start executing tests.
|
|
#define TAU_MAIN() \
|
|
/* Define the global struct that will hold the data we need to run Tau. */ \
|
|
tauTestStateStruct tauTestContext = {0, 0, 0}; \
|
|
TAU_ONLY_GLOBALS() \
|
|
\
|
|
int main(const int argc, const char* const * const argv) { \
|
|
return tau_main(argc, argv); \
|
|
}
|
|
|
|
#endif // TAU_NO_TESTING
|
|
|
|
#ifdef TAU_NO_TESTING
|
|
volatile int checkIsInsideTestSuite = 0;
|
|
volatile int hasCurrentTestFailed = 0;
|
|
// volatile int shouldFailTest = 0;
|
|
// volatile int shouldAbortTest = 0;
|
|
#endif // TAU_NO_TESTING
|
|
|
|
TAU_DISABLE_DEBUG_WARNINGS_POP
|
|
|
|
#endif // TAU_H_
|