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