#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <execinfo.h>
#include <stdlib.h>
//-----------------------------------------------------------------------------------------------//
// Macros and forward declarations
//-----------------------------------------------------------------------------------------------//
// Assert macro for tests
#define TESTASSERT(cond) \
do { \
if (!(cond)) { \
assertHandler(__FILE__, __LINE__, #cond); \
} \
} while (0);
// Macro to register a test function
#define TEST(name) \
static void name(); \
static TestRef s_Test_ ## name(#name, __FILE__, __LINE__, name); \
static void name()
// Forward declarations
class TestRef;
bool runTest(TestRef * test);
void registerTest(TestRef * test);
void crashHandler(int sig);
void assertHandler(const char * file, int line, const char * message);
//-----------------------------------------------------------------------------------------------//
// Class for storing reference details for a test function
// Test references are stored in a simple linked list
//-----------------------------------------------------------------------------------------------//
// Type def for test function pointer
typedef void (*pfnTestFunc)(void);
// Class to store test refence data
class TestRef
{
public:
TestRef(const char * testName, const char * filename, int lineNumber, pfnTestFunc func)
{
function = func;
name = testName;
module = filename;
line = lineNumber;
next = NULL;
// Register this test function to be run by the main process
registerTest(this);
}
pfnTestFunc function; // Pointer to test function
const char * name; // Test name
const char * module; // Module name
int line; // Module line number
TestRef * next; // Pointer to next test reference in the linked list
};
// Linked list to store test references
static TestRef * s_FirstTest = NULL;
static TestRef * s_LastTest = NULL;
//-----------------------------------------------------------------------------------------------//
// Test functions
//-----------------------------------------------------------------------------------------------//
TEST(TestPass1)
{
int x = 1;
int y = 2;
int z = x + y;
TESTASSERT(z == 3);
}
TEST(TestPass2)
{
TESTASSERT(strcmp("abc", "abc") == 0);
}
TEST(TestAssertFailed)
{
TESTASSERT(1 == false);
}
TEST(TestSegFault)
{
TestRef * p = NULL;
p->function();
}
//-----------------------------------------------------------------------------------------------//
// Main test runner
//-----------------------------------------------------------------------------------------------//
int main(int argc, char** argv)
{
// Register crash handlers
signal(SIGFPE, crashHandler);
signal(SIGILL, crashHandler);
signal(SIGSEGV, crashHandler);
fprintf(stdout, "Running tests...\n");
int testCount = 0;
int testPasses = 0;
// Loop round all the tests in the linked list
TestRef * test = s_FirstTest;
while (test != NULL)
{
// Print out the name of the test we're about to run
fprintf(stdout, "%s:%s... ", test->module, test->name);
fflush(stdout);
testCount++;
bool passed = runTest(test);
if (passed == true)
{
testPasses++;
fprintf(stdout, "Ok\n");
}
else
{
fprintf(stdout, "FAILED\n");
}
// Get the next test and loop again
test = test->next;
}
// Print out final report
int exitCode;
if (testPasses == testCount)
{
fprintf(stdout, "\n*** TEST SUCCESS ***\n");
exitCode = 0;
}
else
{
fprintf(stdout, "\n*** TEST FAILED ***\n");
exitCode = 1;
}
fprintf(stdout, "%d/%d Tests Passed\n", testPasses, testCount);
return exitCode;
}
// Wrapper function to run the test in a child process
bool runTest(TestRef * test)
{
// Fork the process, the test will actually be run by the child process
pid_t pid = fork();
switch (pid)
{
case -1:
fprintf(stderr, "Failed to spawn child process, %d\n", errno);
exit(1); // No point running any further tests
case 0:
// We're in the child process so run the test
test->function();
exit(0); // Test passed, so exit the child with a success code
default:{
// Parent process, wait for the child to exit
int stat_val;
pid_t child_pid = wait(&stat_val);
if (WIFEXITED(stat_val))
{
// Child exited normally so check the return code
if (WEXITSTATUS(stat_val) == 0)
{
// Test passed
return true;
}
else
{
// Test failed
return false;
}
}
else
{
// Child process crashed in a way we couldn't handle!
fprintf(stdout, "Child exited abnormally!\n");
return false;
}
break;}
}
}
//-----------------------------------------------------------------------------------------------//
// Support functions
//-----------------------------------------------------------------------------------------------//
// Add a new test to the linked list
void registerTest(TestRef * test)
{
if (s_FirstTest == NULL)
{
s_FirstTest = test;
s_LastTest = test;
}
else
{
s_LastTest->next = test;
s_LastTest = test;
}
}
// Dump a stack trace to stdout
void dumpStack(int topFunctionsToSkip)
{
topFunctionsToSkip += 1; // We always want to skip this dumpStack() function
void * array[64];
size_t size = backtrace(array, 64);
backtrace_symbols_fd(array + topFunctionsToSkip, size - topFunctionsToSkip, 1); // Adjust the array pointer to skip n elements at top of stack
}
// Handler for exception signals
void crashHandler(int sig)
{
fprintf(stdout, "\nGot signal %d in crash handler\n", sig);
fflush(stdout);
dumpStack(1);
_exit(1);
}
// Handler for failed asserts
void assertHandler(const char * file, int line, const char * message)
{
fprintf(stdout, "\nAssert failed (%s:%d):\n", file, line);
fprintf(stdout, " %s\n", message);
fflush(stdout);
dumpStack(1);
_exit(1);
}