1#!/usr/bin/env python 2 3# 4# This is the MS subset of the W3C test suite for XML Schemas. 5# This file is generated from the MS W3c test suite description file. 6# 7 8import sys, os 9import exceptions, optparse 10import libxml2 11 12opa = optparse.OptionParser() 13 14opa.add_option("-b", "--base", action="store", type="string", dest="baseDir", 15 default="", 16 help="""The base directory; i.e. the parent folder of the 17 "nisttest", "suntest" and "msxsdtest" directories.""") 18 19opa.add_option("-o", "--out", action="store", type="string", dest="logFile", 20 default="test.log", 21 help="The filepath of the log file to be created") 22 23opa.add_option("--log", action="store_true", dest="enableLog", 24 default=False, 25 help="Create the log file") 26 27opa.add_option("--no-test-out", action="store_true", dest="disableTestStdOut", 28 default=False, 29 help="Don't output test results") 30 31opa.add_option("-s", "--silent", action="store_true", dest="silent", default=False, 32 help="Disables display of all tests") 33 34opa.add_option("-v", "--verbose", action="store_true", dest="verbose", 35 default=False, 36 help="Displays all tests (only if --silent is not set)") 37 38opa.add_option("-x", "--max", type="int", dest="maxTestCount", 39 default="-1", 40 help="The maximum number of tests to be run") 41 42opa.add_option("-t", "--test", type="string", dest="singleTest", 43 default=None, 44 help="Runs the specified test only") 45 46opa.add_option("--tsw", "--test-starts-with", type="string", dest="testStartsWith", 47 default=None, 48 help="Runs the specified test(s), starting with the given string") 49 50opa.add_option("--rieo", "--report-internal-errors-only", action="store_true", 51 dest="reportInternalErrOnly", default=False, 52 help="Display erroneous tests of type 'internal' only") 53 54opa.add_option("--rueo", "--report-unimplemented-errors-only", action="store_true", 55 dest="reportUnimplErrOnly", default=False, 56 help="Display erroneous tests of type 'unimplemented' only") 57 58opa.add_option("--rmleo", "--report-mem-leak-errors-only", action="store_true", 59 dest="reportMemLeakErrOnly", default=False, 60 help="Display erroneous tests of type 'memory leak' only") 61 62opa.add_option("-c", "--combines", type="string", dest="combines", 63 default=None, 64 help="Combines to be run (all if omitted)") 65 66opa.add_option("--csw", "--csw", type="string", dest="combineStartsWith", 67 default=None, 68 help="Combines to be run (all if omitted)") 69 70opa.add_option("--rc", "--report-combines", action="store_true", 71 dest="reportCombines", default=False, 72 help="Display combine reports") 73 74opa.add_option("--rec", "--report-err-combines", action="store_true", 75 dest="reportErrCombines", default=False, 76 help="Display erroneous combine reports only") 77 78opa.add_option("--debug", action="store_true", 79 dest="debugEnabled", default=False, 80 help="Displays debug messages") 81 82opa.add_option("--info", action="store_true", 83 dest="info", default=False, 84 help="Displays info on the suite only. Does not run any test.") 85opa.add_option("--sax", action="store_true", 86 dest="validationSAX", default=False, 87 help="Use SAX2-driven validation.") 88opa.add_option("--tn", action="store_true", 89 dest="displayTestName", default=False, 90 help="Display the test name in every case.") 91 92(options, args) = opa.parse_args() 93 94if options.combines is not None: 95 options.combines = options.combines.split() 96 97################################################ 98# The vars below are not intended to be changed. 99# 100 101msgSchemaNotValidButShould = "The schema should be valid." 102msgSchemaValidButShouldNot = "The schema should be invalid." 103msgInstanceNotValidButShould = "The instance should be valid." 104msgInstanceValidButShouldNot = "The instance should be invalid." 105vendorNIST = "NIST" 106vendorNIST_2 = "NIST-2" 107vendorSUN = "SUN" 108vendorMS = "MS" 109 110################### 111# Helper functions. 112# 113vendor = None 114 115def handleError(test, msg): 116 global options 117 if not options.silent: 118 test.addLibLog("'%s' LIB: %s" % (test.name, msg)) 119 if msg.find("Unimplemented") > -1: 120 test.failUnimplemented() 121 elif msg.find("Internal") > -1: 122 test.failInternal() 123 124 125def fixFileNames(fileName): 126 if (fileName is None) or (fileName == ""): 127 return "" 128 dirs = fileName.split("/") 129 if dirs[1] != "Tests": 130 fileName = os.path.join(".", "Tests") 131 for dir in dirs[1:]: 132 fileName = os.path.join(fileName, dir) 133 return fileName 134 135class XSTCTestGroup: 136 def __init__(self, name, schemaFileName, descr): 137 global vendor, vendorNIST_2 138 self.name = name 139 self.descr = descr 140 self.mainSchema = True 141 self.schemaFileName = fixFileNames(schemaFileName) 142 self.schemaParsed = False 143 self.schemaTried = False 144 145 def setSchema(self, schemaFileName, parsed): 146 if not self.mainSchema: 147 return 148 self.mainSchema = False 149 self.schemaParsed = parsed 150 self.schemaTried = True 151 152class XSTCTestCase: 153 154 # <!-- groupName, Name, Accepted, File, Val, Descr 155 def __init__(self, isSchema, groupName, name, accepted, file, val, descr): 156 global options 157 # 158 # Constructor. 159 # 160 self.testRunner = None 161 self.isSchema = isSchema 162 self.groupName = groupName 163 self.name = name 164 self.accepted = accepted 165 self.fileName = fixFileNames(file) 166 self.val = val 167 self.descr = descr 168 self.failed = False 169 self.combineName = None 170 171 self.log = [] 172 self.libLog = [] 173 self.initialMemUsed = 0 174 self.memLeak = 0 175 self.excepted = False 176 self.bad = False 177 self.unimplemented = False 178 self.internalErr = False 179 self.noSchemaErr = False 180 self.failed = False 181 # 182 # Init the log. 183 # 184 if not options.silent: 185 if self.descr is not None: 186 self.log.append("'%s' descr: %s\n" % (self.name, self.descr)) 187 self.log.append("'%s' exp validity: %d\n" % (self.name, self.val)) 188 189 def initTest(self, runner): 190 global vendorNIST, vendorSUN, vendorMS, vendorNIST_2, options, vendor 191 # 192 # Get the test-group. 193 # 194 self.runner = runner 195 self.group = runner.getGroup(self.groupName) 196 if vendor == vendorMS or vendor == vendorSUN: 197 # 198 # Use the last given directory for the combine name. 199 # 200 dirs = self.fileName.split("/") 201 self.combineName = dirs[len(dirs) -2] 202 elif vendor == vendorNIST: 203 # 204 # NIST files are named in the following form: 205 # "NISTSchema-short-pattern-1.xsd" 206 # 207 tokens = self.name.split("-") 208 self.combineName = tokens[1] 209 elif vendor == vendorNIST_2: 210 # 211 # Group-names have the form: "atomic-normalizedString-length-1" 212 # 213 tokens = self.groupName.split("-") 214 self.combineName = "%s-%s" % (tokens[0], tokens[1]) 215 else: 216 self.combineName = "unkown" 217 raise Exception("Could not compute the combine name of a test.") 218 if (not options.silent) and (self.group.descr is not None): 219 self.log.append("'%s' group-descr: %s\n" % (self.name, self.group.descr)) 220 221 222 def addLibLog(self, msg): 223 """This one is intended to be used by the error handler 224 function""" 225 global options 226 if not options.silent: 227 self.libLog.append(msg) 228 229 def fail(self, msg): 230 global options 231 self.failed = True 232 if not options.silent: 233 self.log.append("'%s' ( FAILED: %s\n" % (self.name, msg)) 234 235 def failNoSchema(self): 236 global options 237 self.failed = True 238 self.noSchemaErr = True 239 if not options.silent: 240 self.log.append("'%s' X NO-SCHEMA\n" % (self.name)) 241 242 def failInternal(self): 243 global options 244 self.failed = True 245 self.internalErr = True 246 if not options.silent: 247 self.log.append("'%s' * INTERNAL\n" % self.name) 248 249 def failUnimplemented(self): 250 global options 251 self.failed = True 252 self.unimplemented = True 253 if not options.silent: 254 self.log.append("'%s' ? UNIMPLEMENTED\n" % self.name) 255 256 def failCritical(self, msg): 257 global options 258 self.failed = True 259 self.bad = True 260 if not options.silent: 261 self.log.append("'%s' ! BAD: %s\n" % (self.name, msg)) 262 263 def failExcept(self, e): 264 global options 265 self.failed = True 266 self.excepted = True 267 if not options.silent: 268 self.log.append("'%s' # EXCEPTION: %s\n" % (self.name, e.__str__())) 269 270 def setUp(self): 271 # 272 # Set up Libxml2. 273 # 274 self.initialMemUsed = libxml2.debugMemory(1) 275 libxml2.initParser() 276 libxml2.lineNumbersDefault(1) 277 libxml2.registerErrorHandler(handleError, self) 278 279 def tearDown(self): 280 libxml2.schemaCleanupTypes() 281 libxml2.cleanupParser() 282 self.memLeak = libxml2.debugMemory(1) - self.initialMemUsed 283 284 def isIOError(self, file, docType): 285 err = None 286 try: 287 err = libxml2.lastError() 288 except: 289 # Suppress exceptions. 290 pass 291 if (err is None): 292 return False 293 if err.domain() == libxml2.XML_FROM_IO: 294 self.failCritical("failed to access the %s resource '%s'\n" % (docType, file)) 295 296 def debugMsg(self, msg): 297 global options 298 if options.debugEnabled: 299 sys.stdout.write("'%s' DEBUG: %s\n" % (self.name, msg)) 300 301 def finalize(self): 302 global options 303 """Adds additional info to the log.""" 304 # 305 # Add libxml2 messages. 306 # 307 if not options.silent: 308 self.log.extend(self.libLog) 309 # 310 # Add memory leaks. 311 # 312 if self.memLeak != 0: 313 self.log.append("%s + memory leak: %d bytes\n" % (self.name, self.memLeak)) 314 315 def run(self): 316 """Runs a test.""" 317 global options 318 319 ##filePath = os.path.join(options.baseDir, self.fileName) 320 # filePath = "%s/%s/%s/%s" % (options.baseDir, self.test_Folder, self.schema_Folder, self.schema_File) 321 if options.displayTestName: 322 sys.stdout.write("'%s'\n" % self.name) 323 try: 324 self.validate() 325 except (Exception, libxml2.parserError, libxml2.treeError), e: 326 self.failExcept(e) 327 328def parseSchema(fileName): 329 schema = None 330 ctxt = libxml2.schemaNewParserCtxt(fileName) 331 try: 332 try: 333 schema = ctxt.schemaParse() 334 except: 335 pass 336 finally: 337 del ctxt 338 return schema 339 340 341class XSTCSchemaTest(XSTCTestCase): 342 343 def __init__(self, groupName, name, accepted, file, val, descr): 344 XSTCTestCase.__init__(self, 1, groupName, name, accepted, file, val, descr) 345 346 def validate(self): 347 global msgSchemaNotValidButShould, msgSchemaValidButShouldNot 348 schema = None 349 filePath = self.fileName 350 # os.path.join(options.baseDir, self.fileName) 351 valid = 0 352 try: 353 # 354 # Parse the schema. 355 # 356 self.debugMsg("loading schema: %s" % filePath) 357 schema = parseSchema(filePath) 358 self.debugMsg("after loading schema") 359 if schema is None: 360 self.debugMsg("schema is None") 361 self.debugMsg("checking for IO errors...") 362 if self.isIOError(file, "schema"): 363 return 364 self.debugMsg("checking schema result") 365 if (schema is None and self.val) or (schema is not None and self.val == 0): 366 self.debugMsg("schema result is BAD") 367 if (schema == None): 368 self.fail(msgSchemaNotValidButShould) 369 else: 370 self.fail(msgSchemaValidButShouldNot) 371 else: 372 self.debugMsg("schema result is OK") 373 finally: 374 self.group.setSchema(self.fileName, schema is not None) 375 del schema 376 377class XSTCInstanceTest(XSTCTestCase): 378 379 def __init__(self, groupName, name, accepted, file, val, descr): 380 XSTCTestCase.__init__(self, 0, groupName, name, accepted, file, val, descr) 381 382 def validate(self): 383 instance = None 384 schema = None 385 filePath = self.fileName 386 # os.path.join(options.baseDir, self.fileName) 387 388 if not self.group.schemaParsed and self.group.schemaTried: 389 self.failNoSchema() 390 return 391 392 self.debugMsg("loading instance: %s" % filePath) 393 parserCtxt = libxml2.newParserCtxt() 394 if (parserCtxt is None): 395 # TODO: Is this one necessary, or will an exception 396 # be already raised? 397 raise Exception("Could not create the instance parser context.") 398 if not options.validationSAX: 399 try: 400 try: 401 instance = parserCtxt.ctxtReadFile(filePath, None, libxml2.XML_PARSE_NOWARNING) 402 except: 403 # Suppress exceptions. 404 pass 405 finally: 406 del parserCtxt 407 self.debugMsg("after loading instance") 408 if instance is None: 409 self.debugMsg("instance is None") 410 self.failCritical("Failed to parse the instance for unknown reasons.") 411 return 412 try: 413 # 414 # Validate the instance. 415 # 416 self.debugMsg("loading schema: %s" % self.group.schemaFileName) 417 schema = parseSchema(self.group.schemaFileName) 418 try: 419 validationCtxt = schema.schemaNewValidCtxt() 420 #validationCtxt = libxml2.schemaNewValidCtxt(None) 421 if (validationCtxt is None): 422 self.failCritical("Could not create the validation context.") 423 return 424 try: 425 self.debugMsg("validating instance") 426 if options.validationSAX: 427 instance_Err = validationCtxt.schemaValidateFile(filePath, 0) 428 else: 429 instance_Err = validationCtxt.schemaValidateDoc(instance) 430 self.debugMsg("after instance validation") 431 self.debugMsg("instance-err: %d" % instance_Err) 432 if (instance_Err != 0 and self.val == 1) or (instance_Err == 0 and self.val == 0): 433 self.debugMsg("instance result is BAD") 434 if (instance_Err != 0): 435 self.fail(msgInstanceNotValidButShould) 436 else: 437 self.fail(msgInstanceValidButShouldNot) 438 439 else: 440 self.debugMsg("instance result is OK") 441 finally: 442 del validationCtxt 443 finally: 444 del schema 445 finally: 446 if instance is not None: 447 instance.freeDoc() 448 449 450#################### 451# Test runner class. 452# 453 454class XSTCTestRunner: 455 456 CNT_TOTAL = 0 457 CNT_RAN = 1 458 CNT_SUCCEEDED = 2 459 CNT_FAILED = 3 460 CNT_UNIMPLEMENTED = 4 461 CNT_INTERNAL = 5 462 CNT_BAD = 6 463 CNT_EXCEPTED = 7 464 CNT_MEMLEAK = 8 465 CNT_NOSCHEMA = 9 466 CNT_NOTACCEPTED = 10 467 CNT_SCHEMA_TEST = 11 468 469 def __init__(self): 470 self.logFile = None 471 self.counters = self.createCounters() 472 self.testList = [] 473 self.combinesRan = {} 474 self.groups = {} 475 self.curGroup = None 476 477 def createCounters(self): 478 counters = {self.CNT_TOTAL:0, self.CNT_RAN:0, self.CNT_SUCCEEDED:0, 479 self.CNT_FAILED:0, self.CNT_UNIMPLEMENTED:0, self.CNT_INTERNAL:0, self.CNT_BAD:0, 480 self.CNT_EXCEPTED:0, self.CNT_MEMLEAK:0, self.CNT_NOSCHEMA:0, self.CNT_NOTACCEPTED:0, 481 self.CNT_SCHEMA_TEST:0} 482 483 return counters 484 485 def addTest(self, test): 486 self.testList.append(test) 487 test.initTest(self) 488 489 def getGroup(self, groupName): 490 return self.groups[groupName] 491 492 def addGroup(self, group): 493 self.groups[group.name] = group 494 495 def updateCounters(self, test, counters): 496 if test.memLeak != 0: 497 counters[self.CNT_MEMLEAK] += 1 498 if not test.failed: 499 counters[self.CNT_SUCCEEDED] +=1 500 if test.failed: 501 counters[self.CNT_FAILED] += 1 502 if test.bad: 503 counters[self.CNT_BAD] += 1 504 if test.unimplemented: 505 counters[self.CNT_UNIMPLEMENTED] += 1 506 if test.internalErr: 507 counters[self.CNT_INTERNAL] += 1 508 if test.noSchemaErr: 509 counters[self.CNT_NOSCHEMA] += 1 510 if test.excepted: 511 counters[self.CNT_EXCEPTED] += 1 512 if not test.accepted: 513 counters[self.CNT_NOTACCEPTED] += 1 514 if test.isSchema: 515 counters[self.CNT_SCHEMA_TEST] += 1 516 return counters 517 518 def displayResults(self, out, all, combName, counters): 519 out.write("\n") 520 if all: 521 if options.combines is not None: 522 out.write("combine(s): %s\n" % str(options.combines)) 523 elif combName is not None: 524 out.write("combine : %s\n" % combName) 525 out.write(" total : %d\n" % counters[self.CNT_TOTAL]) 526 if all or options.combines is not None: 527 out.write(" ran : %d\n" % counters[self.CNT_RAN]) 528 out.write(" (schemata) : %d\n" % counters[self.CNT_SCHEMA_TEST]) 529 # out.write(" succeeded : %d\n" % counters[self.CNT_SUCCEEDED]) 530 out.write(" not accepted : %d\n" % counters[self.CNT_NOTACCEPTED]) 531 if counters[self.CNT_FAILED] > 0: 532 out.write(" failed : %d\n" % counters[self.CNT_FAILED]) 533 out.write(" -> internal : %d\n" % counters[self.CNT_INTERNAL]) 534 out.write(" -> unimpl. : %d\n" % counters[self.CNT_UNIMPLEMENTED]) 535 out.write(" -> skip-invalid-schema : %d\n" % counters[self.CNT_NOSCHEMA]) 536 out.write(" -> bad : %d\n" % counters[self.CNT_BAD]) 537 out.write(" -> exceptions : %d\n" % counters[self.CNT_EXCEPTED]) 538 out.write(" memory leaks : %d\n" % counters[self.CNT_MEMLEAK]) 539 540 def displayShortResults(self, out, all, combName, counters): 541 out.write("Ran %d of %d tests (%d schemata):" % (counters[self.CNT_RAN], 542 counters[self.CNT_TOTAL], counters[self.CNT_SCHEMA_TEST])) 543 # out.write(" succeeded : %d\n" % counters[self.CNT_SUCCEEDED]) 544 if counters[self.CNT_NOTACCEPTED] > 0: 545 out.write(" %d not accepted" % (counters[self.CNT_NOTACCEPTED])) 546 if counters[self.CNT_FAILED] > 0 or counters[self.CNT_MEMLEAK] > 0: 547 if counters[self.CNT_FAILED] > 0: 548 out.write(" %d failed" % (counters[self.CNT_FAILED])) 549 out.write(" (") 550 if counters[self.CNT_INTERNAL] > 0: 551 out.write(" %d internal" % (counters[self.CNT_INTERNAL])) 552 if counters[self.CNT_UNIMPLEMENTED] > 0: 553 out.write(" %d unimplemented" % (counters[self.CNT_UNIMPLEMENTED])) 554 if counters[self.CNT_NOSCHEMA] > 0: 555 out.write(" %d skip-invalid-schema" % (counters[self.CNT_NOSCHEMA])) 556 if counters[self.CNT_BAD] > 0: 557 out.write(" %d bad" % (counters[self.CNT_BAD])) 558 if counters[self.CNT_EXCEPTED] > 0: 559 out.write(" %d exception" % (counters[self.CNT_EXCEPTED])) 560 out.write(" )") 561 if counters[self.CNT_MEMLEAK] > 0: 562 out.write(" %d leaks" % (counters[self.CNT_MEMLEAK])) 563 out.write("\n") 564 else: 565 out.write(" all passed\n") 566 567 def reportCombine(self, combName): 568 global options 569 570 counters = self.createCounters() 571 # 572 # Compute evaluation counters. 573 # 574 for test in self.combinesRan[combName]: 575 counters[self.CNT_TOTAL] += 1 576 counters[self.CNT_RAN] += 1 577 counters = self.updateCounters(test, counters) 578 if options.reportErrCombines and (counters[self.CNT_FAILED] == 0) and (counters[self.CNT_MEMLEAK] == 0): 579 pass 580 else: 581 if options.enableLog: 582 self.displayResults(self.logFile, False, combName, counters) 583 self.displayResults(sys.stdout, False, combName, counters) 584 585 def displayTestLog(self, test): 586 sys.stdout.writelines(test.log) 587 sys.stdout.write("~~~~~~~~~~\n") 588 589 def reportTest(self, test): 590 global options 591 592 error = test.failed or test.memLeak != 0 593 # 594 # Only erroneous tests will be written to the log, 595 # except @verbose is switched on. 596 # 597 if options.enableLog and (options.verbose or error): 598 self.logFile.writelines(test.log) 599 self.logFile.write("~~~~~~~~~~\n") 600 # 601 # if not @silent, only erroneous tests will be 602 # written to stdout, except @verbose is switched on. 603 # 604 if not options.silent: 605 if options.reportInternalErrOnly and test.internalErr: 606 self.displayTestLog(test) 607 if options.reportMemLeakErrOnly and test.memLeak != 0: 608 self.displayTestLog(test) 609 if options.reportUnimplErrOnly and test.unimplemented: 610 self.displayTestLog(test) 611 if (options.verbose or error) and (not options.reportInternalErrOnly) and (not options.reportMemLeakErrOnly) and (not options.reportUnimplErrOnly): 612 self.displayTestLog(test) 613 614 615 def addToCombines(self, test): 616 found = False 617 if self.combinesRan.has_key(test.combineName): 618 self.combinesRan[test.combineName].append(test) 619 else: 620 self.combinesRan[test.combineName] = [test] 621 622 def run(self): 623 624 global options 625 626 if options.info: 627 for test in self.testList: 628 self.addToCombines(test) 629 sys.stdout.write("Combines: %d\n" % len(self.combinesRan)) 630 sys.stdout.write("%s\n" % self.combinesRan.keys()) 631 return 632 633 if options.enableLog: 634 self.logFile = open(options.logFile, "w") 635 try: 636 for test in self.testList: 637 self.counters[self.CNT_TOTAL] += 1 638 # 639 # Filter tests. 640 # 641 if options.singleTest is not None and options.singleTest != "": 642 if (test.name != options.singleTest): 643 continue 644 elif options.combines is not None: 645 if not options.combines.__contains__(test.combineName): 646 continue 647 elif options.testStartsWith is not None: 648 if not test.name.startswith(options.testStartsWith): 649 continue 650 elif options.combineStartsWith is not None: 651 if not test.combineName.startswith(options.combineStartsWith): 652 continue 653 654 if options.maxTestCount != -1 and self.counters[self.CNT_RAN] >= options.maxTestCount: 655 break 656 self.counters[self.CNT_RAN] += 1 657 # 658 # Run the thing, dammit. 659 # 660 try: 661 test.setUp() 662 try: 663 test.run() 664 finally: 665 test.tearDown() 666 finally: 667 # 668 # Evaluate. 669 # 670 test.finalize() 671 self.reportTest(test) 672 if options.reportCombines or options.reportErrCombines: 673 self.addToCombines(test) 674 self.counters = self.updateCounters(test, self.counters) 675 finally: 676 if options.reportCombines or options.reportErrCombines: 677 # 678 # Build a report for every single combine. 679 # 680 # TODO: How to sort a dict? 681 # 682 self.combinesRan.keys().sort(None) 683 for key in self.combinesRan.keys(): 684 self.reportCombine(key) 685 686 # 687 # Display the final report. 688 # 689 if options.silent: 690 self.displayShortResults(sys.stdout, True, None, self.counters) 691 else: 692 sys.stdout.write("===========================\n") 693 self.displayResults(sys.stdout, True, None, self.counters) 694