testtools: Merge in new upstream.
[nivanova/samba-autobuild/.git] / lib / testtools / testtools / run.py
1 # Copyright (c) 2009 Jonathan M. Lange. See LICENSE for details.
2
3 """python -m testtools.run testspec [testspec...]
4
5 Run some tests with the testtools extended API.
6
7 For instance, to run the testtools test suite.
8  $ python -m testtools.run testtools.tests.test_suite
9 """
10
11 import os
12 import unittest
13 import sys
14
15 from testtools import TextTestResult
16 from testtools.compat import classtypes, istext, unicode_output_stream
17 from testtools.testsuite import iterate_tests
18
19
20 defaultTestLoader = unittest.defaultTestLoader
21 defaultTestLoaderCls = unittest.TestLoader
22
23 if getattr(defaultTestLoader, 'discover', None) is None:
24     try:
25         import discover
26         defaultTestLoader = discover.DiscoveringTestLoader()
27         defaultTestLoaderCls = discover.DiscoveringTestLoader
28         have_discover = True
29     except ImportError:
30         have_discover = False
31 else:
32     have_discover = True
33
34
35 class TestToolsTestRunner(object):
36     """ A thunk object to support unittest.TestProgram."""
37
38     def __init__(self, stdout):
39         self.stdout = stdout
40
41     def run(self, test):
42         "Run the given test case or test suite."
43         result = TextTestResult(unicode_output_stream(self.stdout))
44         result.startTestRun()
45         try:
46             return test.run(result)
47         finally:
48             result.stopTestRun()
49
50
51 ####################
52 # Taken from python 2.7 and slightly modified for compatibility with
53 # older versions. Delete when 2.7 is the oldest supported version.
54 # Modifications:
55 #  - Use have_discover to raise an error if the user tries to use
56 #    discovery on an old version and doesn't have discover installed.
57 #  - If --catch is given check that installHandler is available, as
58 #    it won't be on old python versions.
59 #  - print calls have been been made single-source python3 compatibile.
60 #  - exception handling likewise.
61 #  - The default help has been changed to USAGE_AS_MAIN and USAGE_FROM_MODULE
62 #    removed.
63 #  - A tweak has been added to detect 'python -m *.run' and use a
64 #    better progName in that case.
65 #  - self.module is more comprehensively set to None when being invoked from
66 #    the commandline - __name__ is used as a sentinel value.
67 #  - --list has been added which can list tests (should be upstreamed).
68 #  - --load-list has been added which can reduce the tests used (should be
69 #    upstreamed).
70 #  - The limitation of using getopt is declared to the user.
71
72 FAILFAST     = "  -f, --failfast   Stop on first failure\n"
73 CATCHBREAK   = "  -c, --catch      Catch control-C and display results\n"
74 BUFFEROUTPUT = "  -b, --buffer     Buffer stdout and stderr during test runs\n"
75
76 USAGE_AS_MAIN = """\
77 Usage: %(progName)s [options] [tests]
78
79 Options:
80   -h, --help       Show this message
81   -v, --verbose    Verbose output
82   -q, --quiet      Minimal output
83   -l, --list       List tests rather than executing them.
84   --load-list      Specifies a file containing test ids, only tests matching
85                    those ids are executed.
86 %(failfast)s%(catchbreak)s%(buffer)s
87 Examples:
88   %(progName)s test_module               - run tests from test_module
89   %(progName)s module.TestClass          - run tests from module.TestClass
90   %(progName)s module.Class.test_method  - run specified test method
91
92 All options must come before [tests].  [tests] can be a list of any number of
93 test modules, classes and test methods.
94
95 Alternative Usage: %(progName)s discover [options]
96
97 Options:
98   -v, --verbose    Verbose output
99 %(failfast)s%(catchbreak)s%(buffer)s  -s directory     Directory to start discovery ('.' default)
100   -p pattern       Pattern to match test files ('test*.py' default)
101   -t directory     Top level directory of project (default to
102                    start directory)
103   -l, --list       List tests rather than executing them.
104   --load-list      Specifies a file containing test ids, only tests matching
105                    those ids are executed.
106
107 For test discovery all test modules must be importable from the top
108 level directory of the project.
109 """
110
111
112 class TestProgram(object):
113     """A command-line program that runs a set of tests; this is primarily
114        for making test modules conveniently executable.
115     """
116     USAGE = USAGE_AS_MAIN
117
118     # defaults for testing
119     failfast = catchbreak = buffer = progName = None
120
121     def __init__(self, module=__name__, defaultTest=None, argv=None,
122                     testRunner=None, testLoader=defaultTestLoader,
123                     exit=True, verbosity=1, failfast=None, catchbreak=None,
124                     buffer=None, stdout=None):
125         if module == __name__:
126             self.module = None
127         elif istext(module):
128             self.module = __import__(module)
129             for part in module.split('.')[1:]:
130                 self.module = getattr(self.module, part)
131         else:
132             self.module = module
133         if argv is None:
134             argv = sys.argv
135         if stdout is None:
136             stdout = sys.stdout
137
138         self.exit = exit
139         self.failfast = failfast
140         self.catchbreak = catchbreak
141         self.verbosity = verbosity
142         self.buffer = buffer
143         self.defaultTest = defaultTest
144         self.listtests = False
145         self.load_list = None
146         self.testRunner = testRunner
147         self.testLoader = testLoader
148         progName = argv[0]
149         if progName.endswith('%srun.py' % os.path.sep):
150             elements = progName.split(os.path.sep)
151             progName = '%s.run' % elements[-2]
152         else:
153             progName = os.path.basename(argv[0])
154         self.progName = progName
155         self.parseArgs(argv)
156         if self.load_list:
157             # TODO: preserve existing suites (like testresources does in
158             # OptimisingTestSuite.add, but with a standard protocol).
159             # This is needed because the load_tests hook allows arbitrary
160             # suites, even if that is rarely used.
161             source = file(self.load_list, 'rb')
162             try:
163                 lines = source.readlines()
164             finally:
165                 source.close()
166             test_ids = set(line.strip() for line in lines)
167             filtered = unittest.TestSuite()
168             for test in iterate_tests(self.test):
169                 if test.id() in test_ids:
170                     filtered.addTest(test)
171             self.test = filtered
172         if not self.listtests:
173             self.runTests()
174         else:
175             for test in iterate_tests(self.test):
176                 stdout.write('%s\n' % test.id())
177
178     def usageExit(self, msg=None):
179         if msg:
180             print(msg)
181         usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
182                  'buffer': ''}
183         if self.failfast != False:
184             usage['failfast'] = FAILFAST
185         if self.catchbreak != False:
186             usage['catchbreak'] = CATCHBREAK
187         if self.buffer != False:
188             usage['buffer'] = BUFFEROUTPUT
189         print(self.USAGE % usage)
190         sys.exit(2)
191
192     def parseArgs(self, argv):
193         if len(argv) > 1 and argv[1].lower() == 'discover':
194             self._do_discovery(argv[2:])
195             return
196
197         import getopt
198         long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer',
199             'list', 'load-list=']
200         try:
201             options, args = getopt.getopt(argv[1:], 'hHvqfcbl', long_opts)
202             for opt, value in options:
203                 if opt in ('-h','-H','--help'):
204                     self.usageExit()
205                 if opt in ('-q','--quiet'):
206                     self.verbosity = 0
207                 if opt in ('-v','--verbose'):
208                     self.verbosity = 2
209                 if opt in ('-f','--failfast'):
210                     if self.failfast is None:
211                         self.failfast = True
212                     # Should this raise an exception if -f is not valid?
213                 if opt in ('-c','--catch'):
214                     if self.catchbreak is None:
215                         self.catchbreak = True
216                     # Should this raise an exception if -c is not valid?
217                 if opt in ('-b','--buffer'):
218                     if self.buffer is None:
219                         self.buffer = True
220                     # Should this raise an exception if -b is not valid?
221                 if opt in ('-l', '--list'):
222                     self.listtests = True
223                 if opt == '--load-list':
224                     self.load_list = value
225             if len(args) == 0 and self.defaultTest is None:
226                 # createTests will load tests from self.module
227                 self.testNames = None
228             elif len(args) > 0:
229                 self.testNames = args
230             else:
231                 self.testNames = (self.defaultTest,)
232             self.createTests()
233         except getopt.error:
234             self.usageExit(sys.exc_info()[1])
235
236     def createTests(self):
237         if self.testNames is None:
238             self.test = self.testLoader.loadTestsFromModule(self.module)
239         else:
240             self.test = self.testLoader.loadTestsFromNames(self.testNames,
241                                                            self.module)
242
243     def _do_discovery(self, argv, Loader=defaultTestLoaderCls):
244         # handle command line args for test discovery
245         if not have_discover:
246             raise AssertionError("Unable to use discovery, must use python 2.7 "
247                     "or greater, or install the discover package.")
248         self.progName = '%s discover' % self.progName
249         import optparse
250         parser = optparse.OptionParser()
251         parser.prog = self.progName
252         parser.add_option('-v', '--verbose', dest='verbose', default=False,
253                           help='Verbose output', action='store_true')
254         if self.failfast != False:
255             parser.add_option('-f', '--failfast', dest='failfast', default=False,
256                               help='Stop on first fail or error',
257                               action='store_true')
258         if self.catchbreak != False:
259             parser.add_option('-c', '--catch', dest='catchbreak', default=False,
260                               help='Catch ctrl-C and display results so far',
261                               action='store_true')
262         if self.buffer != False:
263             parser.add_option('-b', '--buffer', dest='buffer', default=False,
264                               help='Buffer stdout and stderr during tests',
265                               action='store_true')
266         parser.add_option('-s', '--start-directory', dest='start', default='.',
267                           help="Directory to start discovery ('.' default)")
268         parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
269                           help="Pattern to match tests ('test*.py' default)")
270         parser.add_option('-t', '--top-level-directory', dest='top', default=None,
271                           help='Top level directory of project (defaults to start directory)')
272         parser.add_option('-l', '--list', dest='listtests', default=False,
273                           help='List tests rather than running them.')
274         parser.add_option('--load-list', dest='load_list', default=None,
275                           help='Specify a filename containing the test ids to use.')
276
277         options, args = parser.parse_args(argv)
278         if len(args) > 3:
279             self.usageExit()
280
281         for name, value in zip(('start', 'pattern', 'top'), args):
282             setattr(options, name, value)
283
284         # only set options from the parsing here
285         # if they weren't set explicitly in the constructor
286         if self.failfast is None:
287             self.failfast = options.failfast
288         if self.catchbreak is None:
289             self.catchbreak = options.catchbreak
290         if self.buffer is None:
291             self.buffer = options.buffer
292         self.listtests = options.listtests
293         self.load_list = options.load_list
294
295         if options.verbose:
296             self.verbosity = 2
297
298         start_dir = options.start
299         pattern = options.pattern
300         top_level_dir = options.top
301
302         loader = Loader()
303         self.test = loader.discover(start_dir, pattern, top_level_dir)
304
305     def runTests(self):
306         if (self.catchbreak
307             and getattr(unittest, 'installHandler', None) is not None):
308             unittest.installHandler()
309         if self.testRunner is None:
310             self.testRunner = runner.TextTestRunner
311         if isinstance(self.testRunner, classtypes()):
312             try:
313                 testRunner = self.testRunner(verbosity=self.verbosity,
314                                              failfast=self.failfast,
315                                              buffer=self.buffer)
316             except TypeError:
317                 # didn't accept the verbosity, buffer or failfast arguments
318                 testRunner = self.testRunner()
319         else:
320             # it is assumed to be a TestRunner instance
321             testRunner = self.testRunner
322         self.result = testRunner.run(self.test)
323         if self.exit:
324             sys.exit(not self.result.wasSuccessful())
325 ################
326
327 def main(argv, stdout):
328     runner = TestToolsTestRunner(stdout)
329     program = TestProgram(argv=argv, testRunner=runner, stdout=stdout)
330
331 if __name__ == '__main__':
332     main(sys.argv, sys.stdout)