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