##// END OF EJS Templates
clean up ipdoctest.py
Srinivas Reddy Thatiparthy -
Show More
@@ -1,770 +1,766 b''
1 1 """Nose Plugin that supports IPython doctests.
2 2
3 3 Limitations:
4 4
5 5 - When generating examples for use as doctests, make sure that you have
6 6 pretty-printing OFF. This can be done either by setting the
7 7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
8 8 by interactively disabling it with %Pprint. This is required so that IPython
9 9 output matches that of normal Python, which is used by doctest for internal
10 10 execution.
11 11
12 12 - Do not rely on specific prompt numbers for results (such as using
13 13 '_34==True', for example). For IPython tests run via an external process the
14 14 prompt numbers may be different, and IPython tests run as normal python code
15 15 won't even have these special _NN variables set at all.
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Module imports
20 20
21 21 # From the standard library
22 22 import doctest
23 23 import inspect
24 24 import logging
25 25 import os
26 26 import re
27 27 import sys
28 28 from importlib import import_module
29 from io import StringIO
29 30
30 31 from testpath import modified_env
31 32
32 33 from inspect import getmodule
33 34
34 35 # We are overriding the default doctest runner, so we need to import a few
35 36 # things from doctest directly
36 37 from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
37 38 _unittest_reportflags, DocTestRunner,
38 39 _extract_future_flags, pdb, _OutputRedirectingPdb,
39 40 _exception_traceback,
40 41 linecache)
41 42
42 43 # Third-party modules
43 44
44 45 from nose.plugins import doctests, Plugin
45 46 from nose.util import anyp, tolist
46 47
47 48 # Our own imports
48 from IPython.utils.py3compat import builtin_mod, PY3
49
50 if PY3:
51 from io import StringIO
52 else:
53 from StringIO import StringIO
49 from IPython.utils.py3compat import builtin_mod
54 50
55 51 #-----------------------------------------------------------------------------
56 52 # Module globals and other constants
57 53 #-----------------------------------------------------------------------------
58 54
59 55 log = logging.getLogger(__name__)
60 56
61 57
62 58 #-----------------------------------------------------------------------------
63 59 # Classes and functions
64 60 #-----------------------------------------------------------------------------
65 61
66 62 def is_extension_module(filename):
67 63 """Return whether the given filename is an extension module.
68 64
69 65 This simply checks that the extension is either .so or .pyd.
70 66 """
71 67 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
72 68
73 69
74 70 class DocTestSkip(object):
75 71 """Object wrapper for doctests to be skipped."""
76 72
77 73 ds_skip = """Doctest to skip.
78 74 >>> 1 #doctest: +SKIP
79 75 """
80 76
81 77 def __init__(self,obj):
82 78 self.obj = obj
83 79
84 80 def __getattribute__(self,key):
85 81 if key == '__doc__':
86 82 return DocTestSkip.ds_skip
87 83 else:
88 84 return getattr(object.__getattribute__(self,'obj'),key)
89 85
90 86 # Modified version of the one in the stdlib, that fixes a python bug (doctests
91 87 # not found in extension modules, http://bugs.python.org/issue3158)
92 88 class DocTestFinder(doctest.DocTestFinder):
93 89
94 90 def _from_module(self, module, object):
95 91 """
96 92 Return true if the given object is defined in the given
97 93 module.
98 94 """
99 95 if module is None:
100 96 return True
101 97 elif inspect.isfunction(object):
102 98 return module.__dict__ is object.__globals__
103 99 elif inspect.isbuiltin(object):
104 100 return module.__name__ == object.__module__
105 101 elif inspect.isclass(object):
106 102 return module.__name__ == object.__module__
107 103 elif inspect.ismethod(object):
108 104 # This one may be a bug in cython that fails to correctly set the
109 105 # __module__ attribute of methods, but since the same error is easy
110 106 # to make by extension code writers, having this safety in place
111 107 # isn't such a bad idea
112 108 return module.__name__ == object.__self__.__class__.__module__
113 109 elif inspect.getmodule(object) is not None:
114 110 return module is inspect.getmodule(object)
115 111 elif hasattr(object, '__module__'):
116 112 return module.__name__ == object.__module__
117 113 elif isinstance(object, property):
118 114 return True # [XX] no way not be sure.
119 115 elif inspect.ismethoddescriptor(object):
120 116 # Unbound PyQt signals reach this point in Python 3.4b3, and we want
121 117 # to avoid throwing an error. See also http://bugs.python.org/issue3158
122 118 return False
123 119 else:
124 120 raise ValueError("object must be a class or function, got %r" % object)
125 121
126 122 def _find(self, tests, obj, name, module, source_lines, globs, seen):
127 123 """
128 124 Find tests for the given object and any contained objects, and
129 125 add them to `tests`.
130 126 """
131 127 print('_find for:', obj, name, module) # dbg
132 128 if hasattr(obj,"skip_doctest"):
133 129 #print 'SKIPPING DOCTEST FOR:',obj # dbg
134 130 obj = DocTestSkip(obj)
135 131
136 132 doctest.DocTestFinder._find(self,tests, obj, name, module,
137 133 source_lines, globs, seen)
138 134
139 135 # Below we re-run pieces of the above method with manual modifications,
140 136 # because the original code is buggy and fails to correctly identify
141 137 # doctests in extension modules.
142 138
143 139 # Local shorthands
144 140 from inspect import isroutine, isclass
145 141
146 142 # Look for tests in a module's contained objects.
147 143 if inspect.ismodule(obj) and self._recurse:
148 144 for valname, val in obj.__dict__.items():
149 145 valname1 = '%s.%s' % (name, valname)
150 146 if ( (isroutine(val) or isclass(val))
151 147 and self._from_module(module, val) ):
152 148
153 149 self._find(tests, val, valname1, module, source_lines,
154 150 globs, seen)
155 151
156 152 # Look for tests in a class's contained objects.
157 153 if inspect.isclass(obj) and self._recurse:
158 154 #print 'RECURSE into class:',obj # dbg
159 155 for valname, val in obj.__dict__.items():
160 156 # Special handling for staticmethod/classmethod.
161 157 if isinstance(val, staticmethod):
162 158 val = getattr(obj, valname)
163 159 if isinstance(val, classmethod):
164 160 val = getattr(obj, valname).__func__
165 161
166 162 # Recurse to methods, properties, and nested classes.
167 163 if ((inspect.isfunction(val) or inspect.isclass(val) or
168 164 inspect.ismethod(val) or
169 165 isinstance(val, property)) and
170 166 self._from_module(module, val)):
171 167 valname = '%s.%s' % (name, valname)
172 168 self._find(tests, val, valname, module, source_lines,
173 169 globs, seen)
174 170
175 171
176 172 class IPDoctestOutputChecker(doctest.OutputChecker):
177 173 """Second-chance checker with support for random tests.
178 174
179 175 If the default comparison doesn't pass, this checker looks in the expected
180 176 output string for flags that tell us to ignore the output.
181 177 """
182 178
183 179 random_re = re.compile(r'#\s*random\s+')
184 180
185 181 def check_output(self, want, got, optionflags):
186 182 """Check output, accepting special markers embedded in the output.
187 183
188 184 If the output didn't pass the default validation but the special string
189 185 '#random' is included, we accept it."""
190 186
191 187 # Let the original tester verify first, in case people have valid tests
192 188 # that happen to have a comment saying '#random' embedded in.
193 189 ret = doctest.OutputChecker.check_output(self, want, got,
194 190 optionflags)
195 191 if not ret and self.random_re.search(want):
196 192 #print >> sys.stderr, 'RANDOM OK:',want # dbg
197 193 return True
198 194
199 195 return ret
200 196
201 197
202 198 class DocTestCase(doctests.DocTestCase):
203 199 """Proxy for DocTestCase: provides an address() method that
204 200 returns the correct address for the doctest case. Otherwise
205 201 acts as a proxy to the test case. To provide hints for address(),
206 202 an obj may also be passed -- this will be used as the test object
207 203 for purposes of determining the test address, if it is provided.
208 204 """
209 205
210 206 # Note: this method was taken from numpy's nosetester module.
211 207
212 208 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
213 209 # its constructor that blocks non-default arguments from being passed
214 210 # down into doctest.DocTestCase
215 211
216 212 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
217 213 checker=None, obj=None, result_var='_'):
218 214 self._result_var = result_var
219 215 doctests.DocTestCase.__init__(self, test,
220 216 optionflags=optionflags,
221 217 setUp=setUp, tearDown=tearDown,
222 218 checker=checker)
223 219 # Now we must actually copy the original constructor from the stdlib
224 220 # doctest class, because we can't call it directly and a bug in nose
225 221 # means it never gets passed the right arguments.
226 222
227 223 self._dt_optionflags = optionflags
228 224 self._dt_checker = checker
229 225 self._dt_test = test
230 226 self._dt_test_globs_ori = test.globs
231 227 self._dt_setUp = setUp
232 228 self._dt_tearDown = tearDown
233 229
234 230 # XXX - store this runner once in the object!
235 231 runner = IPDocTestRunner(optionflags=optionflags,
236 232 checker=checker, verbose=False)
237 233 self._dt_runner = runner
238 234
239 235
240 236 # Each doctest should remember the directory it was loaded from, so
241 237 # things like %run work without too many contortions
242 238 self._ori_dir = os.path.dirname(test.filename)
243 239
244 240 # Modified runTest from the default stdlib
245 241 def runTest(self):
246 242 test = self._dt_test
247 243 runner = self._dt_runner
248 244
249 245 old = sys.stdout
250 246 new = StringIO()
251 247 optionflags = self._dt_optionflags
252 248
253 249 if not (optionflags & REPORTING_FLAGS):
254 250 # The option flags don't include any reporting flags,
255 251 # so add the default reporting flags
256 252 optionflags |= _unittest_reportflags
257 253
258 254 try:
259 255 # Save our current directory and switch out to the one where the
260 256 # test was originally created, in case another doctest did a
261 257 # directory change. We'll restore this in the finally clause.
262 258 curdir = os.getcwd()
263 259 #print 'runTest in dir:', self._ori_dir # dbg
264 260 os.chdir(self._ori_dir)
265 261
266 262 runner.DIVIDER = "-"*70
267 263 failures, tries = runner.run(test,out=new.write,
268 264 clear_globs=False)
269 265 finally:
270 266 sys.stdout = old
271 267 os.chdir(curdir)
272 268
273 269 if failures:
274 270 raise self.failureException(self.format_failure(new.getvalue()))
275 271
276 272 def setUp(self):
277 273 """Modified test setup that syncs with ipython namespace"""
278 274 #print "setUp test", self._dt_test.examples # dbg
279 275 if isinstance(self._dt_test.examples[0], IPExample):
280 276 # for IPython examples *only*, we swap the globals with the ipython
281 277 # namespace, after updating it with the globals (which doctest
282 278 # fills with the necessary info from the module being tested).
283 279 self.user_ns_orig = {}
284 280 self.user_ns_orig.update(_ip.user_ns)
285 281 _ip.user_ns.update(self._dt_test.globs)
286 282 # We must remove the _ key in the namespace, so that Python's
287 283 # doctest code sets it naturally
288 284 _ip.user_ns.pop('_', None)
289 285 _ip.user_ns['__builtins__'] = builtin_mod
290 286 self._dt_test.globs = _ip.user_ns
291 287
292 288 super(DocTestCase, self).setUp()
293 289
294 290 def tearDown(self):
295 291
296 292 # Undo the test.globs reassignment we made, so that the parent class
297 293 # teardown doesn't destroy the ipython namespace
298 294 if isinstance(self._dt_test.examples[0], IPExample):
299 295 self._dt_test.globs = self._dt_test_globs_ori
300 296 _ip.user_ns.clear()
301 297 _ip.user_ns.update(self.user_ns_orig)
302 298
303 299 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
304 300 # it does look like one to me: its tearDown method tries to run
305 301 #
306 302 # delattr(builtin_mod, self._result_var)
307 303 #
308 304 # without checking that the attribute really is there; it implicitly
309 305 # assumes it should have been set via displayhook. But if the
310 306 # displayhook was never called, this doesn't necessarily happen. I
311 307 # haven't been able to find a little self-contained example outside of
312 308 # ipython that would show the problem so I can report it to the nose
313 309 # team, but it does happen a lot in our code.
314 310 #
315 311 # So here, we just protect as narrowly as possible by trapping an
316 312 # attribute error whose message would be the name of self._result_var,
317 313 # and letting any other error propagate.
318 314 try:
319 315 super(DocTestCase, self).tearDown()
320 316 except AttributeError as exc:
321 317 if exc.args[0] != self._result_var:
322 318 raise
323 319
324 320
325 321 # A simple subclassing of the original with a different class name, so we can
326 322 # distinguish and treat differently IPython examples from pure python ones.
327 323 class IPExample(doctest.Example): pass
328 324
329 325
330 326 class IPExternalExample(doctest.Example):
331 327 """Doctest examples to be run in an external process."""
332 328
333 329 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
334 330 options=None):
335 331 # Parent constructor
336 332 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
337 333
338 334 # An EXTRA newline is needed to prevent pexpect hangs
339 335 self.source += '\n'
340 336
341 337
342 338 class IPDocTestParser(doctest.DocTestParser):
343 339 """
344 340 A class used to parse strings containing doctest examples.
345 341
346 342 Note: This is a version modified to properly recognize IPython input and
347 343 convert any IPython examples into valid Python ones.
348 344 """
349 345 # This regular expression is used to find doctest examples in a
350 346 # string. It defines three groups: `source` is the source code
351 347 # (including leading indentation and prompts); `indent` is the
352 348 # indentation of the first (PS1) line of the source code; and
353 349 # `want` is the expected output (including leading indentation).
354 350
355 351 # Classic Python prompts or default IPython ones
356 352 _PS1_PY = r'>>>'
357 353 _PS2_PY = r'\.\.\.'
358 354
359 355 _PS1_IP = r'In\ \[\d+\]:'
360 356 _PS2_IP = r'\ \ \ \.\.\.+:'
361 357
362 358 _RE_TPL = r'''
363 359 # Source consists of a PS1 line followed by zero or more PS2 lines.
364 360 (?P<source>
365 361 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
366 362 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
367 363 \n? # a newline
368 364 # Want consists of any non-blank lines that do not start with PS1.
369 365 (?P<want> (?:(?![ ]*$) # Not a blank line
370 366 (?![ ]*%s) # Not a line starting with PS1
371 367 (?![ ]*%s) # Not a line starting with PS2
372 368 .*$\n? # But any other line
373 369 )*)
374 370 '''
375 371
376 372 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
377 373 re.MULTILINE | re.VERBOSE)
378 374
379 375 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
380 376 re.MULTILINE | re.VERBOSE)
381 377
382 378 # Mark a test as being fully random. In this case, we simply append the
383 379 # random marker ('#random') to each individual example's output. This way
384 380 # we don't need to modify any other code.
385 381 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
386 382
387 383 # Mark tests to be executed in an external process - currently unsupported.
388 384 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
389 385
390 386 def ip2py(self,source):
391 387 """Convert input IPython source into valid Python."""
392 388 block = _ip.input_transformer_manager.transform_cell(source)
393 389 if len(block.splitlines()) == 1:
394 390 return _ip.prefilter(block)
395 391 else:
396 392 return block
397 393
398 394 def parse(self, string, name='<string>'):
399 395 """
400 396 Divide the given string into examples and intervening text,
401 397 and return them as a list of alternating Examples and strings.
402 398 Line numbers for the Examples are 0-based. The optional
403 399 argument `name` is a name identifying this string, and is only
404 400 used for error messages.
405 401 """
406 402
407 403 #print 'Parse string:\n',string # dbg
408 404
409 405 string = string.expandtabs()
410 406 # If all lines begin with the same indentation, then strip it.
411 407 min_indent = self._min_indent(string)
412 408 if min_indent > 0:
413 409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
414 410
415 411 output = []
416 412 charno, lineno = 0, 0
417 413
418 414 # We make 'all random' tests by adding the '# random' mark to every
419 415 # block of output in the test.
420 416 if self._RANDOM_TEST.search(string):
421 417 random_marker = '\n# random'
422 418 else:
423 419 random_marker = ''
424 420
425 421 # Whether to convert the input from ipython to python syntax
426 422 ip2py = False
427 423 # Find all doctest examples in the string. First, try them as Python
428 424 # examples, then as IPython ones
429 425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
430 426 if terms:
431 427 # Normal Python example
432 428 #print '-'*70 # dbg
433 429 #print 'PyExample, Source:\n',string # dbg
434 430 #print '-'*70 # dbg
435 431 Example = doctest.Example
436 432 else:
437 433 # It's an ipython example. Note that IPExamples are run
438 434 # in-process, so their syntax must be turned into valid python.
439 435 # IPExternalExamples are run out-of-process (via pexpect) so they
440 436 # don't need any filtering (a real ipython will be executing them).
441 437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
442 438 if self._EXTERNAL_IP.search(string):
443 439 #print '-'*70 # dbg
444 440 #print 'IPExternalExample, Source:\n',string # dbg
445 441 #print '-'*70 # dbg
446 442 Example = IPExternalExample
447 443 else:
448 444 #print '-'*70 # dbg
449 445 #print 'IPExample, Source:\n',string # dbg
450 446 #print '-'*70 # dbg
451 447 Example = IPExample
452 448 ip2py = True
453 449
454 450 for m in terms:
455 451 # Add the pre-example text to `output`.
456 452 output.append(string[charno:m.start()])
457 453 # Update lineno (lines before this example)
458 454 lineno += string.count('\n', charno, m.start())
459 455 # Extract info from the regexp match.
460 456 (source, options, want, exc_msg) = \
461 457 self._parse_example(m, name, lineno,ip2py)
462 458
463 459 # Append the random-output marker (it defaults to empty in most
464 460 # cases, it's only non-empty for 'all-random' tests):
465 461 want += random_marker
466 462
467 463 if Example is IPExternalExample:
468 464 options[doctest.NORMALIZE_WHITESPACE] = True
469 465 want += '\n'
470 466
471 467 # Create an Example, and add it to the list.
472 468 if not self._IS_BLANK_OR_COMMENT(source):
473 469 output.append(Example(source, want, exc_msg,
474 470 lineno=lineno,
475 471 indent=min_indent+len(m.group('indent')),
476 472 options=options))
477 473 # Update lineno (lines inside this example)
478 474 lineno += string.count('\n', m.start(), m.end())
479 475 # Update charno.
480 476 charno = m.end()
481 477 # Add any remaining post-example text to `output`.
482 478 output.append(string[charno:])
483 479 return output
484 480
485 481 def _parse_example(self, m, name, lineno,ip2py=False):
486 482 """
487 483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
488 484 return a pair `(source, want)`, where `source` is the matched
489 485 example's source code (with prompts and indentation stripped);
490 486 and `want` is the example's expected output (with indentation
491 487 stripped).
492 488
493 489 `name` is the string's name, and `lineno` is the line number
494 490 where the example starts; both are used for error messages.
495 491
496 492 Optional:
497 493 `ip2py`: if true, filter the input via IPython to convert the syntax
498 494 into valid python.
499 495 """
500 496
501 497 # Get the example's indentation level.
502 498 indent = len(m.group('indent'))
503 499
504 500 # Divide source into lines; check that they're properly
505 501 # indented; and then strip their indentation & prompts.
506 502 source_lines = m.group('source').split('\n')
507 503
508 504 # We're using variable-length input prompts
509 505 ps1 = m.group('ps1')
510 506 ps2 = m.group('ps2')
511 507 ps1_len = len(ps1)
512 508
513 509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
514 510 if ps2:
515 511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
516 512
517 513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
518 514
519 515 if ip2py:
520 516 # Convert source input from IPython into valid Python syntax
521 517 source = self.ip2py(source)
522 518
523 519 # Divide want into lines; check that it's properly indented; and
524 520 # then strip the indentation. Spaces before the last newline should
525 521 # be preserved, so plain rstrip() isn't good enough.
526 522 want = m.group('want')
527 523 want_lines = want.split('\n')
528 524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
529 525 del want_lines[-1] # forget final newline & spaces after it
530 526 self._check_prefix(want_lines, ' '*indent, name,
531 527 lineno + len(source_lines))
532 528
533 529 # Remove ipython output prompt that might be present in the first line
534 530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
535 531
536 532 want = '\n'.join([wl[indent:] for wl in want_lines])
537 533
538 534 # If `want` contains a traceback message, then extract it.
539 535 m = self._EXCEPTION_RE.match(want)
540 536 if m:
541 537 exc_msg = m.group('msg')
542 538 else:
543 539 exc_msg = None
544 540
545 541 # Extract options from the source.
546 542 options = self._find_options(source, name, lineno)
547 543
548 544 return source, options, want, exc_msg
549 545
550 546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
551 547 """
552 548 Given the lines of a source string (including prompts and
553 549 leading indentation), check to make sure that every prompt is
554 550 followed by a space character. If any line is not followed by
555 551 a space character, then raise ValueError.
556 552
557 553 Note: IPython-modified version which takes the input prompt length as a
558 554 parameter, so that prompts of variable length can be dealt with.
559 555 """
560 556 space_idx = indent+ps1_len
561 557 min_len = space_idx+1
562 558 for i, line in enumerate(lines):
563 559 if len(line) >= min_len and line[space_idx] != ' ':
564 560 raise ValueError('line %r of the docstring for %s '
565 561 'lacks blank after %s: %r' %
566 562 (lineno+i+1, name,
567 563 line[indent:space_idx], line))
568 564
569 565
570 566 SKIP = doctest.register_optionflag('SKIP')
571 567
572 568
573 569 class IPDocTestRunner(doctest.DocTestRunner,object):
574 570 """Test runner that synchronizes the IPython namespace with test globals.
575 571 """
576 572
577 573 def run(self, test, compileflags=None, out=None, clear_globs=True):
578 574
579 575 # Hack: ipython needs access to the execution context of the example,
580 576 # so that it can propagate user variables loaded by %run into
581 577 # test.globs. We put them here into our modified %run as a function
582 578 # attribute. Our new %run will then only make the namespace update
583 579 # when called (rather than unconconditionally updating test.globs here
584 580 # for all examples, most of which won't be calling %run anyway).
585 581 #_ip._ipdoctest_test_globs = test.globs
586 582 #_ip._ipdoctest_test_filename = test.filename
587 583
588 584 test.globs.update(_ip.user_ns)
589 585
590 586 # Override terminal size to standardise traceback format
591 587 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
592 588 return super(IPDocTestRunner,self).run(test,
593 589 compileflags,out,clear_globs)
594 590
595 591
596 592 class DocFileCase(doctest.DocFileCase):
597 593 """Overrides to provide filename
598 594 """
599 595 def address(self):
600 596 return (self._dt_test.filename, None, None)
601 597
602 598
603 599 class ExtensionDoctest(doctests.Doctest):
604 600 """Nose Plugin that supports doctests in extension modules.
605 601 """
606 602 name = 'extdoctest' # call nosetests with --with-extdoctest
607 603 enabled = True
608 604
609 605 def options(self, parser, env=os.environ):
610 606 Plugin.options(self, parser, env)
611 607 parser.add_option('--doctest-tests', action='store_true',
612 608 dest='doctest_tests',
613 609 default=env.get('NOSE_DOCTEST_TESTS',True),
614 610 help="Also look for doctests in test modules. "
615 611 "Note that classes, methods and functions should "
616 612 "have either doctests or non-doctest tests, "
617 613 "not both. [NOSE_DOCTEST_TESTS]")
618 614 parser.add_option('--doctest-extension', action="append",
619 615 dest="doctestExtension",
620 616 help="Also look for doctests in files with "
621 617 "this extension [NOSE_DOCTEST_EXTENSION]")
622 618 # Set the default as a list, if given in env; otherwise
623 619 # an additional value set on the command line will cause
624 620 # an error.
625 621 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
626 622 if env_setting is not None:
627 623 parser.set_defaults(doctestExtension=tolist(env_setting))
628 624
629 625
630 626 def configure(self, options, config):
631 627 Plugin.configure(self, options, config)
632 628 # Pull standard doctest plugin out of config; we will do doctesting
633 629 config.plugins.plugins = [p for p in config.plugins.plugins
634 630 if p.name != 'doctest']
635 631 self.doctest_tests = options.doctest_tests
636 632 self.extension = tolist(options.doctestExtension)
637 633
638 634 self.parser = doctest.DocTestParser()
639 635 self.finder = DocTestFinder()
640 636 self.checker = IPDoctestOutputChecker()
641 637 self.globs = None
642 638 self.extraglobs = None
643 639
644 640
645 641 def loadTestsFromExtensionModule(self,filename):
646 642 bpath,mod = os.path.split(filename)
647 643 modname = os.path.splitext(mod)[0]
648 644 try:
649 645 sys.path.append(bpath)
650 646 module = import_module(modname)
651 647 tests = list(self.loadTestsFromModule(module))
652 648 finally:
653 649 sys.path.pop()
654 650 return tests
655 651
656 652 # NOTE: the method below is almost a copy of the original one in nose, with
657 653 # a few modifications to control output checking.
658 654
659 655 def loadTestsFromModule(self, module):
660 656 #print '*** ipdoctest - lTM',module # dbg
661 657
662 658 if not self.matches(module.__name__):
663 659 log.debug("Doctest doesn't want module %s", module)
664 660 return
665 661
666 662 tests = self.finder.find(module,globs=self.globs,
667 663 extraglobs=self.extraglobs)
668 664 if not tests:
669 665 return
670 666
671 667 # always use whitespace and ellipsis options
672 668 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
673 669
674 670 tests.sort()
675 671 module_file = module.__file__
676 672 if module_file[-4:] in ('.pyc', '.pyo'):
677 673 module_file = module_file[:-1]
678 674 for test in tests:
679 675 if not test.examples:
680 676 continue
681 677 if not test.filename:
682 678 test.filename = module_file
683 679
684 680 yield DocTestCase(test,
685 681 optionflags=optionflags,
686 682 checker=self.checker)
687 683
688 684
689 685 def loadTestsFromFile(self, filename):
690 686 #print "ipdoctest - from file", filename # dbg
691 687 if is_extension_module(filename):
692 688 for t in self.loadTestsFromExtensionModule(filename):
693 689 yield t
694 690 else:
695 691 if self.extension and anyp(filename.endswith, self.extension):
696 692 name = os.path.basename(filename)
697 693 dh = open(filename)
698 694 try:
699 695 doc = dh.read()
700 696 finally:
701 697 dh.close()
702 698 test = self.parser.get_doctest(
703 699 doc, globs={'__file__': filename}, name=name,
704 700 filename=filename, lineno=0)
705 701 if test.examples:
706 702 #print 'FileCase:',test.examples # dbg
707 703 yield DocFileCase(test)
708 704 else:
709 705 yield False # no tests to load
710 706
711 707
712 708 class IPythonDoctest(ExtensionDoctest):
713 709 """Nose Plugin that supports doctests in extension modules.
714 710 """
715 711 name = 'ipdoctest' # call nosetests with --with-ipdoctest
716 712 enabled = True
717 713
718 714 def makeTest(self, obj, parent):
719 715 """Look for doctests in the given object, which will be a
720 716 function, method or class.
721 717 """
722 718 #print 'Plugin analyzing:', obj, parent # dbg
723 719 # always use whitespace and ellipsis options
724 720 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
725 721
726 722 doctests = self.finder.find(obj, module=getmodule(parent))
727 723 if doctests:
728 724 for test in doctests:
729 725 if len(test.examples) == 0:
730 726 continue
731 727
732 728 yield DocTestCase(test, obj=obj,
733 729 optionflags=optionflags,
734 730 checker=self.checker)
735 731
736 732 def options(self, parser, env=os.environ):
737 733 #print "Options for nose plugin:", self.name # dbg
738 734 Plugin.options(self, parser, env)
739 735 parser.add_option('--ipdoctest-tests', action='store_true',
740 736 dest='ipdoctest_tests',
741 737 default=env.get('NOSE_IPDOCTEST_TESTS',True),
742 738 help="Also look for doctests in test modules. "
743 739 "Note that classes, methods and functions should "
744 740 "have either doctests or non-doctest tests, "
745 741 "not both. [NOSE_IPDOCTEST_TESTS]")
746 742 parser.add_option('--ipdoctest-extension', action="append",
747 743 dest="ipdoctest_extension",
748 744 help="Also look for doctests in files with "
749 745 "this extension [NOSE_IPDOCTEST_EXTENSION]")
750 746 # Set the default as a list, if given in env; otherwise
751 747 # an additional value set on the command line will cause
752 748 # an error.
753 749 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
754 750 if env_setting is not None:
755 751 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
756 752
757 753 def configure(self, options, config):
758 754 #print "Configuring nose plugin:", self.name # dbg
759 755 Plugin.configure(self, options, config)
760 756 # Pull standard doctest plugin out of config; we will do doctesting
761 757 config.plugins.plugins = [p for p in config.plugins.plugins
762 758 if p.name != 'doctest']
763 759 self.doctest_tests = options.ipdoctest_tests
764 760 self.extension = tolist(options.ipdoctest_extension)
765 761
766 762 self.parser = IPDocTestParser()
767 763 self.finder = DocTestFinder(parser=self.parser)
768 764 self.checker = IPDoctestOutputChecker()
769 765 self.globs = None
770 766 self.extraglobs = None
General Comments 0
You need to be logged in to leave comments. Login now