2 * testrecurse.c: C program to run libxml2 regression tests checking entities
5 * To compile on Unixes:
6 * cc -o testrecurse `xml2-config --cflags` testrecurse.c `xml2-config --libs` -lpthread
8 * See Copyright for the status of this software.
19 #if !defined(_WIN32) || defined(__CYGWIN__)
23 #include <sys/types.h>
27 #include <libxml/parser.h>
28 #include <libxml/tree.h>
29 #include <libxml/uri.h>
30 #ifdef LIBXML_READER_ENABLED
31 #include <libxml/xmlreader.h>
35 * O_BINARY is just for Windows compatibility - if it isn't defined
36 * on this system, avoid any compilation error
39 #define RD_FLAGS O_RDONLY | O_BINARY
41 #define RD_FLAGS O_RDONLY
44 typedef int (*functest
) (const char *filename
, const char *result
,
45 const char *error
, int options
);
47 typedef struct testDesc testDesc
;
48 typedef testDesc
*testDescPtr
;
50 const char *desc
; /* descripton of the test */
51 functest func
; /* function implementing the test */
52 const char *in
; /* glob to path for input files */
53 const char *out
; /* output directory */
54 const char *suffix
;/* suffix for output files */
55 const char *err
; /* suffix for error output files */
56 int options
; /* parser options for the test */
59 static int checkTestFile(const char *filename
);
62 #if defined(_WIN32) && !defined(__CYGWIN__)
69 size_t gl_pathc
; /* Count of paths matched so far */
70 char **gl_pathv
; /* List of matched pathnames. */
71 size_t gl_offs
; /* Slots to reserve in 'gl_pathv'. */
75 static int glob(const char *pattern
, int flags
,
76 int errfunc(const char *epath
, int eerrno
),
79 WIN32_FIND_DATA FindFileData
;
81 unsigned int nb_paths
= 0;
85 if ((pattern
== NULL
) || (pglob
== NULL
)) return(-1);
87 strncpy(directory
, pattern
, 499);
88 for (len
= strlen(directory
);len
>= 0;len
--) {
89 if (directory
[len
] == '/') {
100 memset(ret
, 0, sizeof(glob_t
));
102 hFind
= FindFirstFileA(pattern
, &FindFileData
);
103 if (hFind
== INVALID_HANDLE_VALUE
)
106 ret
->gl_pathv
= (char **) malloc(nb_paths
* sizeof(char *));
107 if (ret
->gl_pathv
== NULL
) {
111 strncpy(directory
+ len
, FindFileData
.cFileName
, 499 - len
);
112 ret
->gl_pathv
[ret
->gl_pathc
] = strdup(directory
);
113 if (ret
->gl_pathv
[ret
->gl_pathc
] == NULL
)
116 while(FindNextFileA(hFind
, &FindFileData
)) {
117 if (FindFileData
.cFileName
[0] == '.')
119 if (ret
->gl_pathc
+ 2 > nb_paths
) {
120 char **tmp
= realloc(ret
->gl_pathv
, nb_paths
* 2 * sizeof(char *));
126 strncpy(directory
+ len
, FindFileData
.cFileName
, 499 - len
);
127 ret
->gl_pathv
[ret
->gl_pathc
] = strdup(directory
);
128 if (ret
->gl_pathv
[ret
->gl_pathc
] == NULL
)
132 ret
->gl_pathv
[ret
->gl_pathc
] = NULL
;
141 static void globfree(glob_t
*pglob
) {
146 for (i
= 0;i
< pglob
->gl_pathc
;i
++) {
147 if (pglob
->gl_pathv
[i
] != NULL
)
148 free(pglob
->gl_pathv
[i
]);
151 #define vsnprintf _vsnprintf
152 #define snprintf _snprintf
157 /************************************************************************
159 * Huge document generator *
161 ************************************************************************/
163 #include <libxml/xmlIO.h>
166 static const char *start
= "<!DOCTYPE foo [\
167 <!ENTITY f 'some internal data'> \
168 <!ENTITY e '&f;&f;'> \
169 <!ENTITY d '&e;&e;'> \
173 static const char *segment
= " <bar>&e; &f; &d;</bar>\n";
174 static const char *finish
= "</foo>";
176 static int curseg
= 0;
177 static const char *current
;
182 * @URI: an URI to test
184 * Check for an huge: query
186 * Returns 1 if yes and 0 if another Input module should be used
189 hugeMatch(const char * URI
) {
190 if ((URI
!= NULL
) && (!strncmp(URI
, "huge:", 4)))
197 * @URI: an URI to test
199 * Return a pointer to the huge: query handler, in this example simply
200 * the current pointer...
202 * Returns an Input context or NULL in case or error
205 hugeOpen(const char * URI
) {
206 if ((URI
== NULL
) || (strncmp(URI
, "huge:", 4)))
208 rlen
= strlen(start
);
210 return((void *) current
);
215 * @context: the read context
217 * Close the huge: query handler
219 * Returns 0 or -1 in case of error
222 hugeClose(void * context
) {
223 if (context
== NULL
) return(-1);
227 #define MAX_NODES 1000000
231 * @context: the read context
232 * @buffer: where to store data
233 * @len: number of bytes to read
235 * Implement an huge: query read.
237 * Returns the number of bytes read or -1 in case of error
240 hugeRead(void *context
, char *buffer
, int len
)
242 if ((context
== NULL
) || (buffer
== NULL
) || (len
< 0))
246 if (curseg
>= MAX_NODES
+ 1) {
252 memcpy(buffer
, current
, len
);
254 if (curseg
== MAX_NODES
) {
255 fprintf(stderr
, "\n");
256 rlen
= strlen(finish
);
259 if (curseg
% (MAX_NODES
/ 10) == 0)
260 fprintf(stderr
, ".");
261 rlen
= strlen(segment
);
265 memcpy(buffer
, current
, len
);
272 /************************************************************************
274 * Libxml2 specific routines *
276 ************************************************************************/
278 static int nb_tests
= 0;
279 static int nb_errors
= 0;
280 static int nb_leaks
= 0;
281 static int extraMemoryFromResolver
= 0;
285 fprintf(stderr
, "Exitting tests on fatal error\n");
290 * We need to trap calls to the resolver to not account memory for the catalog
291 * which is shared to the current running test. We also don't want to have
292 * network downloads modifying tests.
294 static xmlParserInputPtr
295 testExternalEntityLoader(const char *URL
, const char *ID
,
296 xmlParserCtxtPtr ctxt
) {
297 xmlParserInputPtr ret
;
299 if (checkTestFile(URL
)) {
300 ret
= xmlNoNetExternalEntityLoader(URL
, ID
, ctxt
);
302 int memused
= xmlMemUsed();
303 ret
= xmlNoNetExternalEntityLoader(URL
, ID
, ctxt
);
304 extraMemoryFromResolver
+= xmlMemUsed() - memused
;
311 * Trapping the error messages at the generic level to grab the equivalent of
312 * stderr messages on CLI tools.
314 static char testErrors
[32769];
315 static int testErrorsSize
= 0;
318 channel(void *ctx ATTRIBUTE_UNUSED
, const char *msg
, ...) {
322 if (testErrorsSize
>= 32768)
325 res
= vsnprintf(&testErrors
[testErrorsSize
],
326 32768 - testErrorsSize
,
329 if (testErrorsSize
+ res
>= 32768) {
331 testErrorsSize
= 32768;
332 testErrors
[testErrorsSize
] = 0;
334 testErrorsSize
+= res
;
336 testErrors
[testErrorsSize
] = 0;
340 * xmlParserPrintFileContext:
341 * @input: an xmlParserInputPtr input
343 * Displays current context within the input content for error tracking
347 xmlParserPrintFileContextInternal(xmlParserInputPtr input
,
348 xmlGenericErrorFunc chanl
, void *data
) {
349 const xmlChar
*cur
, *base
;
350 unsigned int n
, col
; /* GCC warns if signed, because compared with sizeof() */
351 xmlChar content
[81]; /* space for 80 chars + line terminator */
354 if (input
== NULL
) return;
357 /* skip backwards over any end-of-lines */
358 while ((cur
> base
) && ((*(cur
) == '\n') || (*(cur
) == '\r'))) {
362 /* search backwards for beginning-of-line (to max buff size) */
363 while ((n
++ < (sizeof(content
)-1)) && (cur
> base
) &&
364 (*(cur
) != '\n') && (*(cur
) != '\r'))
366 if ((*(cur
) == '\n') || (*(cur
) == '\r')) cur
++;
367 /* calculate the error position in terms of the current position */
368 col
= input
->cur
- cur
;
369 /* search forward for end-of-line (to max buff size) */
372 /* copy selected text to our buffer */
373 while ((*cur
!= 0) && (*(cur
) != '\n') &&
374 (*(cur
) != '\r') && (n
< sizeof(content
)-1)) {
379 /* print out the selected text */
380 chanl(data
,"%s\n", content
);
381 /* create blank line with problem pointer */
384 /* (leave buffer space for pointer + line terminator) */
385 while ((n
<col
) && (n
++ < sizeof(content
)-2) && (*ctnt
!= 0)) {
392 chanl(data
,"%s\n", content
);
396 testStructuredErrorHandler(void *ctx ATTRIBUTE_UNUSED
, xmlErrorPtr err
) {
403 const xmlChar
*name
= NULL
;
406 xmlParserInputPtr input
= NULL
;
407 xmlParserInputPtr cur
= NULL
;
408 xmlParserCtxtPtr ctxt
= NULL
;
416 domain
= err
->domain
;
419 if ((domain
== XML_FROM_PARSER
) || (domain
== XML_FROM_HTML
) ||
420 (domain
== XML_FROM_DTD
) || (domain
== XML_FROM_NAMESPACE
) ||
421 (domain
== XML_FROM_IO
) || (domain
== XML_FROM_VALID
)) {
426 if (code
== XML_ERR_OK
)
429 if ((node
!= NULL
) && (node
->type
== XML_ELEMENT_NODE
))
433 * Maintain the compatibility with the legacy error handling
437 if ((input
!= NULL
) && (input
->filename
== NULL
) &&
438 (ctxt
->inputNr
> 1)) {
440 input
= ctxt
->inputTab
[ctxt
->inputNr
- 2];
444 channel(data
, "%s:%d: ", input
->filename
, input
->line
);
445 else if ((line
!= 0) && (domain
== XML_FROM_PARSER
))
446 channel(data
, "Entity: line %d: ", input
->line
);
450 channel(data
, "%s:%d: ", file
, line
);
451 else if ((line
!= 0) && (domain
== XML_FROM_PARSER
))
452 channel(data
, "Entity: line %d: ", line
);
455 channel(data
, "element %s: ", name
);
457 if (code
== XML_ERR_OK
)
460 case XML_FROM_PARSER
:
461 channel(data
, "parser ");
463 case XML_FROM_NAMESPACE
:
464 channel(data
, "namespace ");
468 channel(data
, "validity ");
471 channel(data
, "HTML parser ");
473 case XML_FROM_MEMORY
:
474 channel(data
, "memory ");
476 case XML_FROM_OUTPUT
:
477 channel(data
, "output ");
480 channel(data
, "I/O ");
482 case XML_FROM_XINCLUDE
:
483 channel(data
, "XInclude ");
486 channel(data
, "XPath ");
488 case XML_FROM_XPOINTER
:
489 channel(data
, "parser ");
491 case XML_FROM_REGEXP
:
492 channel(data
, "regexp ");
494 case XML_FROM_MODULE
:
495 channel(data
, "module ");
497 case XML_FROM_SCHEMASV
:
498 channel(data
, "Schemas validity ");
500 case XML_FROM_SCHEMASP
:
501 channel(data
, "Schemas parser ");
503 case XML_FROM_RELAXNGP
:
504 channel(data
, "Relax-NG parser ");
506 case XML_FROM_RELAXNGV
:
507 channel(data
, "Relax-NG validity ");
509 case XML_FROM_CATALOG
:
510 channel(data
, "Catalog ");
513 channel(data
, "C14N ");
516 channel(data
, "XSLT ");
521 if (code
== XML_ERR_OK
)
527 case XML_ERR_WARNING
:
528 channel(data
, "warning : ");
531 channel(data
, "error : ");
534 channel(data
, "error : ");
537 if (code
== XML_ERR_OK
)
541 len
= xmlStrlen((const xmlChar
*)str
);
542 if ((len
> 0) && (str
[len
- 1] != '\n'))
543 channel(data
, "%s\n", str
);
545 channel(data
, "%s", str
);
547 channel(data
, "%s\n", "out of memory error");
549 if (code
== XML_ERR_OK
)
553 xmlParserPrintFileContextInternal(input
, channel
, data
);
556 channel(data
, "%s:%d: \n", cur
->filename
, cur
->line
);
557 else if ((line
!= 0) && (domain
== XML_FROM_PARSER
))
558 channel(data
, "Entity: line %d: \n", cur
->line
);
559 xmlParserPrintFileContextInternal(cur
, channel
, data
);
562 if ((domain
== XML_FROM_XPATH
) && (err
->str1
!= NULL
) &&
564 (err
->int1
< xmlStrlen((const xmlChar
*)err
->str1
))) {
568 channel(data
, "%s\n", err
->str1
);
569 for (i
=0;i
< err
->int1
;i
++)
573 channel(data
, "%s\n", buf
);
578 initializeLibxml2(void) {
579 xmlGetWarningsDefaultValue
= 0;
580 xmlPedanticParserDefault(0);
582 xmlMemSetup(xmlMemFree
, xmlMemMalloc
, xmlMemRealloc
, xmlMemoryStrdup
);
584 xmlSetExternalEntityLoader(testExternalEntityLoader
);
585 xmlSetStructuredErrorFunc(NULL
, testStructuredErrorHandler
);
587 * register the new I/O handlers
589 if (xmlRegisterInputCallbacks(hugeMatch
, hugeOpen
,
590 hugeRead
, hugeClose
) < 0) {
591 fprintf(stderr
, "failed to register Huge handler\n");
596 /************************************************************************
598 * File name and path utilities *
600 ************************************************************************/
602 static const char *baseFilename(const char *filename
) {
604 if (filename
== NULL
)
606 cur
= &filename
[strlen(filename
)];
607 while ((cur
> filename
) && (*cur
!= '/'))
614 static char *resultFilename(const char *filename
, const char *out
,
615 const char *suffix
) {
618 char suffixbuff
[500];
621 if ((filename[0] == 't') && (filename[1] == 'e') &&
622 (filename[2] == 's') && (filename[3] == 't') &&
623 (filename[4] == '/'))
624 filename = &filename[5];
627 base
= baseFilename(filename
);
633 strncpy(suffixbuff
,suffix
,499);
635 if(strstr(base
,".") && suffixbuff
[0]=='.')
639 snprintf(res
, 499, "%s%s%s", out
, base
, suffixbuff
);
644 static int checkTestFile(const char *filename
) {
647 if (stat(filename
, &buf
) == -1)
650 #if defined(_WIN32) && !defined(__CYGWIN__)
651 if (!(buf
.st_mode
& _S_IFREG
))
654 if (!S_ISREG(buf
.st_mode
))
663 /************************************************************************
665 * Test to detect or not recursive entities *
667 ************************************************************************/
669 * recursiveDetectTest:
670 * @filename: the file to parse
671 * @result: the file with expected result
672 * @err: the file with error messages: unused
674 * Parse a file loading DTD and replacing entities check it fails for
677 * Returns 0 in case of success, an error code otherwise
680 recursiveDetectTest(const char *filename
,
681 const char *result ATTRIBUTE_UNUSED
,
682 const char *err ATTRIBUTE_UNUSED
,
683 int options ATTRIBUTE_UNUSED
) {
685 xmlParserCtxtPtr ctxt
;
691 ctxt
= xmlNewParserCtxt();
694 * base of the test, parse with the old API
696 doc
= xmlCtxtReadFile(ctxt
, filename
, NULL
,
697 XML_PARSE_NOENT
| XML_PARSE_DTDLOAD
);
698 if ((doc
!= NULL
) || (ctxt
->lastError
.code
!= XML_ERR_ENTITY_LOOP
)) {
699 fprintf(stderr
, "Failed to detect recursion in %s\n", filename
);
700 xmlFreeParserCtxt(ctxt
);
704 xmlFreeParserCtxt(ctxt
);
710 * notRecursiveDetectTest:
711 * @filename: the file to parse
712 * @result: the file with expected result
713 * @err: the file with error messages: unused
715 * Parse a file loading DTD and replacing entities check it works for
718 * Returns 0 in case of success, an error code otherwise
721 notRecursiveDetectTest(const char *filename
,
722 const char *result ATTRIBUTE_UNUSED
,
723 const char *err ATTRIBUTE_UNUSED
,
724 int options ATTRIBUTE_UNUSED
) {
726 xmlParserCtxtPtr ctxt
;
732 ctxt
= xmlNewParserCtxt();
735 * base of the test, parse with the old API
737 doc
= xmlCtxtReadFile(ctxt
, filename
, NULL
,
738 XML_PARSE_NOENT
| XML_PARSE_DTDLOAD
);
740 fprintf(stderr
, "Failed to parse correct file %s\n", filename
);
741 xmlFreeParserCtxt(ctxt
);
745 xmlFreeParserCtxt(ctxt
);
750 #ifdef LIBXML_READER_ENABLED
752 * notRecursiveHugeTest:
753 * @filename: the file to parse
754 * @result: the file with expected result
755 * @err: the file with error messages: unused
757 * Parse a memory generated file
760 * Returns 0 in case of success, an error code otherwise
763 notRecursiveHugeTest(const char *filename ATTRIBUTE_UNUSED
,
764 const char *result ATTRIBUTE_UNUSED
,
765 const char *err ATTRIBUTE_UNUSED
,
766 int options ATTRIBUTE_UNUSED
) {
767 xmlTextReaderPtr reader
;
773 reader
= xmlReaderForFile("huge:test" , NULL
,
774 XML_PARSE_NOENT
| XML_PARSE_DTDLOAD
);
775 if (reader
== NULL
) {
776 fprintf(stderr
, "Failed to open huge:test\n");
779 ret
= xmlTextReaderRead(reader
);
781 ret
= xmlTextReaderRead(reader
);
784 fprintf(stderr
, "Failed to parser huge:test with entities\n");
787 xmlFreeTextReader(reader
);
793 /************************************************************************
795 * Tests Descriptions *
797 ************************************************************************/
800 testDesc testDescriptions
[] = {
801 { "Parsing recursive test cases" ,
802 recursiveDetectTest
, "./test/recurse/lol*.xml", NULL
, NULL
, NULL
,
804 { "Parsing non-recursive test cases" ,
805 notRecursiveDetectTest
, "./test/recurse/good*.xml", NULL
, NULL
, NULL
,
807 #ifdef LIBXML_READER_ENABLED
808 { "Parsing non-recursive huge case" ,
809 notRecursiveHugeTest
, NULL
, NULL
, NULL
, NULL
,
812 {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, 0}
815 /************************************************************************
817 * The main code driving the tests *
819 ************************************************************************/
822 launchTests(testDescPtr tst
) {
823 int res
= 0, err
= 0;
829 if (tst
== NULL
) return(-1);
830 if (tst
->in
!= NULL
) {
834 glob(tst
->in
, GLOB_DOOFFS
, NULL
, &globbuf
);
835 for (i
= 0;i
< globbuf
.gl_pathc
;i
++) {
836 if (!checkTestFile(globbuf
.gl_pathv
[i
]))
838 if (tst
->suffix
!= NULL
) {
839 result
= resultFilename(globbuf
.gl_pathv
[i
], tst
->out
,
841 if (result
== NULL
) {
842 fprintf(stderr
, "Out of memory !\n");
848 if (tst
->err
!= NULL
) {
849 error
= resultFilename(globbuf
.gl_pathv
[i
], tst
->out
,
852 fprintf(stderr
, "Out of memory !\n");
858 if ((result
) &&(!checkTestFile(result
))) {
859 fprintf(stderr
, "Missing result file %s\n", result
);
860 } else if ((error
) &&(!checkTestFile(error
))) {
861 fprintf(stderr
, "Missing error file %s\n", error
);
864 extraMemoryFromResolver
= 0;
867 res
= tst
->func(globbuf
.gl_pathv
[i
], result
, error
,
868 tst
->options
| XML_PARSE_COMPACT
);
871 fprintf(stderr
, "File %s generated an error\n",
872 globbuf
.gl_pathv
[i
]);
876 else if (xmlMemUsed() != mem
) {
877 if ((xmlMemUsed() != mem
) &&
878 (extraMemoryFromResolver
== 0)) {
879 fprintf(stderr
, "File %s leaked %d bytes\n",
880 globbuf
.gl_pathv
[i
], xmlMemUsed() - mem
);
896 extraMemoryFromResolver
= 0;
897 res
= tst
->func(NULL
, NULL
, NULL
, tst
->options
);
906 static int verbose
= 0;
907 static int tests_quiet
= 0;
912 int old_errors
, old_tests
, old_leaks
;
914 old_errors
= nb_errors
;
915 old_tests
= nb_tests
;
916 old_leaks
= nb_leaks
;
917 if ((tests_quiet
== 0) && (testDescriptions
[i
].desc
!= NULL
))
918 printf("## %s\n", testDescriptions
[i
].desc
);
919 res
= launchTests(&testDescriptions
[i
]);
923 if ((nb_errors
== old_errors
) && (nb_leaks
== old_leaks
))
924 printf("Ran %d tests, no errors\n", nb_tests
- old_tests
);
926 printf("Ran %d tests, %d errors, %d leaks\n",
927 nb_tests
- old_tests
,
928 nb_errors
- old_errors
,
929 nb_leaks
- old_leaks
);
935 main(int argc ATTRIBUTE_UNUSED
, char **argv ATTRIBUTE_UNUSED
) {
941 for (a
= 1; a
< argc
;a
++) {
942 if (!strcmp(argv
[a
], "-v"))
944 else if (!strcmp(argv
[a
], "-quiet"))
947 for (i
= 0; testDescriptions
[i
].func
!= NULL
; i
++) {
948 if (strstr(testDescriptions
[i
].desc
, argv
[a
])) {
956 for (i
= 0; testDescriptions
[i
].func
!= NULL
; i
++) {
960 if ((nb_errors
== 0) && (nb_leaks
== 0)) {
962 printf("Total %d tests, no errors\n",
966 printf("Total %d tests, %d errors, %d leaks\n",
967 nb_tests
, nb_errors
, nb_leaks
);