##// END OF EJS Templates
Minor cleanups.
Fernando Perez -
Show More
@@ -1,601 +1,598 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 - IPython functions that produce output as a side-effect of calling a system
18 18 process (e.g. 'ls') can be doc-tested, but they must be handled in an
19 19 external IPython process. Such doctests must be tagged with:
20 20
21 21 # ipdoctest: EXTERNAL
22 22
23 23 so that the testing machinery handles them differently. Since these are run
24 24 via pexpect in an external process, they can't deal with exceptions or other
25 25 fancy featurs of regular doctests. You must limit such tests to simple
26 26 matching of the output. For this reason, I recommend you limit these kinds
27 27 of doctests to features that truly require a separate process, and use the
28 28 normal IPython ones (which have all the features of normal doctests) for
29 29 everything else. See the examples at the bottom of this file for a
30 30 comparison of what can be done with both types.
31 31 """
32 32
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Module imports
36 36
37 37 # From the standard library
38 38 import __builtin__
39 39 import commands
40 40 import doctest
41 41 import inspect
42 42 import logging
43 43 import os
44 44 import re
45 45 import sys
46 46 import unittest
47 47
48 48 from inspect import getmodule
49 49
50 50 # Third-party modules
51 51 import nose.core
52 52
53 53 from nose.plugins import doctests, Plugin
54 54 from nose.util import anyp, getpackage, test_address, resolve_name, tolist
55 55
56 56 # Our own imports
57 57 #from extdoctest import ExtensionDoctest, DocTestFinder
58 58 #from dttools import DocTestFinder, DocTestCase
59 59 #-----------------------------------------------------------------------------
60 60 # Module globals and other constants
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64 ###########################################################################
65 65 # *** HACK ***
66 66 # We must start our own ipython object and heavily muck with it so that all the
67 67 # modifications IPython makes to system behavior don't send the doctest
68 68 # machinery into a fit. This code should be considered a gross hack, but it
69 69 # gets the job done.
70 70
71 71 def start_ipython():
72 72 """Start a global IPython shell, which we need for IPython-specific syntax.
73 73 """
74 74 import IPython
75 75
76 76 def xsys(cmd):
77 77 """Execute a command and print its output.
78 78
79 79 This is just a convenience function to replace the IPython system call
80 80 with one that is more doctest-friendly.
81 81 """
82 82 cmd = _ip.IP.var_expand(cmd,depth=1)
83 83 sys.stdout.write(commands.getoutput(cmd))
84 84 sys.stdout.flush()
85 85
86 86 # Store certain global objects that IPython modifies
87 87 _displayhook = sys.displayhook
88 88 _excepthook = sys.excepthook
89 89 _main = sys.modules.get('__main__')
90 90
91 91 # Start IPython instance
92 92 IPython.Shell.IPShell(['--classic','--noterm_title'])
93 93
94 94 # Deactivate the various python system hooks added by ipython for
95 95 # interactive convenience so we don't confuse the doctest system
96 96 sys.modules['__main__'] = _main
97 97 sys.displayhook = _displayhook
98 98 sys.excepthook = _excepthook
99 99
100 100 # So that ipython magics and aliases can be doctested (they work by making
101 101 # a call into a global _ip object)
102 102 _ip = IPython.ipapi.get()
103 103 __builtin__._ip = _ip
104 104
105 105 # Modify the IPython system call with one that uses getoutput, so that we
106 106 # can capture subcommands and print them to Python's stdout, otherwise the
107 107 # doctest machinery would miss them.
108 108 _ip.system = xsys
109 109
110 110 # The start call MUST be made here. I'm not sure yet why it doesn't work if
111 111 # it is made later, at plugin initialization time, but in all my tests, that's
112 112 # the case.
113 113 start_ipython()
114 114
115 115 # *** END HACK ***
116 116 ###########################################################################
117 117
118 #-----------------------------------------------------------------------------
118 # Classes and functions
119
120 def is_extension_module(filename):
121 """Return whether the given filename is an extension module.
122
123 This simply checks that the extension is either .so or .pyd.
124 """
125 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
126
127
119 128 # Modified version of the one in the stdlib, that fixes a python bug (doctests
120 129 # not found in extension modules, http://bugs.python.org/issue3158)
121 130 class DocTestFinder(doctest.DocTestFinder):
122 131
123 132 def _from_module(self, module, object):
124 133 """
125 134 Return true if the given object is defined in the given
126 135 module.
127 136 """
128 137 if module is None:
129 138 #print '_fm C1' # dbg
130 139 return True
131 140 elif inspect.isfunction(object):
132 141 #print '_fm C2' # dbg
133 142 return module.__dict__ is object.func_globals
134 143 elif inspect.isbuiltin(object):
135 144 #print '_fm C2-1' # dbg
136 145 return module.__name__ == object.__module__
137 146 elif inspect.isclass(object):
138 147 #print '_fm C3' # dbg
139 148 return module.__name__ == object.__module__
140 149 elif inspect.ismethod(object):
141 150 # This one may be a bug in cython that fails to correctly set the
142 151 # __module__ attribute of methods, but since the same error is easy
143 152 # to make by extension code writers, having this safety in place
144 153 # isn't such a bad idea
145 154 #print '_fm C3-1' # dbg
146 155 return module.__name__ == object.im_class.__module__
147 156 elif inspect.getmodule(object) is not None:
148 157 #print '_fm C4' # dbg
149 158 #print 'C4 mod',module,'obj',object # dbg
150 159 return module is inspect.getmodule(object)
151 160 elif hasattr(object, '__module__'):
152 161 #print '_fm C5' # dbg
153 162 return module.__name__ == object.__module__
154 163 elif isinstance(object, property):
155 164 #print '_fm C6' # dbg
156 165 return True # [XX] no way not be sure.
157 166 else:
158 167 raise ValueError("object must be a class or function")
159 168
160
161
162 169 def _find(self, tests, obj, name, module, source_lines, globs, seen):
163 170 """
164 171 Find tests for the given object and any contained objects, and
165 172 add them to `tests`.
166 173 """
167 174
168 175 doctest.DocTestFinder._find(self,tests, obj, name, module,
169 176 source_lines, globs, seen)
170 177
171 178 # Below we re-run pieces of the above method with manual modifications,
172 179 # because the original code is buggy and fails to correctly identify
173 180 # doctests in extension modules.
174 181
175 182 # Local shorthands
176 183 from inspect import isroutine, isclass, ismodule
177 184
178 185 # Look for tests in a module's contained objects.
179 186 if inspect.ismodule(obj) and self._recurse:
180 187 for valname, val in obj.__dict__.items():
181 188 valname1 = '%s.%s' % (name, valname)
182 189 if ( (isroutine(val) or isclass(val))
183 190 and self._from_module(module, val) ):
184 191
185 192 self._find(tests, val, valname1, module, source_lines,
186 193 globs, seen)
187 194
188
189 195 # Look for tests in a class's contained objects.
190 196 if inspect.isclass(obj) and self._recurse:
191 197 #print 'RECURSE into class:',obj # dbg
192 198 for valname, val in obj.__dict__.items():
193 199 #valname1 = '%s.%s' % (name, valname) # dbg
194 200 #print 'N',name,'VN:',valname,'val:',str(val)[:77] # dbg
195 201 # Special handling for staticmethod/classmethod.
196 202 if isinstance(val, staticmethod):
197 203 val = getattr(obj, valname)
198 204 if isinstance(val, classmethod):
199 205 val = getattr(obj, valname).im_func
200 206
201 207 # Recurse to methods, properties, and nested classes.
202 208 if ((inspect.isfunction(val) or inspect.isclass(val) or
203 209 inspect.ismethod(val) or
204 210 isinstance(val, property)) and
205 211 self._from_module(module, val)):
206 212 valname = '%s.%s' % (name, valname)
207 213 self._find(tests, val, valname, module, source_lines,
208 214 globs, seen)
209 215
210 216
211 217 class DocTestCase(doctests.DocTestCase):
212 218 """Proxy for DocTestCase: provides an address() method that
213 219 returns the correct address for the doctest case. Otherwise
214 220 acts as a proxy to the test case. To provide hints for address(),
215 221 an obj may also be passed -- this will be used as the test object
216 222 for purposes of determining the test address, if it is provided.
217 223 """
218 224
219 225 # doctests loaded via find(obj) omit the module name
220 226 # so we need to override id, __repr__ and shortDescription
221 227 # bonus: this will squash a 2.3 vs 2.4 incompatiblity
222 228 def id(self):
223 229 name = self._dt_test.name
224 230 filename = self._dt_test.filename
225 231 if filename is not None:
226 232 pk = getpackage(filename)
227 233 if pk is not None and not name.startswith(pk):
228 234 name = "%s.%s" % (pk, name)
229 235 return name
230 236
231 237
232 # Classes and functions
233
234 def is_extension_module(filename):
235 """Return whether the given filename is an extension module.
236
237 This simply checks that the extension is either .so or .pyd.
238 """
239 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
240
241
242 238 # A simple subclassing of the original with a different class name, so we can
243 239 # distinguish and treat differently IPython examples from pure python ones.
244 240 class IPExample(doctest.Example): pass
245 241
242
246 243 class IPExternalExample(doctest.Example):
247 244 """Doctest examples to be run in an external process."""
248 245
249 246 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
250 247 options=None):
251 248 # Parent constructor
252 249 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
253 250
254 251 # An EXTRA newline is needed to prevent pexpect hangs
255 252 self.source += '\n'
256 253
254
257 255 class IPDocTestParser(doctest.DocTestParser):
258 256 """
259 257 A class used to parse strings containing doctest examples.
260 258
261 259 Note: This is a version modified to properly recognize IPython input and
262 260 convert any IPython examples into valid Python ones.
263 261 """
264 262 # This regular expression is used to find doctest examples in a
265 263 # string. It defines three groups: `source` is the source code
266 264 # (including leading indentation and prompts); `indent` is the
267 265 # indentation of the first (PS1) line of the source code; and
268 266 # `want` is the expected output (including leading indentation).
269 267
270 268 # Classic Python prompts or default IPython ones
271 269 _PS1_PY = r'>>>'
272 270 _PS2_PY = r'\.\.\.'
273 271
274 272 _PS1_IP = r'In\ \[\d+\]:'
275 273 _PS2_IP = r'\ \ \ \.\.\.+:'
276 274
277 275 _RE_TPL = r'''
278 276 # Source consists of a PS1 line followed by zero or more PS2 lines.
279 277 (?P<source>
280 278 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
281 279 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
282 280 \n? # a newline
283 281 # Want consists of any non-blank lines that do not start with PS1.
284 282 (?P<want> (?:(?![ ]*$) # Not a blank line
285 283 (?![ ]*%s) # Not a line starting with PS1
286 284 (?![ ]*%s) # Not a line starting with PS2
287 285 .*$\n? # But any other line
288 286 )*)
289 287 '''
290 288
291 289 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
292 290 re.MULTILINE | re.VERBOSE)
293 291
294 292 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
295 293 re.MULTILINE | re.VERBOSE)
296 294
297 295 def ip2py(self,source):
298 296 """Convert input IPython source into valid Python."""
299 297 out = []
300 298 newline = out.append
301 299 for lnum,line in enumerate(source.splitlines()):
302 300 #newline(_ip.IPipython.prefilter(line,True))
303 301 newline(_ip.IP.prefilter(line,lnum>0))
304 302 newline('') # ensure a closing newline, needed by doctest
305 303 return '\n'.join(out)
306 304
307 305 def parse(self, string, name='<string>'):
308 306 """
309 307 Divide the given string into examples and intervening text,
310 308 and return them as a list of alternating Examples and strings.
311 309 Line numbers for the Examples are 0-based. The optional
312 310 argument `name` is a name identifying this string, and is only
313 311 used for error messages.
314 312 """
315 313
316 314 #print 'Parse string:\n',string # dbg
317 315
318 316 string = string.expandtabs()
319 317 # If all lines begin with the same indentation, then strip it.
320 318 min_indent = self._min_indent(string)
321 319 if min_indent > 0:
322 320 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
323 321
324 322 output = []
325 323 charno, lineno = 0, 0
326 324
327 325 # Whether to convert the input from ipython to python syntax
328 326 ip2py = False
329 327 # Find all doctest examples in the string. First, try them as Python
330 328 # examples, then as IPython ones
331 329 terms = list(self._EXAMPLE_RE_PY.finditer(string))
332 330 if terms:
333 331 # Normal Python example
334 332 #print '-'*70 # dbg
335 333 #print 'PyExample, Source:\n',string # dbg
336 334 #print '-'*70 # dbg
337 335 Example = doctest.Example
338 336 else:
339 337 # It's an ipython example. Note that IPExamples are run
340 338 # in-process, so their syntax must be turned into valid python.
341 339 # IPExternalExamples are run out-of-process (via pexpect) so they
342 340 # don't need any filtering (a real ipython will be executing them).
343 341 terms = list(self._EXAMPLE_RE_IP.finditer(string))
344 342 if re.search(r'#\s*ipdoctest:\s*EXTERNAL',string):
345 343 #print '-'*70 # dbg
346 344 #print 'IPExternalExample, Source:\n',string # dbg
347 345 #print '-'*70 # dbg
348 346 Example = IPExternalExample
349 347 else:
350 348 #print '-'*70 # dbg
351 349 #print 'IPExample, Source:\n',string # dbg
352 350 #print '-'*70 # dbg
353 351 Example = IPExample
354 352 ip2py = True
355 353
356 354 for m in terms:
357 355 # Add the pre-example text to `output`.
358 356 output.append(string[charno:m.start()])
359 357 # Update lineno (lines before this example)
360 358 lineno += string.count('\n', charno, m.start())
361 359 # Extract info from the regexp match.
362 360 (source, options, want, exc_msg) = \
363 361 self._parse_example(m, name, lineno,ip2py)
364 362 if Example is IPExternalExample:
365 363 options[doctest.NORMALIZE_WHITESPACE] = True
366 364 want += '\n'
367 365 # Create an Example, and add it to the list.
368 366 if not self._IS_BLANK_OR_COMMENT(source):
369 367 #print 'Example source:', source # dbg
370 368 output.append(Example(source, want, exc_msg,
371 369 lineno=lineno,
372 370 indent=min_indent+len(m.group('indent')),
373 371 options=options))
374 372 # Update lineno (lines inside this example)
375 373 lineno += string.count('\n', m.start(), m.end())
376 374 # Update charno.
377 375 charno = m.end()
378 376 # Add any remaining post-example text to `output`.
379 377 output.append(string[charno:])
380 378
381 379 return output
382 380
383 381 def _parse_example(self, m, name, lineno,ip2py=False):
384 382 """
385 383 Given a regular expression match from `_EXAMPLE_RE` (`m`),
386 384 return a pair `(source, want)`, where `source` is the matched
387 385 example's source code (with prompts and indentation stripped);
388 386 and `want` is the example's expected output (with indentation
389 387 stripped).
390 388
391 389 `name` is the string's name, and `lineno` is the line number
392 390 where the example starts; both are used for error messages.
393 391
394 392 Optional:
395 393 `ip2py`: if true, filter the input via IPython to convert the syntax
396 394 into valid python.
397 395 """
398 396
399 397 # Get the example's indentation level.
400 398 indent = len(m.group('indent'))
401 399
402 400 # Divide source into lines; check that they're properly
403 401 # indented; and then strip their indentation & prompts.
404 402 source_lines = m.group('source').split('\n')
405 403
406 404 # We're using variable-length input prompts
407 405 ps1 = m.group('ps1')
408 406 ps2 = m.group('ps2')
409 407 ps1_len = len(ps1)
410 408
411 409 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
412 410 if ps2:
413 411 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
414 412
415 413 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
416 414
417 415 if ip2py:
418 416 # Convert source input from IPython into valid Python syntax
419 417 source = self.ip2py(source)
420 418
421 419 # Divide want into lines; check that it's properly indented; and
422 420 # then strip the indentation. Spaces before the last newline should
423 421 # be preserved, so plain rstrip() isn't good enough.
424 422 want = m.group('want')
425 423 want_lines = want.split('\n')
426 424 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
427 425 del want_lines[-1] # forget final newline & spaces after it
428 426 self._check_prefix(want_lines, ' '*indent, name,
429 427 lineno + len(source_lines))
430 428
431 429 # Remove ipython output prompt that might be present in the first line
432 430 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
433 431
434 432 want = '\n'.join([wl[indent:] for wl in want_lines])
435 433
436 434 # If `want` contains a traceback message, then extract it.
437 435 m = self._EXCEPTION_RE.match(want)
438 436 if m:
439 437 exc_msg = m.group('msg')
440 438 else:
441 439 exc_msg = None
442 440
443 441 # Extract options from the source.
444 442 options = self._find_options(source, name, lineno)
445 443
446 444 return source, options, want, exc_msg
447 445
448 446 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
449 447 """
450 448 Given the lines of a source string (including prompts and
451 449 leading indentation), check to make sure that every prompt is
452 450 followed by a space character. If any line is not followed by
453 451 a space character, then raise ValueError.
454 452
455 453 Note: IPython-modified version which takes the input prompt length as a
456 454 parameter, so that prompts of variable length can be dealt with.
457 455 """
458 456 space_idx = indent+ps1_len
459 457 min_len = space_idx+1
460 458 for i, line in enumerate(lines):
461 459 if len(line) >= min_len and line[space_idx] != ' ':
462 460 raise ValueError('line %r of the docstring for %s '
463 461 'lacks blank after %s: %r' %
464 462 (lineno+i+1, name,
465 463 line[indent:space_idx], line))
466 464
467 465 SKIP = doctest.register_optionflag('SKIP')
468 466
469 ###########################################################################
470 467
471 468 class DocFileCase(doctest.DocFileCase):
472 469 """Overrides to provide filename
473 470 """
474 471 def address(self):
475 472 return (self._dt_test.filename, None, None)
476 473
477 474
478 475 class ExtensionDoctest(doctests.Doctest):
479 476 """Nose Plugin that supports doctests in extension modules.
480 477 """
481 478 name = 'extdoctest' # call nosetests with --with-extdoctest
482 479 enabled = True
483 480
484 481 def options(self, parser, env=os.environ):
485 482 Plugin.options(self, parser, env)
486 483
487 484 def configure(self, options, config):
488 485 Plugin.configure(self, options, config)
489 486 self.doctest_tests = options.doctest_tests
490 487 self.extension = tolist(options.doctestExtension)
491 488 self.finder = DocTestFinder()
492 489 self.parser = doctest.DocTestParser()
493 490
494 491
495 492 def loadTestsFromExtensionModule(self,filename):
496 493 bpath,mod = os.path.split(filename)
497 494 modname = os.path.splitext(mod)[0]
498 495 try:
499 496 sys.path.append(bpath)
500 497 module = __import__(modname)
501 498 tests = list(self.loadTestsFromModule(module))
502 499 finally:
503 500 sys.path.pop()
504 501 return tests
505 502
506 503 def loadTestsFromFile(self, filename):
507 504 if is_extension_module(filename):
508 505 for t in self.loadTestsFromExtensionModule(filename):
509 506 yield t
510 507 else:
511 508 ## for t in list(doctests.Doctest.loadTestsFromFile(self,filename)):
512 509 ## yield t
513 510 pass
514 511
515 512 if self.extension and anyp(filename.endswith, self.extension):
516 513 #print 'lTF',filename # dbg
517 514 name = os.path.basename(filename)
518 515 dh = open(filename)
519 516 try:
520 517 doc = dh.read()
521 518 finally:
522 519 dh.close()
523 520 test = self.parser.get_doctest(
524 521 doc, globs={'__file__': filename}, name=name,
525 522 filename=filename, lineno=0)
526 523 if test.examples:
527 524 #print 'FileCase:',test.examples # dbg
528 525 yield DocFileCase(test)
529 526 else:
530 527 yield False # no tests to load
531 528
532
533 529 def wantFile(self,filename):
534 530 """Return whether the given filename should be scanned for tests.
535 531
536 532 Modified version that accepts extension modules as valid containers for
537 533 doctests.
538 534 """
539 535 #print 'Filename:',filename # dbg
540 536
541 537 # temporarily hardcoded list, will move to driver later
542 538 exclude = ['IPython/external/',
543 539 'IPython/Extensions/ipy_',
544 540 'IPython/platutils_win32',
545 541 'IPython/frontend/cocoa',
546 542 'IPython_doctest_plugin',
547 543 'IPython/Gnuplot',
548 544 'IPython/Extensions/PhysicalQIn']
549 545
550 546 for fex in exclude:
551 547 if fex in filename: # substring
552 548 #print '###>>> SKIP:',filename # dbg
553 549 return False
554 550
555 551 if is_extension_module(filename):
556 552 return True
557 553 else:
558 554 return doctests.Doctest.wantFile(self,filename)
559 555
560 556 # NOTE: the method below is a *copy* of the one in the nose doctests
561 557 # plugin, but we have to replicate it here in order to have it resolve the
562 558 # DocTestCase (last line) to our local copy, since the nose plugin doesn't
563 559 # provide a public hook for what TestCase class to use. The alternative
564 560 # would be to monkeypatch doctest in the stdlib, but that's ugly and
565 561 # brittle, since a change in plugin load order can break it. So for now,
566 562 # we just paste this in here, inelegant as this may be.
567 563
568 564 def loadTestsFromModule(self, module):
569 565 #print 'lTM',module # dbg
570 566
571 567 if not self.matches(module.__name__):
572 568 log.debug("Doctest doesn't want module %s", module)
573 569 return
574 570 tests = self.finder.find(module)
575 571 if not tests:
576 572 return
577 573 tests.sort()
578 574 module_file = module.__file__
579 575 if module_file[-4:] in ('.pyc', '.pyo'):
580 576 module_file = module_file[:-1]
581 577 for test in tests:
582 578 if not test.examples:
583 579 continue
584 580 if not test.filename:
585 581 test.filename = module_file
586 582 yield DocTestCase(test)
587 583
584
588 585 class IPythonDoctest(ExtensionDoctest):
589 586 """Nose Plugin that supports doctests in extension modules.
590 587 """
591 588 name = 'ipdoctest' # call nosetests with --with-ipdoctest
592 589 enabled = True
593 590
594 591 def configure(self, options, config):
595 592
596 593 Plugin.configure(self, options, config)
597 594 self.doctest_tests = options.doctest_tests
598 595 self.extension = tolist(options.doctestExtension)
599 596 self.parser = IPDocTestParser()
600 597 #self.finder = DocTestFinder(parser=IPDocTestParser())
601 598 self.finder = DocTestFinder(parser=self.parser)
General Comments 0
You need to be logged in to leave comments. Login now