michael@0: /* michael@0: ******************************************************************************** michael@0: * michael@0: * Copyright (C) 1996-2013, International Business Machines michael@0: * Corporation and others. All Rights Reserved. michael@0: * michael@0: ******************************************************************************** michael@0: */ michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "unicode/utrace.h" michael@0: #include "unicode/uclean.h" michael@0: #include "putilimp.h" michael@0: #include "udbgutil.h" michael@0: michael@0: /* NOTES: michael@0: 3/20/1999 srl - strncpy called w/o setting nulls at the end michael@0: */ michael@0: michael@0: #define MAXTESTNAME 128 michael@0: #define MAXTESTS 512 michael@0: #define MAX_TEST_LOG 4096 michael@0: michael@0: /** michael@0: * How may columns to indent the 'OK' markers. michael@0: */ michael@0: #define FLAG_INDENT 45 michael@0: /** michael@0: * How many lines of scrollage can go by before we need to remind the user what the test is. michael@0: */ michael@0: #define PAGE_SIZE_LIMIT 25 michael@0: michael@0: #ifndef SHOW_TIMES michael@0: #define SHOW_TIMES 1 michael@0: #endif michael@0: michael@0: struct TestNode michael@0: { michael@0: void (*test)(void); michael@0: struct TestNode* sibling; michael@0: struct TestNode* child; michael@0: char name[1]; /* This is dynamically allocated off the end with malloc. */ michael@0: }; michael@0: michael@0: michael@0: static const struct TestNode* currentTest; michael@0: michael@0: typedef enum { RUNTESTS, SHOWTESTS } TestMode; michael@0: #define TEST_SEPARATOR '/' michael@0: michael@0: #ifndef C_TEST_IMPL michael@0: #define C_TEST_IMPL michael@0: #endif michael@0: michael@0: #include "unicode/ctest.h" michael@0: michael@0: static char ERROR_LOG[MAX_TEST_LOG][MAXTESTNAME]; michael@0: michael@0: /* Local prototypes */ michael@0: static TestNode* addTestNode( TestNode *root, const char *name ); michael@0: michael@0: static TestNode *createTestNode(const char* name, int32_t nameLen); michael@0: michael@0: static int strncmp_nullcheck( const char* s1, michael@0: const char* s2, michael@0: int n ); michael@0: michael@0: static void getNextLevel( const char* name, michael@0: int* nameLen, michael@0: const char** nextName ); michael@0: michael@0: static void iterateTestsWithLevel( const TestNode *root, int depth, michael@0: const TestNode** nodeList, michael@0: TestMode mode); michael@0: michael@0: static void help ( const char *argv0 ); michael@0: michael@0: /** michael@0: * Do the work of logging an error. Doesn't increase the error count. michael@0: * michael@0: * @prefix optional prefix prepended to message, or NULL. michael@0: * @param pattern printf style pattern michael@0: * @param ap vprintf style arg list michael@0: */ michael@0: static void vlog_err(const char *prefix, const char *pattern, va_list ap); michael@0: static void vlog_verbose(const char *prefix, const char *pattern, va_list ap); michael@0: static UBool vlog_knownIssue(const char *ticket, const char *pattern, va_list ap); michael@0: michael@0: /** michael@0: * Log test structure, with indent michael@0: * @param pattern printf pattern michael@0: */ michael@0: static void log_testinfo_i(const char *pattern, ...); michael@0: michael@0: /** michael@0: * Log test structure, NO indent michael@0: * @param pattern printf pattern michael@0: */ michael@0: static void log_testinfo(const char *pattern, ...); michael@0: michael@0: /* If we need to make the framework multi-thread safe michael@0: we need to pass around the following vars michael@0: */ michael@0: static int ERRONEOUS_FUNCTION_COUNT = 0; michael@0: static int ERROR_COUNT = 0; /* Count of errors from all tests. */ michael@0: static int ONE_ERROR = 0; /* were there any other errors? */ michael@0: static int DATA_ERROR_COUNT = 0; /* count of data related errors or warnings */ michael@0: static int INDENT_LEVEL = 0; michael@0: static UBool NO_KNOWN = FALSE; michael@0: static void *knownList = NULL; michael@0: static char gTestName[1024] = ""; michael@0: static UBool ON_LINE = FALSE; /* are we on the top line with our test name? */ michael@0: static UBool HANGING_OUTPUT = FALSE; /* did the user leave us without a trailing \n ? */ michael@0: static int GLOBAL_PRINT_COUNT = 0; /* global count of printouts */ michael@0: int REPEAT_TESTS_INIT = 0; /* Was REPEAT_TESTS initialized? */ michael@0: int REPEAT_TESTS = 1; /* Number of times to run the test */ michael@0: int VERBOSITY = 0; /* be No-verbose by default */ michael@0: int ERR_MSG =1; /* error messages will be displayed by default*/ michael@0: int QUICK = 1; /* Skip some of the slower tests? */ michael@0: int WARN_ON_MISSING_DATA = 0; /* Reduce data errs to warnings? */ michael@0: UTraceLevel ICU_TRACE = UTRACE_OFF; /* ICU tracing level */ michael@0: size_t MINIMUM_MEMORY_SIZE_FAILURE = (size_t)-1; /* Minimum library memory allocation window that will fail. */ michael@0: size_t MAXIMUM_MEMORY_SIZE_FAILURE = (size_t)-1; /* Maximum library memory allocation window that will fail. */ michael@0: static const char *ARGV_0 = "[ALL]"; michael@0: static const char *XML_FILE_NAME=NULL; michael@0: static char XML_PREFIX[256]; michael@0: static const char *SUMMARY_FILE = NULL; michael@0: FILE *XML_FILE = NULL; michael@0: /*-------------------------------------------*/ michael@0: michael@0: /* strncmp that also makes sure there's a \0 at s2[0] */ michael@0: static int strncmp_nullcheck( const char* s1, michael@0: const char* s2, michael@0: int n ) michael@0: { michael@0: if (((int)strlen(s2) >= n) && s2[n] != 0) { michael@0: return 3; /* null check fails */ michael@0: } michael@0: else { michael@0: return strncmp ( s1, s2, n ); michael@0: } michael@0: } michael@0: michael@0: static void getNextLevel( const char* name, michael@0: int* nameLen, michael@0: const char** nextName ) michael@0: { michael@0: /* Get the next component of the name */ michael@0: *nextName = strchr(name, TEST_SEPARATOR); michael@0: michael@0: if( *nextName != 0 ) michael@0: { michael@0: char n[255]; michael@0: *nameLen = (int)((*nextName) - name); michael@0: (*nextName)++; /* skip '/' */ michael@0: strncpy(n, name, *nameLen); michael@0: n[*nameLen] = 0; michael@0: /*printf("->%s-< [%d] -> [%s]\n", name, *nameLen, *nextName);*/ michael@0: } michael@0: else { michael@0: *nameLen = (int)strlen(name); michael@0: } michael@0: } michael@0: michael@0: static TestNode *createTestNode(const char* name, int32_t nameLen) michael@0: { michael@0: TestNode *newNode; michael@0: michael@0: newNode = (TestNode*)malloc(sizeof(TestNode) + (nameLen + 1)); michael@0: michael@0: newNode->test = NULL; michael@0: newNode->sibling = NULL; michael@0: newNode->child = NULL; michael@0: michael@0: strncpy( newNode->name, name, nameLen ); michael@0: newNode->name[nameLen] = 0; michael@0: michael@0: return newNode; michael@0: } michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: cleanUpTestTree(TestNode *tn) michael@0: { michael@0: if(tn->child != NULL) { michael@0: cleanUpTestTree(tn->child); michael@0: } michael@0: if(tn->sibling != NULL) { michael@0: cleanUpTestTree(tn->sibling); michael@0: } michael@0: michael@0: free(tn); michael@0: michael@0: } michael@0: michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: addTest(TestNode** root, michael@0: TestFunctionPtr test, michael@0: const char* name ) michael@0: { michael@0: TestNode *newNode; michael@0: michael@0: /*if this is the first Test created*/ michael@0: if (*root == NULL) michael@0: *root = createTestNode("", 0); michael@0: michael@0: newNode = addTestNode( *root, name ); michael@0: assert(newNode != 0 ); michael@0: /* printf("addTest: nreName = %s\n", newNode->name );*/ michael@0: michael@0: newNode->test = test; michael@0: } michael@0: michael@0: /* non recursive insert function */ michael@0: static TestNode *addTestNode ( TestNode *root, const char *name ) michael@0: { michael@0: const char* nextName; michael@0: TestNode *nextNode, *curNode; michael@0: int nameLen; /* length of current 'name' */ michael@0: michael@0: /* remove leading slash */ michael@0: if ( *name == TEST_SEPARATOR ) michael@0: name++; michael@0: michael@0: curNode = root; michael@0: michael@0: for(;;) michael@0: { michael@0: /* Start with the next child */ michael@0: nextNode = curNode->child; michael@0: michael@0: getNextLevel ( name, &nameLen, &nextName ); michael@0: michael@0: /* printf("* %s\n", name );*/ michael@0: michael@0: /* if nextNode is already null, then curNode has no children michael@0: -- add them */ michael@0: if( nextNode == NULL ) michael@0: { michael@0: /* Add all children of the node */ michael@0: do michael@0: { michael@0: /* Get the next component of the name */ michael@0: getNextLevel(name, &nameLen, &nextName); michael@0: michael@0: /* update curName to have the next name segment */ michael@0: curNode->child = createTestNode(name, nameLen); michael@0: /* printf("*** added %s\n", curNode->child->name );*/ michael@0: curNode = curNode->child; michael@0: name = nextName; michael@0: } michael@0: while( name != NULL ); michael@0: michael@0: return curNode; michael@0: } michael@0: michael@0: /* Search across for the name */ michael@0: while (strncmp_nullcheck ( name, nextNode->name, nameLen) != 0 ) michael@0: { michael@0: curNode = nextNode; michael@0: nextNode = nextNode -> sibling; michael@0: michael@0: if ( nextNode == NULL ) michael@0: { michael@0: /* Did not find 'name' on this level. */ michael@0: nextNode = createTestNode(name, nameLen); michael@0: curNode->sibling = nextNode; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* nextNode matches 'name' */ michael@0: michael@0: if (nextName == NULL) /* end of the line */ michael@0: { michael@0: return nextNode; michael@0: } michael@0: michael@0: /* Loop again with the next item */ michael@0: name = nextName; michael@0: curNode = nextNode; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Log the time taken. May not output anything. michael@0: * @param deltaTime change in time michael@0: */ michael@0: void T_CTEST_EXPORT2 str_timeDelta(char *str, UDate deltaTime) { michael@0: if (deltaTime > 110000.0 ) { michael@0: double mins = uprv_floor(deltaTime/60000.0); michael@0: sprintf(str, "[(%.0fm %.1fs)]", mins, (deltaTime-(mins*60000.0))/1000.0); michael@0: } else if (deltaTime > 1500.0) { michael@0: sprintf(str, "((%.1fs))", deltaTime/1000.0); michael@0: } else if(deltaTime>900.0) { michael@0: sprintf(str, "( %.2fs )", deltaTime/1000.0); michael@0: } else if(deltaTime > 5.0) { michael@0: sprintf(str, " (%.0fms) ", deltaTime); michael@0: } else { michael@0: str[0]=0; /* at least terminate it. */ michael@0: } michael@0: } michael@0: michael@0: static void print_timeDelta(UDate deltaTime) { michael@0: char str[256]; michael@0: str_timeDelta(str, deltaTime); michael@0: if(str[0]) { michael@0: printf("%s", str); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Run or list tests (according to mode) in a subtree. michael@0: * michael@0: * @param root root of the subtree to operate on michael@0: * @param depth The depth of this tree (0=root) michael@0: * @param nodeList an array of MAXTESTS depth that's used for keeping track of where we are. nodeList[depth] points to the 'parent' at depth depth. michael@0: * @param mode what mode we are operating in. michael@0: */ michael@0: static void iterateTestsWithLevel ( const TestNode* root, michael@0: int depth, michael@0: const TestNode** nodeList, michael@0: TestMode mode) michael@0: { michael@0: int i; michael@0: michael@0: char pathToFunction[MAXTESTNAME] = ""; michael@0: char separatorString[2] = { TEST_SEPARATOR, '\0'}; michael@0: #if SHOW_TIMES michael@0: UDate allStartTime = -1, allStopTime = -1; michael@0: #endif michael@0: michael@0: if(depth<2) { michael@0: allStartTime = uprv_getRawUTCtime(); michael@0: } michael@0: michael@0: if ( root == NULL ) michael@0: return; michael@0: michael@0: /* record the current root node, and increment depth. */ michael@0: nodeList[depth++] = root; michael@0: /* depth is now the depth of root's children. */ michael@0: michael@0: /* Collect the 'path' to the current subtree. */ michael@0: for ( i=0;i<(depth-1);i++ ) michael@0: { michael@0: strcat(pathToFunction, nodeList[i]->name); michael@0: strcat(pathToFunction, separatorString); michael@0: } michael@0: strcat(pathToFunction, nodeList[i]->name); /* including 'root' */ michael@0: michael@0: /* print test name and space. */ michael@0: INDENT_LEVEL = depth-1; michael@0: if(root->name[0]) { michael@0: log_testinfo_i("%s ", root->name); michael@0: } else { michael@0: log_testinfo_i("(%s) ", ARGV_0); michael@0: } michael@0: ON_LINE = TRUE; /* we are still on the line with the test name */ michael@0: michael@0: michael@0: if ( (mode == RUNTESTS) && michael@0: (root->test != NULL)) /* if root is a leaf node, run it */ michael@0: { michael@0: int myERROR_COUNT = ERROR_COUNT; michael@0: int myGLOBAL_PRINT_COUNT = GLOBAL_PRINT_COUNT; michael@0: #if SHOW_TIMES michael@0: UDate startTime, stopTime; michael@0: char timeDelta[256]; michael@0: char timeSeconds[256]; michael@0: #else michael@0: const char timeDelta[] = "(unknown)"; michael@0: const char timeSeconds[] = "0.000"; michael@0: #endif michael@0: currentTest = root; michael@0: INDENT_LEVEL = depth; /* depth of subitems */ michael@0: ONE_ERROR=0; michael@0: HANGING_OUTPUT=FALSE; michael@0: #if SHOW_TIMES michael@0: startTime = uprv_getRawUTCtime(); michael@0: #endif michael@0: strcpy(gTestName, pathToFunction); michael@0: root->test(); /* PERFORM THE TEST ************************/ michael@0: #if SHOW_TIMES michael@0: stopTime = uprv_getRawUTCtime(); michael@0: #endif michael@0: if(HANGING_OUTPUT) { michael@0: log_testinfo("\n"); michael@0: HANGING_OUTPUT=FALSE; michael@0: } michael@0: INDENT_LEVEL = depth-1; /* depth of root */ michael@0: currentTest = NULL; michael@0: if((ONE_ERROR>0)&&(ERROR_COUNT==0)) { michael@0: ERROR_COUNT++; /* There was an error without a newline */ michael@0: } michael@0: ONE_ERROR=0; michael@0: michael@0: #if SHOW_TIMES michael@0: str_timeDelta(timeDelta, stopTime-startTime); michael@0: sprintf(timeSeconds, "%f", (stopTime-startTime)/1000.0); michael@0: #endif michael@0: ctest_xml_testcase(pathToFunction, pathToFunction, timeSeconds, (myERROR_COUNT!=ERROR_COUNT)?"error":NULL); michael@0: michael@0: if (myERROR_COUNT != ERROR_COUNT) { michael@0: log_testinfo_i("} ---[%d ERRORS in %s] ", ERROR_COUNT - myERROR_COUNT, pathToFunction); michael@0: strcpy(ERROR_LOG[ERRONEOUS_FUNCTION_COUNT++], pathToFunction); michael@0: } else { michael@0: if(!ON_LINE) { /* had some output */ michael@0: int spaces = FLAG_INDENT-(depth-1); michael@0: log_testinfo_i("} %*s[OK] ", spaces, "---"); michael@0: if((GLOBAL_PRINT_COUNT-myGLOBAL_PRINT_COUNT)>PAGE_SIZE_LIMIT) { michael@0: log_testinfo(" %s ", pathToFunction); /* in case they forgot. */ michael@0: } michael@0: } else { michael@0: /* put -- out at 30 sp. */ michael@0: int spaces = FLAG_INDENT-(strlen(root->name)+depth); michael@0: if(spaces<0) spaces=0; michael@0: log_testinfo(" %*s[OK] ", spaces,"---"); michael@0: } michael@0: } michael@0: michael@0: #if SHOW_TIMES michael@0: if(timeDelta[0]) printf("%s", timeDelta); michael@0: #endif michael@0: michael@0: ON_LINE = TRUE; /* we are back on-line */ michael@0: } michael@0: michael@0: INDENT_LEVEL = depth-1; /* root */ michael@0: michael@0: /* we want these messages to be at 0 indent. so just push the indent level breifly. */ michael@0: if(mode==SHOWTESTS) { michael@0: log_testinfo("---%s%c\n",pathToFunction, nodeList[i]->test?' ':TEST_SEPARATOR ); michael@0: } michael@0: michael@0: INDENT_LEVEL = depth; michael@0: michael@0: if(root->child) { michael@0: int myERROR_COUNT = ERROR_COUNT; michael@0: int myGLOBAL_PRINT_COUNT = GLOBAL_PRINT_COUNT; michael@0: if(mode!=SHOWTESTS) { michael@0: INDENT_LEVEL=depth-1; michael@0: log_testinfo("{\n"); michael@0: INDENT_LEVEL=depth; michael@0: } michael@0: michael@0: iterateTestsWithLevel ( root->child, depth, nodeList, mode ); michael@0: michael@0: if(mode!=SHOWTESTS) { michael@0: INDENT_LEVEL=depth-1; michael@0: log_testinfo_i("} "); /* TODO: summarize subtests */ michael@0: if((depth>1) && (ERROR_COUNT > myERROR_COUNT)) { michael@0: log_testinfo("[%d %s in %s] ", ERROR_COUNT-myERROR_COUNT, (ERROR_COUNT-myERROR_COUNT)==1?"error":"errors", pathToFunction); michael@0: } else if((GLOBAL_PRINT_COUNT-myGLOBAL_PRINT_COUNT)>PAGE_SIZE_LIMIT || (depth<1)) { michael@0: if(pathToFunction[0]) { michael@0: log_testinfo(" %s ", pathToFunction); /* in case they forgot. */ michael@0: } else { michael@0: log_testinfo(" / (%s) ", ARGV_0); michael@0: } michael@0: } michael@0: michael@0: ON_LINE=TRUE; michael@0: } michael@0: } michael@0: depth--; michael@0: michael@0: #if SHOW_TIMES michael@0: if(depth<2) { michael@0: allStopTime = uprv_getRawUTCtime(); michael@0: print_timeDelta(allStopTime-allStartTime); michael@0: } michael@0: #endif michael@0: michael@0: if(mode!=SHOWTESTS && ON_LINE) { michael@0: log_testinfo("\n"); michael@0: } michael@0: michael@0: if ( depth != 0 ) { /* DO NOT iterate over siblings of the root. TODO: why not? */ michael@0: iterateTestsWithLevel ( root->sibling, depth, nodeList, mode ); michael@0: } michael@0: } michael@0: michael@0: michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: showTests ( const TestNode *root ) michael@0: { michael@0: /* make up one for them */ michael@0: const TestNode *nodeList[MAXTESTS]; michael@0: michael@0: if (root == NULL) michael@0: log_err("TEST CAN'T BE FOUND!"); michael@0: michael@0: iterateTestsWithLevel ( root, 0, nodeList, SHOWTESTS ); michael@0: michael@0: } michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: runTests ( const TestNode *root ) michael@0: { michael@0: int i; michael@0: const TestNode *nodeList[MAXTESTS]; michael@0: /* make up one for them */ michael@0: michael@0: michael@0: if (root == NULL) michael@0: log_err("TEST CAN'T BE FOUND!\n"); michael@0: michael@0: ERRONEOUS_FUNCTION_COUNT = ERROR_COUNT = 0; michael@0: iterateTestsWithLevel ( root, 0, nodeList, RUNTESTS ); michael@0: michael@0: /*print out result summary*/ michael@0: michael@0: ON_LINE=FALSE; /* just in case */ michael@0: michael@0: if(knownList != NULL) { michael@0: if( udbg_knownIssue_print(knownList) ) { michael@0: fprintf(stdout, "(To run suppressed tests, use the -K option.) \n\n"); michael@0: } michael@0: udbg_knownIssue_close(knownList); michael@0: } michael@0: michael@0: if (ERROR_COUNT) michael@0: { michael@0: fprintf(stdout,"\nSUMMARY:\n"); michael@0: fflush(stdout); michael@0: fprintf(stdout,"******* [Total error count:\t%d]\n", ERROR_COUNT); michael@0: fflush(stdout); michael@0: fprintf(stdout, " Errors in\n"); michael@0: for (i=0;i < ERRONEOUS_FUNCTION_COUNT; i++) michael@0: fprintf(stdout, "[%s]\n",ERROR_LOG[i]); michael@0: if(SUMMARY_FILE != NULL) { michael@0: FILE *summf = fopen(SUMMARY_FILE, "w"); michael@0: if(summf!=NULL) { michael@0: for (i=0;i < ERRONEOUS_FUNCTION_COUNT; i++) michael@0: fprintf(summf, "%s\n",ERROR_LOG[i]); michael@0: fclose(summf); michael@0: } michael@0: } michael@0: } michael@0: else michael@0: { michael@0: log_testinfo("\n[All tests passed successfully...]\n"); michael@0: } michael@0: michael@0: if(DATA_ERROR_COUNT) { michael@0: if(WARN_ON_MISSING_DATA==0) { michael@0: log_testinfo("\t*Note* some errors are data-loading related. If the data used is not the \n" michael@0: "\tstock ICU data (i.e some have been added or removed), consider using\n" michael@0: "\tthe '-w' option to turn these errors into warnings.\n"); michael@0: } else { michael@0: log_testinfo("\t*WARNING* some data-loading errors were ignored by the -w option.\n"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: const char* T_CTEST_EXPORT2 michael@0: getTestName(void) michael@0: { michael@0: if(currentTest != NULL) { michael@0: return currentTest->name; michael@0: } else { michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: const TestNode* T_CTEST_EXPORT2 michael@0: getTest(const TestNode* root, const char* name) michael@0: { michael@0: const char* nextName; michael@0: TestNode *nextNode; michael@0: const TestNode* curNode; michael@0: int nameLen; /* length of current 'name' */ michael@0: michael@0: if (root == NULL) { michael@0: log_err("TEST CAN'T BE FOUND!\n"); michael@0: return NULL; michael@0: } michael@0: /* remove leading slash */ michael@0: if ( *name == TEST_SEPARATOR ) michael@0: name++; michael@0: michael@0: curNode = root; michael@0: michael@0: for(;;) michael@0: { michael@0: /* Start with the next child */ michael@0: nextNode = curNode->child; michael@0: michael@0: getNextLevel ( name, &nameLen, &nextName ); michael@0: michael@0: /* printf("* %s\n", name );*/ michael@0: michael@0: /* if nextNode is already null, then curNode has no children michael@0: -- add them */ michael@0: if( nextNode == NULL ) michael@0: { michael@0: return NULL; michael@0: } michael@0: michael@0: /* Search across for the name */ michael@0: while (strncmp_nullcheck ( name, nextNode->name, nameLen) != 0 ) michael@0: { michael@0: curNode = nextNode; michael@0: nextNode = nextNode -> sibling; michael@0: michael@0: if ( nextNode == NULL ) michael@0: { michael@0: /* Did not find 'name' on this level. */ michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: /* nextNode matches 'name' */ michael@0: michael@0: if (nextName == NULL) /* end of the line */ michael@0: { michael@0: return nextNode; michael@0: } michael@0: michael@0: /* Loop again with the next item */ michael@0: name = nextName; michael@0: curNode = nextNode; michael@0: } michael@0: } michael@0: michael@0: /* =========== io functions ======== */ michael@0: michael@0: static void go_offline_with_marker(const char *mrk) { michael@0: UBool wasON_LINE = ON_LINE; michael@0: michael@0: if(ON_LINE) { michael@0: log_testinfo(" {\n"); michael@0: ON_LINE=FALSE; michael@0: } michael@0: michael@0: if(!HANGING_OUTPUT || wasON_LINE) { michael@0: if(mrk != NULL) { michael@0: fputs(mrk, stdout); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void go_offline() { michael@0: go_offline_with_marker(NULL); michael@0: } michael@0: michael@0: static void go_offline_err() { michael@0: go_offline(); michael@0: } michael@0: michael@0: static void first_line_verbose() { michael@0: go_offline_with_marker("v"); michael@0: } michael@0: michael@0: static void first_line_err() { michael@0: go_offline_with_marker("!"); michael@0: } michael@0: michael@0: static void first_line_info() { michael@0: go_offline_with_marker("\""); michael@0: } michael@0: michael@0: static void first_line_test() { michael@0: fputs(" ", stdout); michael@0: } michael@0: michael@0: michael@0: static void vlog_err(const char *prefix, const char *pattern, va_list ap) michael@0: { michael@0: if( ERR_MSG == FALSE){ michael@0: return; michael@0: } michael@0: fputs("!", stdout); /* col 1 - bang */ michael@0: fprintf(stdout, "%-*s", INDENT_LEVEL,"" ); michael@0: if(prefix) { michael@0: fputs(prefix, stdout); michael@0: } michael@0: vfprintf(stdout, pattern, ap); michael@0: fflush(stdout); michael@0: va_end(ap); michael@0: if((*pattern==0) || (pattern[strlen(pattern)-1]!='\n')) { michael@0: HANGING_OUTPUT=1; michael@0: } else { michael@0: HANGING_OUTPUT=0; michael@0: } michael@0: GLOBAL_PRINT_COUNT++; michael@0: } michael@0: michael@0: static UBool vlog_knownIssue(const char *ticket, const char *pattern, va_list ap) michael@0: { michael@0: char buf[2048]; michael@0: UBool firstForTicket; michael@0: UBool firstForWhere; michael@0: michael@0: if(NO_KNOWN) return FALSE; michael@0: if(pattern==NULL) pattern=""; michael@0: michael@0: vsprintf(buf, pattern, ap); michael@0: knownList = udbg_knownIssue_open(knownList, ticket, gTestName, buf, michael@0: &firstForTicket, &firstForWhere); michael@0: michael@0: if(firstForTicket || firstForWhere) { michael@0: log_info("(Known issue #%s) %s", ticket, buf); michael@0: } else { michael@0: log_verbose("(Known issue #%s) %s", ticket, buf); michael@0: } michael@0: michael@0: return TRUE; michael@0: } michael@0: michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: vlog_info(const char *prefix, const char *pattern, va_list ap) michael@0: { michael@0: first_line_info(); michael@0: fprintf(stdout, "%-*s", INDENT_LEVEL,"" ); michael@0: if(prefix) { michael@0: fputs(prefix, stdout); michael@0: } michael@0: vfprintf(stdout, pattern, ap); michael@0: fflush(stdout); michael@0: va_end(ap); michael@0: if((*pattern==0) || (pattern[strlen(pattern)-1]!='\n')) { michael@0: HANGING_OUTPUT=1; michael@0: } else { michael@0: HANGING_OUTPUT=0; michael@0: } michael@0: GLOBAL_PRINT_COUNT++; michael@0: } michael@0: /** michael@0: * Log test structure, with indent michael@0: */ michael@0: static void log_testinfo_i(const char *pattern, ...) michael@0: { michael@0: va_list ap; michael@0: first_line_test(); michael@0: fprintf(stdout, "%-*s", INDENT_LEVEL,"" ); michael@0: va_start(ap, pattern); michael@0: vfprintf(stdout, pattern, ap); michael@0: fflush(stdout); michael@0: va_end(ap); michael@0: GLOBAL_PRINT_COUNT++; michael@0: } michael@0: /** michael@0: * Log test structure (no ident) michael@0: */ michael@0: static void log_testinfo(const char *pattern, ...) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, pattern); michael@0: first_line_test(); michael@0: vfprintf(stdout, pattern, ap); michael@0: fflush(stdout); michael@0: va_end(ap); michael@0: GLOBAL_PRINT_COUNT++; michael@0: } michael@0: michael@0: michael@0: static void vlog_verbose(const char *prefix, const char *pattern, va_list ap) michael@0: { michael@0: if ( VERBOSITY == FALSE ) michael@0: return; michael@0: michael@0: first_line_verbose(); michael@0: fprintf(stdout, "%-*s", INDENT_LEVEL,"" ); michael@0: if(prefix) { michael@0: fputs(prefix, stdout); michael@0: } michael@0: vfprintf(stdout, pattern, ap); michael@0: fflush(stdout); michael@0: va_end(ap); michael@0: GLOBAL_PRINT_COUNT++; michael@0: if((*pattern==0) || (pattern[strlen(pattern)-1]!='\n')) { michael@0: HANGING_OUTPUT=1; michael@0: } else { michael@0: HANGING_OUTPUT=0; michael@0: } michael@0: } michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: log_err(const char* pattern, ...) michael@0: { michael@0: va_list ap; michael@0: first_line_err(); michael@0: if(strchr(pattern, '\n') != NULL) { michael@0: /* michael@0: * Count errors only if there is a line feed in the pattern michael@0: * so that we do not exaggerate our error count. michael@0: */ michael@0: ++ERROR_COUNT; michael@0: } else { michael@0: /* Count at least one error. */ michael@0: ONE_ERROR=1; michael@0: } michael@0: va_start(ap, pattern); michael@0: vlog_err(NULL, pattern, ap); michael@0: } michael@0: michael@0: UBool T_CTEST_EXPORT2 michael@0: log_knownIssue(const char *ticket, const char *pattern, ...) { michael@0: va_list ap; michael@0: va_start(ap, pattern); michael@0: return vlog_knownIssue(ticket, pattern, ap); michael@0: } michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: log_err_status(UErrorCode status, const char* pattern, ...) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, pattern); michael@0: michael@0: if ((status == U_FILE_ACCESS_ERROR || status == U_MISSING_RESOURCE_ERROR)) { michael@0: ++DATA_ERROR_COUNT; /* for informational message at the end */ michael@0: michael@0: if (WARN_ON_MISSING_DATA == 0) { michael@0: first_line_err(); michael@0: /* Fatal error. */ michael@0: if (strchr(pattern, '\n') != NULL) { michael@0: ++ERROR_COUNT; michael@0: } else { michael@0: ++ONE_ERROR; michael@0: } michael@0: vlog_err(NULL, pattern, ap); /* no need for prefix in default case */ michael@0: } else { michael@0: vlog_info("[DATA] ", pattern, ap); michael@0: } michael@0: } else { michael@0: first_line_err(); michael@0: /* Fatal error. */ michael@0: if(strchr(pattern, '\n') != NULL) { michael@0: ++ERROR_COUNT; michael@0: } else { michael@0: ++ONE_ERROR; michael@0: } michael@0: vlog_err(NULL, pattern, ap); /* no need for prefix in default case */ michael@0: } michael@0: } michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: log_info(const char* pattern, ...) michael@0: { michael@0: va_list ap; michael@0: michael@0: va_start(ap, pattern); michael@0: vlog_info(NULL, pattern, ap); michael@0: } michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: log_verbose(const char* pattern, ...) michael@0: { michael@0: va_list ap; michael@0: michael@0: va_start(ap, pattern); michael@0: vlog_verbose(NULL, pattern, ap); michael@0: } michael@0: michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: log_data_err(const char* pattern, ...) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, pattern); michael@0: michael@0: go_offline_err(); michael@0: ++DATA_ERROR_COUNT; /* for informational message at the end */ michael@0: michael@0: if(WARN_ON_MISSING_DATA == 0) { michael@0: /* Fatal error. */ michael@0: if(strchr(pattern, '\n') != NULL) { michael@0: ++ERROR_COUNT; michael@0: } michael@0: vlog_err(NULL, pattern, ap); /* no need for prefix in default case */ michael@0: } else { michael@0: vlog_info("[DATA] ", pattern, ap); michael@0: } michael@0: } michael@0: michael@0: michael@0: /* michael@0: * Tracing functions. michael@0: */ michael@0: static int traceFnNestingDepth = 0; michael@0: U_CDECL_BEGIN michael@0: static void U_CALLCONV TraceEntry(const void *context, int32_t fnNumber) { michael@0: char buf[500]; michael@0: utrace_format(buf, sizeof(buf), traceFnNestingDepth*3, "%s() enter.\n", utrace_functionName(fnNumber)); buf[sizeof(buf)-1]=0; michael@0: fputs(buf, stdout); michael@0: traceFnNestingDepth++; michael@0: } michael@0: michael@0: static void U_CALLCONV TraceExit(const void *context, int32_t fnNumber, const char *fmt, va_list args) { char buf[500]; michael@0: michael@0: if (traceFnNestingDepth>0) { michael@0: traceFnNestingDepth--; michael@0: } michael@0: utrace_format(buf, sizeof(buf), traceFnNestingDepth*3, "%s() ", utrace_functionName(fnNumber)); buf[sizeof(buf)-1]=0; michael@0: fputs(buf, stdout); michael@0: utrace_vformat(buf, sizeof(buf), traceFnNestingDepth*3, fmt, args); michael@0: buf[sizeof(buf)-1]=0; michael@0: fputs(buf, stdout); michael@0: putc('\n', stdout); michael@0: } michael@0: michael@0: static void U_CALLCONV TraceData(const void *context, int32_t fnNumber, michael@0: int32_t level, const char *fmt, va_list args) { michael@0: char buf[500]; michael@0: utrace_vformat(buf, sizeof(buf), traceFnNestingDepth*3, fmt, args); michael@0: buf[sizeof(buf)-1]=0; michael@0: fputs(buf, stdout); michael@0: putc('\n', stdout); michael@0: } michael@0: michael@0: static void *U_CALLCONV ctest_libMalloc(const void *context, size_t size) { michael@0: /*if (VERBOSITY) { michael@0: printf("Allocated %ld\n", (long)size); michael@0: }*/ michael@0: if (MINIMUM_MEMORY_SIZE_FAILURE <= size && size <= MAXIMUM_MEMORY_SIZE_FAILURE) { michael@0: return NULL; michael@0: } michael@0: return malloc(size); michael@0: } michael@0: static void *U_CALLCONV ctest_libRealloc(const void *context, void *mem, size_t size) { michael@0: /*if (VERBOSITY) { michael@0: printf("Reallocated %ld\n", (long)size); michael@0: }*/ michael@0: if (MINIMUM_MEMORY_SIZE_FAILURE <= size && size <= MAXIMUM_MEMORY_SIZE_FAILURE) { michael@0: /*free(mem);*/ /* Realloc doesn't free on failure. */ michael@0: return NULL; michael@0: } michael@0: return realloc(mem, size); michael@0: } michael@0: static void U_CALLCONV ctest_libFree(const void *context, void *mem) { michael@0: free(mem); michael@0: } michael@0: michael@0: int T_CTEST_EXPORT2 michael@0: initArgs( int argc, const char* const argv[], ArgHandlerPtr argHandler, void *context) michael@0: { michael@0: int i; michael@0: int argSkip = 0; michael@0: michael@0: VERBOSITY = FALSE; michael@0: ERR_MSG = TRUE; michael@0: michael@0: ARGV_0=argv[0]; michael@0: michael@0: for( i=1; i=argc) { michael@0: printf("* Error: '-x' option requires an argument. usage: '-x outfile.xml'.\n"); michael@0: return 0; michael@0: } michael@0: if(ctest_xml_setFileName(argv[i])) { /* set the name */ michael@0: return 0; michael@0: } michael@0: } michael@0: else if (strcmp( argv[i], "-t_info") == 0) { michael@0: ICU_TRACE = UTRACE_INFO; michael@0: } michael@0: else if (strcmp( argv[i], "-t_error") == 0) { michael@0: ICU_TRACE = UTRACE_ERROR; michael@0: } michael@0: else if (strcmp( argv[i], "-t_warn") == 0) { michael@0: ICU_TRACE = UTRACE_WARNING; michael@0: } michael@0: else if (strcmp( argv[i], "-t_verbose") == 0) { michael@0: ICU_TRACE = UTRACE_VERBOSE; michael@0: } michael@0: else if (strcmp( argv[i], "-t_oc") == 0) { michael@0: ICU_TRACE = UTRACE_OPEN_CLOSE; michael@0: } michael@0: else if (strcmp( argv[i], "-h" )==0 || strcmp( argv[i], "--help" )==0) michael@0: { michael@0: help( argv[0] ); michael@0: return 0; michael@0: } michael@0: else if (argHandler != NULL && (argSkip = argHandler(i, argc, argv, context)) > 0) michael@0: { michael@0: i += argSkip - 1; michael@0: } michael@0: else michael@0: { michael@0: printf("* unknown option: %s\n", argv[i]); michael@0: help( argv[0] ); michael@0: return 0; michael@0: } michael@0: } michael@0: if (ICU_TRACE != UTRACE_OFF) { michael@0: utrace_setFunctions(NULL, TraceEntry, TraceExit, TraceData); michael@0: utrace_setLevel(ICU_TRACE); michael@0: } michael@0: michael@0: return 1; /* total error count */ michael@0: } michael@0: michael@0: int T_CTEST_EXPORT2 michael@0: runTestRequest(const TestNode* root, michael@0: int argc, michael@0: const char* const argv[]) michael@0: { michael@0: /** michael@0: * This main will parse the l, v, h, n, and path arguments michael@0: */ michael@0: const TestNode* toRun; michael@0: int i; michael@0: int doList = FALSE; michael@0: int subtreeOptionSeen = FALSE; michael@0: michael@0: int errorCount = 0; michael@0: michael@0: toRun = root; michael@0: michael@0: if(ctest_xml_init(ARGV_0)) { michael@0: return 1; /* couldn't fire up XML thing */ michael@0: } michael@0: michael@0: for( i=1; i 0 ) ) michael@0: printf(" Total errors: %d\n", errorCount ); michael@0: } michael@0: michael@0: REPEAT_TESTS_INIT = 1; michael@0: michael@0: if(ctest_xml_fini()) { michael@0: errorCount++; michael@0: } michael@0: michael@0: return errorCount; /* total error count */ michael@0: } michael@0: michael@0: /** michael@0: * Display program invocation arguments michael@0: */ michael@0: michael@0: static void help ( const char *argv0 ) michael@0: { michael@0: printf("Usage: %s [ -l ] [ -v ] [ -verbose] [-a] [ -all] [-n] [ -no_err_msg]\n" michael@0: " [ -h ] [-t_info | -t_error | -t_warn | -t_oc | -t_verbose] [-m n[-q] ]\n" michael@0: " [ /path/to/test ]\n", michael@0: argv0); michael@0: printf(" -l To get a list of test names\n"); michael@0: printf(" -e to do exhaustive testing\n"); michael@0: printf(" -verbose To turn ON verbosity\n"); michael@0: printf(" -v To turn ON verbosity(same as -verbose)\n"); michael@0: printf(" -x file.xml Write junit format output to file.xml\n"); michael@0: printf(" -h To print this message\n"); michael@0: printf(" -K to turn OFF suppressing known issues\n"); michael@0: printf(" -n To turn OFF printing error messages\n"); michael@0: printf(" -w Don't fail on data-loading errs, just warn. Useful if\n" michael@0: " user has reduced/changed the common set of ICU data \n"); michael@0: printf(" -t_info | -t_error | -t_warn | -t_oc | -t_verbose Enable ICU tracing\n"); michael@0: printf(" -no_err_msg (same as -n) \n"); michael@0: printf(" -m n[-q] Min-Max memory size that will cause an allocation failure.\n"); michael@0: printf(" The default is the maximum value of size_t. Max is optional.\n"); michael@0: printf(" -r Repeat tests after calling u_cleanup \n"); michael@0: printf(" [/subtest] To run a subtest \n"); michael@0: printf(" eg: to run just the utility tests type: cintltest /tsutil) \n"); michael@0: } michael@0: michael@0: int32_t T_CTEST_EXPORT2 michael@0: getTestOption ( int32_t testOption ) { michael@0: switch (testOption) { michael@0: case VERBOSITY_OPTION: michael@0: return VERBOSITY; michael@0: case WARN_ON_MISSING_DATA_OPTION: michael@0: return WARN_ON_MISSING_DATA; michael@0: case QUICK_OPTION: michael@0: return QUICK; michael@0: case REPEAT_TESTS_OPTION: michael@0: return REPEAT_TESTS; michael@0: case ERR_MSG_OPTION: michael@0: return ERR_MSG; michael@0: case ICU_TRACE_OPTION: michael@0: return ICU_TRACE; michael@0: default : michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: void T_CTEST_EXPORT2 michael@0: setTestOption ( int32_t testOption, int32_t value) { michael@0: if (value == DECREMENT_OPTION_VALUE) { michael@0: value = getTestOption(testOption); michael@0: --value; michael@0: } michael@0: switch (testOption) { michael@0: case VERBOSITY_OPTION: michael@0: VERBOSITY = value; michael@0: break; michael@0: case WARN_ON_MISSING_DATA_OPTION: michael@0: WARN_ON_MISSING_DATA = value; michael@0: break; michael@0: case QUICK_OPTION: michael@0: QUICK = value; michael@0: break; michael@0: case REPEAT_TESTS_OPTION: michael@0: REPEAT_TESTS = value; michael@0: break; michael@0: case ICU_TRACE_OPTION: michael@0: ICU_TRACE = (UTraceLevel)value; michael@0: break; michael@0: default : michael@0: break; michael@0: } michael@0: } michael@0: michael@0: michael@0: /* michael@0: * ================== JUnit support ================================ michael@0: */ michael@0: michael@0: int32_t michael@0: T_CTEST_EXPORT2 michael@0: ctest_xml_setFileName(const char *name) { michael@0: XML_FILE_NAME=name; michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: int32_t michael@0: T_CTEST_EXPORT2 michael@0: ctest_xml_init(const char *rootName) { michael@0: if(!XML_FILE_NAME) return 0; michael@0: XML_FILE = fopen(XML_FILE_NAME,"w"); michael@0: if(!XML_FILE) { michael@0: perror("fopen"); michael@0: fprintf(stderr," Error: couldn't open XML output file %s\n", XML_FILE_NAME); michael@0: return 1; michael@0: } michael@0: while(*rootName&&!isalnum((int)*rootName)) { michael@0: rootName++; michael@0: } michael@0: strcpy(XML_PREFIX,rootName); michael@0: { michael@0: char *p = XML_PREFIX+strlen(XML_PREFIX); michael@0: for(p--;*p&&p>XML_PREFIX&&!isalnum((int)*p);p--) { michael@0: *p=0; michael@0: } michael@0: } michael@0: /* write prefix */ michael@0: fprintf(XML_FILE, "\n", XML_PREFIX); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int32_t michael@0: T_CTEST_EXPORT2 michael@0: ctest_xml_fini(void) { michael@0: if(!XML_FILE) return 0; michael@0: michael@0: fprintf(XML_FILE, "\n"); michael@0: fclose(XML_FILE); michael@0: printf(" ( test results written to %s )\n", XML_FILE_NAME); michael@0: XML_FILE=0; michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: int32_t michael@0: T_CTEST_EXPORT2 michael@0: ctest_xml_testcase(const char *classname, const char *name, const char *timeSeconds, const char *failMsg) { michael@0: if(!XML_FILE) return 0; michael@0: michael@0: fprintf(XML_FILE, "\t\n\t\t\n\t\n", failMsg); michael@0: } else { michael@0: fprintf(XML_FILE, "/>\n"); michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: