~ubuntu-branches/debian/squeeze/nose/squeeze

« back to all changes in this revision

Viewing changes to nose/plugins/plugintest.py

  • Committer: Bazaar Package Importer
  • Author(s): Torsten Marek, Torsten Marek, Gustavo Noronha Silva
  • Date: 2008-06-12 13:39:43 UTC
  • mfrom: (1.2.1 upstream) (2.1.5 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080612133943-2q7syp67fwl4on13
Tags: 0.10.3-1

[Torsten Marek]
* New upstream release (Closes: #461994)
* debian/control
  - bump standards version to 3.8.0, no changes necessary
  - add suggestions for python-coverage (Closes: #457053)
  - change dependency on python-setuptools into 
    python-pkg-resources (Closes: #468719)
  - added myself to uploaders

[Gustavo Noronha Silva]
* debian/control:
  - remove -1 from build-dep on setuptools

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
Plugin tester
 
3
-------------
 
4
 
 
5
Utilities for testing plugins.
 
6
 
 
7
"""
 
8
 
 
9
import re
 
10
import sys
 
11
from warnings import warn
 
12
 
 
13
try:
 
14
    from cStringIO import StringIO
 
15
except ImportError:
 
16
    from StringIO import StringIO
 
17
    
 
18
__all__ = ['PluginTester', 'run']
 
19
 
 
20
 
 
21
class PluginTester(object):
 
22
    """A mixin for testing nose plugins in their runtime environment.
 
23
    
 
24
    Subclass this and mix in unittest.TestCase to run integration/functional 
 
25
    tests on your plugin.  When setUp() is called, the stub test suite is 
 
26
    executed with your plugin so that during an actual test you can inspect the 
 
27
    artifacts of how your plugin interacted with the stub test suite.
 
28
    
 
29
    Class Variables
 
30
    ---------------
 
31
    - activate
 
32
    
 
33
      - the argument to send nosetests to activate the plugin
 
34
     
 
35
    - suitepath
 
36
    
 
37
      - if set, this is the path of the suite to test.  otherwise, you
 
38
        will need to use the hook, makeSuite()
 
39
      
 
40
    - plugins
 
41
 
 
42
      - the list of plugins to make available during the run. Note
 
43
        that this does not mean these plugins will be *enabled* during
 
44
        the run -- only the plugins enabled by the activate argument
 
45
        or other settings in argv or env will be enabled.
 
46
    
 
47
    - args
 
48
  
 
49
      - a list of arguments to add to the nosetests command, in addition to
 
50
        the activate argument
 
51
    
 
52
    - env
 
53
    
 
54
      - optional dict of environment variables to send nosetests
 
55
 
 
56
    """
 
57
    activate = None
 
58
    suitepath = None
 
59
    args = None
 
60
    env = {}
 
61
    argv = None
 
62
    plugins = []
 
63
    ignoreFiles = None
 
64
    
 
65
    def makeSuite(self):
 
66
        """returns a suite object of tests to run (unittest.TestSuite())
 
67
        
 
68
        If self.suitepath is None, this must be implemented. The returned suite 
 
69
        object will be executed with all plugins activated.  It may return 
 
70
        None.
 
71
        
 
72
        Here is an example of a basic suite object you can return ::
 
73
        
 
74
            >>> import unittest
 
75
            >>> class SomeTest(unittest.TestCase):
 
76
            ...     def runTest(self):
 
77
            ...         raise ValueError("Now do something, plugin!")
 
78
            ... 
 
79
            >>> unittest.TestSuite([SomeTest()]) # doctest: +ELLIPSIS
 
80
            <unittest.TestSuite tests=[<...SomeTest testMethod=runTest>]>
 
81
        
 
82
        """
 
83
        raise NotImplementedError
 
84
    
 
85
    def _execPlugin(self):
 
86
        """execute the plugin on the internal test suite.
 
87
        """
 
88
        from nose.config import Config
 
89
        from nose.core import TestProgram
 
90
        from nose.plugins.manager import PluginManager
 
91
        
 
92
        suite = None
 
93
        stream = StringIO()
 
94
        conf = Config(env=self.env,
 
95
                      stream=stream,
 
96
                      plugins=PluginManager(plugins=self.plugins))
 
97
        if self.ignoreFiles is not None:
 
98
            conf.ignoreFiles = self.ignoreFiles
 
99
        if not self.suitepath:
 
100
            suite = self.makeSuite()
 
101
            
 
102
        self.nose = TestProgram(argv=self.argv, config=conf, suite=suite,
 
103
                                exit=False)
 
104
        self.output = AccessDecorator(stream)
 
105
                                
 
106
    def setUp(self):
 
107
        """runs nosetests with the specified test suite, all plugins 
 
108
        activated.
 
109
        """
 
110
        self.argv = ['nosetests', self.activate]
 
111
        if self.args:
 
112
            self.argv.extend(self.args)
 
113
        if self.suitepath:
 
114
            self.argv.append(self.suitepath)            
 
115
 
 
116
        self._execPlugin()
 
117
 
 
118
 
 
119
class AccessDecorator(object):
 
120
    stream = None
 
121
    _buf = None
 
122
    def __init__(self, stream):
 
123
        self.stream = stream
 
124
        stream.seek(0)
 
125
        self._buf = stream.read()
 
126
        stream.seek(0)
 
127
    def __contains__(self, val):
 
128
        return val in self._buf
 
129
    def __iter__(self):
 
130
        return self.stream
 
131
    def __str__(self):
 
132
        return self._buf
 
133
 
 
134
 
 
135
def blankline_separated_blocks(text):
 
136
    block = []
 
137
    for line in text.splitlines(True):
 
138
        block.append(line)
 
139
        if not line.strip():
 
140
            yield "".join(block)
 
141
            block = []
 
142
    if block:
 
143
        yield "".join(block)
 
144
 
 
145
 
 
146
def remove_stack_traces(out):
 
147
    # this regexp taken from Python 2.5's doctest
 
148
    traceback_re = re.compile(r"""
 
149
        # Grab the traceback header.  Different versions of Python have
 
150
        # said different things on the first traceback line.
 
151
        ^(?P<hdr> Traceback\ \(
 
152
            (?: most\ recent\ call\ last
 
153
            |   innermost\ last
 
154
            ) \) :
 
155
        )
 
156
        \s* $                # toss trailing whitespace on the header.
 
157
        (?P<stack> .*?)      # don't blink: absorb stuff until...
 
158
        ^ (?P<msg> \w+ .*)   #     a line *starts* with alphanum.
 
159
        """, re.VERBOSE | re.MULTILINE | re.DOTALL)
 
160
    blocks = []
 
161
    for block in blankline_separated_blocks(out):
 
162
        blocks.append(traceback_re.sub(r"\g<hdr>\n...\n\g<msg>", block))
 
163
    return "".join(blocks)
 
164
 
 
165
 
 
166
def simplify_warnings(out):
 
167
    warn_re = re.compile(r"""
 
168
        # Cut the file and line no, up to the warning name
 
169
        ^.*:\d+:\s
 
170
        (?P<category>\w+): \s+        # warning category
 
171
        (?P<detail>.+) $ \n?          # warning message
 
172
        ^ .* $                        # stack frame
 
173
        """, re.VERBOSE | re.MULTILINE)
 
174
    return warn_re.sub(r"\g<category>: \g<detail>", out)
 
175
 
 
176
 
 
177
def remove_timings(out):
 
178
    return re.sub(
 
179
        r"Ran (\d+ tests?) in [0-9.]+s", r"Ran \1 in ...s", out)
 
180
 
 
181
 
 
182
def munge_nose_output_for_doctest(out):
 
183
    """Modify nose output to make it easy to use in doctests."""
 
184
    out = remove_stack_traces(out)
 
185
    out = simplify_warnings(out)
 
186
    out = remove_timings(out)
 
187
    return out.strip()
 
188
 
 
189
 
 
190
def run(*arg, **kw):
 
191
    """
 
192
    Specialized version of nose.run for use inside of doctests that
 
193
    test test runs.
 
194
 
 
195
    This version of run() prints the result output to stdout.  Before
 
196
    printing, the output is processed by replacing the timing
 
197
    information with an ellipsis (...), removing traceback stacks, and
 
198
    removing trailing whitespace.
 
199
 
 
200
    Use this version of run wherever you are writing a doctest that
 
201
    tests nose (or unittest) test result output.
 
202
 
 
203
    Note: do not use doctest: +ELLIPSIS when testing nose output,
 
204
    since ellipses ("test_foo ... ok") in your expected test runner
 
205
    output may match multiple lines of output, causing spurious test
 
206
    passes!
 
207
    """
 
208
    from nose import run
 
209
    from nose.config import Config
 
210
    from nose.plugins.manager import PluginManager
 
211
 
 
212
    buffer = StringIO()
 
213
    if 'config' not in kw:
 
214
        plugins = kw.pop('plugins', [])
 
215
        if isinstance(plugins, list):
 
216
            plugins = PluginManager(plugins=plugins)
 
217
        env = kw.pop('env', {})
 
218
        kw['config'] = Config(env=env, plugins=plugins)
 
219
    if 'argv' not in kw:
 
220
        kw['argv'] = ['nosetests', '-v']
 
221
    kw['config'].stream = buffer
 
222
    
 
223
    # Set up buffering so that all output goes to our buffer,
 
224
    # or warn user if deprecated behavior is active. If this is not
 
225
    # done, prints and warnings will either be out of place or
 
226
    # disappear.
 
227
    stderr = sys.stderr
 
228
    stdout = sys.stdout
 
229
    if kw.pop('buffer_all', False):
 
230
        sys.stdout = sys.stderr = buffer
 
231
        restore = True
 
232
    else:
 
233
        restore = False
 
234
        warn("The behavior of nose.plugins.plugintest.run() will change in "
 
235
             "the next release of nose. The current behavior does not "
 
236
             "correctly account for output to stdout and stderr. To enable "
 
237
             "correct behavior, use run_buffered() instead, or pass "
 
238
             "the keyword argument buffer_all=True to run().",
 
239
             DeprecationWarning, stacklevel=2)
 
240
    try:
 
241
        run(*arg, **kw)
 
242
    finally:
 
243
        if restore:
 
244
            sys.stderr = stderr
 
245
            sys.stdout = stdout
 
246
    out = buffer.getvalue()
 
247
    print munge_nose_output_for_doctest(out)
 
248
 
 
249
    
 
250
def run_buffered(*arg, **kw):
 
251
    kw['buffer_all'] = True
 
252
    run(*arg, **kw)
 
253
 
 
254
if __name__ == '__main__':
 
255
    import doctest
 
256
    doctest.testmod()