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