| /* | |
| * runsuite.c: C program to run libxml2 againts published testsuites | |
| * | |
| * See Copyright for the status of this software. | |
| * | |
| * daniel@veillard.com | |
| */ | |
| #include "libxml.h" | |
| #include <stdio.h> | |
| #ifdef LIBXML_XPATH_ENABLED | |
| #if !defined(_WIN32) || defined(__CYGWIN__) | |
| #include <unistd.h> | |
| #endif | |
| #include <string.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <libxml/parser.h> | |
| #include <libxml/parserInternals.h> | |
| #include <libxml/tree.h> | |
| #include <libxml/uri.h> | |
| #include <libxml/xmlreader.h> | |
| #include <libxml/xpath.h> | |
| #include <libxml/xpathInternals.h> | |
| #define LOGFILE "runxmlconf.log" | |
| static FILE *logfile = NULL; | |
| static int verbose = 0; | |
| #define NB_EXPECTED_ERRORS 15 | |
| const char *skipped_tests[] = { | |
| /* http://lists.w3.org/Archives/Public/public-xml-testsuite/2008Jul/0000.html */ | |
| "rmt-ns10-035", | |
| NULL | |
| }; | |
| /************************************************************************ | |
| * * | |
| * 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; | |
| /* | |
| * We need to trap calls to the resolver to not account memory for the catalog | |
| * and not rely on any external resources. | |
| */ | |
| static xmlParserInputPtr | |
| testExternalEntityLoader(const char *URL, const char *ID ATTRIBUTE_UNUSED, | |
| xmlParserCtxtPtr ctxt) { | |
| xmlParserInputPtr ret; | |
| ret = xmlNewInputFromFile(ctxt, (const char *) URL); | |
| 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 int nbError = 0; | |
| static int nbFatal = 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 *userData ATTRIBUTE_UNUSED, xmlErrorPtr error) { | |
| int res; | |
| if (testErrorsSize >= 32768) | |
| return; | |
| res = snprintf(&testErrors[testErrorsSize], | |
| 32768 - testErrorsSize, | |
| "%s:%d: %s\n", (error->file ? error->file : "entity"), | |
| error->line, error->message); | |
| if (error->level == XML_ERR_FATAL) | |
| nbFatal++; | |
| else if (error->level == XML_ERR_ERROR) | |
| nbError++; | |
| 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); | |
| xmlSetStructuredErrorFunc(NULL, testErrorHandler); | |
| } | |
| /************************************************************************ | |
| * * | |
| * Run the xmlconf test if found * | |
| * * | |
| ************************************************************************/ | |
| static int | |
| xmlconfTestInvalid(const char *id, const char *filename, int options) { | |
| xmlDocPtr doc; | |
| xmlParserCtxtPtr ctxt; | |
| int ret = 1; | |
| ctxt = xmlNewParserCtxt(); | |
| if (ctxt == NULL) { | |
| test_log("test %s : %s out of memory\n", | |
| id, filename); | |
| return(0); | |
| } | |
| doc = xmlCtxtReadFile(ctxt, filename, NULL, options); | |
| if (doc == NULL) { | |
| test_log("test %s : %s invalid document turned not well-formed too\n", | |
| id, filename); | |
| } else { | |
| /* invalidity should be reported both in the context and in the document */ | |
| if ((ctxt->valid != 0) || (doc->properties & XML_DOC_DTDVALID)) { | |
| test_log("test %s : %s failed to detect invalid document\n", | |
| id, filename); | |
| nb_errors++; | |
| ret = 0; | |
| } | |
| xmlFreeDoc(doc); | |
| } | |
| xmlFreeParserCtxt(ctxt); | |
| return(ret); | |
| } | |
| static int | |
| xmlconfTestValid(const char *id, const char *filename, int options) { | |
| xmlDocPtr doc; | |
| xmlParserCtxtPtr ctxt; | |
| int ret = 1; | |
| ctxt = xmlNewParserCtxt(); | |
| if (ctxt == NULL) { | |
| test_log("test %s : %s out of memory\n", | |
| id, filename); | |
| return(0); | |
| } | |
| doc = xmlCtxtReadFile(ctxt, filename, NULL, options); | |
| if (doc == NULL) { | |
| test_log("test %s : %s failed to parse a valid document\n", | |
| id, filename); | |
| nb_errors++; | |
| ret = 0; | |
| } else { | |
| /* validity should be reported both in the context and in the document */ | |
| if ((ctxt->valid == 0) || ((doc->properties & XML_DOC_DTDVALID) == 0)) { | |
| test_log("test %s : %s failed to validate a valid document\n", | |
| id, filename); | |
| nb_errors++; | |
| ret = 0; | |
| } | |
| xmlFreeDoc(doc); | |
| } | |
| xmlFreeParserCtxt(ctxt); | |
| return(ret); | |
| } | |
| 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 *version = NULL; | |
| xmlChar *entities = NULL; | |
| xmlChar *edition = NULL; | |
| int options = 0; | |
| int nstest = 0; | |
| int mem, final; | |
| int i; | |
| testErrorsSize = 0; testErrors[0] = 0; | |
| nbError = 0; | |
| nbFatal = 0; | |
| id = xmlGetProp(cur, BAD_CAST "ID"); | |
| if (id == NULL) { | |
| test_log("test missing ID, line %ld\n", xmlGetLineNo(cur)); | |
| goto error; | |
| } | |
| for (i = 0;skipped_tests[i] != NULL;i++) { | |
| if (!strcmp(skipped_tests[i], (char *) id)) { | |
| test_log("Skipping test %s from skipped list\n", (char *) id); | |
| ret = 0; | |
| nb_skipped++; | |
| 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; | |
| } | |
| version = xmlGetProp(cur, BAD_CAST "VERSION"); | |
| 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"))) { | |
| if ((version != NULL) && (!xmlStrEqual(version, BAD_CAST "1.0"))) { | |
| test_log("Skipping test %s for %s\n", (char *) id, | |
| (char *) version); | |
| ret = 0; | |
| nb_skipped++; | |
| goto error; | |
| } | |
| 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")) { | |
| options |= XML_PARSE_DTDVALID; | |
| xmlconfTestValid((char *) id, (char *) filename, options); | |
| } else if (xmlStrEqual(type, BAD_CAST "invalid")) { | |
| options |= XML_PARSE_DTDVALID; | |
| xmlconfTestInvalid((char *) id, (char *) filename, options); | |
| } else if (xmlStrEqual(type, BAD_CAST "error")) { | |
| test_log("Skipping error test %s \n", (char *) id); | |
| ret = 0; | |
| nb_skipped++; | |
| goto 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++; | |
| xmlMemDisplayLast(logfile, final - mem); | |
| } | |
| nb_tests++; | |
| error: | |
| if (type != NULL) | |
| xmlFree(type); | |
| if (entities != NULL) | |
| xmlFree(entities); | |
| if (edition != NULL) | |
| xmlFree(edition); | |
| if (version != NULL) | |
| xmlFree(version); | |
| 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, int level) { | |
| xmlChar *profile; | |
| int ret = 0; | |
| int tests = 0; | |
| int output = 0; | |
| if (level == 1) { | |
| profile = xmlGetProp(cur, BAD_CAST "PROFILE"); | |
| if (profile != NULL) { | |
| output = 1; | |
| level++; | |
| 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, level); | |
| } 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 (output == 1) { | |
| 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, 1); | |
| } 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/xmlts20080827.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); | |
| printf("See %s for detailed output\n", LOGFILE); | |
| if ((nb_leaks == 0) && (nb_errors == NB_EXPECTED_ERRORS)) { | |
| printf("%d errors were expected\n", nb_errors); | |
| ret = 0; | |
| } | |
| } | |
| xmlXPathFreeContext(ctxtXPath); | |
| xmlCleanupParser(); | |
| xmlMemoryDump(); | |
| if (logfile != NULL) | |
| fclose(logfile); | |
| return(ret); | |
| } | |
| #else /* ! LIBXML_XPATH_ENABLED */ | |
| #include <stdio.h> | |
| int | |
| main(int argc, char **argv) { | |
| fprintf(stderr, "%s need XPath support\n", argv[0]); | |
| } | |
| #endif |