blob: 0f0e54fbddfbfb9e09e7df7c4cdf79510f57c7d5 [file] [log] [blame]
Daniel Veillard7e5c3f42008-07-29 16:12:31 +00001/*
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
Daniel Veillard051d52c2008-07-29 16:44:59 +000032#define LOGFILE "runxmlconf.log"
Daniel Veillard7e5c3f42008-07-29 16:12:31 +000033static 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;
Daniel Veillard40ec29a2008-07-30 12:35:40 +0000325 options |= XML_PARSE_NOENT;
Daniel Veillard7e5c3f42008-07-29 16:12:31 +0000326 }
327 rec = xmlGetProp(cur, BAD_CAST "RECOMMENDATION");
328 if ((rec == NULL) ||
329 (xmlStrEqual(rec, BAD_CAST "XML1.0")) ||
330 (xmlStrEqual(rec, BAD_CAST "XML1.0-errata2e")) ||
331 (xmlStrEqual(rec, BAD_CAST "XML1.0-errata3e")) ||
332 (xmlStrEqual(rec, BAD_CAST "XML1.0-errata4e"))) {
333 ret = 1;
334 } else if ((xmlStrEqual(rec, BAD_CAST "NS1.0")) ||
335 (xmlStrEqual(rec, BAD_CAST "NS1.0-errata1e"))) {
336 ret = 1;
337 nstest = 1;
338 } else {
339 test_log("Skipping test %s for REC %s\n", (char *) id, (char *) rec);
340 ret = 0;
341 nb_skipped++;
342 goto error;
343 }
344 edition = xmlGetProp(cur, BAD_CAST "EDITION");
345 if ((edition != NULL) && (xmlStrchr(edition, '5') == NULL)) {
346 /* test limited to all versions before 5th */
347 options |= XML_PARSE_OLD10;
348 }
349
350 /*
351 * Reset errors and check memory usage before the test
352 */
353 xmlResetLastError();
354 testErrorsSize = 0; testErrors[0] = 0;
355 mem = xmlMemUsed();
356
357 if (xmlStrEqual(type, BAD_CAST "not-wf")) {
358 if (nstest == 0)
359 xmlconfTestNotWF((char *) id, (char *) filename, options);
360 else
361 xmlconfTestNotNSWF((char *) id, (char *) filename, options);
362 } else if (xmlStrEqual(type, BAD_CAST "valid")) {
363 } else if (xmlStrEqual(type, BAD_CAST "invalid")) {
364 } else if (xmlStrEqual(type, BAD_CAST "error")) {
365 } else {
366 test_log("test %s unknown TYPE value %s\n", (char *) id, (char *)type);
367 ret = -1;
368 goto error;
369 }
370
371 /*
372 * Reset errors and check memory usage after the test
373 */
374 xmlResetLastError();
375 final = xmlMemUsed();
376 if (final > mem) {
377 test_log("test %s : %s leaked %d bytes\n",
378 id, filename, final - mem);
379 nb_leaks++;
380 }
381 nb_tests++;
382
383error:
384 if (type != NULL)
385 xmlFree(type);
386 if (entities != NULL)
387 xmlFree(entities);
388 if (edition != NULL)
389 xmlFree(edition);
390 if (filename != NULL)
391 xmlFree(filename);
392 if (uri != NULL)
393 xmlFree(uri);
394 if (base != NULL)
395 xmlFree(base);
396 if (id != NULL)
397 xmlFree(id);
398 if (rec != NULL)
399 xmlFree(rec);
400 return(ret);
401}
402
403static int
404xmlconfTestCases(xmlDocPtr doc, xmlNodePtr cur) {
405 xmlChar *profile;
406 int ret = 0;
407 int tests = 0;
408
409 profile = xmlGetProp(cur, BAD_CAST "PROFILE");
410 if (profile != NULL) {
411 printf("Test cases: %s\n", (char *) profile);
412 xmlFree(profile);
413 }
414 cur = cur->children;
415 while (cur != NULL) {
416 /* look only at elements we ignore everything else */
417 if (cur->type == XML_ELEMENT_NODE) {
418 if (xmlStrEqual(cur->name, BAD_CAST "TESTCASES")) {
419 ret += xmlconfTestCases(doc, cur);
420 } else if (xmlStrEqual(cur->name, BAD_CAST "TEST")) {
421 if (xmlconfTestItem(doc, cur) >= 0)
422 ret++;
423 tests++;
424 } else {
425 fprintf(stderr, "Unhandled element %s\n", (char *)cur->name);
426 }
427 }
428 cur = cur->next;
429 }
430 if (tests > 0)
431 printf("Test cases: %d tests\n", tests);
432 return(ret);
433}
434
435static int
436xmlconfTestSuite(xmlDocPtr doc, xmlNodePtr cur) {
437 xmlChar *profile;
438 int ret = 0;
439
440 profile = xmlGetProp(cur, BAD_CAST "PROFILE");
441 if (profile != NULL) {
442 printf("Test suite: %s\n", (char *) profile);
443 xmlFree(profile);
444 } else
445 printf("Test suite\n");
446 cur = cur->children;
447 while (cur != NULL) {
448 /* look only at elements we ignore everything else */
449 if (cur->type == XML_ELEMENT_NODE) {
450 if (xmlStrEqual(cur->name, BAD_CAST "TESTCASES")) {
451 ret += xmlconfTestCases(doc, cur);
452 } else {
453 fprintf(stderr, "Unhandled element %s\n", (char *)cur->name);
454 }
455 }
456 cur = cur->next;
457 }
458 return(ret);
459}
460
461static void
462xmlconfInfo(void) {
463 fprintf(stderr, " you need to fetch and extract the\n");
464 fprintf(stderr, " latest XML Conformance Test Suites\n");
465 fprintf(stderr, " http://www.w3.org/XML/Test/xmlts20080205.tar.gz\n");
466 fprintf(stderr, " see http://www.w3.org/XML/Test/ for informations\n");
467}
468
469static int
470xmlconfTest(void) {
471 const char *confxml = "xmlconf/xmlconf.xml";
472 xmlDocPtr doc;
473 xmlNodePtr cur;
474 int ret = 0;
475
476 if (!checkTestFile(confxml)) {
477 fprintf(stderr, "%s is missing \n", confxml);
478 xmlconfInfo();
479 return(-1);
480 }
481 doc = xmlReadFile(confxml, NULL, XML_PARSE_NOENT);
482 if (doc == NULL) {
483 fprintf(stderr, "%s is corrupted \n", confxml);
484 xmlconfInfo();
485 return(-1);
486 }
487
488 cur = xmlDocGetRootElement(doc);
489 if ((cur == NULL) || (!xmlStrEqual(cur->name, BAD_CAST "TESTSUITE"))) {
490 fprintf(stderr, "Unexpected format %s\n", confxml);
491 xmlconfInfo();
492 ret = -1;
493 } else {
494 ret = xmlconfTestSuite(doc, cur);
495 }
496 xmlFreeDoc(doc);
497 return(ret);
498}
499
500/************************************************************************
501 * *
502 * The driver for the tests *
503 * *
504 ************************************************************************/
505
506int
507main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) {
508 int ret = 0;
509 int old_errors, old_tests, old_leaks;
510
511 logfile = fopen(LOGFILE, "w");
512 if (logfile == NULL) {
513 fprintf(stderr,
514 "Could not open the log file, running in verbose mode\n");
515 verbose = 1;
516 }
517 initializeLibxml2();
518
519 if ((argc >= 2) && (!strcmp(argv[1], "-v")))
520 verbose = 1;
521
522
523 old_errors = nb_errors;
524 old_tests = nb_tests;
525 old_leaks = nb_leaks;
526 xmlconfTest();
527 if ((nb_errors == old_errors) && (nb_leaks == old_leaks))
528 printf("Ran %d tests, no errors\n", nb_tests - old_tests);
529 else
530 printf("Ran %d tests, %d errors, %d leaks\n",
531 nb_tests - old_tests,
532 nb_errors - old_errors,
533 nb_leaks - old_leaks);
534 if ((nb_errors == 0) && (nb_leaks == 0)) {
535 ret = 0;
536 printf("Total %d tests, no errors\n",
537 nb_tests);
538 } else {
539 ret = 1;
540 printf("Total %d tests, %d errors, %d leaks\n",
541 nb_tests, nb_errors, nb_leaks);
542 }
543 xmlXPathFreeContext(ctxtXPath);
544 xmlCleanupParser();
545 xmlMemoryDump();
546
547 if (logfile != NULL)
548 fclose(logfile);
549 return(ret);
550}