476c1fc18335bc0a87a192f45846e35e0e7ee691
[scons.git] / QMTest / unittest.py
1 #!/usr/bin/env python
2 """
3 Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
4 Smalltalk testing framework.
5
6 Further information is available in the bundled documentation, and from
7
8   http://pyunit.sourceforge.net/
9
10 This module contains the core framework classes that form the basis of
11 specific test cases and suites (TestCase, TestSuite etc.), and also a
12 text-based utility class for running the tests and reporting the results
13 (TextTestRunner).
14
15 Copyright (c) 1999, 2000, 2001 Steve Purcell
16 This module is free software, and you may redistribute it and/or modify
17 it under the same terms as Python itself, so long as this copyright message
18 and disclaimer are retained in their original form.
19
20 IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
21 SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
22 THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
23 DAMAGE.
24
25 THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
26 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
27 PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
28 AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
29 SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
30 """
31 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
32
33 __author__ = "Steve Purcell (stephen_purcell@yahoo.com)"
34 __version__ = "$ Revision: 1.23 $"[11:-2]
35
36 import time
37 import sys
38 import traceback
39 import os
40
41 ##############################################################################
42 # A platform-specific concession to help the code work for JPython users
43 ##############################################################################
44
45 plat = sys.platform.lower()
46 _isJPython = plat.find('java') >= 0 or plat.find('jdk') >= 0
47 del plat
48
49
50 ##############################################################################
51 # Test framework core
52 ##############################################################################
53
54 class TestResult:
55     """Holder for test result information.
56
57     Test results are automatically managed by the TestCase and TestSuite
58     classes, and do not need to be explicitly manipulated by writers of tests.
59
60     Each instance holds the total number of tests run, and collections of
61     failures and errors that occurred among those test runs. The collections
62     contain tuples of (testcase, exceptioninfo), where exceptioninfo is a
63     tuple of values as returned by sys.exc_info().
64     """
65     def __init__(self):
66         self.failures = []
67         self.errors = []
68         self.testsRun = 0
69         self.shouldStop = 0
70
71     def startTest(self, test):
72         "Called when the given test is about to be run"
73         self.testsRun = self.testsRun + 1
74
75     def stopTest(self, test):
76         "Called when the given test has been run"
77         pass
78
79     def addError(self, test, err):
80         "Called when an error has occurred"
81         self.errors.append((test, err))
82
83     def addFailure(self, test, err):
84         "Called when a failure has occurred"
85         self.failures.append((test, err))
86
87     def wasSuccessful(self):
88         "Tells whether or not this result was a success"
89         return len(self.failures) == len(self.errors) == 0
90
91     def stop(self):
92         "Indicates that the tests should be aborted"
93         self.shouldStop = 1
94     
95     def __repr__(self):
96         return "<%s run=%i errors=%i failures=%i>" % \
97                (self.__class__, self.testsRun, len(self.errors),
98                 len(self.failures))
99
100
101 class TestCase:
102     """A class whose instances are single test cases.
103
104     Test authors should subclass TestCase for their own tests. Construction 
105     and deconstruction of the test's environment ('fixture') can be
106     implemented by overriding the 'setUp' and 'tearDown' methods respectively.
107
108     By default, the test code itself should be placed in a method named
109     'runTest'.
110     
111     If the fixture may be used for many test cases, create as 
112     many test methods as are needed. When instantiating such a TestCase
113     subclass, specify in the constructor arguments the name of the test method
114     that the instance is to execute.
115
116     If it is necessary to override the __init__ method, the base class
117     __init__ method must always be called.
118     """
119     def __init__(self, methodName='runTest'):
120         """Create an instance of the class that will use the named test
121            method when executed. Raises a ValueError if the instance does
122            not have a method with the specified name.
123         """
124         try:
125             self.__testMethod = getattr(self,methodName)
126         except AttributeError:
127             raise ValueError, "no such test method in %s: %s" % \
128                   (self.__class__, methodName)
129
130     def setUp(self):
131         "Hook method for setting up the test fixture before exercising it."
132         pass
133
134     def tearDown(self):
135         "Hook method for deconstructing the test fixture after testing it."
136         pass
137
138     def countTestCases(self):
139         return 1
140
141     def defaultTestResult(self):
142         return TestResult()
143
144     def shortDescription(self):
145         """Returns a one-line description of the test, or None if no
146         description has been provided.
147
148         The default implementation of this method returns the first line of
149         the specified test method's docstring.
150         """
151         doc = self.__testMethod.__doc__
152         return doc and doc.split("\n")[0].strip() or None
153
154     def id(self):
155         return "%s.%s" % (self.__class__, self.__testMethod.__name__)
156
157     def __str__(self):
158         return "%s (%s)" % (self.__testMethod.__name__, self.__class__)
159
160     def __repr__(self):
161         return "<%s testMethod=%s>" % \
162                (self.__class__, self.__testMethod.__name__)
163
164     def run(self, result=None):
165         return self(result)
166
167     def __call__(self, result=None):
168         if result is None: result = self.defaultTestResult()
169         result.startTest(self)
170         try:
171             try:
172                 self.setUp()
173             except:
174                 result.addError(self,self.__exc_info())
175                 return
176
177             try:
178                 self.__testMethod()
179             except AssertionError, e:
180                 result.addFailure(self,self.__exc_info())
181             except:
182                 result.addError(self,self.__exc_info())
183
184             try:
185                 self.tearDown()
186             except:
187                 result.addError(self,self.__exc_info())
188         finally:
189             result.stopTest(self)
190
191     def debug(self):
192         """Run the test without collecting errors in a TestResult"""
193         self.setUp()
194         self.__testMethod()
195         self.tearDown()
196
197     def assert_(self, expr, msg=None):
198         """Equivalent of built-in 'assert', but is not optimised out when
199            __debug__ is false.
200         """
201         if not expr:
202             raise AssertionError, msg
203
204     failUnless = assert_
205
206     def failIf(self, expr, msg=None):
207         "Fail the test if the expression is true."
208         self.assert_(not expr,msg)
209
210     def assertRaises(self, excClass, callableObj, *args, **kwargs):
211         """Assert that an exception of class excClass is thrown
212            by callableObj when invoked with arguments args and keyword
213            arguments kwargs. If a different type of exception is
214            thrown, it will not be caught, and the test case will be
215            deemed to have suffered an error, exactly as for an
216            unexpected exception.
217         """
218         try:
219             callableObj(*args, **kwargs)
220         except excClass:
221             return
222         else:
223             if hasattr(excClass,'__name__'): excName = excClass.__name__
224             else: excName = str(excClass)
225             raise AssertionError, excName
226
227     def fail(self, msg=None):
228         """Fail immediately, with the given message."""
229         raise AssertionError, msg
230                                    
231     def __exc_info(self):
232         """Return a version of sys.exc_info() with the traceback frame
233            minimised; usually the top level of the traceback frame is not
234            needed.
235         """
236         exctype, excvalue, tb = sys.exc_info()
237         newtb = tb.tb_next
238         if newtb is None:
239             return (exctype, excvalue, tb)
240         return (exctype, excvalue, newtb)
241
242
243 class TestSuite:
244     """A test suite is a composite test consisting of a number of TestCases.
245
246     For use, create an instance of TestSuite, then add test case instances.
247     When all tests have been added, the suite can be passed to a test
248     runner, such as TextTestRunner. It will run the individual test cases
249     in the order in which they were added, aggregating the results. When
250     subclassing, do not forget to call the base class constructor.
251     """
252     def __init__(self, tests=()):
253         self._tests = []
254         self.addTests(tests)
255
256     def __repr__(self):
257         return "<%s tests=%s>" % (self.__class__, self._tests)
258
259     __str__ = __repr__
260
261     def countTestCases(self):
262         cases = 0
263         for test in self._tests:
264             cases = cases + test.countTestCases()
265         return cases
266
267     def addTest(self, test):
268         self._tests.append(test)
269
270     def addTests(self, tests):
271         for test in tests:
272             self.addTest(test)
273
274     def run(self, result):
275         return self(result)
276
277     def __call__(self, result):
278         for test in self._tests:
279             if result.shouldStop:
280                 break
281             test(result)
282         return result
283
284     def debug(self):
285         """Run the tests without collecting errors in a TestResult"""
286         for test in self._tests: test.debug()
287
288
289 class FunctionTestCase(TestCase):
290     """A test case that wraps a test function.
291
292     This is useful for slipping pre-existing test functions into the
293     PyUnit framework. Optionally, set-up and tidy-up functions can be
294     supplied. As with TestCase, the tidy-up ('tearDown') function will
295     always be called if the set-up ('setUp') function ran successfully.
296     """
297
298     def __init__(self, testFunc, setUp=None, tearDown=None,
299                  description=None):
300         TestCase.__init__(self)
301         self.__setUpFunc = setUp
302         self.__tearDownFunc = tearDown
303         self.__testFunc = testFunc
304         self.__description = description
305
306     def setUp(self):
307         if self.__setUpFunc is not None:
308             self.__setUpFunc()
309
310     def tearDown(self):
311         if self.__tearDownFunc is not None:
312             self.__tearDownFunc()
313
314     def runTest(self):
315         self.__testFunc()
316
317     def id(self):
318         return self.__testFunc.__name__
319
320     def __str__(self):
321         return "%s (%s)" % (self.__class__, self.__testFunc.__name__)
322
323     def __repr__(self):
324         return "<%s testFunc=%s>" % (self.__class__, self.__testFunc)
325
326     def shortDescription(self):
327         if self.__description is not None: return self.__description
328         doc = self.__testFunc.__doc__
329         return doc and doc.split("\n")[0].strip() or None
330
331
332
333 ##############################################################################
334 # Convenience functions
335 ##############################################################################
336
337 def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
338     """Extracts all the names of functions in the given test case class
339        and its base classes that start with the given prefix. This is used
340        by makeSuite().
341     """
342     testFnNames = [n for n in dir(testCaseClass) if n[:len(prefix)] == prefix]
343     for baseclass in testCaseClass.__bases__:
344         testFnNames = testFnNames + \
345                       getTestCaseNames(baseclass, prefix, sortUsing=None)
346     if sortUsing:
347         testFnNames.sort(sortUsing)
348     return testFnNames
349
350
351 def makeSuite(testCaseClass, prefix='test', sortUsing=cmp):
352     """Returns a TestSuite instance built from all of the test functions
353        in the given test case class whose names begin with the given
354        prefix. The cases are sorted by their function names
355        using the supplied comparison function, which defaults to 'cmp'.
356     """
357     cases = list(map(testCaseClass,
358                 getTestCaseNames(testCaseClass, prefix, sortUsing)))
359     return TestSuite(cases)
360
361
362 def createTestInstance(name, module=None):
363     """Finds tests by their name, optionally only within the given module.
364
365     Return the newly-constructed test, ready to run. If the name contains a ':'
366     then the portion of the name after the colon is used to find a specific
367     test case within the test case class named before the colon.
368
369     Examples:
370      findTest('examples.listtests.suite')
371         -- returns result of calling 'suite'
372      findTest('examples.listtests.ListTestCase:checkAppend')
373         -- returns result of calling ListTestCase('checkAppend')
374      findTest('examples.listtests.ListTestCase:check-')
375         -- returns result of calling makeSuite(ListTestCase, prefix="check")
376     """
377           
378     spec = name.split(':')
379     if len(spec) > 2: raise ValueError, "illegal test name: %s" % name
380     if len(spec) == 1:
381         testName = spec[0]
382         caseName = None
383     else:
384         testName, caseName = spec
385     parts = testName.split('.')
386     if module is None:
387         if len(parts) < 2:
388             raise ValueError, "incomplete test name: %s" % name
389         constructor = __import__('.'.join(parts[:-1]))
390         parts = parts[1:]
391     else:
392         constructor = module
393     for part in parts:
394         constructor = getattr(constructor, part)
395     if not callable(constructor):
396         raise ValueError, "%s is not a callable object" % constructor
397     if caseName:
398         if caseName[-1] == '-':
399             prefix = caseName[:-1]
400             if not prefix:
401                 raise ValueError, "prefix too short: %s" % name
402             test = makeSuite(constructor, prefix=prefix)
403         else:
404             test = constructor(caseName)
405     else:
406         test = constructor()
407     if not hasattr(test,"countTestCases"):
408         raise TypeError, \
409               "object %s found with spec %s is not a test" % (test, name)
410     return test
411
412
413 ##############################################################################
414 # Text UI
415 ##############################################################################
416
417 class _WritelnDecorator:
418     """Used to decorate file-like objects with a handy 'writeln' method"""
419     def __init__(self,stream):
420         self.stream = stream
421         if _isJPython:
422             import java.lang.System
423             self.linesep = java.lang.System.getProperty("line.separator")
424         else:
425             self.linesep = os.linesep
426
427     def __getattr__(self, attr):
428         return getattr(self.stream,attr)
429
430     def writeln(self, *args):
431         if args: self.write(*args)
432         self.write(self.linesep)
433
434  
435 class _JUnitTextTestResult(TestResult):
436     """A test result class that can print formatted text results to a stream.
437
438     Used by JUnitTextTestRunner.
439     """
440     def __init__(self, stream):
441         self.stream = stream
442         TestResult.__init__(self)
443
444     def addError(self, test, error):
445         TestResult.addError(self,test,error)
446         self.stream.write('E')
447         self.stream.flush()
448         if error[0] is KeyboardInterrupt:
449             self.shouldStop = 1
450  
451     def addFailure(self, test, error):
452         TestResult.addFailure(self,test,error)
453         self.stream.write('F')
454         self.stream.flush()
455  
456     def startTest(self, test):
457         TestResult.startTest(self,test)
458         self.stream.write('.')
459         self.stream.flush()
460
461     def printNumberedErrors(self,errFlavour,errors):
462         if not errors: return
463         if len(errors) == 1:
464             self.stream.writeln("There was 1 %s:" % errFlavour)
465         else:
466             self.stream.writeln("There were %i %ss:" %
467                                 (len(errors), errFlavour))
468         i = 1
469         for test,error in errors:
470             errString = "".join(traceback.format_exception(*error))
471             self.stream.writeln("%i) %s" % (i, test))
472             self.stream.writeln(errString)
473             i = i + 1
474  
475     def printErrors(self):
476         self.printNumberedErrors("error",self.errors)
477
478     def printFailures(self):
479         self.printNumberedErrors("failure",self.failures)
480
481     def printHeader(self):
482         self.stream.writeln()
483         if self.wasSuccessful():
484             self.stream.writeln("OK (%i tests)" % self.testsRun)
485         else:
486             self.stream.writeln("!!!FAILURES!!!")
487             self.stream.writeln("Test Results")
488             self.stream.writeln()
489             self.stream.writeln("Run: %i ; Failures: %i ; Errors: %i" %
490                                 (self.testsRun, len(self.failures),
491                                  len(self.errors)))
492             
493     def printResult(self):
494         self.printHeader()
495         self.printErrors()
496         self.printFailures()
497
498
499 class JUnitTextTestRunner:
500     """A test runner class that displays results in textual form.
501     
502     The display format approximates that of JUnit's 'textui' test runner.
503     This test runner may be removed in a future version of PyUnit.
504     """
505     def __init__(self, stream=sys.stderr):
506         self.stream = _WritelnDecorator(stream)
507
508     def run(self, test):
509         "Run the given test case or test suite."
510         result = _JUnitTextTestResult(self.stream)
511         startTime = time.time()
512         test(result)
513         stopTime = time.time()
514         self.stream.writeln()
515         self.stream.writeln("Time: %.3fs" % float(stopTime - startTime))
516         result.printResult()
517         return result
518
519
520 ##############################################################################
521 # Verbose text UI
522 ##############################################################################
523
524 class _VerboseTextTestResult(TestResult):
525     """A test result class that can print formatted text results to a stream.
526
527     Used by VerboseTextTestRunner.
528     """
529     def __init__(self, stream, descriptions):
530         TestResult.__init__(self)
531         self.stream = stream
532         self.lastFailure = None
533         self.descriptions = descriptions
534         
535     def startTest(self, test):
536         TestResult.startTest(self, test)
537         if self.descriptions:
538             self.stream.write(test.shortDescription() or str(test))
539         else:
540             self.stream.write(str(test))
541         self.stream.write(" ... ")
542
543     def stopTest(self, test):
544         TestResult.stopTest(self, test)
545         if self.lastFailure is not test:
546             self.stream.writeln("ok")
547
548     def addError(self, test, err):
549         TestResult.addError(self, test, err)
550         self._printError("ERROR", test, err)
551         self.lastFailure = test
552         if err[0] is KeyboardInterrupt:
553             self.shouldStop = 1
554
555     def addFailure(self, test, err):
556         TestResult.addFailure(self, test, err)
557         self._printError("FAIL", test, err)
558         self.lastFailure = test
559
560     def _printError(self, flavour, test, err):
561         errLines = []
562         separator1 = "\t" + '=' * 70
563         separator2 = "\t" + '-' * 70
564         if not self.lastFailure is test:
565             self.stream.writeln()
566             self.stream.writeln(separator1)
567         self.stream.writeln("\t%s" % flavour)
568         self.stream.writeln(separator2)
569         for line in traceback.format_exception(*err):
570             for l in line.split("\n")[:-1]:
571                 self.stream.writeln("\t%s" % l)
572         self.stream.writeln(separator1)
573
574
575 class VerboseTextTestRunner:
576     """A test runner class that displays results in textual form.
577     
578     It prints out the names of tests as they are run, errors as they
579     occur, and a summary of the results at the end of the test run.
580     """
581     def __init__(self, stream=sys.stderr, descriptions=1):
582         self.stream = _WritelnDecorator(stream)
583         self.descriptions = descriptions
584
585     def run(self, test):
586         "Run the given test case or test suite."
587         result = _VerboseTextTestResult(self.stream, self.descriptions)
588         startTime = time.time()
589         test(result)
590         stopTime = time.time()
591         timeTaken = float(stopTime - startTime)
592         self.stream.writeln("-" * 78)
593         run = result.testsRun
594         self.stream.writeln("Ran %d test%s in %.3fs" %
595                             (run, run > 1 and "s" or "", timeTaken))
596         self.stream.writeln()
597         if not result.wasSuccessful():
598             self.stream.write("FAILED (")
599             failed = len(result.failures)
600             if failed:
601                 self.stream.write("failures=%d" % failed)
602             errored = len(result.errors)
603             if errored:
604                 if failed: self.stream.write(", ")
605                 self.stream.write("errors=%d" % errored)
606             self.stream.writeln(")")
607         else:
608             self.stream.writeln("OK")
609         return result
610         
611
612 # Which flavour of TextTestRunner is the default?
613 TextTestRunner = VerboseTextTestRunner
614
615
616 ##############################################################################
617 # Facilities for running tests from the command line
618 ##############################################################################
619
620 class TestProgram:
621     """A command-line program that runs a set of tests; this is primarily
622        for making test modules conveniently executable.
623     """
624     USAGE = """\
625 Usage: %(progName)s [-h|--help] [test[:(casename|prefix-)]] [...]
626
627 Examples:
628   %(progName)s                               - run default set of tests
629   %(progName)s MyTestSuite                   - run suite 'MyTestSuite'
630   %(progName)s MyTestCase:checkSomething     - run MyTestCase.checkSomething
631   %(progName)s MyTestCase:check-             - run all 'check*' test methods
632                                                in MyTestCase
633 """
634     def __init__(self, module='__main__', defaultTest=None,
635                  argv=None, testRunner=None):
636         if isinstance(module, str):
637             self.module = __import__(module)
638             for part in module.split('.')[1:]:
639                 self.module = getattr(self.module, part)
640         else:
641             self.module = module
642         if argv is None:
643             argv = sys.argv
644         self.defaultTest = defaultTest
645         self.testRunner = testRunner
646         self.progName = os.path.basename(argv[0])
647         self.parseArgs(argv)
648         self.createTests()
649         self.runTests()
650
651     def usageExit(self, msg=None):
652         if msg: print msg
653         print self.USAGE % self.__dict__
654         sys.exit(2)
655
656     def parseArgs(self, argv):
657         import getopt
658         try:
659             options, args = getopt.getopt(argv[1:], 'hH', ['help'])
660             opts = {}
661             for opt, value in options:
662                 if opt in ('-h','-H','--help'):
663                     self.usageExit()
664             if len(args) == 0 and self.defaultTest is None:
665                 raise getopt.error, "No default test is defined."
666             if len(args) > 0:
667                 self.testNames = args
668             else:
669                 self.testNames = (self.defaultTest,)
670         except getopt.error, msg:
671             self.usageExit(msg)
672
673     def createTests(self):
674         tests = []
675         for testName in self.testNames:
676             tests.append(createTestInstance(testName, self.module))
677         self.test = TestSuite(tests)
678
679     def runTests(self):
680         if self.testRunner is None:
681             self.testRunner = TextTestRunner()
682         result = self.testRunner.run(self.test)
683         sys.exit(not result.wasSuccessful())    
684
685 main = TestProgram
686
687
688 ##############################################################################
689 # Executing this module from the command line
690 ##############################################################################
691
692 if __name__ == "__main__":
693     main(module=None)
694
695 # Local Variables:
696 # tab-width:4
697 # indent-tabs-mode:nil
698 # End:
699 # vim: set expandtab tabstop=4 shiftwidth=4: