• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * runxmlconf.c: C program to run XML W3C conformance testsuites
3  *
4  * See Copyright for the status of this software.
5  *
6  * daniel@veillard.com
7  */
8 
9 #include "libxml.h"
10 #include <stdio.h>
11 #include <libxml/xmlversion.h>
12 
13 #if defined(LIBXML_XPATH_ENABLED) && defined(LIBXML_VALID_ENABLED)
14 
15 #include <string.h>
16 #include <sys/stat.h>
17 
18 #include <libxml/catalog.h>
19 #include <libxml/parser.h>
20 #include <libxml/parserInternals.h>
21 #include <libxml/tree.h>
22 #include <libxml/uri.h>
23 #include <libxml/xmlreader.h>
24 
25 #include <libxml/xpath.h>
26 #include <libxml/xpathInternals.h>
27 
28 #define LOGFILE "runxmlconf.log"
29 static FILE *logfile = NULL;
30 static int verbose = 0;
31 
32 #ifdef LIBXML_REGEXP_ENABLED
33   #define NB_EXPECTED_ERRORS 15
34 #else
35   #define NB_EXPECTED_ERRORS 16
36 #endif
37 
38 
39 const char *skipped_tests[] = {
40 /* http://lists.w3.org/Archives/Public/public-xml-testsuite/2008Jul/0000.html */
41     "rmt-ns10-035",
42     NULL
43 };
44 
45 /************************************************************************
46  *									*
47  *		File name and path utilities				*
48  *									*
49  ************************************************************************/
50 
checkTestFile(const char * filename)51 static int checkTestFile(const char *filename) {
52     struct stat buf;
53 
54     if (stat(filename, &buf) == -1)
55         return(0);
56 
57 #if defined(_WIN32)
58     if (!(buf.st_mode & _S_IFREG))
59         return(0);
60 #else
61     if (!S_ISREG(buf.st_mode))
62         return(0);
63 #endif
64 
65     return(1);
66 }
67 
composeDir(const xmlChar * dir,const xmlChar * path)68 static xmlChar *composeDir(const xmlChar *dir, const xmlChar *path) {
69     char buf[500];
70 
71     if (dir == NULL) return(xmlStrdup(path));
72     if (path == NULL) return(NULL);
73 
74     snprintf(buf, 500, "%s/%s", (const char *) dir, (const char *) path);
75     return(xmlStrdup((const xmlChar *) buf));
76 }
77 
78 /************************************************************************
79  *									*
80  *		Libxml2 specific routines				*
81  *									*
82  ************************************************************************/
83 
84 static int nb_skipped = 0;
85 static int nb_tests = 0;
86 static int nb_errors = 0;
87 static int nb_leaks = 0;
88 
89 /*
90  * Trapping the error messages at the generic level to grab the equivalent of
91  * stderr messages on CLI tools.
92  */
93 static char testErrors[32769];
94 static int testErrorsSize = 0;
95 static int nbError = 0;
96 static int nbFatal = 0;
97 
test_log(const char * msg,...)98 static void test_log(const char *msg, ...) {
99     va_list args;
100     if (logfile != NULL) {
101         fprintf(logfile, "\n------------\n");
102 	va_start(args, msg);
103 	vfprintf(logfile, msg, args);
104 	va_end(args);
105 	fprintf(logfile, "%s", testErrors);
106 	testErrorsSize = 0; testErrors[0] = 0;
107     }
108     if (verbose) {
109 	va_start(args, msg);
110 	vfprintf(stderr, msg, args);
111 	va_end(args);
112     }
113 }
114 
115 static void
testErrorHandler(void * userData ATTRIBUTE_UNUSED,const xmlError * error)116 testErrorHandler(void *userData ATTRIBUTE_UNUSED, const xmlError *error) {
117     int res;
118 
119     if (testErrorsSize >= 32768)
120         return;
121     res = snprintf(&testErrors[testErrorsSize],
122                     32768 - testErrorsSize,
123 		   "%s:%d: %s\n", (error->file ? error->file : "entity"),
124 		   error->line, error->message);
125     if (error->level == XML_ERR_FATAL)
126         nbFatal++;
127     else if (error->level == XML_ERR_ERROR)
128         nbError++;
129     if (testErrorsSize + res >= 32768) {
130         /* buffer is full */
131 	testErrorsSize = 32768;
132 	testErrors[testErrorsSize] = 0;
133     } else {
134         testErrorsSize += res;
135     }
136     testErrors[testErrorsSize] = 0;
137 }
138 
139 static xmlXPathContextPtr ctxtXPath;
140 
141 static void
initializeLibxml2(void)142 initializeLibxml2(void) {
143     xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup);
144     xmlInitParser();
145 #ifdef LIBXML_CATALOG_ENABLED
146     xmlInitializeCatalog();
147     xmlCatalogSetDefaults(XML_CATA_ALLOW_NONE);
148 #endif
149     ctxtXPath = xmlXPathNewContext(NULL);
150     /*
151     * Deactivate the cache if created; otherwise we have to create/free it
152     * for every test, since it will confuse the memory leak detection.
153     * Note that normally this need not be done, since the cache is not
154     * created until set explicitly with xmlXPathContextSetCache();
155     * but for test purposes it is sometimes useful to activate the
156     * cache by default for the whole library.
157     */
158     if (ctxtXPath->cache != NULL)
159 	xmlXPathContextSetCache(ctxtXPath, 0, -1, 0);
160 }
161 
162 /************************************************************************
163  *									*
164  *		Run the xmlconf test if found				*
165  *									*
166  ************************************************************************/
167 
168 static int
xmlconfTestInvalid(const char * id,const char * filename,int options)169 xmlconfTestInvalid(const char *id, const char *filename, int options) {
170     xmlDocPtr doc;
171     xmlParserCtxtPtr ctxt;
172     int ret = 1;
173 
174     ctxt = xmlNewParserCtxt();
175     if (ctxt == NULL) {
176         test_log("test %s : %s out of memory\n",
177 	         id, filename);
178         return(0);
179     }
180     xmlCtxtSetErrorHandler(ctxt, testErrorHandler, NULL);
181     doc = xmlCtxtReadFile(ctxt, filename, NULL, options);
182     if (doc == NULL) {
183         test_log("test %s : %s invalid document turned not well-formed too\n",
184 	         id, filename);
185     } else {
186     /* invalidity should be reported both in the context and in the document */
187         if ((ctxt->valid != 0) || (doc->properties & XML_DOC_DTDVALID)) {
188 	    test_log("test %s : %s failed to detect invalid document\n",
189 		     id, filename);
190 	    nb_errors++;
191 	    ret = 0;
192 	}
193 	xmlFreeDoc(doc);
194     }
195     xmlFreeParserCtxt(ctxt);
196     return(ret);
197 }
198 
199 static int
xmlconfTestValid(const char * id,const char * filename,int options)200 xmlconfTestValid(const char *id, const char *filename, int options) {
201     xmlDocPtr doc;
202     xmlParserCtxtPtr ctxt;
203     int ret = 1;
204 
205     ctxt = xmlNewParserCtxt();
206     if (ctxt == NULL) {
207         test_log("test %s : %s out of memory\n",
208 	         id, filename);
209         return(0);
210     }
211     xmlCtxtSetErrorHandler(ctxt, testErrorHandler, NULL);
212     doc = xmlCtxtReadFile(ctxt, filename, NULL, options);
213     if (doc == NULL) {
214         test_log("test %s : %s failed to parse a valid document\n",
215 	         id, filename);
216         nb_errors++;
217 	ret = 0;
218     } else {
219     /* validity should be reported both in the context and in the document */
220         if ((ctxt->valid == 0) || ((doc->properties & XML_DOC_DTDVALID) == 0)) {
221 	    test_log("test %s : %s failed to validate a valid document\n",
222 		     id, filename);
223 	    nb_errors++;
224 	    ret = 0;
225 	}
226 	xmlFreeDoc(doc);
227     }
228     xmlFreeParserCtxt(ctxt);
229     return(ret);
230 }
231 
232 static int
xmlconfTestNotNSWF(const char * id,const char * filename,int options)233 xmlconfTestNotNSWF(const char *id, const char *filename, int options) {
234     xmlParserCtxtPtr ctxt;
235     xmlDocPtr doc;
236     int ret = 1;
237 
238     ctxt = xmlNewParserCtxt();
239     xmlCtxtSetErrorHandler(ctxt, testErrorHandler, NULL);
240     /*
241      * In case of Namespace errors, libxml2 will still parse the document
242      * but log a Namespace error.
243      */
244     doc = xmlCtxtReadFile(ctxt, filename, NULL, options);
245     if (doc == NULL) {
246         test_log("test %s : %s failed to parse the XML\n",
247 	         id, filename);
248         nb_errors++;
249 	ret = 0;
250     } else {
251         const xmlError *error = xmlGetLastError();
252 
253 	if ((error->code == XML_ERR_OK) ||
254 	    (error->domain != XML_FROM_NAMESPACE)) {
255 	    test_log("test %s : %s failed to detect namespace error\n",
256 		     id, filename);
257 	    nb_errors++;
258 	    ret = 0;
259 	}
260 	xmlFreeDoc(doc);
261     }
262     xmlFreeParserCtxt(ctxt);
263     return(ret);
264 }
265 
266 static int
xmlconfTestNotWF(const char * id,const char * filename,int options)267 xmlconfTestNotWF(const char *id, const char *filename, int options) {
268     xmlParserCtxtPtr ctxt;
269     xmlDocPtr doc;
270     int ret = 1;
271 
272     ctxt = xmlNewParserCtxt();
273     xmlCtxtSetErrorHandler(ctxt, testErrorHandler, NULL);
274     doc = xmlCtxtReadFile(ctxt, filename, NULL, options);
275     if (doc != NULL) {
276         test_log("test %s : %s failed to detect not well formedness\n",
277 	         id, filename);
278         nb_errors++;
279 	xmlFreeDoc(doc);
280 	ret = 0;
281     }
282     xmlFreeParserCtxt(ctxt);
283     return(ret);
284 }
285 
286 static int
xmlconfTestItem(xmlDocPtr doc,xmlNodePtr cur)287 xmlconfTestItem(xmlDocPtr doc, xmlNodePtr cur) {
288     int ret = -1;
289     xmlChar *type = NULL;
290     xmlChar *filename = NULL;
291     xmlChar *uri = NULL;
292     xmlChar *base = NULL;
293     xmlChar *id = NULL;
294     xmlChar *rec = NULL;
295     xmlChar *version = NULL;
296     xmlChar *entities = NULL;
297     xmlChar *edition = NULL;
298     int options = 0;
299     int nstest = 0;
300     int mem, final;
301     int i;
302 
303     testErrorsSize = 0; testErrors[0] = 0;
304     nbError = 0;
305     nbFatal = 0;
306     id = xmlGetProp(cur, BAD_CAST "ID");
307     if (id == NULL) {
308         test_log("test missing ID, line %ld\n", xmlGetLineNo(cur));
309 	goto error;
310     }
311     for (i = 0;skipped_tests[i] != NULL;i++) {
312         if (!strcmp(skipped_tests[i], (char *) id)) {
313 	    test_log("Skipping test %s from skipped list\n", (char *) id);
314 	    ret = 0;
315 	    nb_skipped++;
316 	    goto error;
317 	}
318     }
319     type = xmlGetProp(cur, BAD_CAST "TYPE");
320     if (type == NULL) {
321         test_log("test %s missing TYPE\n", (char *) id);
322 	goto error;
323     }
324     uri = xmlGetProp(cur, BAD_CAST "URI");
325     if (uri == NULL) {
326         test_log("test %s missing URI\n", (char *) id);
327 	goto error;
328     }
329     base = xmlNodeGetBase(doc, cur);
330     filename = composeDir(base, uri);
331     if (!checkTestFile((char *) filename)) {
332         test_log("test %s missing file %s \n", id,
333 	         (filename ? (char *)filename : "NULL"));
334 	goto error;
335     }
336 
337     version = xmlGetProp(cur, BAD_CAST "VERSION");
338 
339     entities = xmlGetProp(cur, BAD_CAST "ENTITIES");
340     if (!xmlStrEqual(entities, BAD_CAST "none")) {
341         options |= XML_PARSE_DTDLOAD;
342         options |= XML_PARSE_NOENT;
343     }
344     rec = xmlGetProp(cur, BAD_CAST "RECOMMENDATION");
345     if ((rec == NULL) ||
346         (xmlStrEqual(rec, BAD_CAST "XML1.0")) ||
347 	(xmlStrEqual(rec, BAD_CAST "XML1.0-errata2e")) ||
348 	(xmlStrEqual(rec, BAD_CAST "XML1.0-errata3e")) ||
349 	(xmlStrEqual(rec, BAD_CAST "XML1.0-errata4e"))) {
350 	if ((version != NULL) && (!xmlStrEqual(version, BAD_CAST "1.0"))) {
351 	    test_log("Skipping test %s for %s\n", (char *) id,
352 	             (char *) version);
353 	    ret = 0;
354 	    nb_skipped++;
355 	    goto error;
356 	}
357 	ret = 1;
358     } else if ((xmlStrEqual(rec, BAD_CAST "NS1.0")) ||
359 	       (xmlStrEqual(rec, BAD_CAST "NS1.0-errata1e"))) {
360 	ret = 1;
361 	nstest = 1;
362     } else {
363         test_log("Skipping test %s for REC %s\n", (char *) id, (char *) rec);
364 	ret = 0;
365 	nb_skipped++;
366 	goto error;
367     }
368     edition = xmlGetProp(cur, BAD_CAST "EDITION");
369     if ((edition != NULL) && (xmlStrchr(edition, '5') == NULL)) {
370         /* test limited to all versions before 5th */
371 	options |= XML_PARSE_OLD10;
372     }
373 
374     /*
375      * Reset errors and check memory usage before the test
376      */
377     xmlResetLastError();
378     testErrorsSize = 0; testErrors[0] = 0;
379     mem = xmlMemUsed();
380 
381     if (xmlStrEqual(type, BAD_CAST "not-wf")) {
382         if (nstest == 0)
383 	    xmlconfTestNotWF((char *) id, (char *) filename, options);
384         else
385 	    xmlconfTestNotNSWF((char *) id, (char *) filename, options);
386     } else if (xmlStrEqual(type, BAD_CAST "valid")) {
387         options |= XML_PARSE_DTDVALID;
388 	xmlconfTestValid((char *) id, (char *) filename, options);
389     } else if (xmlStrEqual(type, BAD_CAST "invalid")) {
390         options |= XML_PARSE_DTDVALID;
391 	xmlconfTestInvalid((char *) id, (char *) filename, options);
392     } else if (xmlStrEqual(type, BAD_CAST "error")) {
393         test_log("Skipping error test %s \n", (char *) id);
394 	ret = 0;
395 	nb_skipped++;
396 	goto error;
397     } else {
398         test_log("test %s unknown TYPE value %s\n", (char *) id, (char *)type);
399 	ret = -1;
400 	goto error;
401     }
402 
403     /*
404      * Reset errors and check memory usage after the test
405      */
406     xmlResetLastError();
407     final = xmlMemUsed();
408     if (final > mem) {
409         test_log("test %s : %s leaked %d bytes\n",
410 	         id, filename, final - mem);
411         nb_leaks++;
412     }
413     nb_tests++;
414 
415 error:
416     if (type != NULL)
417         xmlFree(type);
418     if (entities != NULL)
419         xmlFree(entities);
420     if (edition != NULL)
421         xmlFree(edition);
422     if (version != NULL)
423         xmlFree(version);
424     if (filename != NULL)
425         xmlFree(filename);
426     if (uri != NULL)
427         xmlFree(uri);
428     if (base != NULL)
429         xmlFree(base);
430     if (id != NULL)
431         xmlFree(id);
432     if (rec != NULL)
433         xmlFree(rec);
434     return(ret);
435 }
436 
437 static int
xmlconfTestCases(xmlDocPtr doc,xmlNodePtr cur,int level)438 xmlconfTestCases(xmlDocPtr doc, xmlNodePtr cur, int level) {
439     xmlChar *profile;
440     int ret = 0;
441     int tests = 0;
442     int output = 0;
443 
444     if (level == 1) {
445 	profile = xmlGetProp(cur, BAD_CAST "PROFILE");
446 	if (profile != NULL) {
447 	    output = 1;
448 	    level++;
449 	    printf("Test cases: %s\n", (char *) profile);
450 	    xmlFree(profile);
451 	}
452     }
453     cur = cur->children;
454     while (cur != NULL) {
455         /* look only at elements we ignore everything else */
456         if (cur->type == XML_ELEMENT_NODE) {
457 	    if (xmlStrEqual(cur->name, BAD_CAST "TESTCASES")) {
458 	        ret += xmlconfTestCases(doc, cur, level);
459 	    } else if (xmlStrEqual(cur->name, BAD_CAST "TEST")) {
460 	        if (xmlconfTestItem(doc, cur) >= 0)
461 		    ret++;
462 		tests++;
463 	    } else {
464 	        fprintf(stderr, "Unhandled element %s\n", (char *)cur->name);
465 	    }
466 	}
467         cur = cur->next;
468     }
469     if (output == 1) {
470 	if (tests > 0)
471 	    printf("Test cases: %d tests\n", tests);
472     }
473     return(ret);
474 }
475 
476 static int
xmlconfTestSuite(xmlDocPtr doc,xmlNodePtr cur)477 xmlconfTestSuite(xmlDocPtr doc, xmlNodePtr cur) {
478     xmlChar *profile;
479     int ret = 0;
480 
481     profile = xmlGetProp(cur, BAD_CAST "PROFILE");
482     if (profile != NULL) {
483         printf("Test suite: %s\n", (char *) profile);
484 	xmlFree(profile);
485     } else
486         printf("Test suite\n");
487     cur = cur->children;
488     while (cur != NULL) {
489         /* look only at elements we ignore everything else */
490         if (cur->type == XML_ELEMENT_NODE) {
491 	    if (xmlStrEqual(cur->name, BAD_CAST "TESTCASES")) {
492 	        ret += xmlconfTestCases(doc, cur, 1);
493 	    } else {
494 	        fprintf(stderr, "Unhandled element %s\n", (char *)cur->name);
495 	    }
496 	}
497         cur = cur->next;
498     }
499     return(ret);
500 }
501 
502 static void
xmlconfInfo(void)503 xmlconfInfo(void) {
504     fprintf(stderr, "  you need to fetch and extract the\n");
505     fprintf(stderr, "  latest XML Conformance Test Suites\n");
506     fprintf(stderr, "  http://www.w3.org/XML/Test/xmlts20080827.tar.gz\n");
507     fprintf(stderr, "  see http://www.w3.org/XML/Test/ for information\n");
508 }
509 
510 static int
xmlconfTest(void)511 xmlconfTest(void) {
512     const char *confxml = "xmlconf/xmlconf.xml";
513     xmlDocPtr doc;
514     xmlNodePtr cur;
515     int ret = 0;
516 
517     if (!checkTestFile(confxml)) {
518         fprintf(stderr, "%s is missing \n", confxml);
519 	xmlconfInfo();
520 	return(-1);
521     }
522     doc = xmlReadFile(confxml, NULL, XML_PARSE_NOENT);
523     if (doc == NULL) {
524         fprintf(stderr, "%s is corrupted \n", confxml);
525 	xmlconfInfo();
526 	return(-1);
527     }
528 
529     cur = xmlDocGetRootElement(doc);
530     if ((cur == NULL) || (!xmlStrEqual(cur->name, BAD_CAST "TESTSUITE"))) {
531         fprintf(stderr, "Unexpected format %s\n", confxml);
532 	xmlconfInfo();
533 	ret = -1;
534     } else {
535         ret = xmlconfTestSuite(doc, cur);
536     }
537     xmlFreeDoc(doc);
538     return(ret);
539 }
540 
541 /************************************************************************
542  *									*
543  *		The driver for the tests				*
544  *									*
545  ************************************************************************/
546 
547 int
main(int argc ATTRIBUTE_UNUSED,char ** argv ATTRIBUTE_UNUSED)548 main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) {
549     int ret = 0;
550     int old_errors, old_tests, old_leaks;
551 
552     logfile = fopen(LOGFILE, "wb");
553     if (logfile == NULL) {
554         fprintf(stderr,
555 	        "Could not open the log file, running in verbose mode\n");
556 	verbose = 1;
557     }
558     initializeLibxml2();
559 
560     if ((argc >= 2) && (!strcmp(argv[1], "-v")))
561         verbose = 1;
562 
563 
564     old_errors = nb_errors;
565     old_tests = nb_tests;
566     old_leaks = nb_leaks;
567     xmlconfTest();
568     if ((nb_errors == old_errors) && (nb_leaks == old_leaks))
569 	printf("Ran %d tests, no errors\n", nb_tests - old_tests);
570     else
571 	printf("Ran %d tests, %d errors, %d leaks\n",
572 	       nb_tests - old_tests,
573 	       nb_errors - old_errors,
574 	       nb_leaks - old_leaks);
575     if ((nb_errors == 0) && (nb_leaks == 0)) {
576         ret = 0;
577 	printf("Total %d tests, no errors\n",
578 	       nb_tests);
579     } else {
580 	ret = 1;
581 	printf("Total %d tests, %d errors, %d leaks\n",
582 	       nb_tests, nb_errors, nb_leaks);
583 	printf("See %s for detailed output\n", LOGFILE);
584 	if ((nb_leaks == 0) && (nb_errors == NB_EXPECTED_ERRORS)) {
585 	    printf("%d errors were expected\n", nb_errors);
586 	    ret = 0;
587 	}
588     }
589     xmlXPathFreeContext(ctxtXPath);
590     xmlCleanupParser();
591 
592     if (logfile != NULL)
593         fclose(logfile);
594     return(ret);
595 }
596 
597 #else /* ! LIBXML_XPATH_ENABLED */
598 int
main(int argc ATTRIBUTE_UNUSED,char ** argv)599 main(int argc ATTRIBUTE_UNUSED, char **argv) {
600     fprintf(stderr, "%s need XPath and validation support\n", argv[0]);
601     return(0);
602 }
603 #endif
604