blob: fefbafdbd0674aa6efe8bc99934760df796fd789 [file] [log] [blame]
/*
* runsuite.c: C program to run libxml2 againts external published testsuites
*
* See Copyright for the status of this software.
*
* daniel@veillard.com
*/
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <glob.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/uri.h>
#include <libxml/xmlreader.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/relaxng.h>
#include <libxml/xmlschemas.h>
#include <libxml/xmlschemastypes.h>
/************************************************************************
* *
* File name and path utilities *
* *
************************************************************************/
static int checkTestFile(const char *filename) {
struct stat buf;
if (stat(filename, &buf) == -1)
return(0);
if (!S_ISREG(buf.st_mode))
return(0);
return(1);
}
/************************************************************************
* *
* Libxml2 specific routines *
* *
************************************************************************/
static int nb_tests = 0;
static int nb_errors = 0;
static int nb_leaks = 0;
static long libxmlMemoryAllocatedBase = 0;
static int extraMemoryFromResolver = 0;
static int
fatalError(void) {
fprintf(stderr, "Exitting tests on fatal error\n");
exit(1);
}
/*
* 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;
if (checkTestFile(URL)) {
ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt);
} else {
int memused = xmlMemUsed();
ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt);
extraMemoryFromResolver += xmlMemUsed() - memused;
}
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
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
initializeLibxml2(void) {
xmlGetWarningsDefaultValue = 0;
xmlPedanticParserDefault(0);
xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup);
xmlInitParser();
xmlSetExternalEntityLoader(testExternalEntityLoader);
#ifdef LIBXML_SCHEMAS_ENABLED
xmlSchemaInitTypes();
xmlRelaxNGInitTypes();
#endif
libxmlMemoryAllocatedBase = xmlMemUsed();
}
static xmlNodePtr
getNext(xmlNodePtr cur, const char *xpath) {
xmlNodePtr ret = NULL;
xmlXPathObjectPtr res;
xmlXPathContextPtr ctxt;
xmlXPathCompExprPtr comp;
if ((cur == NULL) || (cur->doc == NULL) || (xpath == NULL))
return(NULL);
ctxt = xmlXPathNewContext(cur->doc);
ctxt->node = cur;
comp = xmlXPathCompile(BAD_CAST xpath);
if (comp == NULL) {
fprintf(stderr, "Failed to compile %s\n", xpath);
xmlXPathFreeContext(ctxt);
return(NULL);
}
res = xmlXPathCompiledEval(comp, ctxt);
xmlXPathFreeCompExpr(comp);
xmlXPathFreeContext(ctxt);
if (res == NULL)
return(NULL);
if ((res->type == XPATH_NODESET) &&
(res->nodesetval != NULL) &&
(res->nodesetval->nodeNr > 0) &&
(res->nodesetval->nodeTab != NULL))
ret = res->nodesetval->nodeTab[0];
xmlXPathFreeObject(res);
return(ret);
}
static xmlChar *
getString(xmlNodePtr cur, const char *xpath) {
xmlChar *ret = NULL;
xmlXPathObjectPtr res;
xmlXPathContextPtr ctxt;
xmlXPathCompExprPtr comp;
if ((cur == NULL) || (cur->doc == NULL) || (xpath == NULL))
return(NULL);
ctxt = xmlXPathNewContext(cur->doc);
ctxt->node = cur;
comp = xmlXPathCompile(BAD_CAST xpath);
if (comp == NULL) {
fprintf(stderr, "Failed to compile %s\n", xpath);
return(NULL);
}
res = xmlXPathCompiledEval(comp, ctxt);
xmlXPathFreeCompExpr(comp);
xmlXPathFreeContext(ctxt);
if (res == NULL)
return(NULL);
if (res->type == XPATH_STRING) {
ret = res->stringval;
res->stringval = NULL;
}
xmlXPathFreeObject(res);
return(ret);
}
/************************************************************************
* *
* Test test/xsdtest/xsdtestsuite.xml *
* *
************************************************************************/
static int
xsdIncorectTestCase(int verbose, xmlNodePtr cur) {
xmlNodePtr test;
xmlBufferPtr buf;
xmlRelaxNGParserCtxtPtr pctxt;
xmlRelaxNGPtr rng = NULL;
int ret = 0, memt;
cur = getNext(cur, "./incorrect[1]");
if (cur == NULL) {
return(0);
}
test = getNext(cur, "./*");
if (test == NULL) {
fprintf(stderr, "Failed to find test in correct line %ld\n",
xmlGetLineNo(cur));
return(1);
}
memt = xmlMemUsed();
extraMemoryFromResolver = 0;
/*
* dump the schemas to a buffer, then reparse it and compile the schemas
*/
buf = xmlBufferCreate();
if (buf == NULL) {
fprintf(stderr, "out of memory !\n");
fatalError();
}
xmlNodeDump(buf, test->doc, test, 0, 0);
pctxt = xmlRelaxNGNewMemParserCtxt((const char *)buf->content, buf->use);
xmlRelaxNGSetParserErrors(pctxt,
(xmlRelaxNGValidityErrorFunc) testErrorHandler,
(xmlRelaxNGValidityWarningFunc) testErrorHandler,
pctxt);
rng = xmlRelaxNGParse(pctxt);
xmlRelaxNGFreeParserCtxt(pctxt);
if (rng != NULL) {
fprintf(stderr, "Failed to detect incorect RNG line %ld\n",
xmlGetLineNo(test));
ret = 1;
goto done;
}
done:
if (buf != NULL)
xmlBufferFree(buf);
if (rng != NULL)
xmlRelaxNGFree(rng);
xmlResetLastError();
if ((memt != xmlMemUsed()) && (extraMemoryFromResolver == 0)) {
fprintf(stderr, "Validation of tests starting line %ld leaked %d\n",
xmlGetLineNo(cur), xmlMemUsed() - memt);
nb_leaks++;
}
return(ret);
}
static int
xsdTestCase(int verbose, xmlNodePtr tst) {
xmlNodePtr test, tmp, cur;
xmlBufferPtr buf;
xmlDocPtr doc = NULL;
xmlRelaxNGParserCtxtPtr pctxt;
xmlRelaxNGValidCtxtPtr ctxt;
xmlRelaxNGPtr rng = NULL;
int ret = 0, mem, memt;
cur = getNext(tst, "./correct[1]");
if (cur == NULL) {
return(xsdIncorectTestCase(verbose, tst));
}
test = getNext(cur, "./*");
if (test == NULL) {
fprintf(stderr, "Failed to find test in correct line %ld\n",
xmlGetLineNo(cur));
return(1);
}
memt = xmlMemUsed();
extraMemoryFromResolver = 0;
/*
* dump the schemas to a buffer, then reparse it and compile the schemas
*/
buf = xmlBufferCreate();
if (buf == NULL) {
fprintf(stderr, "out of memory !\n");
fatalError();
}
xmlNodeDump(buf, test->doc, test, 0, 0);
pctxt = xmlRelaxNGNewMemParserCtxt((const char *)buf->content, buf->use);
xmlRelaxNGSetParserErrors(pctxt,
(xmlRelaxNGValidityErrorFunc) testErrorHandler,
(xmlRelaxNGValidityWarningFunc) testErrorHandler,
pctxt);
rng = xmlRelaxNGParse(pctxt);
xmlRelaxNGFreeParserCtxt(pctxt);
if (extraMemoryFromResolver)
memt = 0;
if (rng == NULL) {
fprintf(stderr, "Failed to parse RNGtest line %ld\n",
xmlGetLineNo(test));
nb_errors++;
ret = 1;
goto done;
}
/*
* now scan all the siblings of correct to process the <valid> tests
*/
tmp = getNext(cur, "following-sibling::valid[1]");
while (tmp != NULL) {
test = getNext(tmp, "./*");
if (test == NULL) {
fprintf(stderr, "Failed to find test in <valid> line %ld\n",
xmlGetLineNo(tmp));
} else {
xmlBufferEmpty(buf);
xmlNodeDump(buf, test->doc, test, 0, 0);
/*
* We are ready to run the test
*/
mem = xmlMemUsed();
extraMemoryFromResolver = 0;
doc = xmlReadMemory((const char *)buf->content, buf->use,
"test", NULL, 0);
if (doc == NULL) {
fprintf(stderr,
"Failed to parse valid instance line %ld\n",
xmlGetLineNo(tmp));
nb_errors++;
} else {
nb_tests++;
ctxt = xmlRelaxNGNewValidCtxt(rng);
xmlRelaxNGSetValidErrors(ctxt,
(xmlRelaxNGValidityErrorFunc) testErrorHandler,
(xmlRelaxNGValidityWarningFunc) testErrorHandler,
ctxt);
ret = xmlRelaxNGValidateDoc(ctxt, doc);
xmlRelaxNGFreeValidCtxt(ctxt);
if (ret > 0) {
fprintf(stderr,
"Failed to validate valid instance line %ld\n",
xmlGetLineNo(tmp));
nb_errors++;
} else if (ret < 0) {
fprintf(stderr,
"Internal error validating instance line %ld\n",
xmlGetLineNo(tmp));
nb_errors++;
}
xmlFreeDoc(doc);
}
xmlResetLastError();
if ((mem != xmlMemUsed()) && (extraMemoryFromResolver == 0)) {
fprintf(stderr, "Validation of instance line %ld leaked %d\n",
xmlGetLineNo(tmp), xmlMemUsed() - mem);
xmlMemoryDump();
nb_leaks++;
}
}
tmp = getNext(tmp, "following-sibling::valid[1]");
}
/*
* now scan all the siblings of correct to process the <invalid> tests
*/
tmp = getNext(cur, "following-sibling::invalid[1]");
while (tmp != NULL) {
test = getNext(tmp, "./*");
if (test == NULL) {
fprintf(stderr, "Failed to find test in <invalid> line %ld\n",
xmlGetLineNo(tmp));
} else {
xmlBufferEmpty(buf);
xmlNodeDump(buf, test->doc, test, 0, 0);
/*
* We are ready to run the test
*/
mem = xmlMemUsed();
extraMemoryFromResolver = 0;
doc = xmlReadMemory((const char *)buf->content, buf->use,
"test", NULL, 0);
if (doc == NULL) {
fprintf(stderr,
"Failed to parse valid instance line %ld\n",
xmlGetLineNo(tmp));
nb_errors++;
} else {
nb_tests++;
ctxt = xmlRelaxNGNewValidCtxt(rng);
xmlRelaxNGSetValidErrors(ctxt,
(xmlRelaxNGValidityErrorFunc) testErrorHandler,
(xmlRelaxNGValidityWarningFunc) testErrorHandler,
ctxt);
ret = xmlRelaxNGValidateDoc(ctxt, doc);
xmlRelaxNGFreeValidCtxt(ctxt);
if (ret == 0) {
fprintf(stderr,
"Failed to detect invalid instance line %ld\n",
xmlGetLineNo(tmp));
nb_errors++;
} else if (ret < 0) {
fprintf(stderr,
"Internal error validating instance line %ld\n",
xmlGetLineNo(tmp));
nb_errors++;
}
xmlFreeDoc(doc);
}
xmlResetLastError();
if ((mem != xmlMemUsed()) && (extraMemoryFromResolver == 0)) {
fprintf(stderr, "Validation of instance line %ld leaked %d\n",
xmlGetLineNo(tmp), xmlMemUsed() - mem);
xmlMemoryDump();
nb_leaks++;
}
}
tmp = getNext(tmp, "following-sibling::invalid[1]");
}
done:
if (buf != NULL)
xmlBufferFree(buf);
if (rng != NULL)
xmlRelaxNGFree(rng);
xmlResetLastError();
if ((memt != xmlMemUsed()) && (memt != 0)) {
fprintf(stderr, "Validation of tests starting line %ld leaked %d\n",
xmlGetLineNo(cur), xmlMemUsed() - memt);
nb_leaks++;
}
return(ret);
}
static int
xsdTestSuite(int verbose, xmlNodePtr cur) {
if (verbose) {
xmlChar *doc = getString(cur, "string(documentation)");
if (doc != NULL) {
printf("Suite %s\n", doc);
xmlFree(doc);
}
}
cur = getNext(cur, "./testCase[1]");
while (cur != NULL) {
xsdTestCase(verbose, cur);
cur = getNext(cur, "following-sibling::testCase[1]");
}
return(0);
}
static int
xsdTest(int verbose) {
xmlDocPtr doc;
xmlNodePtr cur;
const char *filename = "test/xsdtest/xsdtestsuite.xml";
int ret = 0;
doc = xmlReadFile(filename, NULL, XML_PARSE_NOENT);
if (doc == NULL) {
fprintf(stderr, "Failed to parse %s\n", filename);
return(-1);
}
printf("## XML Schemas datatypes test suite from James Clark\n");
cur = xmlDocGetRootElement(doc);
if ((cur == NULL) || (!xmlStrEqual(cur->name, BAD_CAST "testSuite"))) {
fprintf(stderr, "Unexpected format %s\n", filename);
ret = -1;
goto done;
}
cur = getNext(cur, "./testSuite[1]");
if ((cur == NULL) || (!xmlStrEqual(cur->name, BAD_CAST "testSuite"))) {
fprintf(stderr, "Unexpected format %s\n", filename);
ret = -1;
goto done;
}
while (cur != NULL) {
xsdTestSuite(verbose, cur);
cur = getNext(cur, "following-sibling::testSuite[1]");
}
done:
if (doc != NULL)
xmlFreeDoc(doc);
return(ret);
}
static int
rngTestSuite(int verbose, xmlNodePtr cur) {
if (verbose) {
xmlChar *doc = getString(cur, "string(documentation)");
if (doc != NULL) {
printf("Suite %s\n", doc);
xmlFree(doc);
} else {
doc = getString(cur, "string(section)");
if (doc != NULL) {
printf("Section %s\n", doc);
xmlFree(doc);
}
}
}
cur = getNext(cur, "./testSuite[1]");
while (cur != NULL) {
xsdTestSuite(verbose, cur);
cur = getNext(cur, "following-sibling::testSuite[1]");
}
return(0);
}
static int
rngTest1(int verbose) {
xmlDocPtr doc;
xmlNodePtr cur;
const char *filename = "test/relaxng/OASIS/spectest.xml";
int ret = 0;
doc = xmlReadFile(filename, NULL, XML_PARSE_NOENT);
if (doc == NULL) {
fprintf(stderr, "Failed to parse %s\n", filename);
return(-1);
}
printf("## Relax NG test suite 1 from James Clark\n");
cur = xmlDocGetRootElement(doc);
if ((cur == NULL) || (!xmlStrEqual(cur->name, BAD_CAST "testSuite"))) {
fprintf(stderr, "Unexpected format %s\n", filename);
ret = -1;
goto done;
}
cur = getNext(cur, "./testSuite[1]");
if ((cur == NULL) || (!xmlStrEqual(cur->name, BAD_CAST "testSuite"))) {
fprintf(stderr, "Unexpected format %s\n", filename);
ret = -1;
goto done;
}
while (cur != NULL) {
rngTestSuite(verbose, cur);
cur = getNext(cur, "following-sibling::testSuite[1]");
}
done:
if (doc != NULL)
xmlFreeDoc(doc);
return(ret);
}
/************************************************************************
* *
* Libxml2 specific routines *
* *
************************************************************************/
int
main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) {
int res, ret = 0;
int verbose = 0;
int old_errors, old_tests, old_leaks;
initializeLibxml2();
if ((argc >= 2) && (!strcmp(argv[1], "-v")))
verbose = 1;
res = xsdTest(verbose);
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);
old_errors = nb_errors;
old_tests = nb_tests;
old_leaks = nb_leaks;
res = rngTest1(verbose);
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);
old_errors = nb_errors;
old_tests = nb_tests;
old_leaks = nb_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);
}
xmlCleanupParser();
xmlMemoryDump();
return(ret);
}