/* * runsuite.c: C program to run libxml2 againts published testsuites * * See Copyright for the status of this software. * * daniel@veillard.com */ #ifdef HAVE_CONFIG_H #include "libxml.h" #else #include #endif #if !defined(_WIN32) || defined(__CYGWIN__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #define LOGFILE "runxmlconf.log" static FILE *logfile = NULL; static int verbose = 0; #if defined(_WIN32) && !defined(__CYGWIN__) #define vsnprintf _vsnprintf #define snprintf _snprintf #endif /************************************************************************ * * * File name and path utilities * * * ************************************************************************/ static int checkTestFile(const char *filename) { struct stat buf; if (stat(filename, &buf) == -1) return(0); #if defined(_WIN32) && !defined(__CYGWIN__) if (!(buf.st_mode & _S_IFREG)) return(0); #else if (!S_ISREG(buf.st_mode)) return(0); #endif return(1); } static xmlChar *composeDir(const xmlChar *dir, const xmlChar *path) { char buf[500]; if (dir == NULL) return(xmlStrdup(path)); if (path == NULL) return(NULL); snprintf(buf, 500, "%s/%s", (const char *) dir, (const char *) path); return(xmlStrdup((const xmlChar *) buf)); } /************************************************************************ * * * Libxml2 specific routines * * * ************************************************************************/ static int nb_skipped = 0; static int nb_tests = 0; static int nb_errors = 0; static int nb_leaks = 0; static int extraMemoryFromResolver = 0; static int fatalError(void) { fprintf(stderr, "Exitting tests on fatal error\n"); exit(1); } /* * that's needed to implement */ #define MAX_ENTITIES 20 static char *testEntitiesName[MAX_ENTITIES]; static char *testEntitiesValue[MAX_ENTITIES]; static int nb_entities = 0; static void resetEntities(void) { int i; for (i = 0;i < nb_entities;i++) { if (testEntitiesName[i] != NULL) xmlFree(testEntitiesName[i]); if (testEntitiesValue[i] != NULL) xmlFree(testEntitiesValue[i]); } nb_entities = 0; } static int addEntity(char *name, char *content) { if (nb_entities >= MAX_ENTITIES) { fprintf(stderr, "Too many entities defined\n"); return(-1); } testEntitiesName[nb_entities] = name; testEntitiesValue[nb_entities] = content; nb_entities++; return(0); } /* * We need to trap calls to the resolver to not account memory for the catalog * which is shared to the current running test. We also don't want to have * network downloads modifying tests. */ static xmlParserInputPtr testExternalEntityLoader(const char *URL, const char *ID, xmlParserCtxtPtr ctxt) { xmlParserInputPtr ret; int i; for (i = 0;i < nb_entities;i++) { if (!strcmp(testEntitiesName[i], URL)) { ret = xmlNewStringInputStream(ctxt, (const xmlChar *) testEntitiesValue[i]); if (ret != NULL) { ret->filename = (const char *) xmlStrdup((xmlChar *)testEntitiesName[i]); } return(ret); } } if (checkTestFile(URL)) { ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt); } else { int memused = xmlMemUsed(); ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt); extraMemoryFromResolver += xmlMemUsed() - memused; } #if 0 if (ret == NULL) { fprintf(stderr, "Failed to find resource %s\n", URL); } #endif return(ret); } /* * 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 test_log(const char *msg, ...) { va_list args; if (logfile != NULL) { fprintf(logfile, "\n------------\n"); va_start(args, msg); vfprintf(logfile, msg, args); va_end(args); fprintf(logfile, "%s", testErrors); testErrorsSize = 0; testErrors[0] = 0; } if (verbose) { va_start(args, msg); vfprintf(stderr, msg, args); va_end(args); } } static void 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 xmlXPathContextPtr ctxtXPath; static void initializeLibxml2(void) { xmlGetWarningsDefaultValue = 0; xmlPedanticParserDefault(0); xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup); xmlInitParser(); xmlSetExternalEntityLoader(testExternalEntityLoader); ctxtXPath = xmlXPathNewContext(NULL); /* * Deactivate the cache if created; otherwise we have to create/free it * for every test, since it will confuse the memory leak detection. * Note that normally this need not be done, since the cache is not * created until set explicitely with xmlXPathContextSetCache(); * but for test purposes it is sometimes usefull to activate the * cache by default for the whole library. */ if (ctxtXPath->cache != NULL) xmlXPathContextSetCache(ctxtXPath, 0, -1, 0); xmlSetGenericErrorFunc(NULL, testErrorHandler); } /************************************************************************ * * * Run the xmlconf test if found * * * ************************************************************************/ static int xmlconfTestNotNSWF(const char *id, const char *filename, int options) { xmlDocPtr doc; int ret = 1; /* * In case of Namespace errors, libxml2 will still parse the document * but log a Namesapce error. */ doc = xmlReadFile(filename, NULL, options); if (doc == NULL) { test_log("test %s : %s failed to parse the XML\n", id, filename); nb_errors++; ret = 0; } else { if ((xmlLastError.code == XML_ERR_OK) || (xmlLastError.domain != XML_FROM_NAMESPACE)) { test_log("test %s : %s failed to detect namespace error\n", id, filename); nb_errors++; ret = 0; } xmlFreeDoc(doc); } return(ret); } static int xmlconfTestNotWF(const char *id, const char *filename, int options) { xmlDocPtr doc; int ret = 1; doc = xmlReadFile(filename, NULL, options); if (doc != NULL) { test_log("test %s : %s failed to detect not well formedness\n", id, filename); nb_errors++; xmlFreeDoc(doc); ret = 0; } return(ret); } static int xmlconfTestItem(xmlDocPtr doc, xmlNodePtr cur) { int ret = -1; xmlChar *type = NULL; xmlChar *filename = NULL; xmlChar *uri = NULL; xmlChar *base = NULL; xmlChar *id = NULL; xmlChar *rec = NULL; xmlChar *entities = NULL; xmlChar *edition = NULL; int options = 0; int nstest = 0; int mem, final; id = xmlGetProp(cur, BAD_CAST "ID"); if (id == NULL) { test_log("test missing ID, line %ld\n", xmlGetLineNo(cur)); goto error; } type = xmlGetProp(cur, BAD_CAST "TYPE"); if (type == NULL) { test_log("test %s missing TYPE\n", (char *) id); goto error; } uri = xmlGetProp(cur, BAD_CAST "URI"); if (uri == NULL) { test_log("test %s missing URI\n", (char *) id); goto error; } base = xmlNodeGetBase(doc, cur); filename = composeDir(base, uri); if (!checkTestFile((char *) filename)) { test_log("test %s missing file %s \n", id, (filename ? (char *)filename : "NULL")); goto error; } entities = xmlGetProp(cur, BAD_CAST "ENTITIES"); if (!xmlStrEqual(entities, BAD_CAST "none")) { options |= XML_PARSE_DTDLOAD; options |= XML_PARSE_NOENT; } rec = xmlGetProp(cur, BAD_CAST "RECOMMENDATION"); if ((rec == NULL) || (xmlStrEqual(rec, BAD_CAST "XML1.0")) || (xmlStrEqual(rec, BAD_CAST "XML1.0-errata2e")) || (xmlStrEqual(rec, BAD_CAST "XML1.0-errata3e")) || (xmlStrEqual(rec, BAD_CAST "XML1.0-errata4e"))) { ret = 1; } else if ((xmlStrEqual(rec, BAD_CAST "NS1.0")) || (xmlStrEqual(rec, BAD_CAST "NS1.0-errata1e"))) { ret = 1; nstest = 1; } else { test_log("Skipping test %s for REC %s\n", (char *) id, (char *) rec); ret = 0; nb_skipped++; goto error; } edition = xmlGetProp(cur, BAD_CAST "EDITION"); if ((edition != NULL) && (xmlStrchr(edition, '5') == NULL)) { /* test limited to all versions before 5th */ options |= XML_PARSE_OLD10; } /* * Reset errors and check memory usage before the test */ xmlResetLastError(); testErrorsSize = 0; testErrors[0] = 0; mem = xmlMemUsed(); if (xmlStrEqual(type, BAD_CAST "not-wf")) { if (nstest == 0) xmlconfTestNotWF((char *) id, (char *) filename, options); else xmlconfTestNotNSWF((char *) id, (char *) filename, options); } else if (xmlStrEqual(type, BAD_CAST "valid")) { } else if (xmlStrEqual(type, BAD_CAST "invalid")) { } else if (xmlStrEqual(type, BAD_CAST "error")) { } else { test_log("test %s unknown TYPE value %s\n", (char *) id, (char *)type); ret = -1; goto error; } /* * Reset errors and check memory usage after the test */ xmlResetLastError(); final = xmlMemUsed(); if (final > mem) { test_log("test %s : %s leaked %d bytes\n", id, filename, final - mem); nb_leaks++; } nb_tests++; error: if (type != NULL) xmlFree(type); if (entities != NULL) xmlFree(entities); if (edition != NULL) xmlFree(edition); if (filename != NULL) xmlFree(filename); if (uri != NULL) xmlFree(uri); if (base != NULL) xmlFree(base); if (id != NULL) xmlFree(id); if (rec != NULL) xmlFree(rec); return(ret); } static int xmlconfTestCases(xmlDocPtr doc, xmlNodePtr cur) { xmlChar *profile; int ret = 0; int tests = 0; profile = xmlGetProp(cur, BAD_CAST "PROFILE"); if (profile != NULL) { printf("Test cases: %s\n", (char *) profile); xmlFree(profile); } cur = cur->children; while (cur != NULL) { /* look only at elements we ignore everything else */ if (cur->type == XML_ELEMENT_NODE) { if (xmlStrEqual(cur->name, BAD_CAST "TESTCASES")) { ret += xmlconfTestCases(doc, cur); } else if (xmlStrEqual(cur->name, BAD_CAST "TEST")) { if (xmlconfTestItem(doc, cur) >= 0) ret++; tests++; } else { fprintf(stderr, "Unhandled element %s\n", (char *)cur->name); } } cur = cur->next; } if (tests > 0) printf("Test cases: %d tests\n", tests); return(ret); } static int xmlconfTestSuite(xmlDocPtr doc, xmlNodePtr cur) { xmlChar *profile; int ret = 0; profile = xmlGetProp(cur, BAD_CAST "PROFILE"); if (profile != NULL) { printf("Test suite: %s\n", (char *) profile); xmlFree(profile); } else printf("Test suite\n"); cur = cur->children; while (cur != NULL) { /* look only at elements we ignore everything else */ if (cur->type == XML_ELEMENT_NODE) { if (xmlStrEqual(cur->name, BAD_CAST "TESTCASES")) { ret += xmlconfTestCases(doc, cur); } else { fprintf(stderr, "Unhandled element %s\n", (char *)cur->name); } } cur = cur->next; } return(ret); } static void xmlconfInfo(void) { fprintf(stderr, " you need to fetch and extract the\n"); fprintf(stderr, " latest XML Conformance Test Suites\n"); fprintf(stderr, " http://www.w3.org/XML/Test/xmlts20080205.tar.gz\n"); fprintf(stderr, " see http://www.w3.org/XML/Test/ for informations\n"); } static int xmlconfTest(void) { const char *confxml = "xmlconf/xmlconf.xml"; xmlDocPtr doc; xmlNodePtr cur; int ret = 0; if (!checkTestFile(confxml)) { fprintf(stderr, "%s is missing \n", confxml); xmlconfInfo(); return(-1); } doc = xmlReadFile(confxml, NULL, XML_PARSE_NOENT); if (doc == NULL) { fprintf(stderr, "%s is corrupted \n", confxml); xmlconfInfo(); return(-1); } cur = xmlDocGetRootElement(doc); if ((cur == NULL) || (!xmlStrEqual(cur->name, BAD_CAST "TESTSUITE"))) { fprintf(stderr, "Unexpected format %s\n", confxml); xmlconfInfo(); ret = -1; } else { ret = xmlconfTestSuite(doc, cur); } xmlFreeDoc(doc); return(ret); } /************************************************************************ * * * The driver for the tests * * * ************************************************************************/ int main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) { int ret = 0; int old_errors, old_tests, old_leaks; logfile = fopen(LOGFILE, "w"); if (logfile == NULL) { fprintf(stderr, "Could not open the log file, running in verbose mode\n"); verbose = 1; } initializeLibxml2(); if ((argc >= 2) && (!strcmp(argv[1], "-v"))) verbose = 1; old_errors = nb_errors; old_tests = nb_tests; old_leaks = nb_leaks; xmlconfTest(); if ((nb_errors == old_errors) && (nb_leaks == old_leaks)) printf("Ran %d tests, no errors\n", nb_tests - old_tests); else printf("Ran %d tests, %d errors, %d leaks\n", nb_tests - old_tests, nb_errors - old_errors, nb_leaks - old_leaks); if ((nb_errors == 0) && (nb_leaks == 0)) { ret = 0; printf("Total %d tests, no errors\n", nb_tests); } else { ret = 1; printf("Total %d tests, %d errors, %d leaks\n", nb_tests, nb_errors, nb_leaks); } xmlXPathFreeContext(ctxtXPath); xmlCleanupParser(); xmlMemoryDump(); if (logfile != NULL) fclose(logfile); return(ret); }