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