##// END OF EJS Templates
BF - allow nose with-doctest setting in environment...
Matthew Brett -
Show More
@@ -1,443 +1,437 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded.
14 14
15 15 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Copyright (C) 2009 The IPython Development Team
19 19 #
20 20 # Distributed under the terms of the BSD License. The full license is in
21 21 # the file COPYING, distributed as part of this software.
22 22 #-----------------------------------------------------------------------------
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Imports
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # Stdlib
29 29 import os
30 30 import os.path as path
31 31 import signal
32 32 import sys
33 33 import subprocess
34 34 import tempfile
35 35 import time
36 36 import warnings
37 37
38 38 # Note: monkeypatch!
39 39 # We need to monkeypatch a small problem in nose itself first, before importing
40 40 # it for actual use. This should get into nose upstream, but its release cycle
41 41 # is slow and we need it for our parametric tests to work correctly.
42 42 from IPython.testing import nosepatch
43 43 # Now, proceed to import nose itself
44 44 import nose.plugins.builtin
45 45 from nose.core import TestProgram
46 46
47 47 # Our own imports
48 48 from IPython.utils.path import get_ipython_module_path
49 49 from IPython.utils.process import find_cmd, pycmd2argv
50 50 from IPython.utils.sysinfo import sys_info
51 51
52 52 from IPython.testing import globalipapp
53 53 from IPython.testing.plugin.ipdoctest import IPythonDoctest
54 54 from IPython.external.decorators import KnownFailure
55 55
56 56 pjoin = path.join
57 57
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Globals
61 61 #-----------------------------------------------------------------------------
62 62
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Warnings control
66 66 #-----------------------------------------------------------------------------
67 67
68 68 # Twisted generates annoying warnings with Python 2.6, as will do other code
69 69 # that imports 'sets' as of today
70 70 warnings.filterwarnings('ignore', 'the sets module is deprecated',
71 71 DeprecationWarning )
72 72
73 73 # This one also comes from Twisted
74 74 warnings.filterwarnings('ignore', 'the sha module is deprecated',
75 75 DeprecationWarning)
76 76
77 77 # Wx on Fedora11 spits these out
78 78 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
79 79 UserWarning)
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Logic for skipping doctests
83 83 #-----------------------------------------------------------------------------
84 84
85 85 def test_for(mod, min_version=None):
86 86 """Test to see if mod is importable."""
87 87 try:
88 88 __import__(mod)
89 89 except (ImportError, RuntimeError):
90 90 # GTK reports Runtime error if it can't be initialized even if it's
91 91 # importable.
92 92 return False
93 93 else:
94 94 if min_version:
95 95 return sys.modules[mod].__version__ >= min_version
96 96 else:
97 97 return True
98 98
99 99 # Global dict where we can store information on what we have and what we don't
100 100 # have available at test run time
101 101 have = {}
102 102
103 103 have['curses'] = test_for('_curses')
104 104 have['matplotlib'] = test_for('matplotlib')
105 105 have['pexpect'] = test_for('pexpect')
106 106 have['pymongo'] = test_for('pymongo')
107 107 have['wx'] = test_for('wx')
108 108 have['wx.aui'] = test_for('wx.aui')
109 109 if os.name == 'nt':
110 110 have['zmq'] = test_for('zmq', '2.1.7')
111 111 else:
112 112 have['zmq'] = test_for('zmq', '2.1.4')
113 113 have['qt'] = test_for('IPython.external.qt')
114 114
115 115 #-----------------------------------------------------------------------------
116 116 # Functions and classes
117 117 #-----------------------------------------------------------------------------
118 118
119 119 def report():
120 120 """Return a string with a summary report of test-related variables."""
121 121
122 122 out = [ sys_info(), '\n']
123 123
124 124 avail = []
125 125 not_avail = []
126 126
127 127 for k, is_avail in have.items():
128 128 if is_avail:
129 129 avail.append(k)
130 130 else:
131 131 not_avail.append(k)
132 132
133 133 if avail:
134 134 out.append('\nTools and libraries available at test time:\n')
135 135 avail.sort()
136 136 out.append(' ' + ' '.join(avail)+'\n')
137 137
138 138 if not_avail:
139 139 out.append('\nTools and libraries NOT available at test time:\n')
140 140 not_avail.sort()
141 141 out.append(' ' + ' '.join(not_avail)+'\n')
142 142
143 143 return ''.join(out)
144 144
145 145
146 146 def make_exclude():
147 147 """Make patterns of modules and packages to exclude from testing.
148 148
149 149 For the IPythonDoctest plugin, we need to exclude certain patterns that
150 150 cause testing problems. We should strive to minimize the number of
151 151 skipped modules, since this means untested code.
152 152
153 153 These modules and packages will NOT get scanned by nose at all for tests.
154 154 """
155 155 # Simple utility to make IPython paths more readably, we need a lot of
156 156 # these below
157 157 ipjoin = lambda *paths: pjoin('IPython', *paths)
158 158
159 159 exclusions = [ipjoin('external'),
160 160 pjoin('IPython_doctest_plugin'),
161 161 ipjoin('quarantine'),
162 162 ipjoin('deathrow'),
163 163 ipjoin('testing', 'attic'),
164 164 # This guy is probably attic material
165 165 ipjoin('testing', 'mkdoctests'),
166 166 # Testing inputhook will need a lot of thought, to figure out
167 167 # how to have tests that don't lock up with the gui event
168 168 # loops in the picture
169 169 ipjoin('lib', 'inputhook'),
170 170 # Config files aren't really importable stand-alone
171 171 ipjoin('config', 'default'),
172 172 ipjoin('config', 'profile'),
173 173 ]
174 174
175 175 if not have['wx']:
176 176 exclusions.append(ipjoin('lib', 'inputhookwx'))
177 177
178 178 # We do this unconditionally, so that the test suite doesn't import
179 179 # gtk, changing the default encoding and masking some unicode bugs.
180 180 exclusions.append(ipjoin('lib', 'inputhookgtk'))
181 181
182 182 # These have to be skipped on win32 because the use echo, rm, cd, etc.
183 183 # See ticket https://github.com/ipython/ipython/issues/87
184 184 if sys.platform == 'win32':
185 185 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
186 186 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
187 187
188 188 if not have['pexpect']:
189 189 exclusions.extend([ipjoin('scripts', 'irunner'),
190 190 ipjoin('lib', 'irunner'),
191 191 ipjoin('lib', 'tests', 'test_irunner')])
192 192
193 193 if not have['zmq']:
194 194 exclusions.append(ipjoin('zmq'))
195 195 exclusions.append(ipjoin('frontend', 'qt'))
196 196 exclusions.append(ipjoin('parallel'))
197 197 elif not have['qt']:
198 198 exclusions.append(ipjoin('frontend', 'qt'))
199 199
200 200 if not have['pymongo']:
201 201 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
202 202 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
203 203
204 204 if not have['matplotlib']:
205 205 exclusions.extend([ipjoin('lib', 'pylabtools'),
206 206 ipjoin('lib', 'tests', 'test_pylabtools')])
207 207
208 208 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
209 209 if sys.platform == 'win32':
210 210 exclusions = [s.replace('\\','\\\\') for s in exclusions]
211 211
212 212 return exclusions
213 213
214 214
215 215 class IPTester(object):
216 216 """Call that calls iptest or trial in a subprocess.
217 217 """
218 218 #: string, name of test runner that will be called
219 219 runner = None
220 220 #: list, parameters for test runner
221 221 params = None
222 222 #: list, arguments of system call to be made to call test runner
223 223 call_args = None
224 224 #: list, process ids of subprocesses we start (for cleanup)
225 225 pids = None
226 226
227 227 def __init__(self, runner='iptest', params=None):
228 228 """Create new test runner."""
229 229 p = os.path
230 230 if runner == 'iptest':
231 231 iptest_app = get_ipython_module_path('IPython.testing.iptest')
232 232 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
233 233 else:
234 234 raise Exception('Not a valid test runner: %s' % repr(runner))
235 235 if params is None:
236 236 params = []
237 237 if isinstance(params, str):
238 238 params = [params]
239 239 self.params = params
240 240
241 241 # Assemble call
242 242 self.call_args = self.runner+self.params
243 243
244 244 # Store pids of anything we start to clean up on deletion, if possible
245 245 # (on posix only, since win32 has no os.kill)
246 246 self.pids = []
247 247
248 248 if sys.platform == 'win32':
249 249 def _run_cmd(self):
250 250 # On Windows, use os.system instead of subprocess.call, because I
251 251 # was having problems with subprocess and I just don't know enough
252 252 # about win32 to debug this reliably. Os.system may be the 'old
253 253 # fashioned' way to do it, but it works just fine. If someone
254 254 # later can clean this up that's fine, as long as the tests run
255 255 # reliably in win32.
256 256 # What types of problems are you having. They may be related to
257 257 # running Python in unboffered mode. BG.
258 258 return os.system(' '.join(self.call_args))
259 259 else:
260 260 def _run_cmd(self):
261 261 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
262 262 subp = subprocess.Popen(self.call_args)
263 263 self.pids.append(subp.pid)
264 264 # If this fails, the pid will be left in self.pids and cleaned up
265 265 # later, but if the wait call succeeds, then we can clear the
266 266 # stored pid.
267 267 retcode = subp.wait()
268 268 self.pids.pop()
269 269 return retcode
270 270
271 271 def run(self):
272 272 """Run the stored commands"""
273 273 try:
274 274 return self._run_cmd()
275 275 except:
276 276 import traceback
277 277 traceback.print_exc()
278 278 return 1 # signal failure
279 279
280 280 def __del__(self):
281 281 """Cleanup on exit by killing any leftover processes."""
282 282
283 283 if not hasattr(os, 'kill'):
284 284 return
285 285
286 286 for pid in self.pids:
287 287 try:
288 288 print 'Cleaning stale PID:', pid
289 289 os.kill(pid, signal.SIGKILL)
290 290 except OSError:
291 291 # This is just a best effort, if we fail or the process was
292 292 # really gone, ignore it.
293 293 pass
294 294
295 295
296 296 def make_runners():
297 297 """Define the top-level packages that need to be tested.
298 298 """
299 299
300 300 # Packages to be tested via nose, that only depend on the stdlib
301 301 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
302 302 'scripts', 'testing', 'utils' ]
303 303
304 304 if have['zmq']:
305 305 nose_pkg_names.append('parallel')
306 306
307 307 # For debugging this code, only load quick stuff
308 308 #nose_pkg_names = ['core', 'extensions'] # dbg
309 309
310 310 # Make fully qualified package names prepending 'IPython.' to our name lists
311 311 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
312 312
313 313 # Make runners
314 314 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
315 315
316 316 return runners
317 317
318 318
319 319 def run_iptest():
320 320 """Run the IPython test suite using nose.
321 321
322 322 This function is called when this script is **not** called with the form
323 323 `iptest all`. It simply calls nose with appropriate command line flags
324 324 and accepts all of the standard nose arguments.
325 325 """
326 326
327 327 warnings.filterwarnings('ignore',
328 328 'This will be removed soon. Use IPython.testing.util instead')
329 329
330 330 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
331 331
332 332 # Loading ipdoctest causes problems with Twisted, but
333 333 # our test suite runner now separates things and runs
334 334 # all Twisted tests with trial.
335 335 '--with-ipdoctest',
336 336 '--ipdoctest-tests','--ipdoctest-extension=txt',
337 337
338 338 # We add --exe because of setuptools' imbecility (it
339 339 # blindly does chmod +x on ALL files). Nose does the
340 340 # right thing and it tries to avoid executables,
341 341 # setuptools unfortunately forces our hand here. This
342 342 # has been discussed on the distutils list and the
343 343 # setuptools devs refuse to fix this problem!
344 344 '--exe',
345 345 ]
346 346
347 347 if nose.__version__ >= '0.11':
348 348 # I don't fully understand why we need this one, but depending on what
349 349 # directory the test suite is run from, if we don't give it, 0 tests
350 350 # get run. Specifically, if the test suite is run from the source dir
351 351 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
352 352 # even if the same call done in this directory works fine). It appears
353 353 # that if the requested package is in the current dir, nose bails early
354 354 # by default. Since it's otherwise harmless, leave it in by default
355 355 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
356 356 argv.append('--traverse-namespace')
357 357
358 # Construct list of plugins, omitting the existing doctest plugin, which
359 # ours replaces (and extends).
358 # use our plugin for doctesting. It will remove the standard doctest plugin
359 # if it finds it enabled
360 360 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
361 for p in nose.plugins.builtin.plugins:
362 plug = p()
363 if plug.name == 'doctest':
364 continue
365 plugins.append(plug)
366
367 361 # We need a global ipython running in this process
368 362 globalipapp.start_ipython()
369 363 # Now nose can run
370 TestProgram(argv=argv, plugins=plugins)
364 TestProgram(argv=argv, addplugins=plugins)
371 365
372 366
373 367 def run_iptestall():
374 368 """Run the entire IPython test suite by calling nose and trial.
375 369
376 370 This function constructs :class:`IPTester` instances for all IPython
377 371 modules and package and then runs each of them. This causes the modules
378 372 and packages of IPython to be tested each in their own subprocess using
379 373 nose or twisted.trial appropriately.
380 374 """
381 375
382 376 runners = make_runners()
383 377
384 378 # Run the test runners in a temporary dir so we can nuke it when finished
385 379 # to clean up any junk files left over by accident. This also makes it
386 380 # robust against being run in non-writeable directories by mistake, as the
387 381 # temp dir will always be user-writeable.
388 382 curdir = os.getcwdu()
389 383 testdir = tempfile.gettempdir()
390 384 os.chdir(testdir)
391 385
392 386 # Run all test runners, tracking execution time
393 387 failed = []
394 388 t_start = time.time()
395 389 try:
396 390 for (name, runner) in runners:
397 391 print '*'*70
398 392 print 'IPython test group:',name
399 393 res = runner.run()
400 394 if res:
401 395 failed.append( (name, runner) )
402 396 finally:
403 397 os.chdir(curdir)
404 398 t_end = time.time()
405 399 t_tests = t_end - t_start
406 400 nrunners = len(runners)
407 401 nfail = len(failed)
408 402 # summarize results
409 403 print
410 404 print '*'*70
411 405 print 'Test suite completed for system with the following information:'
412 406 print report()
413 407 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
414 408 print
415 409 print 'Status:'
416 410 if not failed:
417 411 print 'OK'
418 412 else:
419 413 # If anything went wrong, point out what command to rerun manually to
420 414 # see the actual errors and individual summary
421 415 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
422 416 for name, failed_runner in failed:
423 417 print '-'*40
424 418 print 'Runner failed:',name
425 419 print 'You may wish to rerun this one individually, with:'
426 420 print ' '.join(failed_runner.call_args)
427 421 print
428 422 # Ensure that our exit code indicates failure
429 423 sys.exit(1)
430 424
431 425
432 426 def main():
433 427 for arg in sys.argv[1:]:
434 428 if arg.startswith('IPython'):
435 429 # This is in-process
436 430 run_iptest()
437 431 else:
438 432 # This starts subprocesses
439 433 run_iptestall()
440 434
441 435
442 436 if __name__ == '__main__':
443 437 main()
@@ -1,793 +1,799 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 221 self._dt_test_globs_ori = test.globs
222 222 self._dt_setUp = setUp
223 223 self._dt_tearDown = tearDown
224 224
225 225 # XXX - store this runner once in the object!
226 226 runner = IPDocTestRunner(optionflags=optionflags,
227 227 checker=checker, verbose=False)
228 228 self._dt_runner = runner
229 229
230 230
231 231 # Each doctest should remember the directory it was loaded from, so
232 232 # things like %run work without too many contortions
233 233 self._ori_dir = os.path.dirname(test.filename)
234 234
235 235 # Modified runTest from the default stdlib
236 236 def runTest(self):
237 237 test = self._dt_test
238 238 runner = self._dt_runner
239 239
240 240 old = sys.stdout
241 241 new = StringIO()
242 242 optionflags = self._dt_optionflags
243 243
244 244 if not (optionflags & REPORTING_FLAGS):
245 245 # The option flags don't include any reporting flags,
246 246 # so add the default reporting flags
247 247 optionflags |= _unittest_reportflags
248 248
249 249 try:
250 250 # Save our current directory and switch out to the one where the
251 251 # test was originally created, in case another doctest did a
252 252 # directory change. We'll restore this in the finally clause.
253 253 curdir = os.getcwdu()
254 254 #print 'runTest in dir:', self._ori_dir # dbg
255 255 os.chdir(self._ori_dir)
256 256
257 257 runner.DIVIDER = "-"*70
258 258 failures, tries = runner.run(test,out=new.write,
259 259 clear_globs=False)
260 260 finally:
261 261 sys.stdout = old
262 262 os.chdir(curdir)
263 263
264 264 if failures:
265 265 raise self.failureException(self.format_failure(new.getvalue()))
266 266
267 267 def setUp(self):
268 268 """Modified test setup that syncs with ipython namespace"""
269 269 #print "setUp test", self._dt_test.examples # dbg
270 270 if isinstance(self._dt_test.examples[0],IPExample):
271 271 # for IPython examples *only*, we swap the globals with the ipython
272 272 # namespace, after updating it with the globals (which doctest
273 273 # fills with the necessary info from the module being tested).
274 274 _ip.user_ns.update(self._dt_test.globs)
275 275 self._dt_test.globs = _ip.user_ns
276 276 # IPython must protect the _ key in the namespace (it can't exist)
277 277 # so that Python's doctest code sets it naturally, so we enable
278 278 # this feature of our testing namespace.
279 279 _ip.user_ns.protect_underscore = True
280 280
281 281 super(DocTestCase, self).setUp()
282 282
283 283 def tearDown(self):
284 284
285 285 # Undo the test.globs reassignment we made, so that the parent class
286 286 # teardown doesn't destroy the ipython namespace
287 287 if isinstance(self._dt_test.examples[0],IPExample):
288 288 self._dt_test.globs = self._dt_test_globs_ori
289 289 # Restore the behavior of the '_' key in the user namespace to
290 290 # normal after each doctest, so that unittests behave normally
291 291 _ip.user_ns.protect_underscore = False
292 292
293 293 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
294 294 # it does look like one to me: its tearDown method tries to run
295 295 #
296 296 # delattr(__builtin__, self._result_var)
297 297 #
298 298 # without checking that the attribute really is there; it implicitly
299 299 # assumes it should have been set via displayhook. But if the
300 300 # displayhook was never called, this doesn't necessarily happen. I
301 301 # haven't been able to find a little self-contained example outside of
302 302 # ipython that would show the problem so I can report it to the nose
303 303 # team, but it does happen a lot in our code.
304 304 #
305 305 # So here, we just protect as narrowly as possible by trapping an
306 306 # attribute error whose message would be the name of self._result_var,
307 307 # and letting any other error propagate.
308 308 try:
309 309 super(DocTestCase, self).tearDown()
310 310 except AttributeError, exc:
311 311 if exc.args[0] != self._result_var:
312 312 raise
313 313
314 314
315 315 # A simple subclassing of the original with a different class name, so we can
316 316 # distinguish and treat differently IPython examples from pure python ones.
317 317 class IPExample(doctest.Example): pass
318 318
319 319
320 320 class IPExternalExample(doctest.Example):
321 321 """Doctest examples to be run in an external process."""
322 322
323 323 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
324 324 options=None):
325 325 # Parent constructor
326 326 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
327 327
328 328 # An EXTRA newline is needed to prevent pexpect hangs
329 329 self.source += '\n'
330 330
331 331
332 332 class IPDocTestParser(doctest.DocTestParser):
333 333 """
334 334 A class used to parse strings containing doctest examples.
335 335
336 336 Note: This is a version modified to properly recognize IPython input and
337 337 convert any IPython examples into valid Python ones.
338 338 """
339 339 # This regular expression is used to find doctest examples in a
340 340 # string. It defines three groups: `source` is the source code
341 341 # (including leading indentation and prompts); `indent` is the
342 342 # indentation of the first (PS1) line of the source code; and
343 343 # `want` is the expected output (including leading indentation).
344 344
345 345 # Classic Python prompts or default IPython ones
346 346 _PS1_PY = r'>>>'
347 347 _PS2_PY = r'\.\.\.'
348 348
349 349 _PS1_IP = r'In\ \[\d+\]:'
350 350 _PS2_IP = r'\ \ \ \.\.\.+:'
351 351
352 352 _RE_TPL = r'''
353 353 # Source consists of a PS1 line followed by zero or more PS2 lines.
354 354 (?P<source>
355 355 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
356 356 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
357 357 \n? # a newline
358 358 # Want consists of any non-blank lines that do not start with PS1.
359 359 (?P<want> (?:(?![ ]*$) # Not a blank line
360 360 (?![ ]*%s) # Not a line starting with PS1
361 361 (?![ ]*%s) # Not a line starting with PS2
362 362 .*$\n? # But any other line
363 363 )*)
364 364 '''
365 365
366 366 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
367 367 re.MULTILINE | re.VERBOSE)
368 368
369 369 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
370 370 re.MULTILINE | re.VERBOSE)
371 371
372 372 # Mark a test as being fully random. In this case, we simply append the
373 373 # random marker ('#random') to each individual example's output. This way
374 374 # we don't need to modify any other code.
375 375 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
376 376
377 377 # Mark tests to be executed in an external process - currently unsupported.
378 378 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
379 379
380 380 def ip2py(self,source):
381 381 """Convert input IPython source into valid Python."""
382 382 out = []
383 383 newline = out.append
384 384 #print 'IPSRC:\n',source,'\n###' # dbg
385 385 # The input source must be first stripped of all bracketing whitespace
386 386 # and turned into lines, so it looks to the parser like regular user
387 387 # input
388 388 for lnum,line in enumerate(source.strip().splitlines()):
389 389 newline(_ip.prefilter(line,lnum>0))
390 390 newline('') # ensure a closing newline, needed by doctest
391 391 #print "PYSRC:", '\n'.join(out) # dbg
392 392 return '\n'.join(out)
393 393
394 394 def parse(self, string, name='<string>'):
395 395 """
396 396 Divide the given string into examples and intervening text,
397 397 and return them as a list of alternating Examples and strings.
398 398 Line numbers for the Examples are 0-based. The optional
399 399 argument `name` is a name identifying this string, and is only
400 400 used for error messages.
401 401 """
402 402
403 403 #print 'Parse string:\n',string # dbg
404 404
405 405 string = string.expandtabs()
406 406 # If all lines begin with the same indentation, then strip it.
407 407 min_indent = self._min_indent(string)
408 408 if min_indent > 0:
409 409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
410 410
411 411 output = []
412 412 charno, lineno = 0, 0
413 413
414 414 # We make 'all random' tests by adding the '# random' mark to every
415 415 # block of output in the test.
416 416 if self._RANDOM_TEST.search(string):
417 417 random_marker = '\n# random'
418 418 else:
419 419 random_marker = ''
420 420
421 421 # Whether to convert the input from ipython to python syntax
422 422 ip2py = False
423 423 # Find all doctest examples in the string. First, try them as Python
424 424 # examples, then as IPython ones
425 425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
426 426 if terms:
427 427 # Normal Python example
428 428 #print '-'*70 # dbg
429 429 #print 'PyExample, Source:\n',string # dbg
430 430 #print '-'*70 # dbg
431 431 Example = doctest.Example
432 432 else:
433 433 # It's an ipython example. Note that IPExamples are run
434 434 # in-process, so their syntax must be turned into valid python.
435 435 # IPExternalExamples are run out-of-process (via pexpect) so they
436 436 # don't need any filtering (a real ipython will be executing them).
437 437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
438 438 if self._EXTERNAL_IP.search(string):
439 439 #print '-'*70 # dbg
440 440 #print 'IPExternalExample, Source:\n',string # dbg
441 441 #print '-'*70 # dbg
442 442 Example = IPExternalExample
443 443 else:
444 444 #print '-'*70 # dbg
445 445 #print 'IPExample, Source:\n',string # dbg
446 446 #print '-'*70 # dbg
447 447 Example = IPExample
448 448 ip2py = True
449 449
450 450 for m in terms:
451 451 # Add the pre-example text to `output`.
452 452 output.append(string[charno:m.start()])
453 453 # Update lineno (lines before this example)
454 454 lineno += string.count('\n', charno, m.start())
455 455 # Extract info from the regexp match.
456 456 (source, options, want, exc_msg) = \
457 457 self._parse_example(m, name, lineno,ip2py)
458 458
459 459 # Append the random-output marker (it defaults to empty in most
460 460 # cases, it's only non-empty for 'all-random' tests):
461 461 want += random_marker
462 462
463 463 if Example is IPExternalExample:
464 464 options[doctest.NORMALIZE_WHITESPACE] = True
465 465 want += '\n'
466 466
467 467 # Create an Example, and add it to the list.
468 468 if not self._IS_BLANK_OR_COMMENT(source):
469 469 output.append(Example(source, want, exc_msg,
470 470 lineno=lineno,
471 471 indent=min_indent+len(m.group('indent')),
472 472 options=options))
473 473 # Update lineno (lines inside this example)
474 474 lineno += string.count('\n', m.start(), m.end())
475 475 # Update charno.
476 476 charno = m.end()
477 477 # Add any remaining post-example text to `output`.
478 478 output.append(string[charno:])
479 479 return output
480 480
481 481 def _parse_example(self, m, name, lineno,ip2py=False):
482 482 """
483 483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
484 484 return a pair `(source, want)`, where `source` is the matched
485 485 example's source code (with prompts and indentation stripped);
486 486 and `want` is the example's expected output (with indentation
487 487 stripped).
488 488
489 489 `name` is the string's name, and `lineno` is the line number
490 490 where the example starts; both are used for error messages.
491 491
492 492 Optional:
493 493 `ip2py`: if true, filter the input via IPython to convert the syntax
494 494 into valid python.
495 495 """
496 496
497 497 # Get the example's indentation level.
498 498 indent = len(m.group('indent'))
499 499
500 500 # Divide source into lines; check that they're properly
501 501 # indented; and then strip their indentation & prompts.
502 502 source_lines = m.group('source').split('\n')
503 503
504 504 # We're using variable-length input prompts
505 505 ps1 = m.group('ps1')
506 506 ps2 = m.group('ps2')
507 507 ps1_len = len(ps1)
508 508
509 509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
510 510 if ps2:
511 511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
512 512
513 513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
514 514
515 515 if ip2py:
516 516 # Convert source input from IPython into valid Python syntax
517 517 source = self.ip2py(source)
518 518
519 519 # Divide want into lines; check that it's properly indented; and
520 520 # then strip the indentation. Spaces before the last newline should
521 521 # be preserved, so plain rstrip() isn't good enough.
522 522 want = m.group('want')
523 523 want_lines = want.split('\n')
524 524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
525 525 del want_lines[-1] # forget final newline & spaces after it
526 526 self._check_prefix(want_lines, ' '*indent, name,
527 527 lineno + len(source_lines))
528 528
529 529 # Remove ipython output prompt that might be present in the first line
530 530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
531 531
532 532 want = '\n'.join([wl[indent:] for wl in want_lines])
533 533
534 534 # If `want` contains a traceback message, then extract it.
535 535 m = self._EXCEPTION_RE.match(want)
536 536 if m:
537 537 exc_msg = m.group('msg')
538 538 else:
539 539 exc_msg = None
540 540
541 541 # Extract options from the source.
542 542 options = self._find_options(source, name, lineno)
543 543
544 544 return source, options, want, exc_msg
545 545
546 546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
547 547 """
548 548 Given the lines of a source string (including prompts and
549 549 leading indentation), check to make sure that every prompt is
550 550 followed by a space character. If any line is not followed by
551 551 a space character, then raise ValueError.
552 552
553 553 Note: IPython-modified version which takes the input prompt length as a
554 554 parameter, so that prompts of variable length can be dealt with.
555 555 """
556 556 space_idx = indent+ps1_len
557 557 min_len = space_idx+1
558 558 for i, line in enumerate(lines):
559 559 if len(line) >= min_len and line[space_idx] != ' ':
560 560 raise ValueError('line %r of the docstring for %s '
561 561 'lacks blank after %s: %r' %
562 562 (lineno+i+1, name,
563 563 line[indent:space_idx], line))
564 564
565 565
566 566 SKIP = doctest.register_optionflag('SKIP')
567 567
568 568
569 569 class IPDocTestRunner(doctest.DocTestRunner,object):
570 570 """Test runner that synchronizes the IPython namespace with test globals.
571 571 """
572 572
573 573 def run(self, test, compileflags=None, out=None, clear_globs=True):
574 574
575 575 # Hack: ipython needs access to the execution context of the example,
576 576 # so that it can propagate user variables loaded by %run into
577 577 # test.globs. We put them here into our modified %run as a function
578 578 # attribute. Our new %run will then only make the namespace update
579 579 # when called (rather than unconconditionally updating test.globs here
580 580 # for all examples, most of which won't be calling %run anyway).
581 581 #_ip._ipdoctest_test_globs = test.globs
582 582 #_ip._ipdoctest_test_filename = test.filename
583 583
584 584 test.globs.update(_ip.user_ns)
585 585
586 586 return super(IPDocTestRunner,self).run(test,
587 587 compileflags,out,clear_globs)
588 588
589 589
590 590 class DocFileCase(doctest.DocFileCase):
591 591 """Overrides to provide filename
592 592 """
593 593 def address(self):
594 594 return (self._dt_test.filename, None, None)
595 595
596 596
597 597 class ExtensionDoctest(doctests.Doctest):
598 598 """Nose Plugin that supports doctests in extension modules.
599 599 """
600 600 name = 'extdoctest' # call nosetests with --with-extdoctest
601 601 enabled = True
602 602
603 603 def __init__(self,exclude_patterns=None):
604 604 """Create a new ExtensionDoctest plugin.
605 605
606 606 Parameters
607 607 ----------
608 608
609 609 exclude_patterns : sequence of strings, optional
610 610 These patterns are compiled as regular expressions, subsequently used
611 611 to exclude any filename which matches them from inclusion in the test
612 612 suite (using pattern.search(), NOT pattern.match() ).
613 613 """
614 614
615 615 if exclude_patterns is None:
616 616 exclude_patterns = []
617 617 self.exclude_patterns = map(re.compile,exclude_patterns)
618 618 doctests.Doctest.__init__(self)
619 619
620 620 def options(self, parser, env=os.environ):
621 621 Plugin.options(self, parser, env)
622 622 parser.add_option('--doctest-tests', action='store_true',
623 623 dest='doctest_tests',
624 624 default=env.get('NOSE_DOCTEST_TESTS',True),
625 625 help="Also look for doctests in test modules. "
626 626 "Note that classes, methods and functions should "
627 627 "have either doctests or non-doctest tests, "
628 628 "not both. [NOSE_DOCTEST_TESTS]")
629 629 parser.add_option('--doctest-extension', action="append",
630 630 dest="doctestExtension",
631 631 help="Also look for doctests in files with "
632 632 "this extension [NOSE_DOCTEST_EXTENSION]")
633 633 # Set the default as a list, if given in env; otherwise
634 634 # an additional value set on the command line will cause
635 635 # an error.
636 636 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
637 637 if env_setting is not None:
638 638 parser.set_defaults(doctestExtension=tolist(env_setting))
639 639
640 640
641 641 def configure(self, options, config):
642 642 Plugin.configure(self, options, config)
643 # Pull standard doctest plugin out of config; we will do doctesting
644 config.plugins.plugins = [p for p in config.plugins.plugins
645 if p.name != 'doctest']
643 646 self.doctest_tests = options.doctest_tests
644 647 self.extension = tolist(options.doctestExtension)
645 648
646 649 self.parser = doctest.DocTestParser()
647 650 self.finder = DocTestFinder()
648 651 self.checker = IPDoctestOutputChecker()
649 652 self.globs = None
650 653 self.extraglobs = None
651 654
652 655
653 656 def loadTestsFromExtensionModule(self,filename):
654 657 bpath,mod = os.path.split(filename)
655 658 modname = os.path.splitext(mod)[0]
656 659 try:
657 660 sys.path.append(bpath)
658 661 module = __import__(modname)
659 662 tests = list(self.loadTestsFromModule(module))
660 663 finally:
661 664 sys.path.pop()
662 665 return tests
663 666
664 667 # NOTE: the method below is almost a copy of the original one in nose, with
665 668 # a few modifications to control output checking.
666 669
667 670 def loadTestsFromModule(self, module):
668 671 #print '*** ipdoctest - lTM',module # dbg
669 672
670 673 if not self.matches(module.__name__):
671 674 log.debug("Doctest doesn't want module %s", module)
672 675 return
673 676
674 677 tests = self.finder.find(module,globs=self.globs,
675 678 extraglobs=self.extraglobs)
676 679 if not tests:
677 680 return
678 681
679 682 # always use whitespace and ellipsis options
680 683 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
681 684
682 685 tests.sort()
683 686 module_file = module.__file__
684 687 if module_file[-4:] in ('.pyc', '.pyo'):
685 688 module_file = module_file[:-1]
686 689 for test in tests:
687 690 if not test.examples:
688 691 continue
689 692 if not test.filename:
690 693 test.filename = module_file
691 694
692 695 yield DocTestCase(test,
693 696 optionflags=optionflags,
694 697 checker=self.checker)
695 698
696 699
697 700 def loadTestsFromFile(self, filename):
698 701 #print "ipdoctest - from file", filename # dbg
699 702 if is_extension_module(filename):
700 703 for t in self.loadTestsFromExtensionModule(filename):
701 704 yield t
702 705 else:
703 706 if self.extension and anyp(filename.endswith, self.extension):
704 707 name = os.path.basename(filename)
705 708 dh = open(filename)
706 709 try:
707 710 doc = dh.read()
708 711 finally:
709 712 dh.close()
710 713 test = self.parser.get_doctest(
711 714 doc, globs={'__file__': filename}, name=name,
712 715 filename=filename, lineno=0)
713 716 if test.examples:
714 717 #print 'FileCase:',test.examples # dbg
715 718 yield DocFileCase(test)
716 719 else:
717 720 yield False # no tests to load
718 721
719 722 def wantFile(self,filename):
720 723 """Return whether the given filename should be scanned for tests.
721 724
722 725 Modified version that accepts extension modules as valid containers for
723 726 doctests.
724 727 """
725 728 #print '*** ipdoctest- wantFile:',filename # dbg
726 729
727 730 for pat in self.exclude_patterns:
728 731 if pat.search(filename):
729 732 # print '###>>> SKIP:',filename # dbg
730 733 return False
731 734
732 735 if is_extension_module(filename):
733 736 return True
734 737 else:
735 738 return doctests.Doctest.wantFile(self,filename)
736 739
737 740
738 741 class IPythonDoctest(ExtensionDoctest):
739 742 """Nose Plugin that supports doctests in extension modules.
740 743 """
741 744 name = 'ipdoctest' # call nosetests with --with-ipdoctest
742 745 enabled = True
743 746
744 747 def makeTest(self, obj, parent):
745 748 """Look for doctests in the given object, which will be a
746 749 function, method or class.
747 750 """
748 751 #print 'Plugin analyzing:', obj, parent # dbg
749 752 # always use whitespace and ellipsis options
750 753 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
751 754
752 755 doctests = self.finder.find(obj, module=getmodule(parent))
753 756 if doctests:
754 757 for test in doctests:
755 758 if len(test.examples) == 0:
756 759 continue
757 760
758 761 yield DocTestCase(test, obj=obj,
759 762 optionflags=optionflags,
760 763 checker=self.checker)
761 764
762 765 def options(self, parser, env=os.environ):
763 766 #print "Options for nose plugin:", self.name # dbg
764 767 Plugin.options(self, parser, env)
765 768 parser.add_option('--ipdoctest-tests', action='store_true',
766 769 dest='ipdoctest_tests',
767 770 default=env.get('NOSE_IPDOCTEST_TESTS',True),
768 771 help="Also look for doctests in test modules. "
769 772 "Note that classes, methods and functions should "
770 773 "have either doctests or non-doctest tests, "
771 774 "not both. [NOSE_IPDOCTEST_TESTS]")
772 775 parser.add_option('--ipdoctest-extension', action="append",
773 776 dest="ipdoctest_extension",
774 777 help="Also look for doctests in files with "
775 778 "this extension [NOSE_IPDOCTEST_EXTENSION]")
776 779 # Set the default as a list, if given in env; otherwise
777 780 # an additional value set on the command line will cause
778 781 # an error.
779 782 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
780 783 if env_setting is not None:
781 784 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
782 785
783 786 def configure(self, options, config):
784 787 #print "Configuring nose plugin:", self.name # dbg
785 788 Plugin.configure(self, options, config)
789 # Pull standard doctest plugin out of config; we will do doctesting
790 config.plugins.plugins = [p for p in config.plugins.plugins
791 if p.name != 'doctest']
786 792 self.doctest_tests = options.ipdoctest_tests
787 793 self.extension = tolist(options.ipdoctest_extension)
788 794
789 795 self.parser = IPDocTestParser()
790 796 self.finder = DocTestFinder(parser=self.parser)
791 797 self.checker = IPDoctestOutputChecker()
792 798 self.globs = None
793 799 self.extraglobs = None
General Comments 0
You need to be logged in to leave comments. Login now