runxmlconf.c 16 KB

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