1823b8294SYaroslav Brustinov"""
2823b8294SYaroslav BrustinovTesting Plugins
3823b8294SYaroslav Brustinov===============
4823b8294SYaroslav Brustinov
5823b8294SYaroslav BrustinovThe plugin interface is well-tested enough to safely unit test your
6823b8294SYaroslav Brustinovuse of its hooks with some level of confidence. However, there is also
7823b8294SYaroslav Brustinova mixin for unittest.TestCase called PluginTester that's designed to
8823b8294SYaroslav Brustinovtest plugins in their native runtime environment.
9823b8294SYaroslav Brustinov
10823b8294SYaroslav BrustinovHere's a simple example with a do-nothing plugin and a composed suite.
11823b8294SYaroslav Brustinov
12823b8294SYaroslav Brustinov    >>> import unittest
13823b8294SYaroslav Brustinov    >>> from nose.plugins import Plugin, PluginTester
14823b8294SYaroslav Brustinov    >>> class FooPlugin(Plugin):
15823b8294SYaroslav Brustinov    ...     pass
16823b8294SYaroslav Brustinov    >>> class TestPluginFoo(PluginTester, unittest.TestCase):
17823b8294SYaroslav Brustinov    ...     activate = '--with-foo'
18823b8294SYaroslav Brustinov    ...     plugins = [FooPlugin()]
19823b8294SYaroslav Brustinov    ...     def test_foo(self):
20823b8294SYaroslav Brustinov    ...         for line in self.output:
21823b8294SYaroslav Brustinov    ...             # i.e. check for patterns
22823b8294SYaroslav Brustinov    ...             pass
23823b8294SYaroslav Brustinov    ...
24823b8294SYaroslav Brustinov    ...         # or check for a line containing ...
25823b8294SYaroslav Brustinov    ...         assert "ValueError" in self.output
26823b8294SYaroslav Brustinov    ...     def makeSuite(self):
27823b8294SYaroslav Brustinov    ...         class TC(unittest.TestCase):
28823b8294SYaroslav Brustinov    ...             def runTest(self):
29823b8294SYaroslav Brustinov    ...                 raise ValueError("I hate foo")
30823b8294SYaroslav Brustinov    ...         return [TC('runTest')]
31823b8294SYaroslav Brustinov    ...
32823b8294SYaroslav Brustinov    >>> res = unittest.TestResult()
33823b8294SYaroslav Brustinov    >>> case = TestPluginFoo('test_foo')
34823b8294SYaroslav Brustinov    >>> _ = case(res)
35823b8294SYaroslav Brustinov    >>> res.errors
36823b8294SYaroslav Brustinov    []
37823b8294SYaroslav Brustinov    >>> res.failures
38823b8294SYaroslav Brustinov    []
39823b8294SYaroslav Brustinov    >>> res.wasSuccessful()
40823b8294SYaroslav Brustinov    True
41823b8294SYaroslav Brustinov    >>> res.testsRun
42823b8294SYaroslav Brustinov    1
43823b8294SYaroslav Brustinov
44823b8294SYaroslav BrustinovAnd here is a more complex example of testing a plugin that has extra
45823b8294SYaroslav Brustinovarguments and reads environment variables.
46823b8294SYaroslav Brustinov
47823b8294SYaroslav Brustinov    >>> import unittest, os
48823b8294SYaroslav Brustinov    >>> from nose.plugins import Plugin, PluginTester
49823b8294SYaroslav Brustinov    >>> class FancyOutputter(Plugin):
50823b8294SYaroslav Brustinov    ...     name = "fancy"
51823b8294SYaroslav Brustinov    ...     def configure(self, options, conf):
52823b8294SYaroslav Brustinov    ...         Plugin.configure(self, options, conf)
53823b8294SYaroslav Brustinov    ...         if not self.enabled:
54823b8294SYaroslav Brustinov    ...             return
55823b8294SYaroslav Brustinov    ...         self.fanciness = 1
56823b8294SYaroslav Brustinov    ...         if options.more_fancy:
57823b8294SYaroslav Brustinov    ...             self.fanciness = 2
58823b8294SYaroslav Brustinov    ...         if 'EVEN_FANCIER' in self.env:
59823b8294SYaroslav Brustinov    ...             self.fanciness = 3
60823b8294SYaroslav Brustinov    ...
61823b8294SYaroslav Brustinov    ...     def options(self, parser, env=os.environ):
62823b8294SYaroslav Brustinov    ...         self.env = env
63823b8294SYaroslav Brustinov    ...         parser.add_option('--more-fancy', action='store_true')
64823b8294SYaroslav Brustinov    ...         Plugin.options(self, parser, env=env)
65823b8294SYaroslav Brustinov    ...
66823b8294SYaroslav Brustinov    ...     def report(self, stream):
67823b8294SYaroslav Brustinov    ...         stream.write("FANCY " * self.fanciness)
68823b8294SYaroslav Brustinov    ...
69823b8294SYaroslav Brustinov    >>> class TestFancyOutputter(PluginTester, unittest.TestCase):
70823b8294SYaroslav Brustinov    ...     activate = '--with-fancy' # enables the plugin
71823b8294SYaroslav Brustinov    ...     plugins = [FancyOutputter()]
72823b8294SYaroslav Brustinov    ...     args = ['--more-fancy']
73823b8294SYaroslav Brustinov    ...     env = {'EVEN_FANCIER': '1'}
74823b8294SYaroslav Brustinov    ...
75823b8294SYaroslav Brustinov    ...     def test_fancy_output(self):
76823b8294SYaroslav Brustinov    ...         assert "FANCY FANCY FANCY" in self.output, (
77823b8294SYaroslav Brustinov    ...                                         "got: %s" % self.output)
78823b8294SYaroslav Brustinov    ...     def makeSuite(self):
79823b8294SYaroslav Brustinov    ...         class TC(unittest.TestCase):
80823b8294SYaroslav Brustinov    ...             def runTest(self):
81823b8294SYaroslav Brustinov    ...                 raise ValueError("I hate fancy stuff")
82823b8294SYaroslav Brustinov    ...         return [TC('runTest')]
83823b8294SYaroslav Brustinov    ...
84823b8294SYaroslav Brustinov    >>> res = unittest.TestResult()
85823b8294SYaroslav Brustinov    >>> case = TestFancyOutputter('test_fancy_output')
86823b8294SYaroslav Brustinov    >>> _ = case(res)
87823b8294SYaroslav Brustinov    >>> res.errors
88823b8294SYaroslav Brustinov    []
89823b8294SYaroslav Brustinov    >>> res.failures
90823b8294SYaroslav Brustinov    []
91823b8294SYaroslav Brustinov    >>> res.wasSuccessful()
92823b8294SYaroslav Brustinov    True
93823b8294SYaroslav Brustinov    >>> res.testsRun
94823b8294SYaroslav Brustinov    1
95823b8294SYaroslav Brustinov
96823b8294SYaroslav Brustinov"""
97823b8294SYaroslav Brustinov
98823b8294SYaroslav Brustinovimport re
99823b8294SYaroslav Brustinovimport sys
100823b8294SYaroslav Brustinovfrom warnings import warn
101823b8294SYaroslav Brustinov
102823b8294SYaroslav Brustinovtry:
103823b8294SYaroslav Brustinov    from cStringIO import StringIO
104823b8294SYaroslav Brustinovexcept ImportError:
105823b8294SYaroslav Brustinov    from StringIO import StringIO
106823b8294SYaroslav Brustinov
107823b8294SYaroslav Brustinov__all__ = ['PluginTester', 'run']
108823b8294SYaroslav Brustinov
109823b8294SYaroslav Brustinovfrom os import getpid
110823b8294SYaroslav Brustinovclass MultiProcessFile(object):
111823b8294SYaroslav Brustinov    """
112823b8294SYaroslav Brustinov    helper for testing multiprocessing
113823b8294SYaroslav Brustinov
114823b8294SYaroslav Brustinov    multiprocessing poses a problem for doctests, since the strategy
115823b8294SYaroslav Brustinov    of replacing sys.stdout/stderr with file-like objects then
116823b8294SYaroslav Brustinov    inspecting the results won't work: the child processes will
117823b8294SYaroslav Brustinov    write to the objects, but the data will not be reflected
118823b8294SYaroslav Brustinov    in the parent doctest-ing process.
119823b8294SYaroslav Brustinov
120823b8294SYaroslav Brustinov    The solution is to create file-like objects which will interact with
121823b8294SYaroslav Brustinov    multiprocessing in a more desirable way.
122823b8294SYaroslav Brustinov
123823b8294SYaroslav Brustinov    All processes can write to this object, but only the creator can read.
124823b8294SYaroslav Brustinov    This allows the testing system to see a unified picture of I/O.
125823b8294SYaroslav Brustinov    """
126823b8294SYaroslav Brustinov    def __init__(self):
127823b8294SYaroslav Brustinov        # per advice at:
128823b8294SYaroslav Brustinov        #    http://docs.python.org/library/multiprocessing.html#all-platforms
129823b8294SYaroslav Brustinov        self.__master = getpid()
130823b8294SYaroslav Brustinov        self.__queue = Manager().Queue()
131823b8294SYaroslav Brustinov        self.__buffer = StringIO()
132823b8294SYaroslav Brustinov        self.softspace = 0
133823b8294SYaroslav Brustinov
134823b8294SYaroslav Brustinov    def buffer(self):
135823b8294SYaroslav Brustinov        if getpid() != self.__master:
136823b8294SYaroslav Brustinov            return
137823b8294SYaroslav Brustinov
138823b8294SYaroslav Brustinov        from Queue import Empty
139823b8294SYaroslav Brustinov        from collections import defaultdict
140823b8294SYaroslav Brustinov        cache = defaultdict(str)
141823b8294SYaroslav Brustinov        while True:
142823b8294SYaroslav Brustinov            try:
143823b8294SYaroslav Brustinov                pid, data = self.__queue.get_nowait()
144823b8294SYaroslav Brustinov            except Empty:
145823b8294SYaroslav Brustinov                break
146823b8294SYaroslav Brustinov            if pid == ():
147823b8294SYaroslav Brustinov                #show parent output after children
148823b8294SYaroslav Brustinov                #this is what users see, usually
149823b8294SYaroslav Brustinov                pid = ( 1e100, ) # googol!
150823b8294SYaroslav Brustinov            cache[pid] += data
151823b8294SYaroslav Brustinov        for pid in sorted(cache):
152823b8294SYaroslav Brustinov            #self.__buffer.write( '%s wrote: %r\n' % (pid, cache[pid]) ) #DEBUG
153823b8294SYaroslav Brustinov            self.__buffer.write( cache[pid] )
154823b8294SYaroslav Brustinov    def write(self, data):
155823b8294SYaroslav Brustinov        # note that these pids are in the form of current_process()._identity
156823b8294SYaroslav Brustinov        # rather than OS pids
157823b8294SYaroslav Brustinov        from multiprocessing import current_process
158823b8294SYaroslav Brustinov        pid = current_process()._identity
159823b8294SYaroslav Brustinov        self.__queue.put((pid, data))
160823b8294SYaroslav Brustinov    def __iter__(self):
161823b8294SYaroslav Brustinov        "getattr doesn't work for iter()"
162823b8294SYaroslav Brustinov        self.buffer()
163823b8294SYaroslav Brustinov        return self.__buffer
164823b8294SYaroslav Brustinov    def seek(self, offset, whence=0):
165823b8294SYaroslav Brustinov        self.buffer()
166823b8294SYaroslav Brustinov        return self.__buffer.seek(offset, whence)
167823b8294SYaroslav Brustinov    def getvalue(self):
168823b8294SYaroslav Brustinov        self.buffer()
169823b8294SYaroslav Brustinov        return self.__buffer.getvalue()
170823b8294SYaroslav Brustinov    def __getattr__(self, attr):
171823b8294SYaroslav Brustinov        return getattr(self.__buffer, attr)
172823b8294SYaroslav Brustinov
173823b8294SYaroslav Brustinovtry:
174823b8294SYaroslav Brustinov    from multiprocessing import Manager
175823b8294SYaroslav Brustinov    Buffer = MultiProcessFile
176823b8294SYaroslav Brustinovexcept ImportError:
177823b8294SYaroslav Brustinov    Buffer = StringIO
178823b8294SYaroslav Brustinov
179823b8294SYaroslav Brustinovclass PluginTester(object):
180823b8294SYaroslav Brustinov    """A mixin for testing nose plugins in their runtime environment.
181823b8294SYaroslav Brustinov
182823b8294SYaroslav Brustinov    Subclass this and mix in unittest.TestCase to run integration/functional
183823b8294SYaroslav Brustinov    tests on your plugin.  When setUp() is called, the stub test suite is
184823b8294SYaroslav Brustinov    executed with your plugin so that during an actual test you can inspect the
185823b8294SYaroslav Brustinov    artifacts of how your plugin interacted with the stub test suite.
186823b8294SYaroslav Brustinov
187823b8294SYaroslav Brustinov    - activate
188823b8294SYaroslav Brustinov
189823b8294SYaroslav Brustinov      - the argument to send nosetests to activate the plugin
190823b8294SYaroslav Brustinov
191823b8294SYaroslav Brustinov    - suitepath
192823b8294SYaroslav Brustinov
193823b8294SYaroslav Brustinov      - if set, this is the path of the suite to test. Otherwise, you
194823b8294SYaroslav Brustinov        will need to use the hook, makeSuite()
195823b8294SYaroslav Brustinov
196823b8294SYaroslav Brustinov    - plugins
197823b8294SYaroslav Brustinov
198823b8294SYaroslav Brustinov      - the list of plugins to make available during the run. Note
199823b8294SYaroslav Brustinov        that this does not mean these plugins will be *enabled* during
200823b8294SYaroslav Brustinov        the run -- only the plugins enabled by the activate argument
201823b8294SYaroslav Brustinov        or other settings in argv or env will be enabled.
202823b8294SYaroslav Brustinov
203823b8294SYaroslav Brustinov    - args
204823b8294SYaroslav Brustinov
205823b8294SYaroslav Brustinov      - a list of arguments to add to the nosetests command, in addition to
206823b8294SYaroslav Brustinov        the activate argument
207823b8294SYaroslav Brustinov
208823b8294SYaroslav Brustinov    - env
209823b8294SYaroslav Brustinov
210823b8294SYaroslav Brustinov      - optional dict of environment variables to send nosetests
211823b8294SYaroslav Brustinov
212823b8294SYaroslav Brustinov    """
213823b8294SYaroslav Brustinov    activate = None
214823b8294SYaroslav Brustinov    suitepath = None
215823b8294SYaroslav Brustinov    args = None
216823b8294SYaroslav Brustinov    env = {}
217823b8294SYaroslav Brustinov    argv = None
218823b8294SYaroslav Brustinov    plugins = []
219823b8294SYaroslav Brustinov    ignoreFiles = None
220823b8294SYaroslav Brustinov
221823b8294SYaroslav Brustinov    def makeSuite(self):
222823b8294SYaroslav Brustinov        """returns a suite object of tests to run (unittest.TestSuite())
223823b8294SYaroslav Brustinov
224823b8294SYaroslav Brustinov        If self.suitepath is None, this must be implemented. The returned suite
225823b8294SYaroslav Brustinov        object will be executed with all plugins activated.  It may return
226823b8294SYaroslav Brustinov        None.
227823b8294SYaroslav Brustinov
228823b8294SYaroslav Brustinov        Here is an example of a basic suite object you can return ::
229823b8294SYaroslav Brustinov
230823b8294SYaroslav Brustinov            >>> import unittest
231823b8294SYaroslav Brustinov            >>> class SomeTest(unittest.TestCase):
232823b8294SYaroslav Brustinov            ...     def runTest(self):
233823b8294SYaroslav Brustinov            ...         raise ValueError("Now do something, plugin!")
234823b8294SYaroslav Brustinov            ...
235823b8294SYaroslav Brustinov            >>> unittest.TestSuite([SomeTest()]) # doctest: +ELLIPSIS
236823b8294SYaroslav Brustinov            <unittest...TestSuite tests=[<...SomeTest testMethod=runTest>]>
237823b8294SYaroslav Brustinov
238823b8294SYaroslav Brustinov        """
239823b8294SYaroslav Brustinov        raise NotImplementedError
240823b8294SYaroslav Brustinov
241823b8294SYaroslav Brustinov    def _execPlugin(self):
242823b8294SYaroslav Brustinov        """execute the plugin on the internal test suite.
243823b8294SYaroslav Brustinov        """
244823b8294SYaroslav Brustinov        from nose.config import Config
245823b8294SYaroslav Brustinov        from nose.core import TestProgram
246823b8294SYaroslav Brustinov        from nose.plugins.manager import PluginManager
247823b8294SYaroslav Brustinov
248823b8294SYaroslav Brustinov        suite = None
249823b8294SYaroslav Brustinov        stream = Buffer()
250823b8294SYaroslav Brustinov        conf = Config(env=self.env,
251823b8294SYaroslav Brustinov                      stream=stream,
252823b8294SYaroslav Brustinov                      plugins=PluginManager(plugins=self.plugins))
253823b8294SYaroslav Brustinov        if self.ignoreFiles is not None:
254823b8294SYaroslav Brustinov            conf.ignoreFiles = self.ignoreFiles
255823b8294SYaroslav Brustinov        if not self.suitepath:
256823b8294SYaroslav Brustinov            suite = self.makeSuite()
257823b8294SYaroslav Brustinov
258823b8294SYaroslav Brustinov        self.nose = TestProgram(argv=self.argv, config=conf, suite=suite,
259823b8294SYaroslav Brustinov                                exit=False)
260823b8294SYaroslav Brustinov        self.output = AccessDecorator(stream)
261823b8294SYaroslav Brustinov
262823b8294SYaroslav Brustinov    def setUp(self):
263823b8294SYaroslav Brustinov        """runs nosetests with the specified test suite, all plugins
264823b8294SYaroslav Brustinov        activated.
265823b8294SYaroslav Brustinov        """
266823b8294SYaroslav Brustinov        self.argv = ['nosetests', self.activate]
267823b8294SYaroslav Brustinov        if self.args:
268823b8294SYaroslav Brustinov            self.argv.extend(self.args)
269823b8294SYaroslav Brustinov        if self.suitepath:
270823b8294SYaroslav Brustinov            self.argv.append(self.suitepath)
271823b8294SYaroslav Brustinov
272823b8294SYaroslav Brustinov        self._execPlugin()
273823b8294SYaroslav Brustinov
274823b8294SYaroslav Brustinov
275823b8294SYaroslav Brustinovclass AccessDecorator(object):
276823b8294SYaroslav Brustinov    stream = None
277823b8294SYaroslav Brustinov    _buf = None
278823b8294SYaroslav Brustinov    def __init__(self, stream):
279823b8294SYaroslav Brustinov        self.stream = stream
280823b8294SYaroslav Brustinov        stream.seek(0)
281823b8294SYaroslav Brustinov        self._buf = stream.read()
282823b8294SYaroslav Brustinov        stream.seek(0)
283823b8294SYaroslav Brustinov    def __contains__(self, val):
284823b8294SYaroslav Brustinov        return val in self._buf
285823b8294SYaroslav Brustinov    def __iter__(self):
286823b8294SYaroslav Brustinov        return iter(self.stream)
287823b8294SYaroslav Brustinov    def __str__(self):
288823b8294SYaroslav Brustinov        return self._buf
289823b8294SYaroslav Brustinov
290823b8294SYaroslav Brustinov
291823b8294SYaroslav Brustinovdef blankline_separated_blocks(text):
292823b8294SYaroslav Brustinov    "a bunch of === characters is also considered a blank line"
293823b8294SYaroslav Brustinov    block = []
294823b8294SYaroslav Brustinov    for line in text.splitlines(True):
295823b8294SYaroslav Brustinov        block.append(line)
296823b8294SYaroslav Brustinov        line = line.strip()
297823b8294SYaroslav Brustinov        if not line or line.startswith('===') and not line.strip('='):
298823b8294SYaroslav Brustinov            yield "".join(block)
299823b8294SYaroslav Brustinov            block = []
300823b8294SYaroslav Brustinov    if block:
301823b8294SYaroslav Brustinov        yield "".join(block)
302823b8294SYaroslav Brustinov
303823b8294SYaroslav Brustinov
304823b8294SYaroslav Brustinovdef remove_stack_traces(out):
305823b8294SYaroslav Brustinov    # this regexp taken from Python 2.5's doctest
306823b8294SYaroslav Brustinov    traceback_re = re.compile(r"""
307823b8294SYaroslav Brustinov        # Grab the traceback header.  Different versions of Python have
308823b8294SYaroslav Brustinov        # said different things on the first traceback line.
309823b8294SYaroslav Brustinov        ^(?P<hdr> Traceback\ \(
310823b8294SYaroslav Brustinov            (?: most\ recent\ call\ last
311823b8294SYaroslav Brustinov            |   innermost\ last
312823b8294SYaroslav Brustinov            ) \) :
313823b8294SYaroslav Brustinov        )
314823b8294SYaroslav Brustinov        \s* $                   # toss trailing whitespace on the header.
315823b8294SYaroslav Brustinov        (?P<stack> .*?)         # don't blink: absorb stuff until...
316823b8294SYaroslav Brustinov        ^(?=\w)                 #     a line *starts* with alphanum.
317823b8294SYaroslav Brustinov        .*?(?P<exception> \w+ ) # exception name
318823b8294SYaroslav Brustinov        (?P<msg> [:\n] .*)      # the rest
319823b8294SYaroslav Brustinov        """, re.VERBOSE | re.MULTILINE | re.DOTALL)
320823b8294SYaroslav Brustinov    blocks = []
321823b8294SYaroslav Brustinov    for block in blankline_separated_blocks(out):
322823b8294SYaroslav Brustinov        blocks.append(traceback_re.sub(r"\g<hdr>\n...\n\g<exception>\g<msg>", block))
323823b8294SYaroslav Brustinov    return "".join(blocks)
324823b8294SYaroslav Brustinov
325823b8294SYaroslav Brustinov
326823b8294SYaroslav Brustinovdef simplify_warnings(out):
327823b8294SYaroslav Brustinov    warn_re = re.compile(r"""
328823b8294SYaroslav Brustinov        # Cut the file and line no, up to the warning name
329823b8294SYaroslav Brustinov        ^.*:\d+:\s
330823b8294SYaroslav Brustinov        (?P<category>\w+): \s+        # warning category
331823b8294SYaroslav Brustinov        (?P<detail>.+) $ \n?          # warning message
332823b8294SYaroslav Brustinov        ^ .* $                        # stack frame
333823b8294SYaroslav Brustinov        """, re.VERBOSE | re.MULTILINE)
334823b8294SYaroslav Brustinov    return warn_re.sub(r"\g<category>: \g<detail>", out)
335823b8294SYaroslav Brustinov
336823b8294SYaroslav Brustinov
337823b8294SYaroslav Brustinovdef remove_timings(out):
338823b8294SYaroslav Brustinov    return re.sub(
339823b8294SYaroslav Brustinov        r"Ran (\d+ tests?) in [0-9.]+s", r"Ran \1 in ...s", out)
340823b8294SYaroslav Brustinov
341823b8294SYaroslav Brustinov
342823b8294SYaroslav Brustinovdef munge_nose_output_for_doctest(out):
343823b8294SYaroslav Brustinov    """Modify nose output to make it easy to use in doctests."""
344823b8294SYaroslav Brustinov    out = remove_stack_traces(out)
345823b8294SYaroslav Brustinov    out = simplify_warnings(out)
346823b8294SYaroslav Brustinov    out = remove_timings(out)
347823b8294SYaroslav Brustinov    return out.strip()
348823b8294SYaroslav Brustinov
349823b8294SYaroslav Brustinov
350823b8294SYaroslav Brustinovdef run(*arg, **kw):
351823b8294SYaroslav Brustinov    """
352823b8294SYaroslav Brustinov    Specialized version of nose.run for use inside of doctests that
353823b8294SYaroslav Brustinov    test test runs.
354823b8294SYaroslav Brustinov
355823b8294SYaroslav Brustinov    This version of run() prints the result output to stdout.  Before
356823b8294SYaroslav Brustinov    printing, the output is processed by replacing the timing
357823b8294SYaroslav Brustinov    information with an ellipsis (...), removing traceback stacks, and
358823b8294SYaroslav Brustinov    removing trailing whitespace.
359823b8294SYaroslav Brustinov
360823b8294SYaroslav Brustinov    Use this version of run wherever you are writing a doctest that
361823b8294SYaroslav Brustinov    tests nose (or unittest) test result output.
362823b8294SYaroslav Brustinov
363823b8294SYaroslav Brustinov    Note: do not use doctest: +ELLIPSIS when testing nose output,
364823b8294SYaroslav Brustinov    since ellipses ("test_foo ... ok") in your expected test runner
365823b8294SYaroslav Brustinov    output may match multiple lines of output, causing spurious test
366823b8294SYaroslav Brustinov    passes!
367823b8294SYaroslav Brustinov    """
368823b8294SYaroslav Brustinov    from nose import run
369823b8294SYaroslav Brustinov    from nose.config import Config
370823b8294SYaroslav Brustinov    from nose.plugins.manager import PluginManager
371823b8294SYaroslav Brustinov
372823b8294SYaroslav Brustinov    buffer = Buffer()
373823b8294SYaroslav Brustinov    if 'config' not in kw:
374823b8294SYaroslav Brustinov        plugins = kw.pop('plugins', [])
375823b8294SYaroslav Brustinov        if isinstance(plugins, list):
376823b8294SYaroslav Brustinov            plugins = PluginManager(plugins=plugins)
377823b8294SYaroslav Brustinov        env = kw.pop('env', {})
378823b8294SYaroslav Brustinov        kw['config'] = Config(env=env, plugins=plugins)
379823b8294SYaroslav Brustinov    if 'argv' not in kw:
380823b8294SYaroslav Brustinov        kw['argv'] = ['nosetests', '-v']
381823b8294SYaroslav Brustinov    kw['config'].stream = buffer
382823b8294SYaroslav Brustinov
383823b8294SYaroslav Brustinov    # Set up buffering so that all output goes to our buffer,
384823b8294SYaroslav Brustinov    # or warn user if deprecated behavior is active. If this is not
385823b8294SYaroslav Brustinov    # done, prints and warnings will either be out of place or
386823b8294SYaroslav Brustinov    # disappear.
387823b8294SYaroslav Brustinov    stderr = sys.stderr
388823b8294SYaroslav Brustinov    stdout = sys.stdout
389823b8294SYaroslav Brustinov    if kw.pop('buffer_all', False):
390823b8294SYaroslav Brustinov        sys.stdout = sys.stderr = buffer
391823b8294SYaroslav Brustinov        restore = True
392823b8294SYaroslav Brustinov    else:
393823b8294SYaroslav Brustinov        restore = False
394823b8294SYaroslav Brustinov        warn("The behavior of nose.plugins.plugintest.run() will change in "
395823b8294SYaroslav Brustinov             "the next release of nose. The current behavior does not "
396823b8294SYaroslav Brustinov             "correctly account for output to stdout and stderr. To enable "
397823b8294SYaroslav Brustinov             "correct behavior, use run_buffered() instead, or pass "
398823b8294SYaroslav Brustinov             "the keyword argument buffer_all=True to run().",
399823b8294SYaroslav Brustinov             DeprecationWarning, stacklevel=2)
400823b8294SYaroslav Brustinov    try:
401823b8294SYaroslav Brustinov        run(*arg, **kw)
402823b8294SYaroslav Brustinov    finally:
403823b8294SYaroslav Brustinov        if restore:
404823b8294SYaroslav Brustinov            sys.stderr = stderr
405823b8294SYaroslav Brustinov            sys.stdout = stdout
406823b8294SYaroslav Brustinov    out = buffer.getvalue()
407823b8294SYaroslav Brustinov    print munge_nose_output_for_doctest(out)
408823b8294SYaroslav Brustinov
409823b8294SYaroslav Brustinov
410823b8294SYaroslav Brustinovdef run_buffered(*arg, **kw):
411823b8294SYaroslav Brustinov    kw['buffer_all'] = True
412823b8294SYaroslav Brustinov    run(*arg, **kw)
413823b8294SYaroslav Brustinov
414823b8294SYaroslav Brustinovif __name__ == '__main__':
415823b8294SYaroslav Brustinov    import doctest
416823b8294SYaroslav Brustinov    doctest.testmod()
417