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