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