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