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.
16 #if !defined(_WIN32) || defined(__CYGWIN__)
20 #include <sys/types.h>
24 #include <libxml/parser.h>
25 #include <libxml/tree.h>
26 #include <libxml/uri.h>
27 #ifdef LIBXML_READER_ENABLED
28 #include <libxml/xmlreader.h>
32 * O_BINARY is just for Windows compatibility - if it isn't defined
33 * on this system, avoid any compilation error
36 #define RD_FLAGS O_RDONLY | O_BINARY
38 #define RD_FLAGS O_RDONLY
41 typedef int (*functest
) (const char *filename
, const char *result
,
42 const char *error
, int options
);
44 typedef struct testDesc testDesc
;
45 typedef testDesc
*testDescPtr
;
47 const char *desc
; /* descripton of the test */
48 functest func
; /* function implementing the test */
49 const char *in
; /* glob to path for input files */
50 const char *out
; /* output directory */
51 const char *suffix
;/* suffix for output files */
52 const char *err
; /* suffix for error output files */
53 int options
; /* parser options for the test */
56 static int checkTestFile(const char *filename
);
59 #if defined(_WIN32) && !defined(__CYGWIN__)
66 size_t gl_pathc
; /* Count of paths matched so far */
67 char **gl_pathv
; /* List of matched pathnames. */
68 size_t gl_offs
; /* Slots to reserve in 'gl_pathv'. */
72 static int glob(const char *pattern
, int flags
,
73 int errfunc(const char *epath
, int eerrno
),
76 WIN32_FIND_DATA FindFileData
;
78 unsigned int nb_paths
= 0;
82 if ((pattern
== NULL
) || (pglob
== NULL
)) return(-1);
84 strncpy(directory
, pattern
, 499);
85 for (len
= strlen(directory
);len
>= 0;len
--) {
86 if (directory
[len
] == '/') {
97 memset(ret
, 0, sizeof(glob_t
));
99 hFind
= FindFirstFileA(pattern
, &FindFileData
);
100 if (hFind
== INVALID_HANDLE_VALUE
)
103 ret
->gl_pathv
= (char **) malloc(nb_paths
* sizeof(char *));
104 if (ret
->gl_pathv
== NULL
) {
108 strncpy(directory
+ len
, FindFileData
.cFileName
, 499 - len
);
109 ret
->gl_pathv
[ret
->gl_pathc
] = strdup(directory
);
110 if (ret
->gl_pathv
[ret
->gl_pathc
] == NULL
)
113 while(FindNextFileA(hFind
, &FindFileData
)) {
114 if (FindFileData
.cFileName
[0] == '.')
116 if (ret
->gl_pathc
+ 2 > nb_paths
) {
117 char **tmp
= realloc(ret
->gl_pathv
, nb_paths
* 2 * sizeof(char *));
123 strncpy(directory
+ len
, FindFileData
.cFileName
, 499 - len
);
124 ret
->gl_pathv
[ret
->gl_pathc
] = strdup(directory
);
125 if (ret
->gl_pathv
[ret
->gl_pathc
] == NULL
)
129 ret
->gl_pathv
[ret
->gl_pathc
] = NULL
;
138 static void globfree(glob_t
*pglob
) {
143 for (i
= 0;i
< pglob
->gl_pathc
;i
++) {
144 if (pglob
->gl_pathv
[i
] != NULL
)
145 free(pglob
->gl_pathv
[i
]);
153 /************************************************************************
155 * Huge document generator *
157 ************************************************************************/
159 #include <libxml/xmlIO.h>
162 static const char *start
= "<!DOCTYPE foo [\
163 <!ENTITY f 'some internal data'> \
164 <!ENTITY e '&f;&f;'> \
165 <!ENTITY d '&e;&e;'> \
169 static const char *segment
= " <bar>&e; &f; &d;</bar>\n";
170 static const char *finish
= "</foo>";
172 static int curseg
= 0;
173 static const char *current
;
178 * @URI: an URI to test
180 * Check for an huge: query
182 * Returns 1 if yes and 0 if another Input module should be used
185 hugeMatch(const char * URI
) {
186 if ((URI
!= NULL
) && (!strncmp(URI
, "huge:", 4)))
193 * @URI: an URI to test
195 * Return a pointer to the huge: query handler, in this example simply
196 * the current pointer...
198 * Returns an Input context or NULL in case or error
201 hugeOpen(const char * URI
) {
202 if ((URI
== NULL
) || (strncmp(URI
, "huge:", 4)))
204 rlen
= strlen(start
);
206 return((void *) current
);
211 * @context: the read context
213 * Close the huge: query handler
215 * Returns 0 or -1 in case of error
218 hugeClose(void * context
) {
219 if (context
== NULL
) return(-1);
223 #define MAX_NODES 1000000
227 * @context: the read context
228 * @buffer: where to store data
229 * @len: number of bytes to read
231 * Implement an huge: query read.
233 * Returns the number of bytes read or -1 in case of error
236 hugeRead(void *context
, char *buffer
, int len
)
238 if ((context
== NULL
) || (buffer
== NULL
) || (len
< 0))
242 if (curseg
>= MAX_NODES
+ 1) {
248 memcpy(buffer
, current
, len
);
250 if (curseg
== MAX_NODES
) {
251 fprintf(stderr
, "\n");
252 rlen
= strlen(finish
);
255 if (curseg
% (MAX_NODES
/ 10) == 0)
256 fprintf(stderr
, ".");
257 rlen
= strlen(segment
);
261 memcpy(buffer
, current
, len
);
268 /************************************************************************
270 * Libxml2 specific routines *
272 ************************************************************************/
274 static int nb_tests
= 0;
275 static int nb_errors
= 0;
276 static int nb_leaks
= 0;
277 static int extraMemoryFromResolver
= 0;
281 fprintf(stderr
, "Exitting tests on fatal error\n");
286 * We need to trap calls to the resolver to not account memory for the catalog
287 * which is shared to the current running test. We also don't want to have
288 * network downloads modifying tests.
290 static xmlParserInputPtr
291 testExternalEntityLoader(const char *URL
, const char *ID
,
292 xmlParserCtxtPtr ctxt
) {
293 xmlParserInputPtr ret
;
295 if (checkTestFile(URL
)) {
296 ret
= xmlNoNetExternalEntityLoader(URL
, ID
, ctxt
);
298 int memused
= xmlMemUsed();
299 ret
= xmlNoNetExternalEntityLoader(URL
, ID
, ctxt
);
300 extraMemoryFromResolver
+= xmlMemUsed() - memused
;
307 * Trapping the error messages at the generic level to grab the equivalent of
308 * stderr messages on CLI tools.
310 static char testErrors
[32769];
311 static int testErrorsSize
= 0;
314 channel(void *ctx ATTRIBUTE_UNUSED
, const char *msg
, ...) {
318 if (testErrorsSize
>= 32768)
321 res
= vsnprintf(&testErrors
[testErrorsSize
],
322 32768 - testErrorsSize
,
325 if (testErrorsSize
+ res
>= 32768) {
327 testErrorsSize
= 32768;
328 testErrors
[testErrorsSize
] = 0;
330 testErrorsSize
+= res
;
332 testErrors
[testErrorsSize
] = 0;
336 * xmlParserPrintFileContext:
337 * @input: an xmlParserInputPtr input
339 * Displays current context within the input content for error tracking
343 xmlParserPrintFileContextInternal(xmlParserInputPtr input
,
344 xmlGenericErrorFunc chanl
, void *data
) {
345 const xmlChar
*cur
, *base
;
346 unsigned int n
, col
; /* GCC warns if signed, because compared with sizeof() */
347 xmlChar content
[81]; /* space for 80 chars + line terminator */
350 if (input
== NULL
) return;
353 /* skip backwards over any end-of-lines */
354 while ((cur
> base
) && ((*(cur
) == '\n') || (*(cur
) == '\r'))) {
358 /* search backwards for beginning-of-line (to max buff size) */
359 while ((n
++ < (sizeof(content
)-1)) && (cur
> base
) &&
360 (*(cur
) != '\n') && (*(cur
) != '\r'))
362 if ((*(cur
) == '\n') || (*(cur
) == '\r')) cur
++;
363 /* calculate the error position in terms of the current position */
364 col
= input
->cur
- cur
;
365 /* search forward for end-of-line (to max buff size) */
368 /* copy selected text to our buffer */
369 while ((*cur
!= 0) && (*(cur
) != '\n') &&
370 (*(cur
) != '\r') && (n
< sizeof(content
)-1)) {
375 /* print out the selected text */
376 chanl(data
,"%s\n", content
);
377 /* create blank line with problem pointer */
380 /* (leave buffer space for pointer + line terminator) */
381 while ((n
<col
) && (n
++ < sizeof(content
)-2) && (*ctnt
!= 0)) {
388 chanl(data
,"%s\n", content
);
392 testStructuredErrorHandler(void *ctx ATTRIBUTE_UNUSED
, xmlErrorPtr err
) {
399 const xmlChar
*name
= NULL
;
402 xmlParserInputPtr input
= NULL
;
403 xmlParserInputPtr cur
= NULL
;
404 xmlParserCtxtPtr ctxt
= NULL
;
412 domain
= err
->domain
;
415 if ((domain
== XML_FROM_PARSER
) || (domain
== XML_FROM_HTML
) ||
416 (domain
== XML_FROM_DTD
) || (domain
== XML_FROM_NAMESPACE
) ||
417 (domain
== XML_FROM_IO
) || (domain
== XML_FROM_VALID
)) {
422 if (code
== XML_ERR_OK
)
425 if ((node
!= NULL
) && (node
->type
== XML_ELEMENT_NODE
))
429 * Maintain the compatibility with the legacy error handling
433 if ((input
!= NULL
) && (input
->filename
== NULL
) &&
434 (ctxt
->inputNr
> 1)) {
436 input
= ctxt
->inputTab
[ctxt
->inputNr
- 2];
440 channel(data
, "%s:%d: ", input
->filename
, input
->line
);
441 else if ((line
!= 0) && (domain
== XML_FROM_PARSER
))
442 channel(data
, "Entity: line %d: ", input
->line
);
446 channel(data
, "%s:%d: ", file
, line
);
447 else if ((line
!= 0) && (domain
== XML_FROM_PARSER
))
448 channel(data
, "Entity: line %d: ", line
);
451 channel(data
, "element %s: ", name
);
453 if (code
== XML_ERR_OK
)
456 case XML_FROM_PARSER
:
457 channel(data
, "parser ");
459 case XML_FROM_NAMESPACE
:
460 channel(data
, "namespace ");
464 channel(data
, "validity ");
467 channel(data
, "HTML parser ");
469 case XML_FROM_MEMORY
:
470 channel(data
, "memory ");
472 case XML_FROM_OUTPUT
:
473 channel(data
, "output ");
476 channel(data
, "I/O ");
478 case XML_FROM_XINCLUDE
:
479 channel(data
, "XInclude ");
482 channel(data
, "XPath ");
484 case XML_FROM_XPOINTER
:
485 channel(data
, "parser ");
487 case XML_FROM_REGEXP
:
488 channel(data
, "regexp ");
490 case XML_FROM_MODULE
:
491 channel(data
, "module ");
493 case XML_FROM_SCHEMASV
:
494 channel(data
, "Schemas validity ");
496 case XML_FROM_SCHEMASP
:
497 channel(data
, "Schemas parser ");
499 case XML_FROM_RELAXNGP
:
500 channel(data
, "Relax-NG parser ");
502 case XML_FROM_RELAXNGV
:
503 channel(data
, "Relax-NG validity ");
505 case XML_FROM_CATALOG
:
506 channel(data
, "Catalog ");
509 channel(data
, "C14N ");
512 channel(data
, "XSLT ");
517 if (code
== XML_ERR_OK
)
523 case XML_ERR_WARNING
:
524 channel(data
, "warning : ");
527 channel(data
, "error : ");
530 channel(data
, "error : ");
533 if (code
== XML_ERR_OK
)
537 len
= xmlStrlen((const xmlChar
*)str
);
538 if ((len
> 0) && (str
[len
- 1] != '\n'))
539 channel(data
, "%s\n", str
);
541 channel(data
, "%s", str
);
543 channel(data
, "%s\n", "out of memory error");
545 if (code
== XML_ERR_OK
)
549 xmlParserPrintFileContextInternal(input
, channel
, data
);
552 channel(data
, "%s:%d: \n", cur
->filename
, cur
->line
);
553 else if ((line
!= 0) && (domain
== XML_FROM_PARSER
))
554 channel(data
, "Entity: line %d: \n", cur
->line
);
555 xmlParserPrintFileContextInternal(cur
, channel
, data
);
558 if ((domain
== XML_FROM_XPATH
) && (err
->str1
!= NULL
) &&
560 (err
->int1
< xmlStrlen((const xmlChar
*)err
->str1
))) {
564 channel(data
, "%s\n", err
->str1
);
565 for (i
=0;i
< err
->int1
;i
++)
569 channel(data
, "%s\n", buf
);
574 initializeLibxml2(void) {
575 xmlGetWarningsDefaultValue
= 0;
576 xmlPedanticParserDefault(0);
578 xmlMemSetup(xmlMemFree
, xmlMemMalloc
, xmlMemRealloc
, xmlMemoryStrdup
);
580 xmlSetExternalEntityLoader(testExternalEntityLoader
);
581 xmlSetStructuredErrorFunc(NULL
, testStructuredErrorHandler
);
583 * register the new I/O handlers
585 if (xmlRegisterInputCallbacks(hugeMatch
, hugeOpen
,
586 hugeRead
, hugeClose
) < 0) {
587 fprintf(stderr
, "failed to register Huge handler\n");
592 /************************************************************************
594 * File name and path utilities *
596 ************************************************************************/
598 static const char *baseFilename(const char *filename
) {
600 if (filename
== NULL
)
602 cur
= &filename
[strlen(filename
)];
603 while ((cur
> filename
) && (*cur
!= '/'))
610 static char *resultFilename(const char *filename
, const char *out
,
611 const char *suffix
) {
614 char suffixbuff
[500];
617 if ((filename[0] == 't') && (filename[1] == 'e') &&
618 (filename[2] == 's') && (filename[3] == 't') &&
619 (filename[4] == '/'))
620 filename = &filename[5];
623 base
= baseFilename(filename
);
629 strncpy(suffixbuff
,suffix
,499);
631 if(strstr(base
,".") && suffixbuff
[0]=='.')
635 snprintf(res
, 499, "%s%s%s", out
, base
, suffixbuff
);
640 static int checkTestFile(const char *filename
) {
643 if (stat(filename
, &buf
) == -1)
646 #if defined(_WIN32) && !defined(__CYGWIN__)
647 if (!(buf
.st_mode
& _S_IFREG
))
650 if (!S_ISREG(buf
.st_mode
))
659 /************************************************************************
661 * Test to detect or not recursive entities *
663 ************************************************************************/
665 * recursiveDetectTest:
666 * @filename: the file to parse
667 * @result: the file with expected result
668 * @err: the file with error messages: unused
670 * Parse a file loading DTD and replacing entities check it fails for
673 * Returns 0 in case of success, an error code otherwise
676 recursiveDetectTest(const char *filename
,
677 const char *result ATTRIBUTE_UNUSED
,
678 const char *err ATTRIBUTE_UNUSED
,
679 int options ATTRIBUTE_UNUSED
) {
681 xmlParserCtxtPtr ctxt
;
686 ctxt
= xmlNewParserCtxt();
688 * base of the test, parse with the old API
690 doc
= xmlCtxtReadFile(ctxt
, filename
, NULL
,
691 XML_PARSE_NOENT
| XML_PARSE_DTDLOAD
);
692 if ((doc
!= NULL
) || (ctxt
->lastError
.code
!= XML_ERR_ENTITY_LOOP
)) {
693 fprintf(stderr
, "Failed to detect recursion in %s\n", filename
);
694 xmlFreeParserCtxt(ctxt
);
698 xmlFreeParserCtxt(ctxt
);
704 * notRecursiveDetectTest:
705 * @filename: the file to parse
706 * @result: the file with expected result
707 * @err: the file with error messages: unused
709 * Parse a file loading DTD and replacing entities check it works for
712 * Returns 0 in case of success, an error code otherwise
715 notRecursiveDetectTest(const char *filename
,
716 const char *result ATTRIBUTE_UNUSED
,
717 const char *err ATTRIBUTE_UNUSED
,
718 int options ATTRIBUTE_UNUSED
) {
720 xmlParserCtxtPtr ctxt
;
725 ctxt
= xmlNewParserCtxt();
727 * base of the test, parse with the old API
729 doc
= xmlCtxtReadFile(ctxt
, filename
, NULL
,
730 XML_PARSE_NOENT
| XML_PARSE_DTDLOAD
);
732 fprintf(stderr
, "Failed to parse correct file %s\n", filename
);
733 xmlFreeParserCtxt(ctxt
);
737 xmlFreeParserCtxt(ctxt
);
742 #ifdef LIBXML_READER_ENABLED
744 * notRecursiveHugeTest:
745 * @filename: the file to parse
746 * @result: the file with expected result
747 * @err: the file with error messages: unused
749 * Parse a memory generated file
752 * Returns 0 in case of success, an error code otherwise
755 notRecursiveHugeTest(const char *filename ATTRIBUTE_UNUSED
,
756 const char *result ATTRIBUTE_UNUSED
,
757 const char *err ATTRIBUTE_UNUSED
,
758 int options ATTRIBUTE_UNUSED
) {
759 xmlTextReaderPtr reader
;
765 reader
= xmlReaderForFile("huge:test" , NULL
,
766 XML_PARSE_NOENT
| XML_PARSE_DTDLOAD
);
767 if (reader
== NULL
) {
768 fprintf(stderr
, "Failed to open huge:test\n");
771 ret
= xmlTextReaderRead(reader
);
773 ret
= xmlTextReaderRead(reader
);
776 fprintf(stderr
, "Failed to parser huge:test with entities\n");
779 xmlFreeTextReader(reader
);
785 /************************************************************************
787 * Tests Descriptions *
789 ************************************************************************/
792 testDesc testDescriptions
[] = {
793 { "Parsing recursive test cases" ,
794 recursiveDetectTest
, "./test/recurse/lol*.xml", NULL
, NULL
, NULL
,
796 { "Parsing non-recursive test cases" ,
797 notRecursiveDetectTest
, "./test/recurse/good*.xml", NULL
, NULL
, NULL
,
799 #ifdef LIBXML_READER_ENABLED
800 { "Parsing non-recursive huge case" ,
801 notRecursiveHugeTest
, NULL
, NULL
, NULL
, NULL
,
804 {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, 0}
807 /************************************************************************
809 * The main code driving the tests *
811 ************************************************************************/
814 launchTests(testDescPtr tst
) {
815 int res
= 0, err
= 0;
821 if (tst
== NULL
) return(-1);
822 if (tst
->in
!= NULL
) {
826 glob(tst
->in
, GLOB_DOOFFS
, NULL
, &globbuf
);
827 for (i
= 0;i
< globbuf
.gl_pathc
;i
++) {
828 if (!checkTestFile(globbuf
.gl_pathv
[i
]))
830 if (tst
->suffix
!= NULL
) {
831 result
= resultFilename(globbuf
.gl_pathv
[i
], tst
->out
,
833 if (result
== NULL
) {
834 fprintf(stderr
, "Out of memory !\n");
840 if (tst
->err
!= NULL
) {
841 error
= resultFilename(globbuf
.gl_pathv
[i
], tst
->out
,
844 fprintf(stderr
, "Out of memory !\n");
850 if ((result
) &&(!checkTestFile(result
))) {
851 fprintf(stderr
, "Missing result file %s\n", result
);
852 } else if ((error
) &&(!checkTestFile(error
))) {
853 fprintf(stderr
, "Missing error file %s\n", error
);
856 extraMemoryFromResolver
= 0;
859 res
= tst
->func(globbuf
.gl_pathv
[i
], result
, error
,
860 tst
->options
| XML_PARSE_COMPACT
);
863 fprintf(stderr
, "File %s generated an error\n",
864 globbuf
.gl_pathv
[i
]);
868 else if (xmlMemUsed() != mem
) {
869 if ((xmlMemUsed() != mem
) &&
870 (extraMemoryFromResolver
== 0)) {
871 fprintf(stderr
, "File %s leaked %d bytes\n",
872 globbuf
.gl_pathv
[i
], xmlMemUsed() - mem
);
888 extraMemoryFromResolver
= 0;
889 res
= tst
->func(NULL
, NULL
, NULL
, tst
->options
);
898 static int verbose
= 0;
899 static int tests_quiet
= 0;
904 int old_errors
, old_tests
, old_leaks
;
906 old_errors
= nb_errors
;
907 old_tests
= nb_tests
;
908 old_leaks
= nb_leaks
;
909 if ((tests_quiet
== 0) && (testDescriptions
[i
].desc
!= NULL
))
910 printf("## %s\n", testDescriptions
[i
].desc
);
911 res
= launchTests(&testDescriptions
[i
]);
915 if ((nb_errors
== old_errors
) && (nb_leaks
== old_leaks
))
916 printf("Ran %d tests, no errors\n", nb_tests
- old_tests
);
918 printf("Ran %d tests, %d errors, %d leaks\n",
919 nb_tests
- old_tests
,
920 nb_errors
- old_errors
,
921 nb_leaks
- old_leaks
);
927 main(int argc ATTRIBUTE_UNUSED
, char **argv ATTRIBUTE_UNUSED
) {
933 for (a
= 1; a
< argc
;a
++) {
934 if (!strcmp(argv
[a
], "-v"))
936 else if (!strcmp(argv
[a
], "-quiet"))
939 for (i
= 0; testDescriptions
[i
].func
!= NULL
; i
++) {
940 if (strstr(testDescriptions
[i
].desc
, argv
[a
])) {
948 for (i
= 0; testDescriptions
[i
].func
!= NULL
; i
++) {
952 if ((nb_errors
== 0) && (nb_leaks
== 0)) {
954 printf("Total %d tests, no errors\n",
958 printf("Total %d tests, %d errors, %d leaks\n",
959 nb_tests
, nb_errors
, nb_leaks
);