/* CUTest - A unit test runner for C Copyright (C) 2022 Pascal Baillehache baillehache.pascal@gmail.com https://baillehachepascal.dev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include // Conversion from macro to string #define CUTEST_TO_STR_(x) #x #define CUTEST_TO_STR(x) CUTEST_TO_STR_(x) // Escape sequence to colorize the output #define CUTEST_START_RED "\033[38;2;255;0;0m" #define CUTEST_END_RED "\033[39m" #define CUTEST_START_GREEN "\033[38;2;0;255;0m" #define CUTEST_END_GREEN "\033[39m" #define CUTEST_START_BOLD "\033[1m" #define CUTEST_END_BOLD "\033[0m" // Print current date and time #define CUTEST_TIME \ { time_t now = time(NULL); printf("%.19s\n", asctime(localtime(&now))); } // Variables to memorise the number of tests and failed test size_t cutestIdxTest; size_t cutestNbTest; size_t cutestNbTestFailed; bool cutestTestSuccess; // Wrapper for the test condition in the unit tests. To be used as follow: // CUTEST_ASSERT(cond, msg, msg_arg1, msg_arg2, ...) // where 'cond' is the condition to be respected by the test, and 'msg', etc... // are the arguments passed to a 'printf' call in case the condition // is not satisfy with the view to explain why the condition wasn't satisfied. #define CUTEST_ASSERT(cond, ...) \ if(!(cond)) { \ if(cutestTestSuccess) \ printf(CUTEST_START_RED "NG" CUTEST_END_RED "\n"); \ printf( \ CUTEST_START_RED " -- Unsatisfied condition on line %u:\n%s\n", \ __LINE__, CUTEST_TO_STR(cond)); \ printf("While:\n"); \ printf(__VA_ARGS__); \ printf(CUTEST_END_RED "\n"); \ cutestTestSuccess = false; \ } // Print the OK result for a successful test and increase the counter of // failed test for an unsuccessful test #define CUTEST_OK \ if(cutestTestSuccess) { \ printf(CUTEST_START_GREEN "OK" CUTEST_END_GREEN "\n"); \ } else { \ ++cutestNbTestFailed; \ } // Unit tests declarations macro. To be used in the unit tests file as follow: // CUTEST(test002, "Second unit test") { // int a = 2; // a *= a; // CUTEST_ASSERT(a == 4, "a equals to %d", a); // } #define CUTEST(name, lbl) void name(void) #include CUTEST_TO_STR(CUTEST_FILE) // Helper function to print and pad the label of the executed tests void CutestPrintLbl(char const* const lbl) { size_t length = strlen(lbl); size_t lengthMax = 80; printf("%03lu/%03lu ", cutestIdxTest, cutestNbTest); size_t i = 0; for(; i < length; ++i) { if(i > 0 && (i % lengthMax) == 0) printf("\n "); printf("%c", lbl[i]); } i %= lengthMax; if(i != 0) for(; i < lengthMax; ++i) printf("."); printf(" "); fflush(stdout); } // Main function int main(int argc, char** argv) { // Variable to memorise the unit test to be executed // (if null, all are executed) char* nameTestToExecute = NULL; for(int iArg = 0; iArg < argc; ++iArg) { if(strcmp(argv[iArg], "-t") == 0 && (iArg + 1) < argc) nameTestToExecute = argv[iArg + 1]; } // Print the unit test file name and start time printf( CUTEST_START_BOLD "\n ==== %s ====" CUTEST_END_BOLD "\n", CUTEST_TO_STR(CUTEST_FILE)); CUTEST_TIME // Init the counters cutestIdxTest = 0; cutestNbTest = 0; cutestNbTestFailed = 0; // Calculate the number of test #undef CUTEST #define CUTEST(name, lbl) \ if( \ nameTestToExecute == NULL || \ strcmp(nameTestToExecute, CUTEST_TO_STR(name)) == 0 \ ) { \ ++cutestNbTest; \ } if(0) #include CUTEST_TO_STR(CUTEST_FILE) // Unit tests call #undef CUTEST #define CUTEST(name, lbl) \ if( \ nameTestToExecute == NULL || \ strcmp(nameTestToExecute, CUTEST_TO_STR(name)) == 0 \ ) { \ ++cutestIdxTest; cutestTestSuccess = true; \ CutestPrintLbl(lbl); name(); CUTEST_OK \ } \ if(0) #include CUTEST_TO_STR(CUTEST_FILE) // Variable to memorise the returned code int ret = 0; // Print a summary if(cutestNbTestFailed == 0) { printf( CUTEST_START_GREEN "All tests succeeded." CUTEST_END_GREEN "\n"); ret = EXIT_SUCCESS; } else { printf( CUTEST_START_RED "%03lu/%03lu test(s) failed." CUTEST_END_RED "\n", cutestNbTestFailed, cutestNbTest); ret = EXIT_FAILURE; } // Print the end time and return the exit code CUTEST_TIME return ret; } // Example of Makefile: // test: // for file in `ls UnitTests/test_*.c`; do // gcc -o cutest cutest.c -DCUTEST_FILE=$$file && cutest; \rm -f cutest; done // // To execute only the unit test 'test002', you can use: // cutest -t test002