blob: b2bd9357b46b0194f3457add13136610e5840b65 [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
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}