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