clikrb5: Move pure krb wrapper functions from libads to clikrb5.
[Samba/id10ts.git] / lib / testtools / testtools / run.py
blob72011c74cabdb7bdde1adae6a567acaa64108ef2
1 # Copyright (c) 2009 testtools developers. See LICENSE for details.
3 """python -m testtools.run testspec [testspec...]
5 Run some tests with the testtools extended API.
7 For instance, to run the testtools test suite.
8 $ python -m testtools.run testtools.tests.test_suite
9 """
11 import os
12 import unittest
13 import sys
15 from testtools import TextTestResult
16 from testtools.compat import classtypes, istext, unicode_output_stream
17 from testtools.testsuite import iterate_tests
20 defaultTestLoader = unittest.defaultTestLoader
21 defaultTestLoaderCls = unittest.TestLoader
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
35 class TestToolsTestRunner(object):
36 """ A thunk object to support unittest.TestProgram."""
38 def __init__(self, stdout):
39 self.stdout = stdout
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()
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.
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"
76 USAGE_AS_MAIN = """\
77 Usage: %(progName)s [options] [tests]
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
92 All options must come before [tests]. [tests] can be a list of any number of
93 test modules, classes and test methods.
95 Alternative Usage: %(progName)s discover [options]
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.
107 For test discovery all test modules must be importable from the top
108 level directory of the project.
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.
116 USAGE = USAGE_AS_MAIN
118 # defaults for testing
119 failfast = catchbreak = buffer = progName = None
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
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 = open(self.load_list, 'rb')
162 try:
163 lines = source.readlines()
164 finally:
165 source.close()
166 test_ids = set(line.strip().decode('utf-8') 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())
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)
192 def parseArgs(self, argv):
193 if len(argv) > 1 and argv[1].lower() == 'discover':
194 self._do_discovery(argv[2:])
195 return
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])
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)
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.')
277 options, args = parser.parse_args(argv)
278 if len(args) > 3:
279 self.usageExit()
281 for name, value in zip(('start', 'pattern', 'top'), args):
282 setattr(options, name, value)
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
295 if options.verbose:
296 self.verbosity = 2
298 start_dir = options.start
299 pattern = options.pattern
300 top_level_dir = options.top
302 loader = Loader()
303 self.test = loader.discover(start_dir, pattern, top_level_dir)
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 ################
327 def main(argv, stdout):
328 runner = TestToolsTestRunner(stdout)
329 program = TestProgram(argv=argv, testRunner=runner, stdout=stdout)
331 if __name__ == '__main__':
332 main(sys.argv, sys.stdout)