1 | """Use the Doctest plugin with ``--with-doctest`` or the NOSE_WITH_DOCTEST |
---|
2 | environment variable to enable collection and execution of :mod:`doctests |
---|
3 | <doctest>`. Because doctests are usually included in the tested package |
---|
4 | (instead of being grouped into packages or modules of their own), nose only |
---|
5 | looks for them in the non-test packages it discovers in the working directory. |
---|
6 | |
---|
7 | Doctests may also be placed into files other than python modules, in which |
---|
8 | case they can be collected and executed by using the ``--doctest-extension`` |
---|
9 | switch or NOSE_DOCTEST_EXTENSION environment variable to indicate which file |
---|
10 | extension(s) to load. |
---|
11 | |
---|
12 | When loading doctests from non-module files, use the ``--doctest-fixtures`` |
---|
13 | switch to specify how to find modules containing fixtures for the tests. A |
---|
14 | module name will be produced by appending the value of that switch to the base |
---|
15 | name of each doctest file loaded. For example, a doctest file "widgets.rst" |
---|
16 | with the switch ``--doctest_fixtures=_fixt`` will load fixtures from the module |
---|
17 | ``widgets_fixt.py``. |
---|
18 | |
---|
19 | A fixtures module may define any or all of the following functions: |
---|
20 | |
---|
21 | * setup([module]) or setup_module([module]) |
---|
22 | |
---|
23 | Called before the test runs. You may raise SkipTest to skip all tests. |
---|
24 | |
---|
25 | * teardown([module]) or teardown_module([module]) |
---|
26 | |
---|
27 | Called after the test runs, if setup/setup_module did not raise an |
---|
28 | unhandled exception. |
---|
29 | |
---|
30 | * setup_test(test) |
---|
31 | |
---|
32 | Called before the test. NOTE: the argument passed is a |
---|
33 | doctest.DocTest instance, *not* a unittest.TestCase. |
---|
34 | |
---|
35 | * teardown_test(test) |
---|
36 | |
---|
37 | Called after the test, if setup_test did not raise an exception. NOTE: the |
---|
38 | argument passed is a doctest.DocTest instance, *not* a unittest.TestCase. |
---|
39 | |
---|
40 | Doctests are run like any other test, with the exception that output |
---|
41 | capture does not work; doctest does its own output capture while running a |
---|
42 | test. |
---|
43 | |
---|
44 | .. note :: |
---|
45 | |
---|
46 | See :doc:`../doc_tests/test_doctest_fixtures/doctest_fixtures` for |
---|
47 | additional documentation and examples. |
---|
48 | |
---|
49 | """ |
---|
50 | from __future__ import generators |
---|
51 | |
---|
52 | import logging |
---|
53 | import os |
---|
54 | import sys |
---|
55 | import unittest |
---|
56 | from inspect import getmodule |
---|
57 | from nose.plugins.base import Plugin |
---|
58 | from nose.suite import ContextList |
---|
59 | from nose.util import anyp, getpackage, test_address, resolve_name, \ |
---|
60 | src, tolist, isproperty |
---|
61 | try: |
---|
62 | from cStringIO import StringIO |
---|
63 | except ImportError: |
---|
64 | from StringIO import StringIO |
---|
65 | import sys |
---|
66 | import __builtin__ |
---|
67 | |
---|
68 | log = logging.getLogger(__name__) |
---|
69 | |
---|
70 | try: |
---|
71 | import doctest |
---|
72 | doctest.DocTestCase |
---|
73 | # system version of doctest is acceptable, but needs a monkeypatch |
---|
74 | except (ImportError, AttributeError): |
---|
75 | # system version is too old |
---|
76 | import nose.ext.dtcompat as doctest |
---|
77 | |
---|
78 | |
---|
79 | # |
---|
80 | # Doctest and coverage don't get along, so we need to create |
---|
81 | # a monkeypatch that will replace the part of doctest that |
---|
82 | # interferes with coverage reports. |
---|
83 | # |
---|
84 | # The monkeypatch is based on this zope patch: |
---|
85 | # http://svn.zope.org/Zope3/trunk/src/zope/testing/doctest.py?rev=28679&r1=28703&r2=28705 |
---|
86 | # |
---|
87 | _orp = doctest._OutputRedirectingPdb |
---|
88 | |
---|
89 | class NoseOutputRedirectingPdb(_orp): |
---|
90 | def __init__(self, out): |
---|
91 | self.__debugger_used = False |
---|
92 | _orp.__init__(self, out) |
---|
93 | |
---|
94 | def set_trace(self): |
---|
95 | self.__debugger_used = True |
---|
96 | _orp.set_trace(self, sys._getframe().f_back) |
---|
97 | |
---|
98 | def set_continue(self): |
---|
99 | # Calling set_continue unconditionally would break unit test |
---|
100 | # coverage reporting, as Bdb.set_continue calls sys.settrace(None). |
---|
101 | if self.__debugger_used: |
---|
102 | _orp.set_continue(self) |
---|
103 | doctest._OutputRedirectingPdb = NoseOutputRedirectingPdb |
---|
104 | |
---|
105 | |
---|
106 | class DoctestSuite(unittest.TestSuite): |
---|
107 | """ |
---|
108 | Doctest suites are parallelizable at the module or file level only, |
---|
109 | since they may be attached to objects that are not individually |
---|
110 | addressable (like properties). This suite subclass is used when |
---|
111 | loading doctests from a module to ensure that behavior. |
---|
112 | |
---|
113 | This class is used only if the plugin is not fully prepared; |
---|
114 | in normal use, the loader's suiteClass is used. |
---|
115 | |
---|
116 | """ |
---|
117 | can_split = False |
---|
118 | |
---|
119 | def __init__(self, tests=(), context=None, can_split=False): |
---|
120 | self.context = context |
---|
121 | self.can_split = can_split |
---|
122 | unittest.TestSuite.__init__(self, tests=tests) |
---|
123 | |
---|
124 | def address(self): |
---|
125 | return test_address(self.context) |
---|
126 | |
---|
127 | def __iter__(self): |
---|
128 | # 2.3 compat |
---|
129 | return iter(self._tests) |
---|
130 | |
---|
131 | def __str__(self): |
---|
132 | return str(self._tests) |
---|
133 | |
---|
134 | |
---|
135 | class Doctest(Plugin): |
---|
136 | """ |
---|
137 | Activate doctest plugin to find and run doctests in non-test modules. |
---|
138 | """ |
---|
139 | extension = None |
---|
140 | suiteClass = DoctestSuite |
---|
141 | |
---|
142 | def options(self, parser, env): |
---|
143 | """Register commmandline options. |
---|
144 | """ |
---|
145 | Plugin.options(self, parser, env) |
---|
146 | parser.add_option('--doctest-tests', action='store_true', |
---|
147 | dest='doctest_tests', |
---|
148 | default=env.get('NOSE_DOCTEST_TESTS'), |
---|
149 | help="Also look for doctests in test modules. " |
---|
150 | "Note that classes, methods and functions should " |
---|
151 | "have either doctests or non-doctest tests, " |
---|
152 | "not both. [NOSE_DOCTEST_TESTS]") |
---|
153 | parser.add_option('--doctest-extension', action="append", |
---|
154 | dest="doctestExtension", |
---|
155 | metavar="EXT", |
---|
156 | help="Also look for doctests in files with " |
---|
157 | "this extension [NOSE_DOCTEST_EXTENSION]") |
---|
158 | parser.add_option('--doctest-result-variable', |
---|
159 | dest='doctest_result_var', |
---|
160 | default=env.get('NOSE_DOCTEST_RESULT_VAR'), |
---|
161 | metavar="VAR", |
---|
162 | help="Change the variable name set to the result of " |
---|
163 | "the last interpreter command from the default '_'. " |
---|
164 | "Can be used to avoid conflicts with the _() " |
---|
165 | "function used for text translation. " |
---|
166 | "[NOSE_DOCTEST_RESULT_VAR]") |
---|
167 | parser.add_option('--doctest-fixtures', action="store", |
---|
168 | dest="doctestFixtures", |
---|
169 | metavar="SUFFIX", |
---|
170 | help="Find fixtures for a doctest file in module " |
---|
171 | "with this name appended to the base name " |
---|
172 | "of the doctest file") |
---|
173 | # Set the default as a list, if given in env; otherwise |
---|
174 | # an additional value set on the command line will cause |
---|
175 | # an error. |
---|
176 | env_setting = env.get('NOSE_DOCTEST_EXTENSION') |
---|
177 | if env_setting is not None: |
---|
178 | parser.set_defaults(doctestExtension=tolist(env_setting)) |
---|
179 | |
---|
180 | def configure(self, options, config): |
---|
181 | """Configure plugin. |
---|
182 | """ |
---|
183 | Plugin.configure(self, options, config) |
---|
184 | self.doctest_result_var = options.doctest_result_var |
---|
185 | self.doctest_tests = options.doctest_tests |
---|
186 | self.extension = tolist(options.doctestExtension) |
---|
187 | self.fixtures = options.doctestFixtures |
---|
188 | self.finder = doctest.DocTestFinder() |
---|
189 | |
---|
190 | def prepareTestLoader(self, loader): |
---|
191 | """Capture loader's suiteClass. |
---|
192 | |
---|
193 | This is used to create test suites from doctest files. |
---|
194 | |
---|
195 | """ |
---|
196 | self.suiteClass = loader.suiteClass |
---|
197 | |
---|
198 | def loadTestsFromModule(self, module): |
---|
199 | """Load doctests from the module. |
---|
200 | """ |
---|
201 | log.debug("loading from %s", module) |
---|
202 | if not self.matches(module.__name__): |
---|
203 | log.debug("Doctest doesn't want module %s", module) |
---|
204 | return |
---|
205 | try: |
---|
206 | tests = self.finder.find(module) |
---|
207 | except AttributeError: |
---|
208 | log.exception("Attribute error loading from %s", module) |
---|
209 | # nose allows module.__test__ = False; doctest does not and throws |
---|
210 | # AttributeError |
---|
211 | return |
---|
212 | if not tests: |
---|
213 | log.debug("No tests found in %s", module) |
---|
214 | return |
---|
215 | tests.sort() |
---|
216 | module_file = src(module.__file__) |
---|
217 | # FIXME this breaks the id plugin somehow (tests probably don't |
---|
218 | # get wrapped in result proxy or something) |
---|
219 | cases = [] |
---|
220 | for test in tests: |
---|
221 | if not test.examples: |
---|
222 | continue |
---|
223 | if not test.filename: |
---|
224 | test.filename = module_file |
---|
225 | cases.append(DocTestCase(test, result_var=self.doctest_result_var)) |
---|
226 | if cases: |
---|
227 | yield self.suiteClass(cases, context=module, can_split=False) |
---|
228 | |
---|
229 | def loadTestsFromFile(self, filename): |
---|
230 | """Load doctests from the file. |
---|
231 | |
---|
232 | Tests are loaded only if filename's extension matches |
---|
233 | configured doctest extension. |
---|
234 | |
---|
235 | """ |
---|
236 | if self.extension and anyp(filename.endswith, self.extension): |
---|
237 | name = os.path.basename(filename) |
---|
238 | dh = open(filename) |
---|
239 | try: |
---|
240 | doc = dh.read() |
---|
241 | finally: |
---|
242 | dh.close() |
---|
243 | |
---|
244 | fixture_context = None |
---|
245 | globs = {'__file__': filename} |
---|
246 | if self.fixtures: |
---|
247 | base, ext = os.path.splitext(name) |
---|
248 | dirname = os.path.dirname(filename) |
---|
249 | sys.path.append(dirname) |
---|
250 | fixt_mod = base + self.fixtures |
---|
251 | try: |
---|
252 | fixture_context = __import__( |
---|
253 | fixt_mod, globals(), locals(), ["nop"]) |
---|
254 | except ImportError, e: |
---|
255 | log.debug( |
---|
256 | "Could not import %s: %s (%s)", fixt_mod, e, sys.path) |
---|
257 | log.debug("Fixture module %s resolved to %s", |
---|
258 | fixt_mod, fixture_context) |
---|
259 | if hasattr(fixture_context, 'globs'): |
---|
260 | globs = fixture_context.globs(globs) |
---|
261 | parser = doctest.DocTestParser() |
---|
262 | test = parser.get_doctest( |
---|
263 | doc, globs=globs, name=name, |
---|
264 | filename=filename, lineno=0) |
---|
265 | if test.examples: |
---|
266 | case = DocFileCase( |
---|
267 | test, |
---|
268 | setUp=getattr(fixture_context, 'setup_test', None), |
---|
269 | tearDown=getattr(fixture_context, 'teardown_test', None), |
---|
270 | result_var=self.doctest_result_var) |
---|
271 | if fixture_context: |
---|
272 | yield ContextList((case,), context=fixture_context) |
---|
273 | else: |
---|
274 | yield case |
---|
275 | else: |
---|
276 | yield False # no tests to load |
---|
277 | |
---|
278 | def makeTest(self, obj, parent): |
---|
279 | """Look for doctests in the given object, which will be a |
---|
280 | function, method or class. |
---|
281 | """ |
---|
282 | name = getattr(obj, '__name__', 'Unnammed %s' % type(obj)) |
---|
283 | doctests = self.finder.find(obj, module=getmodule(parent), name=name) |
---|
284 | if doctests: |
---|
285 | for test in doctests: |
---|
286 | if len(test.examples) == 0: |
---|
287 | continue |
---|
288 | yield DocTestCase(test, obj=obj, |
---|
289 | result_var=self.doctest_result_var) |
---|
290 | |
---|
291 | def matches(self, name): |
---|
292 | # FIXME this seems wrong -- nothing is ever going to |
---|
293 | # fail this test, since we're given a module NAME not FILE |
---|
294 | if name == '__init__.py': |
---|
295 | return False |
---|
296 | # FIXME don't think we need include/exclude checks here? |
---|
297 | return ((self.doctest_tests or not self.conf.testMatch.search(name) |
---|
298 | or (self.conf.include |
---|
299 | and filter(None, |
---|
300 | [inc.search(name) |
---|
301 | for inc in self.conf.include]))) |
---|
302 | and (not self.conf.exclude |
---|
303 | or not filter(None, |
---|
304 | [exc.search(name) |
---|
305 | for exc in self.conf.exclude]))) |
---|
306 | |
---|
307 | def wantFile(self, file): |
---|
308 | """Override to select all modules and any file ending with |
---|
309 | configured doctest extension. |
---|
310 | """ |
---|
311 | # always want .py files |
---|
312 | if file.endswith('.py'): |
---|
313 | return True |
---|
314 | # also want files that match my extension |
---|
315 | if (self.extension |
---|
316 | and anyp(file.endswith, self.extension) |
---|
317 | and (not self.conf.exclude |
---|
318 | or not filter(None, |
---|
319 | [exc.search(file) |
---|
320 | for exc in self.conf.exclude]))): |
---|
321 | return True |
---|
322 | return None |
---|
323 | |
---|
324 | |
---|
325 | class DocTestCase(doctest.DocTestCase): |
---|
326 | """Overrides DocTestCase to |
---|
327 | provide an address() method that returns the correct address for |
---|
328 | the doctest case. To provide hints for address(), an obj may also |
---|
329 | be passed -- this will be used as the test object for purposes of |
---|
330 | determining the test address, if it is provided. |
---|
331 | """ |
---|
332 | def __init__(self, test, optionflags=0, setUp=None, tearDown=None, |
---|
333 | checker=None, obj=None, result_var='_'): |
---|
334 | self._result_var = result_var |
---|
335 | self._nose_obj = obj |
---|
336 | super(DocTestCase, self).__init__( |
---|
337 | test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, |
---|
338 | checker=checker) |
---|
339 | |
---|
340 | def address(self): |
---|
341 | if self._nose_obj is not None: |
---|
342 | return test_address(self._nose_obj) |
---|
343 | obj = resolve_name(self._dt_test.name) |
---|
344 | |
---|
345 | if isproperty(obj): |
---|
346 | # properties have no connection to the class they are in |
---|
347 | # so we can't just look 'em up, we have to first look up |
---|
348 | # the class, then stick the prop on the end |
---|
349 | parts = self._dt_test.name.split('.') |
---|
350 | class_name = '.'.join(parts[:-1]) |
---|
351 | cls = resolve_name(class_name) |
---|
352 | base_addr = test_address(cls) |
---|
353 | return (base_addr[0], base_addr[1], |
---|
354 | '.'.join([base_addr[2], parts[-1]])) |
---|
355 | else: |
---|
356 | return test_address(obj) |
---|
357 | |
---|
358 | # doctests loaded via find(obj) omit the module name |
---|
359 | # so we need to override id, __repr__ and shortDescription |
---|
360 | # bonus: this will squash a 2.3 vs 2.4 incompatiblity |
---|
361 | def id(self): |
---|
362 | name = self._dt_test.name |
---|
363 | filename = self._dt_test.filename |
---|
364 | if filename is not None: |
---|
365 | pk = getpackage(filename) |
---|
366 | if not name.startswith(pk): |
---|
367 | name = "%s.%s" % (pk, name) |
---|
368 | return name |
---|
369 | |
---|
370 | def __repr__(self): |
---|
371 | name = self.id() |
---|
372 | name = name.split('.') |
---|
373 | return "%s (%s)" % (name[-1], '.'.join(name[:-1])) |
---|
374 | __str__ = __repr__ |
---|
375 | |
---|
376 | def shortDescription(self): |
---|
377 | return 'Doctest: %s' % self.id() |
---|
378 | |
---|
379 | def setUp(self): |
---|
380 | if self._result_var is not None: |
---|
381 | self._old_displayhook = sys.displayhook |
---|
382 | sys.displayhook = self._displayhook |
---|
383 | super(DocTestCase, self).setUp() |
---|
384 | |
---|
385 | def _displayhook(self, value): |
---|
386 | if value is None: |
---|
387 | return |
---|
388 | setattr(__builtin__, self._result_var, value) |
---|
389 | print repr(value) |
---|
390 | |
---|
391 | def tearDown(self): |
---|
392 | super(DocTestCase, self).tearDown() |
---|
393 | if self._result_var is not None: |
---|
394 | sys.displayhook = self._old_displayhook |
---|
395 | delattr(__builtin__, self._result_var) |
---|
396 | |
---|
397 | |
---|
398 | class DocFileCase(doctest.DocFileCase): |
---|
399 | """Overrides to provide address() method that returns the correct |
---|
400 | address for the doc file case. |
---|
401 | """ |
---|
402 | def __init__(self, test, optionflags=0, setUp=None, tearDown=None, |
---|
403 | checker=None, result_var='_'): |
---|
404 | self._result_var = result_var |
---|
405 | super(DocFileCase, self).__init__( |
---|
406 | test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, |
---|
407 | checker=None) |
---|
408 | |
---|
409 | def address(self): |
---|
410 | return (self._dt_test.filename, None, None) |
---|
411 | |
---|
412 | def setUp(self): |
---|
413 | if self._result_var is not None: |
---|
414 | self._old_displayhook = sys.displayhook |
---|
415 | sys.displayhook = self._displayhook |
---|
416 | super(DocFileCase, self).setUp() |
---|
417 | |
---|
418 | def _displayhook(self, value): |
---|
419 | if value is None: |
---|
420 | return |
---|
421 | setattr(__builtin__, self._result_var, value) |
---|
422 | print repr(value) |
---|
423 | |
---|
424 | def tearDown(self): |
---|
425 | super(DocFileCase, self).tearDown() |
---|
426 | if self._result_var is not None: |
---|
427 | sys.displayhook = self._old_displayhook |
---|
428 | delattr(__builtin__, self._result_var) |
---|