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