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