c74a4c7652541c238d0f7090c5e2fdcf090a0fc5
[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
32 __author__ = "Steve Purcell (stephen_purcell@yahoo.com)"
33 __version__ = "$ Revision: 1.23 $"[11:-2]
34
35 import time
36 import sys
37 import traceback
38 import string
39 import os
40
41 ##############################################################################
42 # A platform-specific concession to help the code work for JPython users
43 ##############################################################################
44
45 plat = string.lower(sys.platform)
46 _isJPython = string.find(plat, 'java') >= 0 or string.find(plat, '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 string.strip(string.split(doc, "\n")[0]) 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         apply(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             apply(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 string.strip(string.split(doc, "\n")[0]) 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 = filter(lambda n,p=prefix: n[:len(p)] == p,
343                          dir(testCaseClass))
344     for baseclass in testCaseClass.__bases__:
345         testFnNames = testFnNames + \
346                       getTestCaseNames(baseclass, prefix, sortUsing=None)
347     if sortUsing:
348         testFnNames.sort(sortUsing)
349     return testFnNames
350
351
352 def makeSuite(testCaseClass, prefix='test', sortUsing=cmp):
353     """Returns a TestSuite instance built from all of the test functions
354        in the given test case class whose names begin with the given
355        prefix. The cases are sorted by their function names
356        using the supplied comparison function, which defaults to 'cmp'.
357     """
358     cases = map(testCaseClass,
359                 getTestCaseNames(testCaseClass, prefix, sortUsing))
360     return TestSuite(cases)
361
362
363 def createTestInstance(name, module=None):
364     """Finds tests by their name, optionally only within the given module.
365
366     Return the newly-constructed test, ready to run. If the name contains a ':'
367     then the portion of the name after the colon is used to find a specific
368     test case within the test case class named before the colon.
369
370     Examples:
371      findTest('examples.listtests.suite')
372         -- returns result of calling 'suite'
373      findTest('examples.listtests.ListTestCase:checkAppend')
374         -- returns result of calling ListTestCase('checkAppend')
375      findTest('examples.listtests.ListTestCase:check-')
376         -- returns result of calling makeSuite(ListTestCase, prefix="check")
377     """
378           
379     spec = string.split(name, ':')
380     if len(spec) > 2: raise ValueError, "illegal test name: %s" % name
381     if len(spec) == 1:
382         testName = spec[0]
383         caseName = None
384     else:
385         testName, caseName = spec
386     parts = string.split(testName, '.')
387     if module is None:
388         if len(parts) < 2:
389             raise ValueError, "incomplete test name: %s" % name
390         constructor = __import__(string.join(parts[:-1],'.'))
391         parts = parts[1:]
392     else:
393         constructor = module
394     for part in parts:
395         constructor = getattr(constructor, part)
396     if not callable(constructor):
397         raise ValueError, "%s is not a callable object" % constructor
398     if caseName:
399         if caseName[-1] == '-':
400             prefix = caseName[:-1]
401             if not prefix:
402                 raise ValueError, "prefix too short: %s" % name
403             test = makeSuite(constructor, prefix=prefix)
404         else:
405             test = constructor(caseName)
406     else:
407         test = constructor()
408     if not hasattr(test,"countTestCases"):
409         raise TypeError, \
410               "object %s found with spec %s is not a test" % (test, name)
411     return test
412
413
414 ##############################################################################
415 # Text UI
416 ##############################################################################
417
418 class _WritelnDecorator:
419     """Used to decorate file-like objects with a handy 'writeln' method"""
420     def __init__(self,stream):
421         self.stream = stream
422         if _isJPython:
423             import java.lang.System
424             self.linesep = java.lang.System.getProperty("line.separator")
425         else:
426             self.linesep = os.linesep
427
428     def __getattr__(self, attr):
429         return getattr(self.stream,attr)
430
431     def writeln(self, *args):
432         if args: apply(self.write, args)
433         self.write(self.linesep)
434
435  
436 class _JUnitTextTestResult(TestResult):
437     """A test result class that can print formatted text results to a stream.
438
439     Used by JUnitTextTestRunner.
440     """
441     def __init__(self, stream):
442         self.stream = stream
443         TestResult.__init__(self)
444
445     def addError(self, test, error):
446         TestResult.addError(self,test,error)
447         self.stream.write('E')
448         self.stream.flush()
449         if error[0] is KeyboardInterrupt:
450             self.shouldStop = 1
451  
452     def addFailure(self, test, error):
453         TestResult.addFailure(self,test,error)
454         self.stream.write('F')
455         self.stream.flush()
456  
457     def startTest(self, test):
458         TestResult.startTest(self,test)
459         self.stream.write('.')
460         self.stream.flush()
461
462     def printNumberedErrors(self,errFlavour,errors):
463         if not errors: return
464         if len(errors) == 1:
465             self.stream.writeln("There was 1 %s:" % errFlavour)
466         else:
467             self.stream.writeln("There were %i %ss:" %
468                                 (len(errors), errFlavour))
469         i = 1
470         for test,error in errors:
471             errString = string.join(apply(traceback.format_exception,error),"")
472             self.stream.writeln("%i) %s" % (i, test))
473             self.stream.writeln(errString)
474             i = i + 1
475  
476     def printErrors(self):
477         self.printNumberedErrors("error",self.errors)
478
479     def printFailures(self):
480         self.printNumberedErrors("failure",self.failures)
481
482     def printHeader(self):
483         self.stream.writeln()
484         if self.wasSuccessful():
485             self.stream.writeln("OK (%i tests)" % self.testsRun)
486         else:
487             self.stream.writeln("!!!FAILURES!!!")
488             self.stream.writeln("Test Results")
489             self.stream.writeln()
490             self.stream.writeln("Run: %i ; Failures: %i ; Errors: %i" %
491                                 (self.testsRun, len(self.failures),
492                                  len(self.errors)))
493             
494     def printResult(self):
495         self.printHeader()
496         self.printErrors()
497         self.printFailures()
498
499
500 class JUnitTextTestRunner:
501     """A test runner class that displays results in textual form.
502     
503     The display format approximates that of JUnit's 'textui' test runner.
504     This test runner may be removed in a future version of PyUnit.
505     """
506     def __init__(self, stream=sys.stderr):
507         self.stream = _WritelnDecorator(stream)
508
509     def run(self, test):
510         "Run the given test case or test suite."
511         result = _JUnitTextTestResult(self.stream)
512         startTime = time.time()
513         test(result)
514         stopTime = time.time()
515         self.stream.writeln()
516         self.stream.writeln("Time: %.3fs" % float(stopTime - startTime))
517         result.printResult()
518         return result
519
520
521 ##############################################################################
522 # Verbose text UI
523 ##############################################################################
524
525 class _VerboseTextTestResult(TestResult):
526     """A test result class that can print formatted text results to a stream.
527
528     Used by VerboseTextTestRunner.
529     """
530     def __init__(self, stream, descriptions):
531         TestResult.__init__(self)
532         self.stream = stream
533         self.lastFailure = None
534         self.descriptions = descriptions
535         
536     def startTest(self, test):
537         TestResult.startTest(self, test)
538         if self.descriptions:
539             self.stream.write(test.shortDescription() or str(test))
540         else:
541             self.stream.write(str(test))
542         self.stream.write(" ... ")
543
544     def stopTest(self, test):
545         TestResult.stopTest(self, test)
546         if self.lastFailure is not test:
547             self.stream.writeln("ok")
548
549     def addError(self, test, err):
550         TestResult.addError(self, test, err)
551         self._printError("ERROR", test, err)
552         self.lastFailure = test
553         if err[0] is KeyboardInterrupt:
554             self.shouldStop = 1
555
556     def addFailure(self, test, err):
557         TestResult.addFailure(self, test, err)
558         self._printError("FAIL", test, err)
559         self.lastFailure = test
560
561     def _printError(self, flavour, test, err):
562         errLines = []
563         separator1 = "\t" + '=' * 70
564         separator2 = "\t" + '-' * 70
565         if not self.lastFailure is test:
566             self.stream.writeln()
567             self.stream.writeln(separator1)
568         self.stream.writeln("\t%s" % flavour)
569         self.stream.writeln(separator2)
570         for line in apply(traceback.format_exception, err):
571             for l in string.split(line,"\n")[:-1]:
572                 self.stream.writeln("\t%s" % l)
573         self.stream.writeln(separator1)
574
575
576 class VerboseTextTestRunner:
577     """A test runner class that displays results in textual form.
578     
579     It prints out the names of tests as they are run, errors as they
580     occur, and a summary of the results at the end of the test run.
581     """
582     def __init__(self, stream=sys.stderr, descriptions=1):
583         self.stream = _WritelnDecorator(stream)
584         self.descriptions = descriptions
585
586     def run(self, test):
587         "Run the given test case or test suite."
588         result = _VerboseTextTestResult(self.stream, self.descriptions)
589         startTime = time.time()
590         test(result)
591         stopTime = time.time()
592         timeTaken = float(stopTime - startTime)
593         self.stream.writeln("-" * 78)
594         run = result.testsRun
595         self.stream.writeln("Ran %d test%s in %.3fs" %
596                             (run, run > 1 and "s" or "", timeTaken))
597         self.stream.writeln()
598         if not result.wasSuccessful():
599             self.stream.write("FAILED (")
600             failed, errored = map(len, (result.failures, result.errors))
601             if failed:
602                 self.stream.write("failures=%d" % failed)
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 type(module) == type(''):
637             self.module = __import__(module)
638             for part in string.split(module,'.')[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: