/* Stupidly simple test framework Copyright (C) 2001-2002, 2004, 2005, 2007, Joe Orton 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 2 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "config.h" #include #include #include #ifdef HAVE_SIGNAL_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_LOCALE_H #include #endif #include "ne_string.h" #include "ne_utils.h" #include "ne_socket.h" #include "ne_i18n.h" #include "tests.h" #include "child.h" char test_context[BUFSIZ]; int have_context = 0; static FILE *child_debug, *debug; char **test_argv; int test_argc; const char *test_suite; int test_num; /* statistics for all tests so far */ static int passes = 0, fails = 0, skipped = 0, warnings = 0; /* per-test globals: */ static int warned, aborted = 0; static const char *test_name; /* current test name */ static int use_colour = 0; static int flag_child; /* resource for ANSI escape codes: * http://www.isthe.com/chongo/tech/comp/ansi_escapes.html */ #define COL(x) do { if (use_colour) printf("\033[" x "m"); } while (0) #define NOCOL COL("00") void t_context(const char *context, ...) { va_list ap; va_start(ap, context); ne_vsnprintf(test_context, BUFSIZ, context, ap); va_end(ap); if (flag_child) { NE_DEBUG(NE_DBG_HTTP, "context: %s\n", test_context); } have_context = 1; } void t_warning(const char *str, ...) { va_list ap; COL("43;01"); printf("WARNING:"); NOCOL; putchar(' '); va_start(ap, str); vprintf(str, ap); va_end(ap); warnings++; warned++; putchar('\n'); } #define TEST_DEBUG \ (NE_DBG_HTTP | NE_DBG_SOCKET | NE_DBG_HTTPBODY | NE_DBG_HTTPAUTH | \ NE_DBG_LOCKS | NE_DBG_XMLPARSE | NE_DBG_XML | NE_DBG_SSL | \ NE_DBG_HTTPPLAIN) #define W(m) do { if (write(0, m, strlen(m)) < 0) exit(99); } while(0) #define W_RED(m) do { if (use_colour) W("\033[41;37;01m"); \ W(m); if (use_colour) W("\033[00m\n"); } while (0); /* Signal handler for child processes. */ static void child_segv(int signo) { signal(SIGSEGV, SIG_DFL); signal(SIGABRT, SIG_DFL); W_RED("Fatal signal in child!"); kill(getpid(), SIGSEGV); minisleep(); } /* Signal handler for parent process. */ static void parent_segv(int signo) { signal(SIGSEGV, SIG_DFL); signal(SIGABRT, SIG_DFL); if (signo == SIGSEGV) { W_RED("FAILED - segmentation fault"); } else if (signo == SIGABRT) { W_RED("ABORTED"); } reap_server(); kill(getpid(), SIGSEGV); minisleep(); } void in_child(void) { ne_debug_init(child_debug, TEST_DEBUG); NE_DEBUG(TEST_DEBUG, "**** Child forked for test %s ****\n", test_name); signal(SIGSEGV, child_segv); signal(SIGABRT, child_segv); flag_child = 1; } int main(int argc, char *argv[]) { int n; static const char dots[] = "......................"; /* get basename(argv[0]) */ test_suite = strrchr(argv[0], '/'); if (test_suite == NULL) { test_suite = argv[0]; } else { test_suite++; } #ifdef HAVE_SETLOCALE setlocale(LC_MESSAGES, ""); #endif ne_i18n_init(NULL); #if defined(HAVE_ISATTY) && defined(STDOUT_FILENO) if (isatty(STDOUT_FILENO)) { use_colour = 1; } #endif test_argc = argc; test_argv = argv; debug = fopen("debug.log", "a"); if (debug == NULL) { fprintf(stderr, "%s: Could not open debug.log: %s\n", test_suite, strerror(errno)); return -1; } child_debug = fopen("child.log", "a"); if (child_debug == NULL) { fprintf(stderr, "%s: Could not open child.log: %s\n", test_suite, strerror(errno)); fclose(debug); return -1; } if (tests[0].fn == NULL) { printf("-> no tests found in `%s'\n", test_suite); return -1; } /* install special SEGV handler. */ signal(SIGSEGV, parent_segv); signal(SIGABRT, parent_segv); /* test the "no-debugging" mode of ne_debug. */ ne_debug_init(NULL, 0); NE_DEBUG(TEST_DEBUG, "This message should go to /dev/null"); /* enable debugging for real. */ ne_debug_init(debug, TEST_DEBUG); NE_DEBUG(TEST_DEBUG | NE_DBG_FLUSH, "Version string: %s\n", ne_version_string()); /* another silly test. */ NE_DEBUG(0, "This message should also go to /dev/null"); if (ne_sock_init()) { COL("43;01"); printf("WARNING:"); NOCOL; printf(" Socket library initalization failed.\n"); } printf("-> running `%s':\n", test_suite); for (n = 0; !aborted && tests[n].fn != NULL; n++) { int result, is_xfail = 0; #ifdef NEON_MEMLEAK size_t allocated = ne_alloc_used; int is_xleaky = 0; #endif test_name = tests[n].name; printf("%2d. %s%.*s ", n, test_name, (int) (strlen(dots) - strlen(test_name)), dots); have_context = 0; test_num = n; warned = 0; fflush(stdout); NE_DEBUG(TEST_DEBUG, "******* Running test %d: %s ********\n", n, test_name); /* run the test. */ result = tests[n].fn(); #ifdef NEON_MEMLEAK /* issue warnings for memory leaks, if requested */ if ((tests[n].flags & T_CHECK_LEAKS) && result == OK && ne_alloc_used > allocated) { t_context("memory leak of %" NE_FMT_SIZE_T " bytes", ne_alloc_used - allocated); fprintf(debug, "Blocks leaked: "); ne_alloc_dump(debug); result = FAIL; } else if (tests[n].flags & T_EXPECT_LEAKS && result == OK && ne_alloc_used == allocated) { t_context("expected memory leak not detected"); result = FAIL; } else if (tests[n].flags & T_EXPECT_LEAKS && result == OK) { fprintf(debug, "Blocks leaked (expected): "); ne_alloc_dump(debug); is_xleaky = 1; } #endif if (tests[n].flags & T_EXPECT_FAIL) { if (result == OK) { t_context("test passed but expected failure"); result = FAIL; } else if (result == FAIL) { result = OK; is_xfail = 1; } } /* align the result column if we've had warnings. */ if (warned) { printf(" %s ", dots); } switch (result) { case OK: if (is_xfail) { COL("32;07"); printf("xfail"); } else { COL("32"); printf("pass"); } NOCOL; if (warned) { printf(" (with %d warning%s)", warned, (warned > 1)?"s":""); } #ifdef NEON_MEMLEAK if (is_xleaky) { printf(" (with expected leak, %" NE_FMT_SIZE_T " bytes)", ne_alloc_used - allocated); } #endif putchar('\n'); passes++; break; case FAILHARD: aborted = 1; /* fall-through */ case FAIL: COL("41;37;01"); printf("FAIL"); NOCOL; if (have_context) { printf(" (%s)", test_context); } putchar('\n'); fails++; break; case SKIPREST: aborted = 1; /* fall-through */ case SKIP: COL("44;37;01"); printf("SKIPPED"); NOCOL; if (have_context) { printf(" (%s)", test_context); } putchar('\n'); skipped++; break; default: COL("41;37;01"); printf("OOPS"); NOCOL; printf(" unexpected test result `%d'\n", result); break; } reap_server(); } /* discount skipped tests */ if (skipped) { printf("-> %d %s.\n", skipped, skipped==1?"test was skipped":"tests were skipped"); n -= skipped; if (passes + fails != n) { printf("-> ARGH! Number of test results does not match " "number of tests.\n" "-> ARGH! Test Results are INRELIABLE.\n"); } } /* print the summary. */ if (skipped && n == 0) { printf("<- all tests skipped for `%s'.\n", test_suite); } else { printf("<- summary for `%s': " "of %d tests run: %d passed, %d failed. %.1f%%\n", test_suite, n, passes, fails, 100*(float)passes/n); if (warnings) { printf("-> %d warning%s issued.\n", warnings, warnings==1?" was":"s were"); } } if (fclose(debug)) { fprintf(stderr, "Error closing debug.log: %s\n", strerror(errno)); fails = 1; } if (fclose(child_debug)) { fprintf(stderr, "Error closing child.log: %s\n", strerror(errno)); fails = 1; } ne_sock_exit(); return fails; }