├── .gitignore ├── Makefile ├── README.md ├── example.c ├── example2.c ├── output.png ├── package.json ├── ptest.c └── ptest.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | STD=-ansi 3 | CFLAGS= $(STD) -Wall -Werror -Wno-unused -g 4 | 5 | all: example example2 6 | 7 | example: example.c ptest.c 8 | $(CC) $(CFLAGS) $^ -o $@ 9 | ./$@; true 10 | 11 | example2: example2.c ptest.c 12 | $(CC) $(CFLAGS) $^ -o $@ 13 | ./$@; true 14 | 15 | clean: 16 | rm -f example.exe example2.exe example example2 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ptest 2 | ===== 3 | 4 | ptest is a DRY (Don't Repeat Yourself) Microtesting framework for C. 5 | 6 | Example 7 | ------- 8 | 9 | Usage in this format requires a standard or compiler that supports nested function declarations. 10 | 11 | ```c 12 | #include "ptest.h" 13 | 14 | PT_SUITE(suite_basic) { 15 | 16 | PT_TEST(test_maths) { 17 | int x = 3; 18 | int y = 11; 19 | 20 | PT_ASSERT(1 + 1 == 2); 21 | PT_ASSERT(2 + 2 == 4); 22 | PT_ASSERT(y - x == 8); 23 | } 24 | 25 | PT_TEST(test_strings) { 26 | PT_ASSERT(strcmp("x", "x") == 0); 27 | PT_ASSERT_STR_EQ("x", "x"); 28 | } 29 | } 30 | 31 | PT_SUITE(suite_other) { 32 | 33 | PT_TEST(test_stuff) { 34 | PT_ASSERT(1); 35 | PT_ASSERT(!0); 36 | PT_ASSERT("string" != NULL); 37 | } 38 | 39 | PT_TEST(test_failure) { 40 | PT_ASSERT(0); 41 | } 42 | } 43 | 44 | int main(int argc, char** argv) { 45 | pt_add_suite(suite_basic); 46 | pt_add_suite(suite_other); 47 | return pt_run(); 48 | } 49 | ``` 50 | 51 | Output 52 | ------ 53 | 54 | ![Output](https://raw.github.com/orangeduck/ptest/master/output.png) 55 | 56 | Example 2 57 | --------- 58 | 59 | ptest can still be used without nested functions at the cost of some repetition. 60 | 61 | ```c 62 | #include "ptest.h" 63 | 64 | void test_maths(void) { 65 | int x = 3; 66 | int y = 11; 67 | 68 | PT_ASSERT(1 + 1 == 2); 69 | PT_ASSERT(2 + 2 == 4); 70 | PT_ASSERT(y - x == 8); 71 | } 72 | 73 | void test_strings(void) { 74 | PT_ASSERT(strcmp("x", "x") == 0); 75 | PT_ASSERT_STR_EQ("x", "x"); 76 | } 77 | 78 | void test_stuff(void) { 79 | PT_ASSERT(1); 80 | PT_ASSERT(!0); 81 | PT_ASSERT("string" != NULL); 82 | } 83 | 84 | void test_failure(void) { 85 | PT_ASSERT(0); 86 | } 87 | 88 | void suite_basic(void) { 89 | pt_add_test(test_maths, "Test Maths", "Suite Basic"); 90 | pt_add_test(test_strings, "Test Strings", "Suite Basic"); 91 | } 92 | 93 | void suite_other(void) { 94 | pt_add_test(test_stuff, "Test Stuff", "Suite Other"); 95 | pt_add_test(test_failure, "Test Failure", "Suite Other"); 96 | } 97 | 98 | int main(int argc, char** argv) { 99 | pt_add_suite(suite_basic); 100 | pt_add_suite(suite_other); 101 | return pt_run(); 102 | } 103 | ``` 104 | 105 | License 106 | ------- 107 | 108 | Work is Licensed under BSD3 109 | 110 | Copyright (c) 2013, Daniel Holden 111 | All rights reserved. 112 | 113 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 114 | 115 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 116 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 117 | * Neither the name of the ptest nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 118 | 119 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 120 | 121 | 122 | -------------------------------------------------------------------------------- /example.c: -------------------------------------------------------------------------------- 1 | #include "ptest.h" 2 | 3 | PT_SUITE(suite_basic) { 4 | 5 | PT_TEST(test_maths) { 6 | int x = 3; 7 | int y = 11; 8 | 9 | PT_ASSERT(1 + 1 == 2); 10 | PT_ASSERT(2 + 2 == 4); 11 | PT_ASSERT(y - x == 8); 12 | } 13 | 14 | PT_TEST(test_strings) { 15 | PT_ASSERT(strcmp("x", "x") == 0); 16 | PT_ASSERT_STR_EQ("x", "x"); 17 | } 18 | } 19 | 20 | PT_SUITE(suite_other) { 21 | 22 | PT_TEST(test_stuff) { 23 | PT_ASSERT(1); 24 | PT_ASSERT(!0); 25 | PT_ASSERT("string" != NULL); 26 | } 27 | 28 | PT_TEST(test_failure) { 29 | PT_ASSERT(0); 30 | } 31 | } 32 | 33 | int main(int argc, char** argv) { 34 | pt_add_suite(suite_basic); 35 | pt_add_suite(suite_other); 36 | return pt_run(); 37 | } -------------------------------------------------------------------------------- /example2.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ptest.h" 4 | 5 | void test_maths(void) { 6 | int x = 3; 7 | int y = 11; 8 | 9 | PT_ASSERT(1 + 1 == 2); 10 | PT_ASSERT(2 + 2 == 4); 11 | PT_ASSERT(y - x == 8); 12 | } 13 | 14 | void test_strings(void) { 15 | PT_ASSERT(strcmp("x", "x") == 0); 16 | PT_ASSERT_STR_EQ("x", "x"); 17 | } 18 | 19 | void test_stuff(void) { 20 | PT_ASSERT(1); 21 | PT_ASSERT(!0); 22 | PT_ASSERT("string" != NULL); 23 | } 24 | 25 | void test_failure(void) { 26 | PT_ASSERT(false == true); 27 | } 28 | 29 | void suite_basic(void) { 30 | pt_add_test(test_maths, "Test Maths", "Suite Basic"); 31 | pt_add_test(test_strings, "Test Strings", "Suite Basic"); 32 | } 33 | 34 | void suite_other(void) { 35 | pt_add_test(test_stuff, "Test Stuff", "Suite Other"); 36 | pt_add_test(test_failure, "Test Failure", "Suite Other"); 37 | } 38 | 39 | int main(int argc, char** argv) { 40 | pt_add_suite(suite_basic); 41 | pt_add_suite(suite_other); 42 | return pt_run(); 43 | } 44 | -------------------------------------------------------------------------------- /output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangeduck/ptest/798aa244f6445faae0618a03a72c831ab85c2763/output.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ptest", 3 | "version": "1.0.0", 4 | "repo": "orangeduck/ptest", 5 | "description": "A DRY Microtesting framework for C", 6 | "keywords": ["testing", "microtesting", "DRY", "c", "ptest"], 7 | "license": "BSD", 8 | "src": ["ptest.c", "ptest.h"] 9 | } 10 | -------------------------------------------------------------------------------- /ptest.c: -------------------------------------------------------------------------------- 1 | #include "ptest.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* Globals */ 10 | 11 | enum { 12 | MAX_NAME = 512 13 | }; 14 | 15 | enum { 16 | MAX_ERROR = 2048 17 | }; 18 | 19 | enum { 20 | MAX_TESTS = 2048 21 | }; 22 | 23 | static int test_passing = 0; 24 | static int suite_passing = 0; 25 | 26 | /* Colors */ 27 | 28 | enum { 29 | BLACK = 0, 30 | BLUE = 1, 31 | GREEN = 2, 32 | AQUA = 3, 33 | RED = 4, 34 | PURPLE = 5, 35 | YELLOW = 6, 36 | WHITE = 7, 37 | GRAY = 8, 38 | 39 | LIGHT_BLUE = 9, 40 | LIGHT_GREEN = 10, 41 | LIGHT_AQUA = 11, 42 | LIGHT_RED = 12, 43 | LIGHT_PURPLE = 13, 44 | LIGHT_YELLOW = 14, 45 | LIGHT_WHITE = 15, 46 | 47 | DEFAULT = 16, 48 | }; 49 | 50 | #ifdef _WIN32 51 | 52 | #include 53 | 54 | static WORD defaults; 55 | static int defaults_loaded = 0; 56 | 57 | static void pt_color(int color) { 58 | 59 | HANDLE cnsl = GetStdHandle(STD_OUTPUT_HANDLE); 60 | 61 | if (!defaults_loaded) { 62 | CONSOLE_SCREEN_BUFFER_INFO info; 63 | GetConsoleScreenBufferInfo(cnsl, &info); 64 | defaults = info.wAttributes; 65 | defaults_loaded = 1; 66 | } 67 | 68 | SetConsoleTextAttribute(cnsl, color == DEFAULT ? defaults : color); 69 | } 70 | 71 | #else 72 | 73 | static const char* colors[] = { 74 | "\x1B[0m", 75 | "\x1B[34m", 76 | "\x1B[32m", 77 | "\x1B[36m", 78 | "\x1B[31m", 79 | "\x1B[35m", 80 | "\x1B[33m", 81 | "\x1B[37m", 82 | "", 83 | "\x1B[34m", 84 | "\x1B[32m", 85 | "\x1B[36m", 86 | "\x1B[31m", 87 | "\x1B[35m", 88 | "\x1B[33m", 89 | "\x1B[37m", 90 | "\x1B[39m", 91 | }; 92 | 93 | static void pt_color(int color) { 94 | printf("%s", colors[color]); 95 | } 96 | 97 | #endif 98 | 99 | /* Asserts */ 100 | 101 | static int num_asserts = 0; 102 | static int num_assert_passes = 0; 103 | static int num_assert_fails = 0; 104 | 105 | static char assert_err[MAX_ERROR]; 106 | static char assert_err_buff[MAX_ERROR]; 107 | static int assert_err_num = 0; 108 | 109 | void pt_assert_run( 110 | int result, const char* expr, const char* func, const char* file, int line) { 111 | 112 | num_asserts++; 113 | test_passing = test_passing && result; 114 | 115 | if (result) { 116 | num_assert_passes++; 117 | } else { 118 | sprintf(assert_err_buff, 119 | " %i. Assert [ %s ] (%s:%i)\n", 120 | assert_err_num+1, expr, file, line ); 121 | strcat(assert_err, assert_err_buff); 122 | assert_err_num++; 123 | num_assert_fails++; 124 | } 125 | 126 | } 127 | 128 | static void ptest_signal(int sig) { 129 | 130 | test_passing = 0; 131 | 132 | switch( sig ) { 133 | case SIGFPE: sprintf(assert_err_buff, 134 | " %i. Division by Zero\n", assert_err_num+1); 135 | break; 136 | case SIGILL: sprintf(assert_err_buff, 137 | " %i. Illegal Instruction\n", assert_err_num+1); 138 | break; 139 | case SIGSEGV: sprintf(assert_err_buff, 140 | " %i. Segmentation Fault\n", assert_err_num+1); 141 | break; 142 | } 143 | 144 | assert_err_num++; 145 | strcat(assert_err, assert_err_buff); 146 | 147 | pt_color(RED); 148 | printf("Failed! \n\n%s\n", assert_err); 149 | pt_color(DEFAULT); 150 | 151 | puts(" | Stopping Execution."); 152 | fflush(stdout); 153 | exit(0); 154 | 155 | } 156 | 157 | /* Tests */ 158 | 159 | static void pt_title_case(char* output, const char* input) { 160 | 161 | int space = 1; 162 | unsigned int i; 163 | 164 | strcpy(output, input); 165 | 166 | for(i = 0; i < strlen(output); i++) { 167 | 168 | if (output[i] == '_' || output[i] == ' ') { 169 | space = 1; 170 | output[i] = ' '; 171 | continue; 172 | } 173 | 174 | if (space && output[i] >= 'a' && output[i] <= 'z') { 175 | space = 0; 176 | output[i] = output[i] - 32; 177 | continue; 178 | } 179 | 180 | space = 0; 181 | 182 | } 183 | 184 | } 185 | 186 | typedef struct { 187 | void (*func)(void); 188 | char name[MAX_NAME]; 189 | char suite[MAX_NAME]; 190 | } test_t; 191 | 192 | static test_t tests[MAX_TESTS]; 193 | 194 | static int num_tests = 0; 195 | static int num_tests_passes = 0; 196 | static int num_tests_fails = 0; 197 | 198 | void pt_add_test(void (*func)(void), const char* name, const char* suite) { 199 | 200 | test_t test; 201 | 202 | if (num_tests == MAX_TESTS) { 203 | printf("ERROR: Exceeded maximum test count of %i!\n", 204 | MAX_TESTS); abort(); 205 | } 206 | 207 | if (strlen(name) >= MAX_NAME) { 208 | printf("ERROR: Test name '%s' too long (Maximum is %i characters)\n", 209 | name, MAX_NAME); abort(); 210 | } 211 | 212 | if (strlen(suite) >= MAX_NAME) { 213 | printf("ERROR: Test suite '%s' too long (Maximum is %i characters)\n", 214 | suite, MAX_NAME); abort(); 215 | } 216 | 217 | test.func = func; 218 | pt_title_case(test.name, name); 219 | pt_title_case(test.suite, suite); 220 | 221 | tests[num_tests] = test; 222 | num_tests++; 223 | } 224 | 225 | /* Suites */ 226 | 227 | static int num_suites = 0; 228 | static int num_suites_passes = 0; 229 | static int num_suites_fails = 0; 230 | 231 | void pt_add_suite(void (*func)(void)) { 232 | num_suites++; 233 | func(); 234 | } 235 | 236 | /* Running */ 237 | 238 | static clock_t start, end; 239 | static char current_suite[MAX_NAME]; 240 | 241 | int pt_run(void) { 242 | 243 | unsigned int i; 244 | double total; 245 | test_t test; 246 | 247 | puts(""); 248 | puts(" +-------------------------------------------+"); 249 | puts(" | ptest MicroTesting Magic for C |"); 250 | puts(" | |"); 251 | puts(" | http://github.com/orangeduck/ptest |"); 252 | puts(" | |"); 253 | puts(" | Daniel Holden (contact@theorangeduck.com) |"); 254 | puts(" +-------------------------------------------+"); 255 | 256 | signal(SIGFPE, ptest_signal); 257 | signal(SIGILL, ptest_signal); 258 | signal(SIGSEGV, ptest_signal); 259 | 260 | start = clock(); 261 | strcpy(current_suite, ""); 262 | 263 | for(i = 0; i < num_tests; i++) { 264 | 265 | test = tests[i]; 266 | 267 | /* Check for transition to a new suite */ 268 | if (strcmp(test.suite, current_suite)) { 269 | 270 | /* Don't increment any counter for first entrance */ 271 | if (strcmp(current_suite, "")) { 272 | if (suite_passing) { 273 | num_suites_passes++; 274 | } else { 275 | num_suites_fails++; 276 | } 277 | } 278 | 279 | suite_passing = 1; 280 | strcpy(current_suite, test.suite); 281 | printf("\n\n ===== %s =====\n\n", current_suite); 282 | } 283 | 284 | /* Run Test */ 285 | 286 | test_passing = 1; 287 | strcpy(assert_err, ""); 288 | strcpy(assert_err_buff, ""); 289 | assert_err_num = 0; 290 | printf(" | %s ... ", test.name); 291 | fflush(stdout); 292 | 293 | test.func(); 294 | 295 | suite_passing = suite_passing && test_passing; 296 | 297 | if (test_passing) { 298 | num_tests_passes++; 299 | pt_color(GREEN); 300 | puts("Passed!"); 301 | pt_color(DEFAULT); 302 | } else { 303 | num_tests_fails++; 304 | pt_color(RED); 305 | printf("Failed! \n\n%s\n", assert_err); 306 | pt_color(DEFAULT); 307 | } 308 | 309 | } 310 | 311 | if (suite_passing) { 312 | num_suites_passes++; 313 | } else { 314 | num_suites_fails++; 315 | } 316 | 317 | end = clock(); 318 | 319 | puts(""); 320 | puts(" +---------------------------------------------------+"); 321 | puts(" | Summary |"); 322 | puts(" +---------++------------+-------------+-------------+"); 323 | 324 | printf(" | Suites ||"); 325 | pt_color(YELLOW); printf(" Total %4d ", num_suites); 326 | pt_color(DEFAULT); putchar('|'); 327 | pt_color(GREEN); printf(" Passed %4d ", num_suites_passes); 328 | pt_color(DEFAULT); putchar('|'); 329 | pt_color(RED); printf(" Failed %4d ", num_suites_fails); 330 | pt_color(DEFAULT); puts("|"); 331 | 332 | printf(" | Tests ||"); 333 | pt_color(YELLOW); printf(" Total %4d ", num_tests); 334 | pt_color(DEFAULT); putchar('|'); 335 | pt_color(GREEN); printf(" Passed %4d ", num_tests_passes); 336 | pt_color(DEFAULT); putchar('|'); 337 | pt_color(RED); printf(" Failed %4d ", num_tests_fails); 338 | pt_color(DEFAULT); puts("|"); 339 | 340 | printf(" | Asserts ||"); 341 | pt_color(YELLOW); printf(" Total %4d ", num_asserts); 342 | pt_color(DEFAULT); putchar('|'); 343 | pt_color(GREEN); printf(" Passed %4d ", num_assert_passes); 344 | pt_color(DEFAULT); putchar('|'); 345 | pt_color(RED); printf(" Failed %4d ", num_assert_fails); 346 | pt_color(DEFAULT); puts("|"); 347 | 348 | puts(" +---------++------------+-------------+-------------+"); 349 | puts(""); 350 | 351 | total = (double)(end - start) / CLOCKS_PER_SEC; 352 | 353 | printf(" Total Running Time: %0.3fs\n\n", total); 354 | 355 | if (num_suites_fails > 0) { return 1; } else { return 0; } 356 | } 357 | -------------------------------------------------------------------------------- /ptest.h: -------------------------------------------------------------------------------- 1 | #ifndef ptest_h 2 | #define ptest_h 3 | 4 | #include 5 | 6 | #define PT_SUITE(name) void name(void) 7 | 8 | #define PT_FUNC(name) static void name(void) 9 | #define PT_REG(name) pt_add_test(name, #name, __func__) 10 | #define PT_TEST(name) auto void name(void); PT_REG(name); void name(void) 11 | 12 | #define PT_ASSERT(expr) pt_assert_run((int)(expr), #expr, __func__, __FILE__, __LINE__) 13 | #define PT_ASSERT_STR_EQ(fst, snd) pt_assert_run(strcmp(fst, snd) == 0, "strcmp( " #fst ", " #snd " ) == 0", __func__, __FILE__, __LINE__) 14 | 15 | void pt_assert_run(int result, const char* expr, const char* func, const char* file, int line); 16 | 17 | void pt_add_test(void (*func)(void), const char* name, const char* suite); 18 | void pt_add_suite(void (*func)(void)); 19 | int pt_run(void); 20 | 21 | #endif --------------------------------------------------------------------------------