| /* |
| * testlimits.c: C program to run libxml2 regression tests checking various |
| * limits in document size. Will consume a lot of RAM and CPU cycles |
| * |
| * To compile on Unixes: |
| * cc -o testlimits `xml2-config --cflags` testlimits.c `xml2-config --libs` -lpthread |
| * |
| * See Copyright for the status of this software. |
| * |
| * daniel@veillard.com |
| */ |
| |
| #include "libxml.h" |
| #include <stdio.h> |
| |
| #if !defined(_WIN32) || defined(__CYGWIN__) |
| #include <unistd.h> |
| #endif |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <time.h> |
| |
| #include <libxml/parser.h> |
| #include <libxml/parserInternals.h> |
| #include <libxml/tree.h> |
| #include <libxml/uri.h> |
| #ifdef LIBXML_READER_ENABLED |
| #include <libxml/xmlreader.h> |
| #endif |
| |
| static int verbose = 0; |
| static int tests_quiet = 0; |
| |
| /************************************************************************ |
| * * |
| * time handling * |
| * * |
| ************************************************************************/ |
| |
| /* maximum time for one parsing before declaring a timeout */ |
| #define MAX_TIME 2 /* seconds */ |
| |
| static clock_t t0; |
| int timeout = 0; |
| |
| static void reset_timout(void) { |
| timeout = 0; |
| t0 = clock(); |
| } |
| |
| static int check_time(void) { |
| clock_t tnow = clock(); |
| if (((tnow - t0) / CLOCKS_PER_SEC) > MAX_TIME) { |
| timeout = 1; |
| return(0); |
| } |
| return(1); |
| } |
| |
| /************************************************************************ |
| * * |
| * Huge document generator * |
| * * |
| ************************************************************************/ |
| |
| #include <libxml/xmlIO.h> |
| |
| /* |
| * Huge documents are built using fixed start and end chunks |
| * and filling between the two an unconventional amount of char data |
| */ |
| typedef struct hugeTest hugeTest; |
| typedef hugeTest *hugeTestPtr; |
| struct hugeTest { |
| const char *description; |
| const char *name; |
| const char *start; |
| const char *end; |
| }; |
| |
| static struct hugeTest hugeTests[] = { |
| { "Huge text node", "huge:textNode", "<foo>", "</foo>" }, |
| { "Huge attribute node", "huge:attrNode", "<foo bar='", "'/>" }, |
| { "Huge comment node", "huge:commentNode", "<foo><!--", "--></foo>" }, |
| { "Huge PI node", "huge:piNode", "<foo><?bar ", "?></foo>" }, |
| }; |
| |
| static const char *current; |
| static int rlen; |
| static unsigned int currentTest = 0; |
| static int instate = 0; |
| |
| /** |
| * hugeMatch: |
| * @URI: an URI to test |
| * |
| * Check for an huge: query |
| * |
| * Returns 1 if yes and 0 if another Input module should be used |
| */ |
| static int |
| hugeMatch(const char * URI) { |
| if ((URI != NULL) && (!strncmp(URI, "huge:", 5))) |
| return(1); |
| return(0); |
| } |
| |
| /** |
| * hugeOpen: |
| * @URI: an URI to test |
| * |
| * Return a pointer to the huge: query handler, in this example simply |
| * the current pointer... |
| * |
| * Returns an Input context or NULL in case or error |
| */ |
| static void * |
| hugeOpen(const char * URI) { |
| if ((URI == NULL) || (strncmp(URI, "huge:", 5))) |
| return(NULL); |
| |
| for (currentTest = 0;currentTest < sizeof(hugeTests)/sizeof(hugeTests[0]); |
| currentTest++) |
| if (!strcmp(hugeTests[currentTest].name, URI)) |
| goto found; |
| |
| return(NULL); |
| |
| found: |
| rlen = strlen(hugeTests[currentTest].start); |
| current = hugeTests[currentTest].start; |
| instate = 0; |
| return((void *) current); |
| } |
| |
| /** |
| * hugeClose: |
| * @context: the read context |
| * |
| * Close the huge: query handler |
| * |
| * Returns 0 or -1 in case of error |
| */ |
| static int |
| hugeClose(void * context) { |
| if (context == NULL) return(-1); |
| fprintf(stderr, "\n"); |
| return(0); |
| } |
| |
| #define CHUNK 4096 |
| |
| char filling[CHUNK + 1]; |
| |
| static void fillFilling(void) { |
| int i; |
| |
| for (i = 0;i < CHUNK;i++) { |
| filling[i] = 'a'; |
| } |
| filling[CHUNK] = 0; |
| } |
| |
| size_t maxlen = 64 * 1024 * 1024; |
| size_t curlen = 0; |
| size_t dotlen; |
| |
| /** |
| * hugeRead: |
| * @context: the read context |
| * @buffer: where to store data |
| * @len: number of bytes to read |
| * |
| * Implement an huge: query read. |
| * |
| * Returns the number of bytes read or -1 in case of error |
| */ |
| static int |
| hugeRead(void *context, char *buffer, int len) |
| { |
| if ((context == NULL) || (buffer == NULL) || (len < 0)) |
| return (-1); |
| |
| if (instate == 0) { |
| if (len >= rlen) { |
| len = rlen; |
| rlen = 0; |
| memcpy(buffer, current, len); |
| instate = 1; |
| curlen = 0; |
| dotlen = maxlen / 10; |
| } else { |
| memcpy(buffer, current, len); |
| rlen -= len; |
| current += len; |
| } |
| } else if (instate == 2) { |
| if (len >= rlen) { |
| len = rlen; |
| rlen = 0; |
| memcpy(buffer, current, len); |
| instate = 3; |
| curlen = 0; |
| } else { |
| memcpy(buffer, current, len); |
| rlen -= len; |
| current += len; |
| } |
| } else if (instate == 1) { |
| if (len > CHUNK) len = CHUNK; |
| memcpy(buffer, &filling[0], len); |
| curlen += len; |
| if (curlen >= maxlen) { |
| rlen = strlen(hugeTests[currentTest].end); |
| current = hugeTests[currentTest].end; |
| instate = 2; |
| } else { |
| if (curlen > dotlen) { |
| fprintf(stderr, "."); |
| dotlen += maxlen / 10; |
| } |
| } |
| } else |
| len = 0; |
| return (len); |
| } |
| |
| /************************************************************************ |
| * * |
| * Crazy document generator * |
| * * |
| ************************************************************************/ |
| |
| unsigned int crazy_indx = 0; |
| |
| const char *crazy = "<?xml version='1.0' encoding='UTF-8'?>\ |
| <?tst ?>\ |
| <!-- tst -->\ |
| <!DOCTYPE foo [\ |
| <?tst ?>\ |
| <!-- tst -->\ |
| <!ELEMENT foo (#PCDATA)>\ |
| <!ELEMENT p (#PCDATA|emph)* >\ |
| ]>\ |
| <?tst ?>\ |
| <!-- tst -->\ |
| <foo bar='foo'>\ |
| <?tst ?>\ |
| <!-- tst -->\ |
| foo\ |
| <![CDATA[ ]]>\ |
| </foo>\ |
| <?tst ?>\ |
| <!-- tst -->"; |
| |
| /** |
| * crazyMatch: |
| * @URI: an URI to test |
| * |
| * Check for a crazy: query |
| * |
| * Returns 1 if yes and 0 if another Input module should be used |
| */ |
| static int |
| crazyMatch(const char * URI) { |
| if ((URI != NULL) && (!strncmp(URI, "crazy:", 6))) |
| return(1); |
| return(0); |
| } |
| |
| /** |
| * crazyOpen: |
| * @URI: an URI to test |
| * |
| * Return a pointer to the crazy: query handler, in this example simply |
| * the current pointer... |
| * |
| * Returns an Input context or NULL in case or error |
| */ |
| static void * |
| crazyOpen(const char * URI) { |
| if ((URI == NULL) || (strncmp(URI, "crazy:", 6))) |
| return(NULL); |
| |
| if (crazy_indx > strlen(crazy)) |
| return(NULL); |
| reset_timout(); |
| rlen = crazy_indx; |
| current = &crazy[0]; |
| instate = 0; |
| return((void *) current); |
| } |
| |
| /** |
| * crazyClose: |
| * @context: the read context |
| * |
| * Close the crazy: query handler |
| * |
| * Returns 0 or -1 in case of error |
| */ |
| static int |
| crazyClose(void * context) { |
| if (context == NULL) return(-1); |
| return(0); |
| } |
| |
| |
| /** |
| * crazyRead: |
| * @context: the read context |
| * @buffer: where to store data |
| * @len: number of bytes to read |
| * |
| * Implement an crazy: query read. |
| * |
| * Returns the number of bytes read or -1 in case of error |
| */ |
| static int |
| crazyRead(void *context, char *buffer, int len) |
| { |
| if ((context == NULL) || (buffer == NULL) || (len < 0)) |
| return (-1); |
| |
| if ((check_time() <= 0) && (instate == 1)) { |
| fprintf(stderr, "\ntimeout in crazy(%d)\n", crazy_indx); |
| rlen = strlen(crazy) - crazy_indx; |
| current = &crazy[crazy_indx]; |
| instate = 2; |
| } |
| if (instate == 0) { |
| if (len >= rlen) { |
| len = rlen; |
| rlen = 0; |
| memcpy(buffer, current, len); |
| instate = 1; |
| curlen = 0; |
| } else { |
| memcpy(buffer, current, len); |
| rlen -= len; |
| current += len; |
| } |
| } else if (instate == 2) { |
| if (len >= rlen) { |
| len = rlen; |
| rlen = 0; |
| memcpy(buffer, current, len); |
| instate = 3; |
| curlen = 0; |
| } else { |
| memcpy(buffer, current, len); |
| rlen -= len; |
| current += len; |
| } |
| } else if (instate == 1) { |
| if (len > CHUNK) len = CHUNK; |
| memcpy(buffer, &filling[0], len); |
| curlen += len; |
| if (curlen >= maxlen) { |
| rlen = strlen(crazy) - crazy_indx; |
| current = &crazy[crazy_indx]; |
| instate = 2; |
| } |
| } else |
| len = 0; |
| return (len); |
| } |
| /************************************************************************ |
| * * |
| * Libxml2 specific routines * |
| * * |
| ************************************************************************/ |
| |
| static int nb_tests = 0; |
| static int nb_errors = 0; |
| static int nb_leaks = 0; |
| static int extraMemoryFromResolver = 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 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 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 ((n<col) && (n++ < sizeof(content)-2) && (*ctnt != 0)) { |
| if (*(ctnt) != '\t') |
| *(ctnt) = ' '; |
| ctnt++; |
| } |
| *ctnt++ = '^'; |
| *ctnt = 0; |
| chanl(data ,"%s\n", content); |
| } |
| |
| static void |
| testStructuredErrorHandler(void *ctx ATTRIBUTE_UNUSED, xmlErrorPtr err) { |
| char *file = NULL; |
| int line = 0; |
| int code = -1; |
| int domain; |
| void *data = NULL; |
| const char *str; |
| const xmlChar *name = NULL; |
| xmlNodePtr node; |
| xmlErrorLevel level; |
| xmlParserInputPtr input = NULL; |
| xmlParserInputPtr cur = NULL; |
| xmlParserCtxtPtr ctxt = NULL; |
| |
| if (err == NULL) |
| return; |
| |
| file = err->file; |
| 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) { |
| xmlGetWarningsDefaultValue = 0; |
| xmlPedanticParserDefault(0); |
| |
| xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup); |
| xmlInitParser(); |
| xmlSetExternalEntityLoader(testExternalEntityLoader); |
| xmlSetStructuredErrorFunc(NULL, testStructuredErrorHandler); |
| /* |
| * register the new I/O handlers |
| */ |
| if (xmlRegisterInputCallbacks(hugeMatch, hugeOpen, |
| hugeRead, hugeClose) < 0) { |
| fprintf(stderr, "failed to register Huge handlers\n"); |
| exit(1); |
| } |
| if (xmlRegisterInputCallbacks(crazyMatch, crazyOpen, |
| crazyRead, crazyClose) < 0) { |
| fprintf(stderr, "failed to register Crazy handlers\n"); |
| exit(1); |
| } |
| } |
| |
| /************************************************************************ |
| * * |
| * SAX empty callbacks * |
| * * |
| ************************************************************************/ |
| |
| unsigned long callbacks = 0; |
| |
| /** |
| * isStandaloneCallback: |
| * @ctxt: An XML parser context |
| * |
| * Is this document tagged standalone ? |
| * |
| * Returns 1 if true |
| */ |
| static int |
| isStandaloneCallback(void *ctx ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return (0); |
| } |
| |
| /** |
| * hasInternalSubsetCallback: |
| * @ctxt: An XML parser context |
| * |
| * Does this document has an internal subset |
| * |
| * Returns 1 if true |
| */ |
| static int |
| hasInternalSubsetCallback(void *ctx ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return (0); |
| } |
| |
| /** |
| * hasExternalSubsetCallback: |
| * @ctxt: An XML parser context |
| * |
| * Does this document has an external subset |
| * |
| * Returns 1 if true |
| */ |
| static int |
| hasExternalSubsetCallback(void *ctx ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return (0); |
| } |
| |
| /** |
| * internalSubsetCallback: |
| * @ctxt: An XML parser context |
| * |
| * Does this document has an internal subset |
| */ |
| static void |
| internalSubsetCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED, |
| const xmlChar * ExternalID ATTRIBUTE_UNUSED, |
| const xmlChar * SystemID ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * externalSubsetCallback: |
| * @ctxt: An XML parser context |
| * |
| * Does this document has an external subset |
| */ |
| static void |
| externalSubsetCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED, |
| const xmlChar * ExternalID ATTRIBUTE_UNUSED, |
| const xmlChar * SystemID ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * resolveEntityCallback: |
| * @ctxt: An XML parser context |
| * @publicId: The public ID of the entity |
| * @systemId: The system ID of the entity |
| * |
| * Special entity resolver, better left to the parser, it has |
| * more context than the application layer. |
| * The default behaviour is to NOT resolve the entities, in that case |
| * the ENTITY_REF nodes are built in the structure (and the parameter |
| * values). |
| * |
| * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour. |
| */ |
| static xmlParserInputPtr |
| resolveEntityCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * publicId ATTRIBUTE_UNUSED, |
| const xmlChar * systemId ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return (NULL); |
| } |
| |
| /** |
| * getEntityCallback: |
| * @ctxt: An XML parser context |
| * @name: The entity name |
| * |
| * Get an entity by name |
| * |
| * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour. |
| */ |
| static xmlEntityPtr |
| getEntityCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return (NULL); |
| } |
| |
| /** |
| * getParameterEntityCallback: |
| * @ctxt: An XML parser context |
| * @name: The entity name |
| * |
| * Get a parameter entity by name |
| * |
| * Returns the xmlParserInputPtr |
| */ |
| static xmlEntityPtr |
| getParameterEntityCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return (NULL); |
| } |
| |
| |
| /** |
| * entityDeclCallback: |
| * @ctxt: An XML parser context |
| * @name: the entity name |
| * @type: the entity type |
| * @publicId: The public ID of the entity |
| * @systemId: The system ID of the entity |
| * @content: the entity value (without processing). |
| * |
| * An entity definition has been parsed |
| */ |
| static void |
| entityDeclCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED, |
| int type ATTRIBUTE_UNUSED, |
| const xmlChar * publicId ATTRIBUTE_UNUSED, |
| const xmlChar * systemId ATTRIBUTE_UNUSED, |
| xmlChar * content ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * attributeDeclCallback: |
| * @ctxt: An XML parser context |
| * @name: the attribute name |
| * @type: the attribute type |
| * |
| * An attribute definition has been parsed |
| */ |
| static void |
| attributeDeclCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * elem ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED, |
| int type ATTRIBUTE_UNUSED, int def ATTRIBUTE_UNUSED, |
| const xmlChar * defaultValue ATTRIBUTE_UNUSED, |
| xmlEnumerationPtr tree ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * elementDeclCallback: |
| * @ctxt: An XML parser context |
| * @name: the element name |
| * @type: the element type |
| * @content: the element value (without processing). |
| * |
| * An element definition has been parsed |
| */ |
| static void |
| elementDeclCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED, |
| int type ATTRIBUTE_UNUSED, |
| xmlElementContentPtr content ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * notationDeclCallback: |
| * @ctxt: An XML parser context |
| * @name: The name of the notation |
| * @publicId: The public ID of the entity |
| * @systemId: The system ID of the entity |
| * |
| * What to do when a notation declaration has been parsed. |
| */ |
| static void |
| notationDeclCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED, |
| const xmlChar * publicId ATTRIBUTE_UNUSED, |
| const xmlChar * systemId ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * unparsedEntityDeclCallback: |
| * @ctxt: An XML parser context |
| * @name: The name of the entity |
| * @publicId: The public ID of the entity |
| * @systemId: The system ID of the entity |
| * @notationName: the name of the notation |
| * |
| * What to do when an unparsed entity declaration is parsed |
| */ |
| static void |
| unparsedEntityDeclCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED, |
| const xmlChar * publicId ATTRIBUTE_UNUSED, |
| const xmlChar * systemId ATTRIBUTE_UNUSED, |
| const xmlChar * notationName ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * setDocumentLocatorCallback: |
| * @ctxt: An XML parser context |
| * @loc: A SAX Locator |
| * |
| * Receive the document locator at startup, actually xmlDefaultSAXLocator |
| * Everything is available on the context, so this is useless in our case. |
| */ |
| static void |
| setDocumentLocatorCallback(void *ctx ATTRIBUTE_UNUSED, |
| xmlSAXLocatorPtr loc ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * startDocumentCallback: |
| * @ctxt: An XML parser context |
| * |
| * called when the document start being processed. |
| */ |
| static void |
| startDocumentCallback(void *ctx ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * endDocumentCallback: |
| * @ctxt: An XML parser context |
| * |
| * called when the document end has been detected. |
| */ |
| static void |
| endDocumentCallback(void *ctx ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| #if 0 |
| /** |
| * startElementCallback: |
| * @ctxt: An XML parser context |
| * @name: The element name |
| * |
| * called when an opening tag has been processed. |
| */ |
| static void |
| startElementCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED, |
| const xmlChar ** atts ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * endElementCallback: |
| * @ctxt: An XML parser context |
| * @name: The element name |
| * |
| * called when the end of an element has been detected. |
| */ |
| static void |
| endElementCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| #endif |
| |
| /** |
| * charactersCallback: |
| * @ctxt: An XML parser context |
| * @ch: a xmlChar string |
| * @len: the number of xmlChar |
| * |
| * receiving some chars from the parser. |
| * Question: how much at a time ??? |
| */ |
| static void |
| charactersCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * ch ATTRIBUTE_UNUSED, |
| int len ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * referenceCallback: |
| * @ctxt: An XML parser context |
| * @name: The entity name |
| * |
| * called when an entity reference is detected. |
| */ |
| static void |
| referenceCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * name ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * ignorableWhitespaceCallback: |
| * @ctxt: An XML parser context |
| * @ch: a xmlChar string |
| * @start: the first char in the string |
| * @len: the number of xmlChar |
| * |
| * receiving some ignorable whitespaces from the parser. |
| * Question: how much at a time ??? |
| */ |
| static void |
| ignorableWhitespaceCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * ch ATTRIBUTE_UNUSED, |
| int len ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * processingInstructionCallback: |
| * @ctxt: An XML parser context |
| * @target: the target name |
| * @data: the PI data's |
| * @len: the number of xmlChar |
| * |
| * A processing instruction has been parsed. |
| */ |
| static void |
| processingInstructionCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * target ATTRIBUTE_UNUSED, |
| const xmlChar * data ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * cdataBlockCallback: |
| * @ctx: the user data (XML parser context) |
| * @value: The pcdata content |
| * @len: the block length |
| * |
| * called when a pcdata block has been parsed |
| */ |
| static void |
| cdataBlockCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * value ATTRIBUTE_UNUSED, |
| int len ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * commentCallback: |
| * @ctxt: An XML parser context |
| * @value: the comment content |
| * |
| * A comment has been parsed. |
| */ |
| static void |
| commentCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * value ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * warningCallback: |
| * @ctxt: An XML parser context |
| * @msg: the message to display/transmit |
| * @...: extra parameters for the message display |
| * |
| * Display and format a warning messages, gives file, line, position and |
| * extra parameters. |
| */ |
| static void XMLCDECL |
| warningCallback(void *ctx ATTRIBUTE_UNUSED, |
| const char *msg ATTRIBUTE_UNUSED, ...) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * errorCallback: |
| * @ctxt: An XML parser context |
| * @msg: the message to display/transmit |
| * @...: extra parameters for the message display |
| * |
| * Display and format a error messages, gives file, line, position and |
| * extra parameters. |
| */ |
| static void XMLCDECL |
| errorCallback(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED, |
| ...) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * fatalErrorCallback: |
| * @ctxt: An XML parser context |
| * @msg: the message to display/transmit |
| * @...: extra parameters for the message display |
| * |
| * Display and format a fatalError messages, gives file, line, position and |
| * extra parameters. |
| */ |
| static void XMLCDECL |
| fatalErrorCallback(void *ctx ATTRIBUTE_UNUSED, |
| const char *msg ATTRIBUTE_UNUSED, ...) |
| { |
| return; |
| } |
| |
| |
| /* |
| * SAX2 specific callbacks |
| */ |
| |
| /** |
| * startElementNsCallback: |
| * @ctxt: An XML parser context |
| * @name: The element name |
| * |
| * called when an opening tag has been processed. |
| */ |
| static void |
| startElementNsCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * localname ATTRIBUTE_UNUSED, |
| const xmlChar * prefix ATTRIBUTE_UNUSED, |
| const xmlChar * URI ATTRIBUTE_UNUSED, |
| int nb_namespaces ATTRIBUTE_UNUSED, |
| const xmlChar ** namespaces ATTRIBUTE_UNUSED, |
| int nb_attributes ATTRIBUTE_UNUSED, |
| int nb_defaulted ATTRIBUTE_UNUSED, |
| const xmlChar ** attributes ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| /** |
| * endElementCallback: |
| * @ctxt: An XML parser context |
| * @name: The element name |
| * |
| * called when the end of an element has been detected. |
| */ |
| static void |
| endElementNsCallback(void *ctx ATTRIBUTE_UNUSED, |
| const xmlChar * localname ATTRIBUTE_UNUSED, |
| const xmlChar * prefix ATTRIBUTE_UNUSED, |
| const xmlChar * URI ATTRIBUTE_UNUSED) |
| { |
| callbacks++; |
| return; |
| } |
| |
| static xmlSAXHandler callbackSAX2HandlerStruct = { |
| internalSubsetCallback, |
| isStandaloneCallback, |
| hasInternalSubsetCallback, |
| hasExternalSubsetCallback, |
| resolveEntityCallback, |
| getEntityCallback, |
| entityDeclCallback, |
| notationDeclCallback, |
| attributeDeclCallback, |
| elementDeclCallback, |
| unparsedEntityDeclCallback, |
| setDocumentLocatorCallback, |
| startDocumentCallback, |
| endDocumentCallback, |
| NULL, |
| NULL, |
| referenceCallback, |
| charactersCallback, |
| ignorableWhitespaceCallback, |
| processingInstructionCallback, |
| commentCallback, |
| warningCallback, |
| errorCallback, |
| fatalErrorCallback, |
| getParameterEntityCallback, |
| cdataBlockCallback, |
| externalSubsetCallback, |
| XML_SAX2_MAGIC, |
| NULL, |
| startElementNsCallback, |
| endElementNsCallback, |
| NULL |
| }; |
| |
| static xmlSAXHandlerPtr callbackSAX2Handler = &callbackSAX2HandlerStruct; |
| |
| /************************************************************************ |
| * * |
| * The tests front-ends * |
| * * |
| ************************************************************************/ |
| |
| /** |
| * readerTest: |
| * @filename: the file to parse |
| * @max_size: size of the limit to test |
| * @options: parsing options |
| * @fail: should a failure be reported |
| * |
| * Parse a memory generated file using SAX |
| * |
| * Returns 0 in case of success, an error code otherwise |
| */ |
| static int |
| saxTest(const char *filename, size_t limit, int options, int fail) { |
| int res = 0; |
| xmlParserCtxtPtr ctxt; |
| xmlDocPtr doc; |
| xmlSAXHandlerPtr old_sax; |
| |
| nb_tests++; |
| |
| maxlen = limit; |
| ctxt = xmlNewParserCtxt(); |
| if (ctxt == NULL) { |
| fprintf(stderr, "Failed to create parser context\n"); |
| return(1); |
| } |
| old_sax = ctxt->sax; |
| ctxt->sax = callbackSAX2Handler; |
| ctxt->userData = NULL; |
| doc = xmlCtxtReadFile(ctxt, filename, NULL, options); |
| |
| if (doc != NULL) { |
| fprintf(stderr, "SAX parsing generated a document !\n"); |
| xmlFreeDoc(doc); |
| res = 0; |
| } else if (ctxt->wellFormed == 0) { |
| if (fail) |
| res = 0; |
| else { |
| fprintf(stderr, "Failed to parse '%s' %lu\n", filename, limit); |
| res = 1; |
| } |
| } else { |
| if (fail) { |
| fprintf(stderr, "Failed to get failure for '%s' %lu\n", |
| filename, limit); |
| res = 1; |
| } else |
| res = 0; |
| } |
| ctxt->sax = old_sax; |
| xmlFreeParserCtxt(ctxt); |
| |
| return(res); |
| } |
| #ifdef LIBXML_READER_ENABLED |
| /** |
| * readerTest: |
| * @filename: the file to parse |
| * @max_size: size of the limit to test |
| * @options: parsing options |
| * @fail: should a failure be reported |
| * |
| * Parse a memory generated file using the xmlReader |
| * |
| * Returns 0 in case of success, an error code otherwise |
| */ |
| static int |
| readerTest(const char *filename, size_t limit, int options, int fail) { |
| xmlTextReaderPtr reader; |
| int res = 0; |
| int ret; |
| |
| nb_tests++; |
| |
| maxlen = limit; |
| reader = xmlReaderForFile(filename , NULL, options); |
| if (reader == NULL) { |
| fprintf(stderr, "Failed to open '%s' test\n", filename); |
| return(1); |
| } |
| ret = xmlTextReaderRead(reader); |
| while (ret == 1) { |
| ret = xmlTextReaderRead(reader); |
| } |
| if (ret != 0) { |
| if (fail) |
| res = 0; |
| else { |
| if (strncmp(filename, "crazy:", 6) == 0) |
| fprintf(stderr, "Failed to parse '%s' %u\n", |
| filename, crazy_indx); |
| else |
| fprintf(stderr, "Failed to parse '%s' %lu\n", |
| filename, limit); |
| res = 1; |
| } |
| } else { |
| if (fail) { |
| if (strncmp(filename, "crazy:", 6) == 0) |
| fprintf(stderr, "Failed to get failure for '%s' %u\n", |
| filename, crazy_indx); |
| else |
| fprintf(stderr, "Failed to get failure for '%s' %lu\n", |
| filename, limit); |
| res = 1; |
| } else |
| res = 0; |
| } |
| if (timeout) |
| res = 1; |
| xmlFreeTextReader(reader); |
| |
| return(res); |
| } |
| #endif |
| |
| /************************************************************************ |
| * * |
| * Tests descriptions * |
| * * |
| ************************************************************************/ |
| |
| typedef int (*functest) (const char *filename, size_t limit, int options, |
| int fail); |
| |
| typedef struct limitDesc limitDesc; |
| typedef limitDesc *limitDescPtr; |
| struct limitDesc { |
| const char *name; /* the huge generator name */ |
| size_t limit; /* the limit to test */ |
| int options; /* extra parser options */ |
| int fail; /* whether the test should fail */ |
| }; |
| |
| static limitDesc limitDescriptions[] = { |
| /* max length of a text node in content */ |
| {"huge:textNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0}, |
| {"huge:textNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1}, |
| {"huge:textNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0}, |
| /* max length of a text node in content */ |
| {"huge:attrNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0}, |
| {"huge:attrNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1}, |
| {"huge:attrNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0}, |
| /* max length of a comment node */ |
| {"huge:commentNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0}, |
| {"huge:commentNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1}, |
| {"huge:commentNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0}, |
| /* max length of a PI node */ |
| {"huge:piNode", XML_MAX_TEXT_LENGTH - CHUNK, 0, 0}, |
| {"huge:piNode", XML_MAX_TEXT_LENGTH + CHUNK, 0, 1}, |
| {"huge:piNode", XML_MAX_TEXT_LENGTH + CHUNK, XML_PARSE_HUGE, 0}, |
| }; |
| |
| typedef struct testDesc testDesc; |
| typedef testDesc *testDescPtr; |
| struct testDesc { |
| const char *desc; /* descripton of the test */ |
| functest func; /* function implementing the test */ |
| }; |
| |
| static |
| testDesc testDescriptions[] = { |
| { "Parsing of huge files with the sax parser", saxTest}, |
| /* { "Parsing of huge files with the tree parser", treeTest}, */ |
| #ifdef LIBXML_READER_ENABLED |
| { "Parsing of huge files with the reader", readerTest}, |
| #endif |
| {NULL, NULL} |
| }; |
| |
| typedef struct testException testException; |
| typedef testException *testExceptionPtr; |
| struct testException { |
| unsigned int test; /* the parser test number */ |
| unsigned int limit; /* the limit test number */ |
| int fail; /* new fail value or -1*/ |
| size_t size; /* new limit value or 0 */ |
| }; |
| |
| static |
| testException testExceptions[] = { |
| /* the SAX parser doesn't hit a limit of XML_MAX_TEXT_LENGTH text nodes */ |
| { 0, 1, 0, 0}, |
| }; |
| |
| static int |
| launchTests(testDescPtr tst, unsigned int test) { |
| int res = 0, err = 0; |
| unsigned int i, j; |
| size_t limit; |
| int fail; |
| |
| if (tst == NULL) return(-1); |
| |
| for (i = 0;i < sizeof(limitDescriptions)/sizeof(limitDescriptions[0]);i++) { |
| limit = limitDescriptions[i].limit; |
| fail = limitDescriptions[i].fail; |
| /* |
| * Handle exceptions if any |
| */ |
| for (j = 0;j < sizeof(testExceptions)/sizeof(testExceptions[0]);j++) { |
| if ((testExceptions[j].test == test) && |
| (testExceptions[j].limit == i)) { |
| if (testExceptions[j].fail != -1) |
| fail = testExceptions[j].fail; |
| if (testExceptions[j].size != 0) |
| limit = testExceptions[j].size; |
| break; |
| } |
| } |
| res = tst->func(limitDescriptions[i].name, limit, |
| limitDescriptions[i].options, fail); |
| if (res != 0) { |
| nb_errors++; |
| err++; |
| } |
| } |
| return(err); |
| } |
| |
| |
| static int |
| runtest(unsigned int i) { |
| int ret = 0, res; |
| int old_errors, old_tests, old_leaks; |
| |
| old_errors = nb_errors; |
| old_tests = nb_tests; |
| old_leaks = nb_leaks; |
| if ((tests_quiet == 0) && (testDescriptions[i].desc != NULL)) |
| printf("## %s\n", testDescriptions[i].desc); |
| res = launchTests(&testDescriptions[i], i); |
| if (res != 0) |
| ret++; |
| if (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); |
| } |
| return(ret); |
| } |
| |
| static int |
| launchCrazySAX(unsigned int test, int fail) { |
| int res = 0, err = 0; |
| |
| crazy_indx = test; |
| |
| res = saxTest("crazy::test", XML_MAX_LOOKUP_LIMIT - CHUNK, 0, fail); |
| if (res != 0) { |
| nb_errors++; |
| err++; |
| } |
| if (tests_quiet == 0) |
| fprintf(stderr, "%c", crazy[test]); |
| |
| return(err); |
| } |
| |
| #ifdef LIBXML_READER_ENABLED |
| static int |
| launchCrazy(unsigned int test, int fail) { |
| int res = 0, err = 0; |
| |
| crazy_indx = test; |
| |
| res = readerTest("crazy::test", XML_MAX_LOOKUP_LIMIT - CHUNK, 0, fail); |
| if (res != 0) { |
| nb_errors++; |
| err++; |
| } |
| if (tests_quiet == 0) |
| fprintf(stderr, "%c", crazy[test]); |
| |
| return(err); |
| } |
| #endif |
| |
| static int get_crazy_fail(int test) { |
| /* |
| * adding 1000000 of character 'a' leads to parser failure mostly |
| * everywhere except in those special spots. Need to be updated |
| * each time crazy is updated |
| */ |
| int fail = 1; |
| if ((test == 44) || /* PI in Misc */ |
| ((test >= 50) && (test <= 55)) || /* Comment in Misc */ |
| (test == 79) || /* PI in DTD */ |
| ((test >= 85) && (test <= 90)) || /* Comment in DTD */ |
| (test == 154) || /* PI in Misc */ |
| ((test >= 160) && (test <= 165)) || /* Comment in Misc */ |
| ((test >= 178) && (test <= 181)) || /* attribute value */ |
| (test == 183) || /* Text */ |
| (test == 189) || /* PI in Content */ |
| (test == 191) || /* Text */ |
| ((test >= 195) && (test <= 200)) || /* Comment in Content */ |
| ((test >= 203) && (test <= 206)) || /* Text */ |
| (test == 215) || (test == 216) || /* in CDATA */ |
| (test == 219) || /* Text */ |
| (test == 231) || /* PI in Misc */ |
| ((test >= 237) && (test <= 242))) /* Comment in Misc */ |
| fail = 0; |
| return(fail); |
| } |
| |
| static int |
| runcrazy(void) { |
| int ret = 0, res = 0; |
| int old_errors, old_tests, old_leaks; |
| unsigned int i; |
| |
| old_errors = nb_errors; |
| old_tests = nb_tests; |
| old_leaks = nb_leaks; |
| |
| #ifdef LIBXML_READER_ENABLED |
| if (tests_quiet == 0) { |
| printf("## Crazy tests on reader\n"); |
| } |
| for (i = 0;i < strlen(crazy);i++) { |
| res += launchCrazy(i, get_crazy_fail(i)); |
| if (res != 0) |
| ret++; |
| } |
| #endif |
| |
| if (tests_quiet == 0) { |
| printf("\n## Crazy tests on SAX\n"); |
| } |
| for (i = 0;i < strlen(crazy);i++) { |
| res += launchCrazySAX(i, get_crazy_fail(i)); |
| if (res != 0) |
| ret++; |
| } |
| if (tests_quiet == 0) |
| fprintf(stderr, "\n"); |
| if (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); |
| } |
| return(ret); |
| } |
| |
| |
| int |
| main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) { |
| int i, a, ret = 0; |
| int subset = 0; |
| |
| fillFilling(); |
| initializeLibxml2(); |
| |
| for (a = 1; a < argc;a++) { |
| if (!strcmp(argv[a], "-v")) |
| verbose = 1; |
| else if (!strcmp(argv[a], "-quiet")) |
| tests_quiet = 1; |
| else if (!strcmp(argv[a], "-crazy")) |
| subset = 1; |
| } |
| if (subset == 0) { |
| for (i = 0; testDescriptions[i].func != NULL; i++) { |
| ret += runtest(i); |
| } |
| } |
| ret += runcrazy(); |
| 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); |
| } |