Merge lp:~james-w/testtools/discover into lp:~testtools-committers/testtools/trunk

Proposed by James Westby
Status: Merged
Approved by: Robert Collins
Approved revision: 67
Merged at revision: 67
Proposed branch: lp:~james-w/testtools/discover
Merge into: lp:~testtools-committers/testtools/trunk
Diff against target: 326 lines (+277/-9)
3 files modified
LICENSE (+19/-0)
README (+4/-0)
testtools/run.py (+254/-9)
To merge this branch: bzr merge lp:~james-w/testtools/discover
Reviewer Review Type Date Requested Status
testtools developers Pending
Review via email: mp+26994@code.launchpad.net

Description of the change

Support test discovery in testtools.run.

This is done by backporting TestProgram from 2.7 so that
we can use one interface to run the tests.

I had to make various tweaks where it assumed features that
won't be in older versions.

I dropped the parsing in the __name__ == '__main__' block, as
by my reading it swallows all options.

Thanks,

James

To post a comment you must log in.
lp:~james-w/testtools/discover updated
68. By James Westby

Move has_discover = True until we know we have set up everything we need.

69. By James Westby

Comment on the changes made to TestProgram.

Revision history for this message
Robert Collins (lifeless) wrote :

Approved-with-tweaks:
small bugfix in the conditional import (put the ok guard at the end)
document the changes made to the python2.7 code
list the (C) PSF code in README and COPYING

Revision history for this message
James Westby (james-w) wrote :

Tweaks made as requested.

Thanks,

James

lp:~james-w/testtools/discover updated
70. By James Westby

Add (C)/License information.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'LICENSE'
--- LICENSE 2009-12-01 10:04:35 +0000
+++ LICENSE 2010-06-10 22:16:25 +0000
@@ -17,3 +17,22 @@
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19SOFTWARE.19SOFTWARE.
20
21Some code in testtools/run.py taken from Python's unittest module:
22Copyright (c) 1999-2003 Steve Purcell
23Copyright (c) 2003-2010 Python Software Foundation
24
25This module is free software, and you may redistribute it and/or modify
26it under the same terms as Python itself, so long as this copyright message
27and disclaimer are retained in their original form.
28
29IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
30SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
31THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
32DAMAGE.
33
34THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
35LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
36PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
37AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
38SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
2039
=== modified file 'README'
--- README 2009-12-01 10:38:07 +0000
+++ README 2010-06-10 22:16:25 +0000
@@ -14,6 +14,10 @@
14This project is distributed under the MIT license and copyright is owned by14This project is distributed under the MIT license and copyright is owned by
15Jonathan M. Lange. See LICENSE for details.15Jonathan M. Lange. See LICENSE for details.
1616
17Some code in testtools/run.py is taken from Python's unittest module, and
18is copyright Steve Purcell and the Python Software Foundation, it is
19distributed under the same license as Python, see LICENSE for details.
20
1721
18Dependencies22Dependencies
19------------23------------
2024
=== modified file 'testtools/run.py'
--- testtools/run.py 2010-01-01 00:24:51 +0000
+++ testtools/run.py 2010-06-10 22:16:25 +0000
@@ -8,12 +8,29 @@
8 $ python -m testtools.run testtools.tests.test_suite8 $ python -m testtools.run testtools.tests.test_suite
9"""9"""
1010
11import os
12import unittest
13import types
11import sys14import sys
1215
13from testtools.tests import test_suite
14from testtools import TextTestResult16from testtools import TextTestResult
1517
1618
19defaultTestLoader = unittest.defaultTestLoader
20defaultTestLoaderCls = unittest.TestLoader
21
22if getattr(defaultTestLoader, 'discover', None) is None:
23 try:
24 import discover
25 defaultTestLoader = discover.DiscoveringTestLoader()
26 defaultTestLoaderCls = discover.DiscoveringTestLoader
27 have_discover = True
28 except ImportError:
29 have_discover = False
30else:
31 have_discover = True
32
33
17class TestToolsTestRunner(object):34class TestToolsTestRunner(object):
18 """ A thunk object to support unittest.TestProgram."""35 """ A thunk object to support unittest.TestProgram."""
1936
@@ -27,13 +44,241 @@
27 result.stopTestRun()44 result.stopTestRun()
2845
2946
47####################
48# Taken from python 2.7 and slightly modified for compatibility with
49# older versions. Delete when 2.7 is the oldest supported version.
50# Modifications:
51# - Use have_discover to raise an error if the user tries to use
52# discovery on an old version and doesn't have discover installed.
53# - If --catch is given check that installHandler is available, as
54# it won't be on old python versions.
55
56FAILFAST = " -f, --failfast Stop on first failure\n"
57CATCHBREAK = " -c, --catch Catch control-C and display results\n"
58BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n"
59
60USAGE_AS_MAIN = """\
61Usage: %(progName)s [options] [tests]
62
63Options:
64 -h, --help Show this message
65 -v, --verbose Verbose output
66 -q, --quiet Minimal output
67%(failfast)s%(catchbreak)s%(buffer)s
68Examples:
69 %(progName)s test_module - run tests from test_module
70 %(progName)s module.TestClass - run tests from module.TestClass
71 %(progName)s module.Class.test_method - run specified test method
72
73[tests] can be a list of any number of test modules, classes and test
74methods.
75
76Alternative Usage: %(progName)s discover [options]
77
78Options:
79 -v, --verbose Verbose output
80%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default)
81 -p pattern Pattern to match test files ('test*.py' default)
82 -t directory Top level directory of project (default to
83 start directory)
84
85For test discovery all test modules must be importable from the top
86level directory of the project.
87"""
88
89USAGE_FROM_MODULE = """\
90Usage: %(progName)s [options] [test] [...]
91
92Options:
93 -h, --help Show this message
94 -v, --verbose Verbose output
95 -q, --quiet Minimal output
96%(failfast)s%(catchbreak)s%(buffer)s
97Examples:
98 %(progName)s - run default set of tests
99 %(progName)s MyTestSuite - run suite 'MyTestSuite'
100 %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
101 %(progName)s MyTestCase - run all 'test*' test methods
102 in MyTestCase
103"""
104
105
106class TestProgram(object):
107 """A command-line program that runs a set of tests; this is primarily
108 for making test modules conveniently executable.
109 """
110 USAGE = USAGE_FROM_MODULE
111
112 # defaults for testing
113 failfast = catchbreak = buffer = progName = None
114
115 def __init__(self, module='__main__', defaultTest=None, argv=None,
116 testRunner=None, testLoader=defaultTestLoader,
117 exit=True, verbosity=1, failfast=None, catchbreak=None,
118 buffer=None):
119 if isinstance(module, basestring):
120 self.module = __import__(module)
121 for part in module.split('.')[1:]:
122 self.module = getattr(self.module, part)
123 else:
124 self.module = module
125 if argv is None:
126 argv = sys.argv
127
128 self.exit = exit
129 self.failfast = failfast
130 self.catchbreak = catchbreak
131 self.verbosity = verbosity
132 self.buffer = buffer
133 self.defaultTest = defaultTest
134 self.testRunner = testRunner
135 self.testLoader = testLoader
136 self.progName = os.path.basename(argv[0])
137 self.parseArgs(argv)
138 self.runTests()
139
140 def usageExit(self, msg=None):
141 if msg:
142 print msg
143 usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
144 'buffer': ''}
145 if self.failfast != False:
146 usage['failfast'] = FAILFAST
147 if self.catchbreak != False:
148 usage['catchbreak'] = CATCHBREAK
149 if self.buffer != False:
150 usage['buffer'] = BUFFEROUTPUT
151 print self.USAGE % usage
152 sys.exit(2)
153
154 def parseArgs(self, argv):
155 if len(argv) > 1 and argv[1].lower() == 'discover':
156 self._do_discovery(argv[2:])
157 return
158
159 import getopt
160 long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer']
161 try:
162 options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts)
163 for opt, value in options:
164 if opt in ('-h','-H','--help'):
165 self.usageExit()
166 if opt in ('-q','--quiet'):
167 self.verbosity = 0
168 if opt in ('-v','--verbose'):
169 self.verbosity = 2
170 if opt in ('-f','--failfast'):
171 if self.failfast is None:
172 self.failfast = True
173 # Should this raise an exception if -f is not valid?
174 if opt in ('-c','--catch'):
175 if self.catchbreak is None:
176 self.catchbreak = True
177 # Should this raise an exception if -c is not valid?
178 if opt in ('-b','--buffer'):
179 if self.buffer is None:
180 self.buffer = True
181 # Should this raise an exception if -b is not valid?
182 if len(args) == 0 and self.defaultTest is None:
183 # createTests will load tests from self.module
184 self.testNames = None
185 elif len(args) > 0:
186 self.testNames = args
187 if __name__ == '__main__':
188 # to support python -m unittest ...
189 self.module = None
190 else:
191 self.testNames = (self.defaultTest,)
192 self.createTests()
193 except getopt.error, msg:
194 self.usageExit(msg)
195
196 def createTests(self):
197 if self.testNames is None:
198 self.test = self.testLoader.loadTestsFromModule(self.module)
199 else:
200 self.test = self.testLoader.loadTestsFromNames(self.testNames,
201 self.module)
202
203 def _do_discovery(self, argv, Loader=defaultTestLoaderCls):
204 # handle command line args for test discovery
205 if not have_discover:
206 raise AssertionError("Unable to use discovery, must use python 2.7 "
207 "or greater, or install the discover package.")
208 self.progName = '%s discover' % self.progName
209 import optparse
210 parser = optparse.OptionParser()
211 parser.prog = self.progName
212 parser.add_option('-v', '--verbose', dest='verbose', default=False,
213 help='Verbose output', action='store_true')
214 if self.failfast != False:
215 parser.add_option('-f', '--failfast', dest='failfast', default=False,
216 help='Stop on first fail or error',
217 action='store_true')
218 if self.catchbreak != False:
219 parser.add_option('-c', '--catch', dest='catchbreak', default=False,
220 help='Catch ctrl-C and display results so far',
221 action='store_true')
222 if self.buffer != False:
223 parser.add_option('-b', '--buffer', dest='buffer', default=False,
224 help='Buffer stdout and stderr during tests',
225 action='store_true')
226 parser.add_option('-s', '--start-directory', dest='start', default='.',
227 help="Directory to start discovery ('.' default)")
228 parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
229 help="Pattern to match tests ('test*.py' default)")
230 parser.add_option('-t', '--top-level-directory', dest='top', default=None,
231 help='Top level directory of project (defaults to start directory)')
232
233 options, args = parser.parse_args(argv)
234 if len(args) > 3:
235 self.usageExit()
236
237 for name, value in zip(('start', 'pattern', 'top'), args):
238 setattr(options, name, value)
239
240 # only set options from the parsing here
241 # if they weren't set explicitly in the constructor
242 if self.failfast is None:
243 self.failfast = options.failfast
244 if self.catchbreak is None:
245 self.catchbreak = options.catchbreak
246 if self.buffer is None:
247 self.buffer = options.buffer
248
249 if options.verbose:
250 self.verbosity = 2
251
252 start_dir = options.start
253 pattern = options.pattern
254 top_level_dir = options.top
255
256 loader = Loader()
257 self.test = loader.discover(start_dir, pattern, top_level_dir)
258
259 def runTests(self):
260 if (self.catchbreak
261 and getattr(unittest, 'installHandler', None) is not None):
262 unittest.installHandler()
263 if self.testRunner is None:
264 self.testRunner = runner.TextTestRunner
265 if isinstance(self.testRunner, (type, types.ClassType)):
266 try:
267 testRunner = self.testRunner(verbosity=self.verbosity,
268 failfast=self.failfast,
269 buffer=self.buffer)
270 except TypeError:
271 # didn't accept the verbosity, buffer or failfast arguments
272 testRunner = self.testRunner()
273 else:
274 # it is assumed to be a TestRunner instance
275 testRunner = self.testRunner
276 self.result = testRunner.run(self.test)
277 if self.exit:
278 sys.exit(not self.result.wasSuccessful())
279################
280
281
30if __name__ == '__main__':282if __name__ == '__main__':
31 import optparse
32 from unittest import TestProgram
33 parser = optparse.OptionParser(__doc__)
34 args = parser.parse_args()[1]
35 if not args:
36 parser.error("No testspecs given.")
37 runner = TestToolsTestRunner()283 runner = TestToolsTestRunner()
38 program = TestProgram(module=None, argv=[sys.argv[0]] + args,284 program = TestProgram(argv=sys.argv, testRunner=runner)
39 testRunner=runner)

Subscribers

People subscribed via source and target branches