[3] | 1 | """Implements nose test program and collector. |
---|
| 2 | """ |
---|
| 3 | from __future__ import generators |
---|
| 4 | |
---|
| 5 | import logging |
---|
| 6 | import os |
---|
| 7 | import sys |
---|
| 8 | import time |
---|
| 9 | import unittest |
---|
| 10 | |
---|
| 11 | from nose.config import Config, all_config_files |
---|
| 12 | from nose.loader import defaultTestLoader |
---|
| 13 | from nose.plugins.manager import PluginManager, DefaultPluginManager, \ |
---|
| 14 | RestrictedPluginManager |
---|
| 15 | from nose.result import TextTestResult |
---|
| 16 | from nose.suite import FinalizingSuiteWrapper |
---|
| 17 | from nose.util import isclass, tolist |
---|
| 18 | |
---|
| 19 | |
---|
| 20 | log = logging.getLogger('nose.core') |
---|
| 21 | compat_24 = sys.version_info >= (2, 4) |
---|
| 22 | |
---|
| 23 | __all__ = ['TestProgram', 'main', 'run', 'run_exit', 'runmodule', 'collector', |
---|
| 24 | 'TextTestRunner'] |
---|
| 25 | |
---|
| 26 | |
---|
| 27 | class TextTestRunner(unittest.TextTestRunner): |
---|
| 28 | """Test runner that uses nose's TextTestResult to enable errorClasses, |
---|
| 29 | as well as providing hooks for plugins to override or replace the test |
---|
| 30 | output stream, results, and the test case itself. |
---|
| 31 | """ |
---|
| 32 | def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, |
---|
| 33 | config=None): |
---|
| 34 | if config is None: |
---|
| 35 | config = Config() |
---|
| 36 | self.config = config |
---|
| 37 | unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity) |
---|
| 38 | |
---|
| 39 | |
---|
| 40 | def _makeResult(self): |
---|
| 41 | return TextTestResult(self.stream, |
---|
| 42 | self.descriptions, |
---|
| 43 | self.verbosity, |
---|
| 44 | self.config) |
---|
| 45 | |
---|
| 46 | def run(self, test): |
---|
| 47 | """Overrides to provide plugin hooks and defer all output to |
---|
| 48 | the test result class. |
---|
| 49 | """ |
---|
| 50 | wrapper = self.config.plugins.prepareTest(test) |
---|
| 51 | if wrapper is not None: |
---|
| 52 | test = wrapper |
---|
| 53 | |
---|
| 54 | # plugins can decorate or capture the output stream |
---|
| 55 | wrapped = self.config.plugins.setOutputStream(self.stream) |
---|
| 56 | if wrapped is not None: |
---|
| 57 | self.stream = wrapped |
---|
| 58 | |
---|
| 59 | result = self._makeResult() |
---|
| 60 | start = time.time() |
---|
| 61 | test(result) |
---|
| 62 | stop = time.time() |
---|
| 63 | result.printErrors() |
---|
| 64 | result.printSummary(start, stop) |
---|
| 65 | self.config.plugins.finalize(result) |
---|
| 66 | return result |
---|
| 67 | |
---|
| 68 | |
---|
| 69 | class TestProgram(unittest.TestProgram): |
---|
| 70 | """Collect and run tests, returning success or failure. |
---|
| 71 | |
---|
| 72 | The arguments to TestProgram() are the same as to |
---|
| 73 | :func:`main()` and :func:`run()`: |
---|
| 74 | |
---|
| 75 | * module: All tests are in this module (default: None) |
---|
| 76 | * defaultTest: Tests to load (default: '.') |
---|
| 77 | * argv: Command line arguments (default: None; sys.argv is read) |
---|
| 78 | * testRunner: Test runner instance (default: None) |
---|
| 79 | * testLoader: Test loader instance (default: None) |
---|
| 80 | * env: Environment; ignored if config is provided (default: None; |
---|
| 81 | os.environ is read) |
---|
| 82 | * config: :class:`nose.config.Config` instance (default: None) |
---|
| 83 | * suite: Suite or list of tests to run (default: None). Passing a |
---|
| 84 | suite or lists of tests will bypass all test discovery and |
---|
| 85 | loading. *ALSO NOTE* that if you pass a unittest.TestSuite |
---|
| 86 | instance as the suite, context fixtures at the class, module and |
---|
| 87 | package level will not be used, and many plugin hooks will not |
---|
| 88 | be called. If you want normal nose behavior, either pass a list |
---|
| 89 | of tests, or a fully-configured :class:`nose.suite.ContextSuite`. |
---|
| 90 | * exit: Exit after running tests and printing report (default: True) |
---|
| 91 | * plugins: List of plugins to use; ignored if config is provided |
---|
| 92 | (default: load plugins with DefaultPluginManager) |
---|
| 93 | * addplugins: List of **extra** plugins to use. Pass a list of plugin |
---|
| 94 | instances in this argument to make custom plugins available while |
---|
| 95 | still using the DefaultPluginManager. |
---|
| 96 | """ |
---|
| 97 | verbosity = 1 |
---|
| 98 | |
---|
| 99 | def __init__(self, module=None, defaultTest='.', argv=None, |
---|
| 100 | testRunner=None, testLoader=None, env=None, config=None, |
---|
| 101 | suite=None, exit=True, plugins=None, addplugins=None): |
---|
| 102 | if env is None: |
---|
| 103 | env = os.environ |
---|
| 104 | if config is None: |
---|
| 105 | config = self.makeConfig(env, plugins) |
---|
| 106 | if addplugins: |
---|
| 107 | config.plugins.addPlugins(addplugins) |
---|
| 108 | self.config = config |
---|
| 109 | self.suite = suite |
---|
| 110 | self.exit = exit |
---|
| 111 | unittest.TestProgram.__init__( |
---|
| 112 | self, module=module, defaultTest=defaultTest, |
---|
| 113 | argv=argv, testRunner=testRunner, testLoader=testLoader) |
---|
| 114 | |
---|
| 115 | def makeConfig(self, env, plugins=None): |
---|
| 116 | """Load a Config, pre-filled with user config files if any are |
---|
| 117 | found. |
---|
| 118 | """ |
---|
| 119 | cfg_files = all_config_files() |
---|
| 120 | if plugins: |
---|
| 121 | manager = PluginManager(plugins=plugins) |
---|
| 122 | else: |
---|
| 123 | manager = DefaultPluginManager() |
---|
| 124 | return Config( |
---|
| 125 | env=env, files=cfg_files, plugins=manager) |
---|
| 126 | |
---|
| 127 | def parseArgs(self, argv): |
---|
| 128 | """Parse argv and env and configure running environment. |
---|
| 129 | """ |
---|
| 130 | self.config.configure(argv, doc=self.usage()) |
---|
| 131 | log.debug("configured %s", self.config) |
---|
| 132 | |
---|
| 133 | # quick outs: version, plugins (optparse would have already |
---|
| 134 | # caught and exited on help) |
---|
| 135 | if self.config.options.version: |
---|
| 136 | from nose import __version__ |
---|
| 137 | sys.stdout = sys.__stdout__ |
---|
| 138 | print "%s version %s" % (os.path.basename(sys.argv[0]), __version__) |
---|
| 139 | sys.exit(0) |
---|
| 140 | |
---|
| 141 | if self.config.options.showPlugins: |
---|
| 142 | self.showPlugins() |
---|
| 143 | sys.exit(0) |
---|
| 144 | |
---|
| 145 | if self.testLoader is None: |
---|
| 146 | self.testLoader = defaultTestLoader(config=self.config) |
---|
| 147 | elif isclass(self.testLoader): |
---|
| 148 | self.testLoader = self.testLoader(config=self.config) |
---|
| 149 | plug_loader = self.config.plugins.prepareTestLoader(self.testLoader) |
---|
| 150 | if plug_loader is not None: |
---|
| 151 | self.testLoader = plug_loader |
---|
| 152 | log.debug("test loader is %s", self.testLoader) |
---|
| 153 | |
---|
| 154 | # FIXME if self.module is a string, add it to self.testNames? not sure |
---|
| 155 | |
---|
| 156 | if self.config.testNames: |
---|
| 157 | self.testNames = self.config.testNames |
---|
| 158 | else: |
---|
| 159 | self.testNames = tolist(self.defaultTest) |
---|
| 160 | log.debug('defaultTest %s', self.defaultTest) |
---|
| 161 | log.debug('Test names are %s', self.testNames) |
---|
| 162 | if self.config.workingDir is not None: |
---|
| 163 | os.chdir(self.config.workingDir) |
---|
| 164 | self.createTests() |
---|
| 165 | |
---|
| 166 | def createTests(self): |
---|
| 167 | """Create the tests to run. If a self.suite |
---|
| 168 | is set, then that suite will be used. Otherwise, tests will be |
---|
| 169 | loaded from the given test names (self.testNames) using the |
---|
| 170 | test loader. |
---|
| 171 | """ |
---|
| 172 | log.debug("createTests called with %s", self.suite) |
---|
| 173 | if self.suite is not None: |
---|
| 174 | # We were given an explicit suite to run. Make sure it's |
---|
| 175 | # loaded and wrapped correctly. |
---|
| 176 | self.test = self.testLoader.suiteClass(self.suite) |
---|
| 177 | else: |
---|
| 178 | self.test = self.testLoader.loadTestsFromNames(self.testNames) |
---|
| 179 | |
---|
| 180 | def runTests(self): |
---|
| 181 | """Run Tests. Returns true on success, false on failure, and sets |
---|
| 182 | self.success to the same value. |
---|
| 183 | """ |
---|
| 184 | log.debug("runTests called") |
---|
| 185 | if self.testRunner is None: |
---|
| 186 | self.testRunner = TextTestRunner(stream=self.config.stream, |
---|
| 187 | verbosity=self.config.verbosity, |
---|
| 188 | config=self.config) |
---|
| 189 | plug_runner = self.config.plugins.prepareTestRunner(self.testRunner) |
---|
| 190 | if plug_runner is not None: |
---|
| 191 | self.testRunner = plug_runner |
---|
| 192 | result = self.testRunner.run(self.test) |
---|
| 193 | self.success = result.wasSuccessful() |
---|
| 194 | if self.exit: |
---|
| 195 | sys.exit(not self.success) |
---|
| 196 | return self.success |
---|
| 197 | |
---|
| 198 | def showPlugins(self): |
---|
| 199 | """Print list of available plugins. |
---|
| 200 | """ |
---|
| 201 | import textwrap |
---|
| 202 | |
---|
| 203 | class DummyParser: |
---|
| 204 | def __init__(self): |
---|
| 205 | self.options = [] |
---|
| 206 | def add_option(self, *arg, **kw): |
---|
| 207 | self.options.append((arg, kw.pop('help', ''))) |
---|
| 208 | |
---|
| 209 | v = self.config.verbosity |
---|
| 210 | self.config.plugins.sort() |
---|
| 211 | for p in self.config.plugins: |
---|
| 212 | print "Plugin %s" % p.name |
---|
| 213 | if v >= 2: |
---|
| 214 | print " score: %s" % p.score |
---|
| 215 | print '\n'.join(textwrap.wrap(p.help().strip(), |
---|
| 216 | initial_indent=' ', |
---|
| 217 | subsequent_indent=' ')) |
---|
| 218 | if v >= 3: |
---|
| 219 | print |
---|
| 220 | print " Options:" |
---|
| 221 | parser = DummyParser() |
---|
| 222 | p.addOptions(parser) |
---|
| 223 | for opts, help in parser.options: |
---|
| 224 | print ' %s' % (', '.join(opts)) |
---|
| 225 | if help: |
---|
| 226 | print '\n'.join( |
---|
| 227 | textwrap.wrap(help.strip(), |
---|
| 228 | initial_indent=' ', |
---|
| 229 | subsequent_indent=' ')) |
---|
| 230 | print |
---|
| 231 | |
---|
| 232 | def usage(cls): |
---|
| 233 | return open(os.path.join( |
---|
| 234 | os.path.dirname(__file__), 'usage.txt'), 'r').read() |
---|
| 235 | usage = classmethod(usage) |
---|
| 236 | |
---|
| 237 | # backwards compatibility |
---|
| 238 | run_exit = main = TestProgram |
---|
| 239 | |
---|
| 240 | |
---|
| 241 | def run(*arg, **kw): |
---|
| 242 | """Collect and run tests, returning success or failure. |
---|
| 243 | |
---|
| 244 | The arguments to `run()` are the same as to `main()`: |
---|
| 245 | |
---|
| 246 | * module: All tests are in this module (default: None) |
---|
| 247 | * defaultTest: Tests to load (default: '.') |
---|
| 248 | * argv: Command line arguments (default: None; sys.argv is read) |
---|
| 249 | * testRunner: Test runner instance (default: None) |
---|
| 250 | * testLoader: Test loader instance (default: None) |
---|
| 251 | * env: Environment; ignored if config is provided (default: None; |
---|
| 252 | os.environ is read) |
---|
| 253 | * config: :class:`nose.config.Config` instance (default: None) |
---|
| 254 | * suite: Suite or list of tests to run (default: None). Passing a |
---|
| 255 | suite or lists of tests will bypass all test discovery and |
---|
| 256 | loading. *ALSO NOTE* that if you pass a unittest.TestSuite |
---|
| 257 | instance as the suite, context fixtures at the class, module and |
---|
| 258 | package level will not be used, and many plugin hooks will not |
---|
| 259 | be called. If you want normal nose behavior, either pass a list |
---|
| 260 | of tests, or a fully-configured :class:`nose.suite.ContextSuite`. |
---|
| 261 | * plugins: List of plugins to use; ignored if config is provided |
---|
| 262 | (default: load plugins with DefaultPluginManager) |
---|
| 263 | * addplugins: List of **extra** plugins to use. Pass a list of plugin |
---|
| 264 | instances in this argument to make custom plugins available while |
---|
| 265 | still using the DefaultPluginManager. |
---|
| 266 | |
---|
| 267 | With the exception that the ``exit`` argument is always set |
---|
| 268 | to False. |
---|
| 269 | """ |
---|
| 270 | kw['exit'] = False |
---|
| 271 | return TestProgram(*arg, **kw).success |
---|
| 272 | |
---|
| 273 | |
---|
| 274 | def runmodule(name='__main__', **kw): |
---|
| 275 | """Collect and run tests in a single module only. Defaults to running |
---|
| 276 | tests in __main__. Additional arguments to TestProgram may be passed |
---|
| 277 | as keyword arguments. |
---|
| 278 | """ |
---|
| 279 | main(defaultTest=name, **kw) |
---|
| 280 | |
---|
| 281 | |
---|
| 282 | def collector(): |
---|
| 283 | """TestSuite replacement entry point. Use anywhere you might use a |
---|
| 284 | unittest.TestSuite. The collector will, by default, load options from |
---|
| 285 | all config files and execute loader.loadTestsFromNames() on the |
---|
| 286 | configured testNames, or '.' if no testNames are configured. |
---|
| 287 | """ |
---|
| 288 | # plugins that implement any of these methods are disabled, since |
---|
| 289 | # we don't control the test runner and won't be able to run them |
---|
| 290 | # finalize() is also not called, but plugins that use it aren't disabled, |
---|
| 291 | # because capture needs it. |
---|
| 292 | setuptools_incompat = ('report', 'prepareTest', |
---|
| 293 | 'prepareTestLoader', 'prepareTestRunner', |
---|
| 294 | 'setOutputStream') |
---|
| 295 | |
---|
| 296 | plugins = RestrictedPluginManager(exclude=setuptools_incompat) |
---|
| 297 | conf = Config(files=all_config_files(), |
---|
| 298 | plugins=plugins) |
---|
| 299 | conf.configure(argv=['collector']) |
---|
| 300 | loader = defaultTestLoader(conf) |
---|
| 301 | |
---|
| 302 | if conf.testNames: |
---|
| 303 | suite = loader.loadTestsFromNames(conf.testNames) |
---|
| 304 | else: |
---|
| 305 | suite = loader.loadTestsFromNames(('.',)) |
---|
| 306 | return FinalizingSuiteWrapper(suite, plugins.finalize) |
---|
| 307 | |
---|
| 308 | |
---|
| 309 | |
---|
| 310 | if __name__ == '__main__': |
---|
| 311 | main() |
---|