1 /*
2  * CUTest -- C/C++ Unit Test facility
3  * <http://github.com/mity/cutest>
4  *
5  * Copyright (c) 2013-2017 Martin Mitas
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a
8  * copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation
10  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11  * and/or sell copies of the Software, and to permit persons to whom the
12  * Software is furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23  * IN THE SOFTWARE.
24  */
25 
26 #ifndef CUTEST_H__
27 #define CUTEST_H__
28 
29 /************************
30  *** Public interface ***
31  ************************/
32 
33 /* By default, <cutest.h> provides the main program entry point (function
34  * main()). However, if the test suite is composed of multiple source files
35  * which include <cutest.h>, then this causes a problem of multiple main()
36  * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all
37  * compilation units but one.
38  */
39 
40 /* Macro to specify list of unit tests in the suite.
41  * The unit test implementation MUST provide list of unit tests it implements
42  * with this macro:
43  *
44  *   TEST_LIST = {
45  *       { "test1_name", test1_func_ptr },
46  *       { "test2_name", test2_func_ptr },
47  *       ...
48  *       { 0 }
49  *   };
50  *
51  * The list specifies names of each test (must be unique) and pointer to
52  * a function implementing it. The function does not take any arguments
53  * and has no return values, i.e. every test function has tp be compatible
54  * with this prototype:
55  *
56  *   void test_func(void);
57  */
58 #define TEST_LIST const struct test__ test_list__[]
59 
60 /* Macros for testing whether an unit test succeeds or fails. These macros
61  * can be used arbitrarily in functions implementing the unit tests.
62  *
63  * If any condition fails throughout execution of a test, the test fails.
64  *
65  * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows
66  * also to specify an error message to print out if the condition fails.
67  * (It expects printf-like format string and its parameters). The macros
68  * return non-zero (condition passes) or 0 (condition fails).
69  *
70  * That can be useful when more conditions should be checked only if some
71  * preceding condition passes, as illustrated in this code snippet:
72  *
73  *   SomeStruct* ptr = allocate_some_struct();
74  *   if(TEST_CHECK(ptr != NULL)) {
75  *       TEST_CHECK(ptr->member1 < 100);
76  *       TEST_CHECK(ptr->member2 > 200);
77  *   }
78  */
79 #define TEST_CHECK_(cond, ...)                                                 \
80     test_check__((cond), __FILE__, __LINE__, __VA_ARGS__)
81 #define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond)
82 
83 /**********************
84  *** Implementation ***
85  **********************/
86 
87 /* The unit test files should not rely on anything below. */
88 
89 #include <stdarg.h>
90 #include <stdio.h>
91 #include <stdlib.h>
92 #include <string.h>
93 
94 #if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__)
95 #define CUTEST_UNIX__ 1
96 #include <errno.h>
97 #include <unistd.h>
98 #include <sys/types.h>
99 #include <sys/wait.h>
100 #include <signal.h>
101 #endif
102 
103 #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
104 #define CUTEST_WIN__ 1
105 #include <windows.h>
106 #include <io.h>
107 #endif
108 
109 #ifdef __cplusplus
110 #include <exception>
111 #endif
112 
113 /* Note our global private identifiers end with '__' to mitigate risk of clash
114  * with the unit tests implementation. */
115 
116 #ifdef __cplusplus
117 extern "C" {
118 #endif
119 
120 struct test__ {
121     const char *name;
122     void (*func)(void);
123 };
124 
125 extern const struct test__ test_list__[];
126 
127 int test_check__(int cond, const char *file, int line, const char *fmt, ...);
128 
129 #ifndef TEST_NO_MAIN
130 
131 static char *test_argv0__ = NULL;
132 static int test_count__ = 0;
133 static int test_no_exec__ = 0;
134 static int test_no_summary__ = 0;
135 static int test_skip_mode__ = 0;
136 
137 static int test_stat_failed_units__ = 0;
138 static int test_stat_run_units__ = 0;
139 
140 static const struct test__ *test_current_unit__ = NULL;
141 static int test_current_already_logged__ = 0;
142 static int test_verbose_level__ = 2;
143 static int test_current_failures__ = 0;
144 static int test_colorize__ = 0;
145 
146 #define CUTEST_COLOR_DEFAULT__ 0
147 #define CUTEST_COLOR_GREEN__ 1
148 #define CUTEST_COLOR_RED__ 2
149 #define CUTEST_COLOR_DEFAULT_INTENSIVE__ 3
150 #define CUTEST_COLOR_GREEN_INTENSIVE__ 4
151 #define CUTEST_COLOR_RED_INTENSIVE__ 5
152 
test_print_in_color__(int color,const char * fmt,...)153 static size_t test_print_in_color__(int color, const char *fmt, ...)
154 {
155     va_list args;
156     char buffer[256];
157     size_t n;
158 
159     va_start(args, fmt);
160     vsnprintf(buffer, sizeof(buffer), fmt, args);
161     va_end(args);
162     buffer[sizeof(buffer) - 1] = '\0';
163 
164     if (!test_colorize__) {
165         return printf("%s", buffer);
166     }
167 
168 #if defined CUTEST_UNIX__
169     {
170         const char *col_str;
171         switch (color) {
172         case CUTEST_COLOR_GREEN__:
173             col_str = "\033[0;32m";
174             break;
175         case CUTEST_COLOR_RED__:
176             col_str = "\033[0;31m";
177             break;
178         case CUTEST_COLOR_GREEN_INTENSIVE__:
179             col_str = "\033[1;32m";
180             break;
181         case CUTEST_COLOR_RED_INTENSIVE__:
182             col_str = "\033[1;30m";
183             break;
184         case CUTEST_COLOR_DEFAULT_INTENSIVE__:
185             col_str = "\033[1m";
186             break;
187         default:
188             col_str = "\033[0m";
189             break;
190         }
191         printf("%s", col_str);
192         n = printf("%s", buffer);
193         printf("\033[0m");
194         return n;
195     }
196 #elif defined CUTEST_WIN__
197     {
198         HANDLE h;
199         CONSOLE_SCREEN_BUFFER_INFO info;
200         WORD attr;
201 
202         h = GetStdHandle(STD_OUTPUT_HANDLE);
203         GetConsoleScreenBufferInfo(h, &info);
204 
205         switch (color) {
206         case CUTEST_COLOR_GREEN__:
207             attr = FOREGROUND_GREEN;
208             break;
209         case CUTEST_COLOR_RED__:
210             attr = FOREGROUND_RED;
211             break;
212         case CUTEST_COLOR_GREEN_INTENSIVE__:
213             attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
214             break;
215         case CUTEST_COLOR_RED_INTENSIVE__:
216             attr = FOREGROUND_RED | FOREGROUND_INTENSITY;
217             break;
218         case CUTEST_COLOR_DEFAULT_INTENSIVE__:
219             attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED |
220                    FOREGROUND_INTENSITY;
221             break;
222         default:
223             attr = 0;
224             break;
225         }
226         if (attr != 0)
227             SetConsoleTextAttribute(h, attr);
228         n = printf("%s", buffer);
229         SetConsoleTextAttribute(h, info.wAttributes);
230         return n;
231     }
232 #else
233     n = printf("%s", buffer);
234     return n;
235 #endif
236 }
237 
test_check__(int cond,const char * file,int line,const char * fmt,...)238 int test_check__(int cond, const char *file, int line, const char *fmt, ...)
239 {
240     const char *result_str;
241     int result_color;
242     int verbose_level;
243 
244     if (cond) {
245         result_str = "ok";
246         result_color = CUTEST_COLOR_GREEN__;
247         verbose_level = 3;
248     } else {
249         if (!test_current_already_logged__ && test_current_unit__ != NULL) {
250             printf("[ ");
251             test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, "FAILED");
252             printf(" ]\n");
253         }
254         result_str = "failed";
255         result_color = CUTEST_COLOR_RED__;
256         verbose_level = 2;
257         test_current_failures__++;
258         test_current_already_logged__++;
259     }
260 
261     if (test_verbose_level__ >= verbose_level) {
262         size_t n = 0;
263         va_list args;
264 
265         printf("  ");
266 
267         if (file != NULL)
268             n += printf("%s:%d: Check ", file, line);
269 
270         va_start(args, fmt);
271         n += vprintf(fmt, args);
272         va_end(args);
273 
274         printf("... ");
275         test_print_in_color__(result_color, result_str);
276         printf("\n");
277         test_current_already_logged__++;
278     }
279 
280     return (cond != 0);
281 }
282 
test_list_names__(void)283 static void test_list_names__(void)
284 {
285     const struct test__ *test;
286 
287     printf("Unit tests:\n");
288     for (test = &test_list__[0]; test->func != NULL; test++)
289         printf("  %s\n", test->name);
290 }
291 
test_by_name__(const char * name)292 static const struct test__ *test_by_name__(const char *name)
293 {
294     const struct test__ *test;
295 
296     for (test = &test_list__[0]; test->func != NULL; test++) {
297         if (strcmp(test->name, name) == 0)
298             return test;
299     }
300 
301     return NULL;
302 }
303 
304 /* Call directly the given test unit function. */
test_do_run__(const struct test__ * test)305 static int test_do_run__(const struct test__ *test)
306 {
307     test_current_unit__ = test;
308     test_current_failures__ = 0;
309     test_current_already_logged__ = 0;
310 
311     if (test_verbose_level__ >= 3) {
312         test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n",
313                               test->name);
314         test_current_already_logged__++;
315     } else if (test_verbose_level__ >= 1) {
316         size_t n;
317         char spaces[32];
318 
319         n = test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__,
320                                   "Test %s... ", test->name);
321         memset(spaces, ' ', sizeof(spaces));
322         if (n < sizeof(spaces))
323             printf("%.*s", (int)(sizeof(spaces) - n), spaces);
324     } else {
325         test_current_already_logged__ = 1;
326     }
327 
328 #ifdef __cplusplus
329     try {
330 #endif
331 
332         /* This is good to do for case the test unit e.g. crashes. */
333         fflush(stdout);
334         fflush(stderr);
335 
336         test->func();
337 
338 #ifdef __cplusplus
339     } catch (std::exception &e) {
340         const char *what = e.what();
341         if (what != NULL)
342             test_check__(0, NULL, 0, "Threw std::exception: %s", what);
343         else
344             test_check__(0, NULL, 0, "Threw std::exception");
345     } catch (...) {
346         test_check__(0, NULL, 0, "Threw an exception");
347     }
348 #endif
349 
350     if (test_verbose_level__ >= 3) {
351         switch (test_current_failures__) {
352         case 0:
353             test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__,
354                                   "  All conditions have passed.\n\n");
355             break;
356         case 1:
357             test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__,
358                                   "  One condition has FAILED.\n\n");
359             break;
360         default:
361             test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__,
362                                   "  %d conditions have FAILED.\n\n",
363                                   test_current_failures__);
364             break;
365         }
366     } else if (test_verbose_level__ >= 1 && test_current_failures__ == 0) {
367         printf("[   ");
368         test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__, "OK");
369         printf("   ]\n");
370     }
371 
372     test_current_unit__ = NULL;
373     return (test_current_failures__ == 0) ? 0 : -1;
374 }
375 
376 #if defined(CUTEST_UNIX__) || defined(CUTEST_WIN__)
377 /* Called if anything goes bad in cutest, or if the unit test ends in other
378  * way then by normal returning from its function (e.g. exception or some
379  * abnormal child process termination). */
test_error__(const char * fmt,...)380 static void test_error__(const char *fmt, ...)
381 {
382     va_list args;
383 
384     if (test_verbose_level__ == 0)
385         return;
386 
387     if (test_verbose_level__ <= 2 && !test_current_already_logged__ &&
388         test_current_unit__ != NULL) {
389         printf("[ ");
390         test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, "FAILED");
391         printf(" ]\n");
392     }
393 
394     if (test_verbose_level__ >= 2) {
395         test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, "  Error: ");
396         va_start(args, fmt);
397         vprintf(fmt, args);
398         va_end(args);
399         printf("\n");
400     }
401 }
402 #endif
403 
404 /* Trigger the unit test. If possible (and not suppressed) it starts a child
405  * process who calls test_do_run__(), otherwise it calls test_do_run__()
406  * directly. */
test_run__(const struct test__ * test)407 static void test_run__(const struct test__ *test)
408 {
409     int failed = 1;
410 
411     test_current_unit__ = test;
412     test_current_already_logged__ = 0;
413 
414     if (!test_no_exec__) {
415 #if defined(CUTEST_UNIX__)
416 
417         pid_t pid;
418         int exit_code;
419 
420         pid = fork();
421         if (pid == (pid_t)-1) {
422             test_error__("Cannot fork. %s [%d]", strerror(errno), errno);
423             failed = 1;
424         } else if (pid == 0) {
425             /* Child: Do the test. */
426             failed = (test_do_run__(test) != 0);
427             exit(failed ? 1 : 0);
428         } else {
429             /* Parent: Wait until child terminates and analyze its exit code. */
430             waitpid(pid, &exit_code, 0);
431             if (WIFEXITED(exit_code)) {
432                 switch (WEXITSTATUS(exit_code)) {
433                 case 0:
434                     failed = 0;
435                     break; /* test has passed. */
436                 case 1:    /* noop */
437                     break; /* "normal" failure. */
438                 default:
439                     test_error__("Unexpected exit code [%d]",
440                                  WEXITSTATUS(exit_code));
441                 }
442             } else if (WIFSIGNALED(exit_code)) {
443                 char tmp[32];
444                 const char *signame;
445                 switch (WTERMSIG(exit_code)) {
446                 case SIGINT:
447                     signame = "SIGINT";
448                     break;
449                 case SIGHUP:
450                     signame = "SIGHUP";
451                     break;
452                 case SIGQUIT:
453                     signame = "SIGQUIT";
454                     break;
455                 case SIGABRT:
456                     signame = "SIGABRT";
457                     break;
458                 case SIGKILL:
459                     signame = "SIGKILL";
460                     break;
461                 case SIGSEGV:
462                     signame = "SIGSEGV";
463                     break;
464                 case SIGILL:
465                     signame = "SIGILL";
466                     break;
467                 case SIGTERM:
468                     signame = "SIGTERM";
469                     break;
470                 default:
471                     sprintf(tmp, "signal %d", WTERMSIG(exit_code));
472                     signame = tmp;
473                     break;
474                 }
475                 test_error__("Test interrupted by %s", signame);
476             } else {
477                 test_error__("Test ended in an unexpected way [%d]", exit_code);
478             }
479         }
480 
481 #elif defined(CUTEST_WIN__)
482 
483         char buffer[512] = { 0 };
484         STARTUPINFOA startupInfo = { 0 };
485         PROCESS_INFORMATION processInfo;
486         DWORD exitCode;
487 
488         /* Windows has no fork(). So we propagate all info into the child
489          * through a command line arguments. */
490         _snprintf(buffer, sizeof(buffer) - 1,
491                   "%s --no-exec --no-summary --verbose=%d --color=%s -- \"%s\"",
492                   test_argv0__, test_verbose_level__,
493                   test_colorize__ ? "always" : "never", test->name);
494         startupInfo.cb = sizeof(STARTUPINFO);
495         if (CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL,
496                            &startupInfo, &processInfo)) {
497             WaitForSingleObject(processInfo.hProcess, INFINITE);
498             GetExitCodeProcess(processInfo.hProcess, &exitCode);
499             CloseHandle(processInfo.hThread);
500             CloseHandle(processInfo.hProcess);
501             failed = (exitCode != 0);
502         } else {
503             test_error__("Cannot create unit test subprocess [%ld].",
504                          GetLastError());
505             failed = 1;
506         }
507 
508 #else
509 
510         /* A platform where we don't know how to run child process. */
511         failed = (test_do_run__(test) != 0);
512 
513 #endif
514 
515     } else {
516         /* Child processes suppressed through --no-exec. */
517         failed = (test_do_run__(test) != 0);
518     }
519 
520     test_current_unit__ = NULL;
521 
522     test_stat_run_units__++;
523     if (failed)
524         test_stat_failed_units__++;
525 }
526 
527 #if defined(CUTEST_WIN__)
528 /* Callback for SEH events. */
test_exception_filter__(EXCEPTION_POINTERS * ptrs)529 static LONG CALLBACK test_exception_filter__(EXCEPTION_POINTERS *ptrs)
530 {
531     test_error__("Unhandled SEH exception %08lx at %p.",
532                  ptrs->ExceptionRecord->ExceptionCode,
533                  ptrs->ExceptionRecord->ExceptionAddress);
534     fflush(stdout);
535     fflush(stderr);
536     return EXCEPTION_EXECUTE_HANDLER;
537 }
538 #endif
539 
test_help__(void)540 static void test_help__(void)
541 {
542     printf("Usage: %s [options] [test...]\n", test_argv0__);
543     printf("Run the specified unit tests; or if the option '--skip' is used, "
544            "run all\n");
545     printf("tests in the suite but those listed.  By default, if no tests are "
546            "specified\n");
547     printf("on the command line, all unit tests in the suite are run.\n");
548     printf("\n");
549     printf("Options:\n");
550     printf(
551         "  -s, --skip            Execute all unit tests but the listed ones\n");
552     printf("      --no-exec         Do not execute unit tests as child "
553            "processes\n");
554     printf(
555         "      --no-summary      Suppress printing of test results summary\n");
556     printf("  -l, --list            List unit tests in the suite and exit\n");
557     printf("  -v, --verbose         Enable more verbose output\n");
558     printf("      --verbose=LEVEL   Set verbose level to LEVEL:\n");
559     printf("                          0 ... Be silent\n");
560     printf("                          1 ... Output one line per test (and "
561            "summary)\n");
562     printf("                          2 ... As 1 and failed conditions (this "
563            "is default)\n");
564     printf("                          3 ... As 1 and all conditions (and "
565            "extended summary)\n");
566     printf("      --color=WHEN      Enable colorized output (WHEN is one of "
567            "'auto', 'always', 'never')\n");
568     printf("  -h, --help            Display this help and exit\n");
569     printf("\n");
570     test_list_names__();
571 }
572 
main(int argc,char ** argv)573 int main(int argc, char **argv)
574 {
575     const struct test__ **tests = NULL;
576     int i, j, n = 0;
577     int seen_double_dash = 0;
578 
579     test_argv0__ = argv[0];
580 
581 #if defined CUTEST_UNIX__
582     test_colorize__ = isatty(STDOUT_FILENO);
583 #elif defined CUTEST_WIN__
584     test_colorize__ = _isatty(_fileno(stdout));
585 #else
586     test_colorize__ = 0;
587 #endif
588 
589     /* Parse options */
590     for (i = 1; i < argc; i++) {
591         if (seen_double_dash || argv[i][0] != '-') {
592             tests = (const struct test__ **)realloc(
593                 (void *)tests, (n + 1) * sizeof(const struct test__ *));
594             if (tests == NULL) {
595                 fprintf(stderr, "Out of memory.\n");
596                 exit(2);
597             }
598             tests[n] = test_by_name__(argv[i]);
599             if (tests[n] == NULL) {
600                 fprintf(stderr, "%s: Unrecognized unit test '%s'\n", argv[0],
601                         argv[i]);
602                 fprintf(stderr, "Try '%s --list' for list of unit tests.\n",
603                         argv[0]);
604                 exit(2);
605             }
606             n++;
607         } else if (strcmp(argv[i], "--") == 0) {
608             seen_double_dash = 1;
609         } else if (strcmp(argv[i], "--help") == 0 ||
610                    strcmp(argv[i], "-h") == 0) {
611             test_help__();
612             exit(0);
613         } else if (strcmp(argv[i], "--verbose") == 0 ||
614                    strcmp(argv[i], "-v") == 0) {
615             test_verbose_level__++;
616         } else if (strncmp(argv[i], "--verbose=", 10) == 0) {
617             test_verbose_level__ = atoi(argv[i] + 10);
618         } else if (strcmp(argv[i], "--color=auto") == 0) {
619             /* noop (set from above) */
620         } else if (strcmp(argv[i], "--color=always") == 0 ||
621                    strcmp(argv[i], "--color") == 0) {
622             test_colorize__ = 1;
623         } else if (strcmp(argv[i], "--color=never") == 0) {
624             test_colorize__ = 0;
625         } else if (strcmp(argv[i], "--skip") == 0 ||
626                    strcmp(argv[i], "-s") == 0) {
627             test_skip_mode__ = 1;
628         } else if (strcmp(argv[i], "--no-exec") == 0) {
629             test_no_exec__ = 1;
630         } else if (strcmp(argv[i], "--no-summary") == 0) {
631             test_no_summary__ = 1;
632         } else if (strcmp(argv[i], "--list") == 0 ||
633                    strcmp(argv[i], "-l") == 0) {
634             test_list_names__();
635             exit(0);
636         } else {
637             fprintf(stderr, "%s: Unrecognized option '%s'\n", argv[0], argv[i]);
638             fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
639             exit(2);
640         }
641     }
642 
643 #if defined(CUTEST_WIN__)
644     SetUnhandledExceptionFilter(test_exception_filter__);
645 #endif
646 
647     /* Count all test units */
648     test_count__ = 0;
649     for (i = 0; test_list__[i].func != NULL; i++)
650         test_count__++;
651 
652     /* Run the tests */
653     if (n == 0) {
654         /* Run all tests */
655         for (i = 0; test_list__[i].func != NULL; i++)
656             test_run__(&test_list__[i]);
657     } else if (!test_skip_mode__) {
658         /* Run the listed tests */
659         for (i = 0; i < n; i++)
660             test_run__(tests[i]);
661     } else {
662         /* Run all tests except those listed */
663         for (i = 0; test_list__[i].func != NULL; i++) {
664             int want_skip = 0;
665             for (j = 0; j < n; j++) {
666                 if (tests[j] == &test_list__[i]) {
667                     want_skip = 1;
668                     break;
669                 }
670             }
671             if (!want_skip)
672                 test_run__(&test_list__[i]);
673         }
674     }
675 
676     /* Write a summary */
677     if (!test_no_summary__ && test_verbose_level__ >= 1) {
678         test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__, "\nSummary:\n");
679 
680         if (test_verbose_level__ >= 3) {
681             printf("  Count of all unit tests:     %4d\n", test_count__);
682             printf("  Count of run unit tests:     %4d\n",
683                    test_stat_run_units__);
684             printf("  Count of failed unit tests:  %4d\n",
685                    test_stat_failed_units__);
686             printf("  Count of skipped unit tests: %4d\n",
687                    test_count__ - test_stat_run_units__);
688         }
689 
690         if (test_stat_failed_units__ == 0) {
691             test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__,
692                                   "  SUCCESS: All unit tests have passed.\n");
693         } else {
694             test_print_in_color__(
695                 CUTEST_COLOR_RED_INTENSIVE__,
696                 "  FAILED: %d of %d unit tests have failed.\n",
697                 test_stat_failed_units__, test_stat_run_units__);
698         }
699     }
700 
701     if (tests != NULL)
702         free((void *)tests);
703 
704     return (test_stat_failed_units__ == 0) ? 0 : 1;
705 }
706 
707 #endif /* #ifndef TEST_NO_MAIN */
708 
709 #ifdef __cplusplus
710 } /* extern "C" */
711 #endif
712 
713 #endif /* #ifndef CUTEST_H__ */
714