/
case.py
340 lines (286 loc) · 11.3 KB
/
case.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
"""nose unittest.TestCase subclasses. It is not necessary to subclass these
classes when writing tests; they are used internally by nose.loader.TestLoader
to create test cases from test functions and methods in test classes.
"""
import logging
import sys
import unittest
from traceback import format_tb
from nose.config import Config
from nose.util import test_address, try_run
log = logging.getLogger(__name__)
# FIXME probably not the best name, since it is mainly used for errors
class Failure(unittest.TestCase):
__test__ = False # do not collect
def __init__(self, exc_class, exc_val, tb=None):
log.debug("A failure! %s %s %s", exc_class, exc_val, format_tb(tb))
self.exc_class = exc_class
self.exc_val = exc_val
self.tb = tb
unittest.TestCase.__init__(self)
def __str__(self):
return "Failure: %s(%s)" % (self.exc_class, self.exc_val)
def runTest(self):
if self.tb is not None:
raise self.exc_class, self.exc_val, self.tb
else:
raise self.exc_class(self.exc_val)
class Test(unittest.TestCase):
"""The universal test case wrapper.
When a plugin sees a test, it will always see an instance of this
class. To access the actual test case that will be run, access the
test property of the nose.case.Test instance.
"""
__test__ = False # do not collect
def __init__(self, test, config=None, resultProxy=None):
log.debug("Test(%s)", test)
# sanity check
if not callable(test):
raise TypeError("nose.case.Test called with argument %r that "
"is not callable. A callable is required."
% test)
self.test = test
if config is None:
config = Config()
self.config = config
self.assertDetails = None
self.capturedOutput = None
self.resultProxy = resultProxy
self.plugins = config.plugins
unittest.TestCase.__init__(self)
def __call__(self, *arg, **kwarg):
log.debug("Test call %s %s %s", self, arg, kwarg)
return self.run(*arg, **kwarg)
def __str__(self):
name = self.plugins.testName(self)
if name is not None:
return name
return str(self.test)
def __repr__(self):
return "Test(%r)" % self.test
def afterTest(self, result):
log.debug("Test afterTest %s", self)
try:
result.afterTest(self.test)
except AttributeError:
pass
def beforeTest(self, result):
log.debug("Test beforeTest %s", self)
try:
result.beforeTest(self.test)
except AttributeError:
pass
def exc_info(self):
"""Extract exception info into a useable form, including
appending captured output and assert introspection information, if
so configured.
"""
# FIXME add in the stdout capture and such
exc, exv, tb = sys.exc_info()
return (exc, exv, tb)
def id(self):
return self.test.id()
def address(self):
"""Return a round-trip name for this test, a name that can be
fed back as input to loadTestByName and (assuming the same
plugin configuration) result in the loading of this test.
"""
try:
return self.test.address()
except AttributeError:
# not a nose case
return test_address(self.test)
def run(self, result):
"""Modified run for the test wrapper.
From here we don't call result.startTest or stopTest or
addSuccess. The wrapper calls addError/addFailure only if its
own setup or teardown fails, or running the wrapped test fails
(eg, if the wrapped "test" is not callable).
Two additional methods are called, beforeTest and
afterTest. These give plugins a chance to modify the wrapped
test before it is called and do cleanup after it is
called. They are called unconditionally.
"""
if self.resultProxy:
result = self.resultProxy(result, self)
self.beforeTest(result)
try:
try:
self.runTest(result)
except KeyboardInterrupt:
raise
except:
err = sys.exc_info()
result.addError(self, err)
finally:
self.afterTest(result)
def runTest(self, result):
log.debug('run test %s with result %s', self.test, result)
self.test(result)
def shortDescription(self):
desc = self.plugins.describeTest(self)
if desc is not None:
return desc
return self.test.shortDescription()
class TestBase(unittest.TestCase):
"""Common functionality for FunctionTestCase and MethodTestCase.
"""
__test__ = False # do not collect
def id(self):
return str(self)
def runTest(self):
log.debug("%s.runTest(%s)", self.__class__.__name__, self)
self.test(*self.arg)
def shortDescription(self):
func, arg = self._descriptors()
doc = getattr(func, '__doc__', None)
if not doc:
doc = str(self)
return doc.split("\n")[0].strip()
class FunctionTestCase(TestBase):
"""TestCase wrapper for test functions.
Don't use this class directly; it is used internally in nose to
create test cases for test functions.
This class is very similar to unittest.FunctionTestCase, with a few
extensions:
* The test descriptions are disambiguated by including the full
module path when a test with a similar name has been seen in
the test run.
* It allows setup and teardown functions to be defined as attributes
of the test function. A convenient way to set this up is via the
provided with_setup decorator:
def setup_func():
# ...
def teardown_func():
# ...
@with_setup(setup_func, teardown_func)
def test_something():
# ...
"""
__test__ = False # do not collect
def __init__(self, test, setUp=None, tearDown=None, arg=tuple(),
descriptor=None):
self.test = test
self.setUpFunc = setUp
self.tearDownFunc = tearDown
self.arg = arg
self.descriptor = descriptor
unittest.TestCase.__init__(self)
def address(self):
"""Return a round-trip name for this test, a name that can be
fed back as input to loadTestByName and (assuming the same
plugin configuration) result in the loading of this test.
"""
return test_address(self.test)
def setUp(self):
"""Run any setup function attached to the test function
"""
if self.setUpFunc:
self.setUpFunc()
else:
names = ('setup', 'setUp', 'setUpFunc')
try_run(self.test, names)
def tearDown(self):
"""Run any teardown function attached to the test function
"""
if self.tearDownFunc:
self.tearDownFunc()
else:
names = ('teardown', 'tearDown', 'tearDownFunc')
try_run(self.test, names)
def __str__(self):
func, arg = self._descriptors()
if hasattr(func, 'compat_func_name'):
name = func.compat_func_name
else:
name = func.__name__
name = "%s.%s" % (func.__module__, name)
if arg:
name = "%s%s" % (name, arg)
# FIXME need to include the full dir path to disambiguate
# in cases where test module of the same name was seen in
# another directory (old fromDirectory)
return name
__repr__ = __str__
def _descriptors(self):
"""Get the descriptors of the test function: the function and
arguments that will be used to construct the test name. In
most cases, this is the function itself and no arguments. For
tests generated by generator functions, the original
(generator) function and args passed to the generated function
are returned.
"""
if self.descriptor:
return self.descriptor, self.arg
else:
return self.test, self.arg
class MethodTestCase(TestBase):
"""Test case wrapper for test methods.
Don't use this class directly; it is used internally in nose to
create test cases for test methods.
"""
__test__ = False # do not collect
def __init__(self, method, test=None, arg=tuple(), descriptor=None):
"""Initialize the MethodTestCase.
Required argument:
* method -- the method to call, may be bound or unbound. In either
case, a new instance of the method's class will be instantiated to
make the call.
Optional arguments:
* test -- the test function to call. If this is passed, it will be
called instead of getting a new bound method of the same name as the
desired method from the test instance. This is to support generator
methods that yield inline functions.
* arg -- arguments to pass to the test function. This is to support
generator methods that yield arguments.
* descriptor -- the function, other than the test, that should be used
to construct the test name. This is to support generator methods.
"""
log.debug("MethodTestCase for %s", method)
self.method = method
self.test = test
self.arg = arg
self.descriptor = descriptor
self.cls = method.im_class
self.inst = self.cls()
if self.test is None:
method_name = self.method.__name__
self.test = getattr(self.inst, method_name)
unittest.TestCase.__init__(self)
def __str__(self):
func, arg = self._descriptors()
if hasattr(func, 'compat_func_name'):
name = func.compat_func_name
else:
name = func.__name__
name = "%s.%s.%s" % (func.__module__,
self.cls.__name__,
name)
if arg:
name = "%s%s" % (name, arg)
return name
__repr__ = __str__
def address(self):
"""Return a round-trip name for this test, a name that can be
fed back as input to loadTestByName and (assuming the same
plugin configuration) result in the loading of this test.
"""
return test_address(self.method)
def setUp(self):
log.debug("MethodTestCase.setUp(%s)", self)
try_run(self.inst, ('setup', 'setUp'))
def tearDown(self):
log.debug("MethodTestCase.tearDown(%s)", self)
try_run(self.inst, ('teardown', 'tearDown'))
def _descriptors(self):
"""Get the descriptors of the test method: the method and
arguments that will be used to construct the test name. In
most cases, this is the method itself and no arguments. For
tests generated by generator methods, the original
(generator) method and args passed to the generated method
or function are returned.
"""
if self.descriptor:
return self.descriptor, self.arg
else:
return self.method, self.arg