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