#include "ptest.h" #include #include #include #include #include /* Globals */ enum { MAX_NAME = 512 }; enum { MAX_ERROR = 2048 }; enum { MAX_TESTS = 2048 }; static int test_passing = 0; static int suite_passing = 0; /* Colors */ enum { BLACK = 0x0, BLUE = 0x1, GREEN = 0x2, AQUA = 0x3, RED = 0x4, PURPLE = 0x5, YELLOW = 0x6, WHITE = 0x7, GRAY = 0x8, LIGHT_BLUE = 0x9, LIGHT_GREEN = 0xA, LIGHT_AQUA = 0xB, LIGHT_RED = 0xC, LIGHT_PURPLE = 0xD, LIGHT_YELLOW = 0xE, LIGHT_WHITE = 0xF }; #ifdef _WIN32 #include static void pt_color(int color) { HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hCon, color); } #else static const char* colors[] = { "\x1B[0m", "\x1B[34m", "\x1B[32m", "\x1B[36m", "\x1B[31m", "\x1B[35m", "\x1B[33m", "\x1B[37m", "", "\x1B[34m", "\x1B[32m", "\x1B[36m", "\x1B[31m", "\x1B[35m", "\x1B[33m", "\x1B[37m" }; static void pt_color(int color) { printf("%s", colors[color]); } #endif /* Asserts */ static int num_asserts = 0; static int num_assert_passes = 0; static int num_assert_fails = 0; static char assert_err[MAX_ERROR]; static char assert_err_buff[MAX_ERROR]; static int assert_err_num = 0; void pt_assert_run(int result, const char* expr, const char* func, const char* file, int line) { num_asserts++; test_passing = test_passing && result; if (result) { num_assert_passes++; } else { sprintf(assert_err_buff, " %i. Assert [ %s ] (%s:%i)\n", assert_err_num+1, expr, file, line ); strcat(assert_err, assert_err_buff); assert_err_num++; num_assert_fails++; } } static void ptest_signal(int sig) { test_passing = 0; switch( sig ) { case SIGFPE: sprintf(assert_err_buff, " %i. Division by Zero\n", assert_err_num+1); break; case SIGILL: sprintf(assert_err_buff, " %i. Illegal Instruction\n", assert_err_num+1); break; case SIGSEGV: sprintf(assert_err_buff, " %i. Segmentation Fault\n", assert_err_num+1); break; } assert_err_num++; strcat(assert_err, assert_err_buff); pt_color(WHITE); pt_color(RED); printf("Failed! \n\n%s\n", assert_err); pt_color(WHITE); printf(" | Stopping Execution.\n"); fflush(stdout); exit(0); } /* Tests */ static void pt_title_case(char* output, const char* input) { int space = 1; unsigned int i; strcpy(output, input); for(i = 0; i < strlen(output); i++) { if (output[i] == '_' || output[i] == ' ') { space = 1; output[i] = ' '; continue; } if (space && output[i] >= 'a' && output[i] <= 'z') { space = 0; output[i] = output[i] - 32; continue; } space = 0; } } typedef struct { void (*func)(void); char name[MAX_NAME]; char suite[MAX_NAME]; } test_t; static test_t tests[MAX_TESTS]; static int num_tests = 0; static int num_tests_passes = 0; static int num_tests_fails = 0; void pt_add_test(void (*func)(void), const char* name, const char* suite) { test_t test; if (num_tests == MAX_TESTS) { printf("ERROR: Exceeded maximum test count of %i!\n", MAX_TESTS); abort(); } if (strlen(name) >= MAX_NAME) { printf("ERROR: Test name '%s' too long (Maximum is %i characters)\n", name, MAX_NAME); abort(); } if (strlen(suite) >= MAX_NAME) { printf("ERROR: Test suite '%s' too long (Maximum is %i characters)\n", suite, MAX_NAME); abort(); } test.func = func; pt_title_case(test.name, name); pt_title_case(test.suite, suite); tests[num_tests] = test; num_tests++; } /* Suites */ static int num_suites = 0; static int num_suites_passes = 0; static int num_suites_fails = 0; void pt_add_suite(void (*func)(void)) { num_suites++; func(); } /* Running */ static clock_t start, end; static char current_suite[MAX_NAME]; int pt_run(void) { unsigned int i; double total; printf(" \n"); printf(" +-------------------------------------------+\n"); printf(" | ptest MicroTesting Magic for C |\n"); printf(" | |\n"); printf(" | http://github.com/orangeduck/ptest |\n"); printf(" | |\n"); printf(" | Daniel Holden (contact@theorangeduck.com) |\n"); printf(" +-------------------------------------------+\n"); signal(SIGFPE, ptest_signal); signal(SIGILL, ptest_signal); signal(SIGSEGV, ptest_signal); start = clock(); strcpy(current_suite, ""); for(i = 0; i < num_tests; i++) { test_t test = tests[i]; /* Check for transition to a new suite */ if (strcmp(test.suite, current_suite)) { /* Don't increment any counter for first entrance */ if (strcmp(current_suite, "")) { if (suite_passing) { num_suites_passes++; } else { num_suites_fails++; } } suite_passing = 1; strcpy(current_suite, test.suite); printf("\n\n ===== %s =====\n\n", current_suite); } /* Run Test */ test_passing = 1; strcpy(assert_err, ""); strcpy(assert_err_buff, ""); assert_err_num = 0; printf(" | %s ... ", test.name); test.func(); suite_passing = suite_passing && test_passing; if (test_passing) { num_tests_passes++; pt_color(GREEN); printf("Passed! \n"); pt_color(WHITE); } else { num_tests_fails++; pt_color(RED); printf("Failed! \n\n%s\n", assert_err); pt_color(WHITE); } } if (suite_passing) { num_suites_passes++; } else { num_suites_fails++; } end = clock(); printf(" \n"); printf(" +---------------------------------------------------+\n"); printf(" | Summary |\n"); printf(" +---------++------------+-------------+-------------+\n"); printf(" | Suites ||"); pt_color(YELLOW); printf(" Total %4d ", num_suites); pt_color(WHITE); printf("|"); pt_color(GREEN); printf(" Passed %4d ", num_suites_passes); pt_color(WHITE); printf("|"); pt_color(RED); printf(" Failed %4d ", num_suites_fails); pt_color(WHITE); printf("|\n"); printf(" | Tests ||"); pt_color(YELLOW); printf(" Total %4d ", num_tests); pt_color(WHITE); printf("|"); pt_color(GREEN); printf(" Passed %4d ", num_tests_passes); pt_color(WHITE); printf("|"); pt_color(RED); printf(" Failed %4d ", num_tests_fails); pt_color(WHITE); printf("|\n"); printf(" | Asserts ||"); pt_color(YELLOW); printf(" Total %4d ", num_asserts); pt_color(WHITE); printf("|"); pt_color(GREEN); printf(" Passed %4d ", num_assert_passes); pt_color(WHITE); printf("|"); pt_color(RED); printf(" Failed %4d ", num_assert_fails); pt_color(WHITE); printf("|\n"); printf(" +---------++------------+-------------+-------------+\n"); printf(" \n"); total = (double)(end - start) / CLOCKS_PER_SEC; printf(" Total Running Time: %0.3fs\n\n", total); if (num_suites_fails > 0) { return 1; } else { return 0; } }