##// END OF EJS Templates
Cleanup ipdoctest...
Nikita Kniazev -
Show More
@@ -1,452 +1,437 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
27
28 from testpath import modified_env
28 from testpath import modified_env
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Module globals and other constants
31 # Module globals and other constants
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Classes and functions
38 # Classes and functions
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 def is_extension_module(filename):
42 """Return whether the given filename is an extension module.
43
44 This simply checks that the extension is either .so or .pyd.
45 """
46 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
47
48
49 class DocTestSkip(object):
41 class DocTestSkip(object):
50 """Object wrapper for doctests to be skipped."""
42 """Object wrapper for doctests to be skipped."""
51
43
52 ds_skip = """Doctest to skip.
44 ds_skip = """Doctest to skip.
53 >>> 1 #doctest: +SKIP
45 >>> 1 #doctest: +SKIP
54 """
46 """
55
47
56 def __init__(self,obj):
48 def __init__(self,obj):
57 self.obj = obj
49 self.obj = obj
58
50
59 def __getattribute__(self,key):
51 def __getattribute__(self,key):
60 if key == '__doc__':
52 if key == '__doc__':
61 return DocTestSkip.ds_skip
53 return DocTestSkip.ds_skip
62 else:
54 else:
63 return getattr(object.__getattribute__(self,'obj'),key)
55 return getattr(object.__getattribute__(self,'obj'),key)
64
56
65 # Modified version of the one in the stdlib, that fixes a python bug (doctests
57 # Modified version of the one in the stdlib, that fixes a python bug (doctests
66 # not found in extension modules, http://bugs.python.org/issue3158)
58 # not found in extension modules, http://bugs.python.org/issue3158)
67 class DocTestFinder(doctest.DocTestFinder):
59 class DocTestFinder(doctest.DocTestFinder):
68
60
69 def _from_module(self, module, object):
61 def _from_module(self, module, object):
70 """
62 """
71 Return true if the given object is defined in the given
63 Return true if the given object is defined in the given
72 module.
64 module.
73 """
65 """
74 if module is None:
66 if module is None:
75 return True
67 return True
76 elif inspect.isfunction(object):
68 elif inspect.isfunction(object):
77 return module.__dict__ is object.__globals__
69 return module.__dict__ is object.__globals__
78 elif inspect.isbuiltin(object):
70 elif inspect.isbuiltin(object):
79 return module.__name__ == object.__module__
71 return module.__name__ == object.__module__
80 elif inspect.isclass(object):
72 elif inspect.isclass(object):
81 return module.__name__ == object.__module__
73 return module.__name__ == object.__module__
82 elif inspect.ismethod(object):
74 elif inspect.ismethod(object):
83 # This one may be a bug in cython that fails to correctly set the
75 # This one may be a bug in cython that fails to correctly set the
84 # __module__ attribute of methods, but since the same error is easy
76 # __module__ attribute of methods, but since the same error is easy
85 # to make by extension code writers, having this safety in place
77 # to make by extension code writers, having this safety in place
86 # isn't such a bad idea
78 # isn't such a bad idea
87 return module.__name__ == object.__self__.__class__.__module__
79 return module.__name__ == object.__self__.__class__.__module__
88 elif inspect.getmodule(object) is not None:
80 elif inspect.getmodule(object) is not None:
89 return module is inspect.getmodule(object)
81 return module is inspect.getmodule(object)
90 elif hasattr(object, '__module__'):
82 elif hasattr(object, '__module__'):
91 return module.__name__ == object.__module__
83 return module.__name__ == object.__module__
92 elif isinstance(object, property):
84 elif isinstance(object, property):
93 return True # [XX] no way not be sure.
85 return True # [XX] no way not be sure.
94 elif inspect.ismethoddescriptor(object):
86 elif inspect.ismethoddescriptor(object):
95 # Unbound PyQt signals reach this point in Python 3.4b3, and we want
87 # Unbound PyQt signals reach this point in Python 3.4b3, and we want
96 # to avoid throwing an error. See also http://bugs.python.org/issue3158
88 # to avoid throwing an error. See also http://bugs.python.org/issue3158
97 return False
89 return False
98 else:
90 else:
99 raise ValueError("object must be a class or function, got %r" % object)
91 raise ValueError("object must be a class or function, got %r" % object)
100
92
101 def _find(self, tests, obj, name, module, source_lines, globs, seen):
93 def _find(self, tests, obj, name, module, source_lines, globs, seen):
102 """
94 """
103 Find tests for the given object and any contained objects, and
95 Find tests for the given object and any contained objects, and
104 add them to `tests`.
96 add them to `tests`.
105 """
97 """
106 print('_find for:', obj, name, module) # dbg
98 print('_find for:', obj, name, module) # dbg
107 if bool(getattr(obj, "__skip_doctest__", False)):
99 if bool(getattr(obj, "__skip_doctest__", False)):
108 #print 'SKIPPING DOCTEST FOR:',obj # dbg
100 #print 'SKIPPING DOCTEST FOR:',obj # dbg
109 obj = DocTestSkip(obj)
101 obj = DocTestSkip(obj)
110
102
111 doctest.DocTestFinder._find(self,tests, obj, name, module,
103 doctest.DocTestFinder._find(self,tests, obj, name, module,
112 source_lines, globs, seen)
104 source_lines, globs, seen)
113
105
114 # Below we re-run pieces of the above method with manual modifications,
106 # Below we re-run pieces of the above method with manual modifications,
115 # because the original code is buggy and fails to correctly identify
107 # because the original code is buggy and fails to correctly identify
116 # doctests in extension modules.
108 # doctests in extension modules.
117
109
118 # Local shorthands
110 # Local shorthands
119 from inspect import isroutine, isclass
111 from inspect import isroutine, isclass
120
112
121 # Look for tests in a module's contained objects.
113 # Look for tests in a module's contained objects.
122 if inspect.ismodule(obj) and self._recurse:
114 if inspect.ismodule(obj) and self._recurse:
123 for valname, val in obj.__dict__.items():
115 for valname, val in obj.__dict__.items():
124 valname1 = '%s.%s' % (name, valname)
116 valname1 = '%s.%s' % (name, valname)
125 if ( (isroutine(val) or isclass(val))
117 if ( (isroutine(val) or isclass(val))
126 and self._from_module(module, val) ):
118 and self._from_module(module, val) ):
127
119
128 self._find(tests, val, valname1, module, source_lines,
120 self._find(tests, val, valname1, module, source_lines,
129 globs, seen)
121 globs, seen)
130
122
131 # Look for tests in a class's contained objects.
123 # Look for tests in a class's contained objects.
132 if inspect.isclass(obj) and self._recurse:
124 if inspect.isclass(obj) and self._recurse:
133 #print 'RECURSE into class:',obj # dbg
125 #print 'RECURSE into class:',obj # dbg
134 for valname, val in obj.__dict__.items():
126 for valname, val in obj.__dict__.items():
135 # Special handling for staticmethod/classmethod.
127 # Special handling for staticmethod/classmethod.
136 if isinstance(val, staticmethod):
128 if isinstance(val, staticmethod):
137 val = getattr(obj, valname)
129 val = getattr(obj, valname)
138 if isinstance(val, classmethod):
130 if isinstance(val, classmethod):
139 val = getattr(obj, valname).__func__
131 val = getattr(obj, valname).__func__
140
132
141 # Recurse to methods, properties, and nested classes.
133 # Recurse to methods, properties, and nested classes.
142 if ((inspect.isfunction(val) or inspect.isclass(val) or
134 if ((inspect.isfunction(val) or inspect.isclass(val) or
143 inspect.ismethod(val) or
135 inspect.ismethod(val) or
144 isinstance(val, property)) and
136 isinstance(val, property)) and
145 self._from_module(module, val)):
137 self._from_module(module, val)):
146 valname = '%s.%s' % (name, valname)
138 valname = '%s.%s' % (name, valname)
147 self._find(tests, val, valname, module, source_lines,
139 self._find(tests, val, valname, module, source_lines,
148 globs, seen)
140 globs, seen)
149
141
150
142
151 class IPDoctestOutputChecker(doctest.OutputChecker):
143 class IPDoctestOutputChecker(doctest.OutputChecker):
152 """Second-chance checker with support for random tests.
144 """Second-chance checker with support for random tests.
153
145
154 If the default comparison doesn't pass, this checker looks in the expected
146 If the default comparison doesn't pass, this checker looks in the expected
155 output string for flags that tell us to ignore the output.
147 output string for flags that tell us to ignore the output.
156 """
148 """
157
149
158 random_re = re.compile(r'#\s*random\s+')
150 random_re = re.compile(r'#\s*random\s+')
159
151
160 def check_output(self, want, got, optionflags):
152 def check_output(self, want, got, optionflags):
161 """Check output, accepting special markers embedded in the output.
153 """Check output, accepting special markers embedded in the output.
162
154
163 If the output didn't pass the default validation but the special string
155 If the output didn't pass the default validation but the special string
164 '#random' is included, we accept it."""
156 '#random' is included, we accept it."""
165
157
166 # Let the original tester verify first, in case people have valid tests
158 # Let the original tester verify first, in case people have valid tests
167 # that happen to have a comment saying '#random' embedded in.
159 # that happen to have a comment saying '#random' embedded in.
168 ret = doctest.OutputChecker.check_output(self, want, got,
160 ret = doctest.OutputChecker.check_output(self, want, got,
169 optionflags)
161 optionflags)
170 if not ret and self.random_re.search(want):
162 if not ret and self.random_re.search(want):
171 #print >> sys.stderr, 'RANDOM OK:',want # dbg
163 #print >> sys.stderr, 'RANDOM OK:',want # dbg
172 return True
164 return True
173
165
174 return ret
166 return ret
175
167
176
168
177 # A simple subclassing of the original with a different class name, so we can
169 # A simple subclassing of the original with a different class name, so we can
178 # distinguish and treat differently IPython examples from pure python ones.
170 # distinguish and treat differently IPython examples from pure python ones.
179 class IPExample(doctest.Example): pass
171 class IPExample(doctest.Example): pass
180
172
181
173
182 class IPExternalExample(doctest.Example):
174 class IPExternalExample(doctest.Example):
183 """Doctest examples to be run in an external process."""
175 """Doctest examples to be run in an external process."""
184
176
185 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
177 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
186 options=None):
178 options=None):
187 # Parent constructor
179 # Parent constructor
188 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
180 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
189
181
190 # An EXTRA newline is needed to prevent pexpect hangs
182 # An EXTRA newline is needed to prevent pexpect hangs
191 self.source += '\n'
183 self.source += '\n'
192
184
193
185
194 class IPDocTestParser(doctest.DocTestParser):
186 class IPDocTestParser(doctest.DocTestParser):
195 """
187 """
196 A class used to parse strings containing doctest examples.
188 A class used to parse strings containing doctest examples.
197
189
198 Note: This is a version modified to properly recognize IPython input and
190 Note: This is a version modified to properly recognize IPython input and
199 convert any IPython examples into valid Python ones.
191 convert any IPython examples into valid Python ones.
200 """
192 """
201 # This regular expression is used to find doctest examples in a
193 # This regular expression is used to find doctest examples in a
202 # string. It defines three groups: `source` is the source code
194 # string. It defines three groups: `source` is the source code
203 # (including leading indentation and prompts); `indent` is the
195 # (including leading indentation and prompts); `indent` is the
204 # indentation of the first (PS1) line of the source code; and
196 # indentation of the first (PS1) line of the source code; and
205 # `want` is the expected output (including leading indentation).
197 # `want` is the expected output (including leading indentation).
206
198
207 # Classic Python prompts or default IPython ones
199 # Classic Python prompts or default IPython ones
208 _PS1_PY = r'>>>'
200 _PS1_PY = r'>>>'
209 _PS2_PY = r'\.\.\.'
201 _PS2_PY = r'\.\.\.'
210
202
211 _PS1_IP = r'In\ \[\d+\]:'
203 _PS1_IP = r'In\ \[\d+\]:'
212 _PS2_IP = r'\ \ \ \.\.\.+:'
204 _PS2_IP = r'\ \ \ \.\.\.+:'
213
205
214 _RE_TPL = r'''
206 _RE_TPL = r'''
215 # Source consists of a PS1 line followed by zero or more PS2 lines.
207 # Source consists of a PS1 line followed by zero or more PS2 lines.
216 (?P<source>
208 (?P<source>
217 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
209 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
218 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
210 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
219 \n? # a newline
211 \n? # a newline
220 # Want consists of any non-blank lines that do not start with PS1.
212 # Want consists of any non-blank lines that do not start with PS1.
221 (?P<want> (?:(?![ ]*$) # Not a blank line
213 (?P<want> (?:(?![ ]*$) # Not a blank line
222 (?![ ]*%s) # Not a line starting with PS1
214 (?![ ]*%s) # Not a line starting with PS1
223 (?![ ]*%s) # Not a line starting with PS2
215 (?![ ]*%s) # Not a line starting with PS2
224 .*$\n? # But any other line
216 .*$\n? # But any other line
225 )*)
217 )*)
226 '''
218 '''
227
219
228 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
220 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
229 re.MULTILINE | re.VERBOSE)
221 re.MULTILINE | re.VERBOSE)
230
222
231 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
223 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
232 re.MULTILINE | re.VERBOSE)
224 re.MULTILINE | re.VERBOSE)
233
225
234 # Mark a test as being fully random. In this case, we simply append the
226 # Mark a test as being fully random. In this case, we simply append the
235 # random marker ('#random') to each individual example's output. This way
227 # random marker ('#random') to each individual example's output. This way
236 # we don't need to modify any other code.
228 # we don't need to modify any other code.
237 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
229 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
238
230
239 # Mark tests to be executed in an external process - currently unsupported.
231 # Mark tests to be executed in an external process - currently unsupported.
240 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
232 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
241
233
242 def ip2py(self,source):
234 def ip2py(self,source):
243 """Convert input IPython source into valid Python."""
235 """Convert input IPython source into valid Python."""
244 block = _ip.input_transformer_manager.transform_cell(source)
236 block = _ip.input_transformer_manager.transform_cell(source)
245 if len(block.splitlines()) == 1:
237 if len(block.splitlines()) == 1:
246 return _ip.prefilter(block)
238 return _ip.prefilter(block)
247 else:
239 else:
248 return block
240 return block
249
241
250 def parse(self, string, name='<string>'):
242 def parse(self, string, name='<string>'):
251 """
243 """
252 Divide the given string into examples and intervening text,
244 Divide the given string into examples and intervening text,
253 and return them as a list of alternating Examples and strings.
245 and return them as a list of alternating Examples and strings.
254 Line numbers for the Examples are 0-based. The optional
246 Line numbers for the Examples are 0-based. The optional
255 argument `name` is a name identifying this string, and is only
247 argument `name` is a name identifying this string, and is only
256 used for error messages.
248 used for error messages.
257 """
249 """
258
250
259 #print 'Parse string:\n',string # dbg
251 #print 'Parse string:\n',string # dbg
260
252
261 string = string.expandtabs()
253 string = string.expandtabs()
262 # If all lines begin with the same indentation, then strip it.
254 # If all lines begin with the same indentation, then strip it.
263 min_indent = self._min_indent(string)
255 min_indent = self._min_indent(string)
264 if min_indent > 0:
256 if min_indent > 0:
265 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
257 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
266
258
267 output = []
259 output = []
268 charno, lineno = 0, 0
260 charno, lineno = 0, 0
269
261
270 # We make 'all random' tests by adding the '# random' mark to every
262 # We make 'all random' tests by adding the '# random' mark to every
271 # block of output in the test.
263 # block of output in the test.
272 if self._RANDOM_TEST.search(string):
264 if self._RANDOM_TEST.search(string):
273 random_marker = '\n# random'
265 random_marker = '\n# random'
274 else:
266 else:
275 random_marker = ''
267 random_marker = ''
276
268
277 # Whether to convert the input from ipython to python syntax
269 # Whether to convert the input from ipython to python syntax
278 ip2py = False
270 ip2py = False
279 # Find all doctest examples in the string. First, try them as Python
271 # Find all doctest examples in the string. First, try them as Python
280 # examples, then as IPython ones
272 # examples, then as IPython ones
281 terms = list(self._EXAMPLE_RE_PY.finditer(string))
273 terms = list(self._EXAMPLE_RE_PY.finditer(string))
282 if terms:
274 if terms:
283 # Normal Python example
275 # Normal Python example
284 #print '-'*70 # dbg
276 #print '-'*70 # dbg
285 #print 'PyExample, Source:\n',string # dbg
277 #print 'PyExample, Source:\n',string # dbg
286 #print '-'*70 # dbg
278 #print '-'*70 # dbg
287 Example = doctest.Example
279 Example = doctest.Example
288 else:
280 else:
289 # It's an ipython example. Note that IPExamples are run
281 # It's an ipython example. Note that IPExamples are run
290 # in-process, so their syntax must be turned into valid python.
282 # in-process, so their syntax must be turned into valid python.
291 # IPExternalExamples are run out-of-process (via pexpect) so they
283 # IPExternalExamples are run out-of-process (via pexpect) so they
292 # don't need any filtering (a real ipython will be executing them).
284 # don't need any filtering (a real ipython will be executing them).
293 terms = list(self._EXAMPLE_RE_IP.finditer(string))
285 terms = list(self._EXAMPLE_RE_IP.finditer(string))
294 if self._EXTERNAL_IP.search(string):
286 if self._EXTERNAL_IP.search(string):
295 #print '-'*70 # dbg
287 #print '-'*70 # dbg
296 #print 'IPExternalExample, Source:\n',string # dbg
288 #print 'IPExternalExample, Source:\n',string # dbg
297 #print '-'*70 # dbg
289 #print '-'*70 # dbg
298 Example = IPExternalExample
290 Example = IPExternalExample
299 else:
291 else:
300 #print '-'*70 # dbg
292 #print '-'*70 # dbg
301 #print 'IPExample, Source:\n',string # dbg
293 #print 'IPExample, Source:\n',string # dbg
302 #print '-'*70 # dbg
294 #print '-'*70 # dbg
303 Example = IPExample
295 Example = IPExample
304 ip2py = True
296 ip2py = True
305
297
306 for m in terms:
298 for m in terms:
307 # Add the pre-example text to `output`.
299 # Add the pre-example text to `output`.
308 output.append(string[charno:m.start()])
300 output.append(string[charno:m.start()])
309 # Update lineno (lines before this example)
301 # Update lineno (lines before this example)
310 lineno += string.count('\n', charno, m.start())
302 lineno += string.count('\n', charno, m.start())
311 # Extract info from the regexp match.
303 # Extract info from the regexp match.
312 (source, options, want, exc_msg) = \
304 (source, options, want, exc_msg) = \
313 self._parse_example(m, name, lineno,ip2py)
305 self._parse_example(m, name, lineno,ip2py)
314
306
315 # Append the random-output marker (it defaults to empty in most
307 # Append the random-output marker (it defaults to empty in most
316 # cases, it's only non-empty for 'all-random' tests):
308 # cases, it's only non-empty for 'all-random' tests):
317 want += random_marker
309 want += random_marker
318
310
319 if Example is IPExternalExample:
311 if Example is IPExternalExample:
320 options[doctest.NORMALIZE_WHITESPACE] = True
312 options[doctest.NORMALIZE_WHITESPACE] = True
321 want += '\n'
313 want += '\n'
322
314
323 # Create an Example, and add it to the list.
315 # Create an Example, and add it to the list.
324 if not self._IS_BLANK_OR_COMMENT(source):
316 if not self._IS_BLANK_OR_COMMENT(source):
325 output.append(Example(source, want, exc_msg,
317 output.append(Example(source, want, exc_msg,
326 lineno=lineno,
318 lineno=lineno,
327 indent=min_indent+len(m.group('indent')),
319 indent=min_indent+len(m.group('indent')),
328 options=options))
320 options=options))
329 # Update lineno (lines inside this example)
321 # Update lineno (lines inside this example)
330 lineno += string.count('\n', m.start(), m.end())
322 lineno += string.count('\n', m.start(), m.end())
331 # Update charno.
323 # Update charno.
332 charno = m.end()
324 charno = m.end()
333 # Add any remaining post-example text to `output`.
325 # Add any remaining post-example text to `output`.
334 output.append(string[charno:])
326 output.append(string[charno:])
335 return output
327 return output
336
328
337 def _parse_example(self, m, name, lineno,ip2py=False):
329 def _parse_example(self, m, name, lineno,ip2py=False):
338 """
330 """
339 Given a regular expression match from `_EXAMPLE_RE` (`m`),
331 Given a regular expression match from `_EXAMPLE_RE` (`m`),
340 return a pair `(source, want)`, where `source` is the matched
332 return a pair `(source, want)`, where `source` is the matched
341 example's source code (with prompts and indentation stripped);
333 example's source code (with prompts and indentation stripped);
342 and `want` is the example's expected output (with indentation
334 and `want` is the example's expected output (with indentation
343 stripped).
335 stripped).
344
336
345 `name` is the string's name, and `lineno` is the line number
337 `name` is the string's name, and `lineno` is the line number
346 where the example starts; both are used for error messages.
338 where the example starts; both are used for error messages.
347
339
348 Optional:
340 Optional:
349 `ip2py`: if true, filter the input via IPython to convert the syntax
341 `ip2py`: if true, filter the input via IPython to convert the syntax
350 into valid python.
342 into valid python.
351 """
343 """
352
344
353 # Get the example's indentation level.
345 # Get the example's indentation level.
354 indent = len(m.group('indent'))
346 indent = len(m.group('indent'))
355
347
356 # Divide source into lines; check that they're properly
348 # Divide source into lines; check that they're properly
357 # indented; and then strip their indentation & prompts.
349 # indented; and then strip their indentation & prompts.
358 source_lines = m.group('source').split('\n')
350 source_lines = m.group('source').split('\n')
359
351
360 # We're using variable-length input prompts
352 # We're using variable-length input prompts
361 ps1 = m.group('ps1')
353 ps1 = m.group('ps1')
362 ps2 = m.group('ps2')
354 ps2 = m.group('ps2')
363 ps1_len = len(ps1)
355 ps1_len = len(ps1)
364
356
365 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
357 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
366 if ps2:
358 if ps2:
367 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
359 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
368
360
369 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
361 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
370
362
371 if ip2py:
363 if ip2py:
372 # Convert source input from IPython into valid Python syntax
364 # Convert source input from IPython into valid Python syntax
373 source = self.ip2py(source)
365 source = self.ip2py(source)
374
366
375 # Divide want into lines; check that it's properly indented; and
367 # Divide want into lines; check that it's properly indented; and
376 # then strip the indentation. Spaces before the last newline should
368 # then strip the indentation. Spaces before the last newline should
377 # be preserved, so plain rstrip() isn't good enough.
369 # be preserved, so plain rstrip() isn't good enough.
378 want = m.group('want')
370 want = m.group('want')
379 want_lines = want.split('\n')
371 want_lines = want.split('\n')
380 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
372 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
381 del want_lines[-1] # forget final newline & spaces after it
373 del want_lines[-1] # forget final newline & spaces after it
382 self._check_prefix(want_lines, ' '*indent, name,
374 self._check_prefix(want_lines, ' '*indent, name,
383 lineno + len(source_lines))
375 lineno + len(source_lines))
384
376
385 # Remove ipython output prompt that might be present in the first line
377 # Remove ipython output prompt that might be present in the first line
386 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
378 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
387
379
388 want = '\n'.join([wl[indent:] for wl in want_lines])
380 want = '\n'.join([wl[indent:] for wl in want_lines])
389
381
390 # If `want` contains a traceback message, then extract it.
382 # If `want` contains a traceback message, then extract it.
391 m = self._EXCEPTION_RE.match(want)
383 m = self._EXCEPTION_RE.match(want)
392 if m:
384 if m:
393 exc_msg = m.group('msg')
385 exc_msg = m.group('msg')
394 else:
386 else:
395 exc_msg = None
387 exc_msg = None
396
388
397 # Extract options from the source.
389 # Extract options from the source.
398 options = self._find_options(source, name, lineno)
390 options = self._find_options(source, name, lineno)
399
391
400 return source, options, want, exc_msg
392 return source, options, want, exc_msg
401
393
402 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
394 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
403 """
395 """
404 Given the lines of a source string (including prompts and
396 Given the lines of a source string (including prompts and
405 leading indentation), check to make sure that every prompt is
397 leading indentation), check to make sure that every prompt is
406 followed by a space character. If any line is not followed by
398 followed by a space character. If any line is not followed by
407 a space character, then raise ValueError.
399 a space character, then raise ValueError.
408
400
409 Note: IPython-modified version which takes the input prompt length as a
401 Note: IPython-modified version which takes the input prompt length as a
410 parameter, so that prompts of variable length can be dealt with.
402 parameter, so that prompts of variable length can be dealt with.
411 """
403 """
412 space_idx = indent+ps1_len
404 space_idx = indent+ps1_len
413 min_len = space_idx+1
405 min_len = space_idx+1
414 for i, line in enumerate(lines):
406 for i, line in enumerate(lines):
415 if len(line) >= min_len and line[space_idx] != ' ':
407 if len(line) >= min_len and line[space_idx] != ' ':
416 raise ValueError('line %r of the docstring for %s '
408 raise ValueError('line %r of the docstring for %s '
417 'lacks blank after %s: %r' %
409 'lacks blank after %s: %r' %
418 (lineno+i+1, name,
410 (lineno+i+1, name,
419 line[indent:space_idx], line))
411 line[indent:space_idx], line))
420
412
421
413
422 SKIP = doctest.register_optionflag('SKIP')
414 SKIP = doctest.register_optionflag('SKIP')
423
415
424
416
425 class IPDocTestRunner(doctest.DocTestRunner,object):
417 class IPDocTestRunner(doctest.DocTestRunner,object):
426 """Test runner that synchronizes the IPython namespace with test globals.
418 """Test runner that synchronizes the IPython namespace with test globals.
427 """
419 """
428
420
429 def run(self, test, compileflags=None, out=None, clear_globs=True):
421 def run(self, test, compileflags=None, out=None, clear_globs=True):
430
422
431 # Hack: ipython needs access to the execution context of the example,
423 # Hack: ipython needs access to the execution context of the example,
432 # so that it can propagate user variables loaded by %run into
424 # so that it can propagate user variables loaded by %run into
433 # test.globs. We put them here into our modified %run as a function
425 # test.globs. We put them here into our modified %run as a function
434 # attribute. Our new %run will then only make the namespace update
426 # attribute. Our new %run will then only make the namespace update
435 # when called (rather than unconditionally updating test.globs here
427 # when called (rather than unconditionally updating test.globs here
436 # for all examples, most of which won't be calling %run anyway).
428 # for all examples, most of which won't be calling %run anyway).
437 #_ip._ipdoctest_test_globs = test.globs
429 #_ip._ipdoctest_test_globs = test.globs
438 #_ip._ipdoctest_test_filename = test.filename
430 #_ip._ipdoctest_test_filename = test.filename
439
431
440 test.globs.update(_ip.user_ns)
432 test.globs.update(_ip.user_ns)
441
433
442 # Override terminal size to standardise traceback format
434 # Override terminal size to standardise traceback format
443 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
435 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
444 return super(IPDocTestRunner,self).run(test,
436 return super(IPDocTestRunner,self).run(test,
445 compileflags,out,clear_globs)
437 compileflags,out,clear_globs)
446
447
448 class DocFileCase(doctest.DocFileCase):
449 """Overrides to provide filename
450 """
451 def address(self):
452 return (self._dt_test.filename, None, None)
General Comments 0
You need to be logged in to leave comments. Login now