/* * runtest.c: libxslt test suite * * See Copyright for the status of this software. */ #include "config.h" #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #else #include #include #endif #include #include #include #include #include #include #include /* * O_BINARY is just for Windows compatibility - if it isn't defined * on this system, avoid any compilation error */ #ifdef O_BINARY #define RD_FLAGS O_RDONLY | O_BINARY #define WR_FLAGS O_WRONLY | O_CREAT | O_TRUNC | O_BINARY #else #define RD_FLAGS O_RDONLY #define WR_FLAGS O_WRONLY | O_CREAT | O_TRUNC #endif typedef int (*functest) (const char *filename, int options); typedef struct testDesc testDesc; typedef testDesc *testDescPtr; struct testDesc { const char *desc; /* description of the test */ functest func; /* function implementing the test */ const char *dir; /* directory to change to */ const char *in; /* glob to path for input files */ int options; /* parser options for the test */ }; static int update_results = 0; static char* temp_directory = NULL; static int checkTestFile(const char *filename); #if defined(_WIN32) typedef struct { size_t gl_pathc; /* Count of paths matched so far */ char **gl_pathv; /* List of matched pathnames. */ size_t gl_offs; /* Slots to reserve in 'gl_pathv'. */ } glob_t; #define GLOB_DOOFFS 0 static int glob(const char *pattern, ATTRIBUTE_UNUSED int flags, ATTRIBUTE_UNUSED int errfunc(const char *epath, int eerrno), glob_t *pglob) { glob_t *ret; WIN32_FIND_DATA FindFileData; HANDLE hFind; unsigned int nb_paths = 0; char directory[500]; int len; if ((pattern == NULL) || (pglob == NULL)) return(-1); strncpy(directory, pattern, 499); for (len = strlen(directory);len >= 0;len--) { if (directory[len] == '/') { len++; directory[len] = 0; break; } } if (len <= 0) len = 0; ret = pglob; memset(ret, 0, sizeof(glob_t)); hFind = FindFirstFileA(pattern, &FindFileData); if (hFind == INVALID_HANDLE_VALUE) return(0); nb_paths = 20; ret->gl_pathv = (char **) malloc(nb_paths * sizeof(char *)); if (ret->gl_pathv == NULL) { FindClose(hFind); return(-1); } strncpy(directory + len, FindFileData.cFileName, 499 - len); ret->gl_pathv[ret->gl_pathc] = strdup(directory); if (ret->gl_pathv[ret->gl_pathc] == NULL) goto done; ret->gl_pathc++; while(FindNextFileA(hFind, &FindFileData)) { if (FindFileData.cFileName[0] == '.') continue; if (ret->gl_pathc + 2 > nb_paths) { char **tmp = realloc(ret->gl_pathv, nb_paths * 2 * sizeof(char *)); if (tmp == NULL) break; ret->gl_pathv = tmp; nb_paths *= 2; } strncpy(directory + len, FindFileData.cFileName, 499 - len); ret->gl_pathv[ret->gl_pathc] = strdup(directory); if (ret->gl_pathv[ret->gl_pathc] == NULL) break; ret->gl_pathc++; } ret->gl_pathv[ret->gl_pathc] = NULL; done: FindClose(hFind); return(0); } static void globfree(glob_t *pglob) { unsigned int i; if (pglob == NULL) return; for (i = 0;i < pglob->gl_pathc;i++) { if (pglob->gl_pathv[i] != NULL) free(pglob->gl_pathv[i]); } } #endif /************************************************************************ * * * Libxml2 specific routines * * * ************************************************************************/ static int nb_tests = 0; static int nb_errors = 0; static int fatalError(void) { fprintf(stderr, "Exiting tests on fatal error\n"); exit(1); } /* * Trapping the error messages at the generic level to grab the equivalent of * stderr messages on CLI tools. */ static char testErrors[32769]; static int testErrorsSize = 0; static void XMLCDECL testErrorHandler(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) { va_list args; int res; if (testErrorsSize >= 32768) return; va_start(args, msg); res = vsnprintf(&testErrors[testErrorsSize], 32768 - testErrorsSize, msg, args); va_end(args); if (testErrorsSize + res >= 32768) { /* buffer is full */ testErrorsSize = 32768; testErrors[testErrorsSize] = 0; } else { testErrorsSize += res; } testErrors[testErrorsSize] = 0; } static void XMLCDECL channel(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) { va_list args; int res; if (testErrorsSize >= 32768) return; va_start(args, msg); res = vsnprintf(&testErrors[testErrorsSize], 32768 - testErrorsSize, msg, args); va_end(args); if (testErrorsSize + res >= 32768) { /* buffer is full */ testErrorsSize = 32768; testErrors[testErrorsSize] = 0; } else { testErrorsSize += res; } testErrors[testErrorsSize] = 0; } /** * xmlParserPrintFileContext: * @input: an xmlParserInputPtr input * * Displays current context within the input content for error tracking */ static void xmlParserPrintFileContextInternal(xmlParserInputPtr input , xmlGenericErrorFunc chanl, void *data ) { const xmlChar *cur, *base; unsigned int n, col; /* GCC warns if signed, because compared with sizeof() */ xmlChar content[81]; /* space for 80 chars + line terminator */ xmlChar *ctnt; if (input == NULL) return; cur = input->cur; base = input->base; /* skip backwards over any end-of-lines */ while ((cur > base) && ((*(cur) == '\n') || (*(cur) == '\r'))) { cur--; } n = 0; /* search backwards for beginning-of-line (to max buff size) */ while ((n++ < (sizeof(content)-1)) && (cur > base) && (*(cur) != '\n') && (*(cur) != '\r')) cur--; if ((*(cur) == '\n') || (*(cur) == '\r')) cur++; /* calculate the error position in terms of the current position */ col = input->cur - cur; /* search forward for end-of-line (to max buff size) */ n = 0; ctnt = content; /* copy selected text to our buffer */ while ((*cur != 0) && (*(cur) != '\n') && (*(cur) != '\r') && (n < sizeof(content)-1)) { *ctnt++ = *cur++; n++; } *ctnt = 0; /* print out the selected text */ chanl(data ,"%s\n", content); /* create blank line with problem pointer */ n = 0; ctnt = content; /* (leave buffer space for pointer + line terminator) */ while ((nfile; line = err->line; code = err->code; domain = err->domain; level = err->level; node = err->node; if ((domain == XML_FROM_PARSER) || (domain == XML_FROM_HTML) || (domain == XML_FROM_DTD) || (domain == XML_FROM_NAMESPACE) || (domain == XML_FROM_IO) || (domain == XML_FROM_VALID)) { ctxt = err->ctxt; } str = err->message; if (code == XML_ERR_OK) return; if ((node != NULL) && (node->type == XML_ELEMENT_NODE)) name = node->name; /* * Maintain the compatibility with the legacy error handling */ if (ctxt != NULL) { input = ctxt->input; if ((input != NULL) && (input->filename == NULL) && (ctxt->inputNr > 1)) { cur = input; input = ctxt->inputTab[ctxt->inputNr - 2]; } if (input != NULL) { if (input->filename) channel(data, "%s:%d: ", input->filename, input->line); else if ((line != 0) && (domain == XML_FROM_PARSER)) channel(data, "Entity: line %d: ", input->line); } } else { if (file != NULL) channel(data, "%s:%d: ", file, line); else if ((line != 0) && (domain == XML_FROM_PARSER)) channel(data, "Entity: line %d: ", line); } if (name != NULL) { channel(data, "element %s: ", name); } if (code == XML_ERR_OK) return; switch (domain) { case XML_FROM_PARSER: channel(data, "parser "); break; case XML_FROM_NAMESPACE: channel(data, "namespace "); break; case XML_FROM_DTD: case XML_FROM_VALID: channel(data, "validity "); break; case XML_FROM_HTML: channel(data, "HTML parser "); break; case XML_FROM_MEMORY: channel(data, "memory "); break; case XML_FROM_OUTPUT: channel(data, "output "); break; case XML_FROM_IO: channel(data, "I/O "); break; case XML_FROM_XINCLUDE: channel(data, "XInclude "); break; case XML_FROM_XPATH: channel(data, "XPath "); break; case XML_FROM_XPOINTER: channel(data, "parser "); break; case XML_FROM_REGEXP: channel(data, "regexp "); break; case XML_FROM_MODULE: channel(data, "module "); break; case XML_FROM_SCHEMASV: channel(data, "Schemas validity "); break; case XML_FROM_SCHEMASP: channel(data, "Schemas parser "); break; case XML_FROM_RELAXNGP: channel(data, "Relax-NG parser "); break; case XML_FROM_RELAXNGV: channel(data, "Relax-NG validity "); break; case XML_FROM_CATALOG: channel(data, "Catalog "); break; case XML_FROM_C14N: channel(data, "C14N "); break; case XML_FROM_XSLT: channel(data, "XSLT "); break; default: break; } if (code == XML_ERR_OK) return; switch (level) { case XML_ERR_NONE: channel(data, ": "); break; case XML_ERR_WARNING: channel(data, "warning : "); break; case XML_ERR_ERROR: channel(data, "error : "); break; case XML_ERR_FATAL: channel(data, "error : "); break; } if (code == XML_ERR_OK) return; if (str != NULL) { int len; len = xmlStrlen((const xmlChar *)str); if ((len > 0) && (str[len - 1] != '\n')) channel(data, "%s\n", str); else channel(data, "%s", str); } else { channel(data, "%s\n", "out of memory error"); } if (code == XML_ERR_OK) return; if (ctxt != NULL) { xmlParserPrintFileContextInternal(input, channel, data); if (cur != NULL) { if (cur->filename) channel(data, "%s:%d: \n", cur->filename, cur->line); else if ((line != 0) && (domain == XML_FROM_PARSER)) channel(data, "Entity: line %d: \n", cur->line); xmlParserPrintFileContextInternal(cur, channel, data); } } if ((domain == XML_FROM_XPATH) && (err->str1 != NULL) && (err->int1 < 100) && (err->int1 < xmlStrlen((const xmlChar *)err->str1))) { xmlChar buf[150]; int i; channel(data, "%s\n", err->str1); for (i=0;i < err->int1;i++) buf[i] = ' '; buf[i++] = '^'; buf[i] = 0; channel(data, "%s\n", buf); } } static void initializeLibxml2(void) { xmlInitParser(); xmlSetExternalEntityLoader(xmlNoNetExternalEntityLoader); xmlSetGenericErrorFunc(NULL, testErrorHandler); xsltSetGenericErrorFunc(NULL, testErrorHandler); xmlSetStructuredErrorFunc(NULL, testStructuredErrorHandler); exsltRegisterAll(); xsltRegisterTestModule(); xsltMaxDepth = 200; } /************************************************************************ * * * File name and path utilities * * * ************************************************************************/ static char * changeSuffix(const char *filename, const char *suffix) { const char *dot; char *ret; char res[500]; int baseLen; dot = strrchr(filename, '.'); baseLen = dot ? dot - filename : (int) strlen(filename); snprintf(res, sizeof(res), "%.*s%s", baseLen, filename, suffix); ret = strdup(res); if (ret == NULL) { fprintf(stderr, "strdup failed\n"); fatalError(); } return(ret); } static int checkTestFile(const char *filename) { struct stat buf; if (stat(filename, &buf) == -1) return(0); #if defined(_WIN32) if (!(buf.st_mode & _S_IFREG)) return(0); #else if (!S_ISREG(buf.st_mode)) return(0); #endif return(1); } static int compareFileMem(const char *filename, const char *mem, int size) { int res; int fd; char bytes[4096]; int idx = 0; struct stat info; if (update_results) { if (size == 0) { unlink(filename); return(0); } fd = open(filename, WR_FLAGS, 0644); if (fd < 0) { fprintf(stderr, "failed to open %s for writing", filename); return(-1); } res = write(fd, mem, size); close(fd); return(res != size); } if (stat(filename, &info) < 0) { if (size == 0) return(0); fprintf(stderr, "failed to stat %s\n", filename); return(-1); } if (info.st_size != size) { fprintf(stderr, "file %s is %ld bytes, result is %d bytes\n", filename, (long) info.st_size, size); return(-1); } fd = open(filename, RD_FLAGS); if (fd < 0) { fprintf(stderr, "failed to open %s for reading", filename); return(-1); } while (idx < size) { res = read(fd, bytes, 4096); if (res <= 0) break; if (res + idx > size) break; if (memcmp(bytes, &mem[idx], res) != 0) { int ix; for (ix=0; ixdir) { if (getcwd(oldDir, sizeof(oldDir)) == NULL) { fprintf(stderr, "Can't can't get current directory\n"); nb_errors++; return(1); } if (chdir(tst->dir) < 0) { fprintf(stderr, "Can't change directory to %s\n", tst->dir); nb_errors++; return(1); } } if (tst->in != NULL) { glob_t globbuf; globbuf.gl_offs = 0; glob(tst->in, GLOB_DOOFFS, NULL, &globbuf); for (i = 0;i < globbuf.gl_pathc;i++) { testErrorsSize = 0; testErrors[0] = 0; nb_tests++; res = tst->func(globbuf.gl_pathv[i], tst->options); xmlResetLastError(); if (res != 0) { fprintf(stderr, "File %s generated an error\n", globbuf.gl_pathv[i]); nb_errors++; err++; } testErrorsSize = 0; } globfree(&globbuf); } else { testErrorsSize = 0; testErrors[0] = 0; nb_tests++; res = tst->func(NULL, tst->options); if (res != 0) { nb_errors++; err++; } } if (oldDir[0] && chdir(oldDir) < 0) { fprintf(stderr, "Can't change directory to %s\n", oldDir); nb_errors++; err++; } return(err); } static int verbose = 0; static int tests_quiet = 0; static int runtest(int i) { int ret = 0, res; int old_errors, old_tests; old_errors = nb_errors; old_tests = nb_tests; if ((tests_quiet == 0) && (testDescriptions[i].desc != NULL)) printf("## Running %s\n", testDescriptions[i].desc); res = launchTests(&testDescriptions[i]); if (res != 0) ret++; if (verbose) { if (nb_errors == old_errors) printf("Ran %d tests, no errors\n", nb_tests - old_tests); else printf("Ran %d tests, %d errors\n", nb_tests - old_tests, nb_errors - old_errors); } return(ret); } int main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) { int i, a, ret = 0; int subset = 0; #if defined(_WIN32) setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); #endif #if defined(_MSC_VER) && _MSC_VER >= 1400 && _MSC_VER < 1900 _set_output_format(_TWO_DIGIT_EXPONENT); #endif initializeLibxml2(); for (a = 1; a < argc;a++) { if (!strcmp(argv[a], "-v")) verbose = 1; else if (!strcmp(argv[a], "-u")) update_results = 1; else if (!strcmp(argv[a], "-quiet")) tests_quiet = 1; else if (!strcmp(argv[a], "--out")) temp_directory = argv[++a]; else { for (i = 0; testDescriptions[i].func != NULL; i++) { if (strstr(testDescriptions[i].desc, argv[a])) { ret += runtest(i); subset++; } } } } if (subset == 0) { for (i = 0; testDescriptions[i].func != NULL; i++) { ret += runtest(i); } } if (nb_errors == 0) { ret = 0; printf("Total %d tests, no errors\n", nb_tests); } else { ret = 1; printf("Total %d tests, %d errors\n", nb_tests, nb_errors); } xmlCleanupParser(); return(ret); }