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