##// END OF EJS Templates
Fix a number of bugs with %history, add proper tests....
Fernando Perez -
Show More
@@ -1,281 +1,258 b''
1 1 # -*- coding: utf-8 -*-
2
3 2 """ History related magics and functionality """
4 3
5 4 # Stdlib imports
6 5 import fnmatch
7 6 import os
8 7
9 8 # IPython imports
10 from IPython.genutils import Term, ask_yes_no
9 from IPython.genutils import Term, ask_yes_no, warn
11 10 import IPython.ipapi
12 11
13 12 def magic_history(self, parameter_s = ''):
14 13 """Print input history (_i<n> variables), with most recent last.
15 14
16 15 %history -> print at most 40 inputs (some may be multi-line)\\
17 16 %history n -> print at most n inputs\\
18 17 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
19 18
20 19 Each input's number <n> is shown, and is accessible as the
21 20 automatically generated variable _i<n>. Multi-line statements are
22 21 printed starting at a new line for easy copy/paste.
23 22
24 23
25 24 Options:
26 25
27 26 -n: do NOT print line numbers. This is useful if you want to get a
28 27 printout of many lines which can be directly pasted into a text
29 28 editor.
30 29
31 30 This feature is only available if numbered prompts are in use.
32 31
33 32 -t: (default) print the 'translated' history, as IPython understands it.
34 33 IPython filters your input and converts it all into valid Python source
35 34 before executing it (things like magics or aliases are turned into
36 35 function calls, for example). With this option, you'll see the native
37 36 history instead of the user-entered version: '%cd /' will be seen as
38 37 '_ip.magic("%cd /")' instead of '%cd /'.
39 38
40 39 -r: print the 'raw' history, i.e. the actual commands you typed.
41 40
42 41 -g: treat the arg as a pattern to grep for in (full) history.
43 42 This includes the "shadow history" (almost all commands ever written).
44 43 Use '%hist -g' to show full shadow history (may be very long).
45 44 In shadow history, every index nuwber starts with 0.
46 45
47 46 -f FILENAME: instead of printing the output to the screen, redirect it to
48 47 the given file. The file is always overwritten, though IPython asks for
49 48 confirmation first if it already exists.
50
51
52 49 """
53 50
54 51 ip = self.api
55 52 shell = self.shell
56 53 if not shell.outputcache.do_full_cache:
57 54 print 'This feature is only available if numbered prompts are in use.'
58 55 return
59 56 opts,args = self.parse_options(parameter_s,'gntsrf:',mode='list')
60 57
61 58 # Check if output to specific file was requested.
62 59 try:
63 60 outfname = opts['f']
64 61 except KeyError:
65 outfile = Term.cout
62 outfile = Term.cout # default
66 63 # We don't want to close stdout at the end!
67 64 close_at_end = False
68 65 else:
69 66 if os.path.exists(outfname):
70 ans = ask_yes_no("File %r exists. Overwrite?" % outfname)
71 if not ans:
67 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
72 68 print 'Aborting.'
73 69 return
74 else:
75 outfile = open(outfname,'w')
76 close_at_end = True
77
78 70
79 if opts.has_key('t'):
71 outfile = open(outfname,'w')
72 close_at_end = True
73
74 if 't' in opts:
80 75 input_hist = shell.input_hist
81 elif opts.has_key('r'):
76 elif 'r' in opts:
82 77 input_hist = shell.input_hist_raw
83 78 else:
84 79 input_hist = shell.input_hist
85
86
80
87 81 default_length = 40
88 82 pattern = None
89 if opts.has_key('g'):
83 if 'g' in opts:
90 84 init = 1
91 85 final = len(input_hist)
92 86 parts = parameter_s.split(None,1)
93 87 if len(parts) == 1:
94 88 parts += '*'
95 89 head, pattern = parts
96 90 pattern = "*" + pattern + "*"
97 91 elif len(args) == 0:
98 92 final = len(input_hist)
99 93 init = max(1,final-default_length)
100 94 elif len(args) == 1:
101 95 final = len(input_hist)
102 96 init = max(1,final-int(args[0]))
103 97 elif len(args) == 2:
104 98 init,final = map(int,args)
105 99 else:
106 100 warn('%hist takes 0, 1 or 2 arguments separated by spaces.')
107 101 print self.magic_hist.__doc__
108 102 return
109 103 width = len(str(final))
110 104 line_sep = ['','\n']
111 105 print_nums = not opts.has_key('n')
112 106
113 107 found = False
114 108 if pattern is not None:
115 109 sh = ip.IP.shadowhist.all()
116 110 for idx, s in sh:
117 111 if fnmatch.fnmatch(s, pattern):
118 112 print "0%d: %s" %(idx, s)
119 113 found = True
120 114
121 115 if found:
122 116 print "==="
123 117 print "shadow history ends, fetch by %rep <number> (must start with 0)"
124 118 print "=== start of normal history ==="
125 119
126 120 for in_num in range(init,final):
127 121 inline = input_hist[in_num]
128 122 if pattern is not None and not fnmatch.fnmatch(inline, pattern):
129 123 continue
130 124
131 125 multiline = int(inline.count('\n') > 1)
132 126 if print_nums:
133 127 print >> outfile, \
134 128 '%s:%s' % (str(in_num).ljust(width),line_sep[multiline]),
135 129 print >> outfile, inline,
136 130
137 131 if close_at_end:
138 132 outfile.close()
139 133
140 134
141
142 135 def magic_hist(self, parameter_s=''):
143 136 """Alternate name for %history."""
144 137 return self.magic_history(parameter_s)
145 138
146 139
147
148 140 def rep_f(self, arg):
149 141 r""" Repeat a command, or get command to input line for editing
150 142
151 143 - %rep (no arguments):
152 144
153 145 Place a string version of last computation result (stored in the special '_'
154 146 variable) to the next input prompt. Allows you to create elaborate command
155 147 lines without using copy-paste::
156 148
157 149 $ l = ["hei", "vaan"]
158 150 $ "".join(l)
159 151 ==> heivaan
160 152 $ %rep
161 153 $ heivaan_ <== cursor blinking
162 154
163 155 %rep 45
164 156
165 157 Place history line 45 to next input prompt. Use %hist to find out the
166 158 number.
167 159
168 160 %rep 1-4 6-7 3
169 161
170 162 Repeat the specified lines immediately. Input slice syntax is the same as
171 163 in %macro and %save.
172 164
173 165 %rep foo
174 166
175 167 Place the most recent line that has the substring "foo" to next input.
176 (e.g. 'svn ci -m foobar').
177
168 (e.g. 'svn ci -m foobar').
178 169 """
179 170
180
181 171 opts,args = self.parse_options(arg,'',mode='list')
182 172 ip = self.api
183 173 if not args:
184 174 ip.set_next_input(str(ip.user_ns["_"]))
185 175 return
186 176
187 177 if len(args) == 1 and not '-' in args[0]:
188 178 arg = args[0]
189 179 if len(arg) > 1 and arg.startswith('0'):
190 180 # get from shadow hist
191 181 num = int(arg[1:])
192 182 line = self.shadowhist.get(num)
193 183 ip.set_next_input(str(line))
194 184 return
195 185 try:
196 186 num = int(args[0])
197 187 ip.set_next_input(str(ip.IP.input_hist_raw[num]).rstrip())
198 188 return
199 189 except ValueError:
200 190 pass
201 191
202 192 for h in reversed(self.shell.input_hist_raw):
203 193 if 'rep' in h:
204 194 continue
205 195 if fnmatch.fnmatch(h,'*' + arg + '*'):
206 196 ip.set_next_input(str(h).rstrip())
207 197 return
208 198
209
210 199 try:
211 200 lines = self.extract_input_slices(args, True)
212 201 print "lines",lines
213 202 ip.runlines(lines)
214 203 except ValueError:
215 204 print "Not found in recent history:", args
216 205
217 206
218
219 207 _sentinel = object()
220 208
221 209 class ShadowHist:
222 210 def __init__(self,db):
223 211 # cmd => idx mapping
224 212 self.curidx = 0
225 213 self.db = db
226 214 self.disabled = False
227 215
228 216 def inc_idx(self):
229 217 idx = self.db.get('shadowhist_idx', 1)
230 218 self.db['shadowhist_idx'] = idx + 1
231 219 return idx
232 220
233 221 def add(self, ent):
234 222 if self.disabled:
235 223 return
236 224 try:
237 225 old = self.db.hget('shadowhist', ent, _sentinel)
238 226 if old is not _sentinel:
239 227 return
240 228 newidx = self.inc_idx()
241 229 #print "new",newidx # dbg
242 230 self.db.hset('shadowhist',ent, newidx)
243 231 except:
244 232 IPython.ipapi.get().IP.showtraceback()
245 233 print "WARNING: disabling shadow history"
246 234 self.disabled = True
247 235
248 236 def all(self):
249 237 d = self.db.hdict('shadowhist')
250 238 items = [(i,s) for (s,i) in d.items()]
251 239 items.sort()
252 240 return items
253 241
254 242 def get(self, idx):
255 243 all = self.all()
256 244
257 245 for k, v in all:
258 246 #print k,v
259 247 if k == idx:
260 248 return v
261 249
262 def test_shist():
263 from IPython.Extensions import pickleshare
264 db = pickleshare.PickleShareDB('~/shist')
265 s = ShadowHist(db)
266 s.add('hello')
267 s.add('world')
268 s.add('hello')
269 s.add('hello')
270 s.add('karhu')
271 print "all",s.all()
272 print s.get(2)
273 250
274 251 def init_ipython(ip):
252 import ipy_completers
253
275 254 ip.expose_magic("rep",rep_f)
276 255 ip.expose_magic("hist",magic_hist)
277 256 ip.expose_magic("history",magic_history)
278 257
279 import ipy_completers
280 258 ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
281 #test_shist()
@@ -1,805 +1,820 b''
1 1 """Nose Plugin that supports IPython doctests.
2 2
3 3 Limitations:
4 4
5 5 - When generating examples for use as doctests, make sure that you have
6 6 pretty-printing OFF. This can be done either by starting ipython with the
7 7 flag '--nopprint', by setting pprint to 0 in your ipythonrc file, or by
8 8 interactively disabling it with %Pprint. This is required so that IPython
9 9 output matches that of normal Python, which is used by doctest for internal
10 10 execution.
11 11
12 12 - Do not rely on specific prompt numbers for results (such as using
13 13 '_34==True', for example). For IPython tests run via an external process the
14 14 prompt numbers may be different, and IPython tests run as normal python code
15 15 won't even have these special _NN variables set at all.
16 16 """
17 17
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Module imports
21 21
22 22 # From the standard library
23 23 import __builtin__
24 24 import commands
25 25 import doctest
26 26 import inspect
27 27 import logging
28 28 import os
29 29 import re
30 30 import sys
31 31 import traceback
32 32 import unittest
33 33
34 34 from inspect import getmodule
35 35 from StringIO import StringIO
36 36
37 37 # We are overriding the default doctest runner, so we need to import a few
38 38 # things from doctest directly
39 39 from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
40 40 _unittest_reportflags, DocTestRunner,
41 41 _extract_future_flags, pdb, _OutputRedirectingPdb,
42 42 _exception_traceback,
43 43 linecache)
44 44
45 45 # Third-party modules
46 46 import nose.core
47 47
48 48 from nose.plugins import doctests, Plugin
49 49 from nose.util import anyp, getpackage, test_address, resolve_name, tolist
50 50
51 51 #-----------------------------------------------------------------------------
52 52 # Module globals and other constants
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56 ###########################################################################
57 57 # *** HACK ***
58 58 # We must start our own ipython object and heavily muck with it so that all the
59 59 # modifications IPython makes to system behavior don't send the doctest
60 60 # machinery into a fit. This code should be considered a gross hack, but it
61 61 # gets the job done.
62 62
63 63
64 64 # Hack to modify the %run command so we can sync the user's namespace with the
65 65 # test globals. Once we move over to a clean magic system, this will be done
66 66 # with much less ugliness.
67 67
68 class py_file_finder(object):
69 def __init__(self,test_filename):
70 self.test_filename = test_filename
71
72 def __call__(self,name):
73 from IPython.genutils import get_py_filename
74 try:
75 get_py_filename(name)
76 except IOError:
77 test_dir = os.path.dirname(self.test_filename)
78 new_path = os.path.join(test_dir,name)
79 return get_py_filename(new_path)
80
81
68 82 def _run_ns_sync(self,arg_s,runner=None):
69 83 """Modified version of %run that syncs testing namespaces.
70 84
71 85 This is strictly needed for running doctests that call %run.
72 86 """
73 87
74 out = _ip.IP.magic_run_ori(arg_s,runner)
88 finder = py_file_finder(_run_ns_sync.test_filename)
89 out = _ip.IP.magic_run_ori(arg_s,runner,finder)
75 90 _run_ns_sync.test_globs.update(_ip.user_ns)
76 91 return out
77 92
78 93
79 94 class ipnsdict(dict):
80 95 """A special subclass of dict for use as an IPython namespace in doctests.
81 96
82 97 This subclass adds a simple checkpointing capability so that when testing
83 98 machinery clears it (we use it as the test execution context), it doesn't
84 99 get completely destroyed.
85 100 """
86 101
87 102 def __init__(self,*a):
88 103 dict.__init__(self,*a)
89 104 self._savedict = {}
90 105
91 106 def clear(self):
92 107 dict.clear(self)
93 108 self.update(self._savedict)
94 109
95 110 def _checkpoint(self):
96 111 self._savedict.clear()
97 112 self._savedict.update(self)
98 113
99 114 def update(self,other):
100 115 self._checkpoint()
101 116 dict.update(self,other)
102 117 # If '_' is in the namespace, python won't set it when executing code,
103 118 # and we have examples that test it. So we ensure that the namespace
104 119 # is always 'clean' of it before it's used for test code execution.
105 120 self.pop('_',None)
106 121
107 122
108 123 def start_ipython():
109 124 """Start a global IPython shell, which we need for IPython-specific syntax.
110 125 """
111 126 import new
112 127
113 128 import IPython
114 129
115 130 def xsys(cmd):
116 131 """Execute a command and print its output.
117 132
118 133 This is just a convenience function to replace the IPython system call
119 134 with one that is more doctest-friendly.
120 135 """
121 136 cmd = _ip.IP.var_expand(cmd,depth=1)
122 137 sys.stdout.write(commands.getoutput(cmd))
123 138 sys.stdout.flush()
124 139
125 140 # Store certain global objects that IPython modifies
126 141 _displayhook = sys.displayhook
127 142 _excepthook = sys.excepthook
128 143 _main = sys.modules.get('__main__')
129 144
130 145 # Start IPython instance. We customize it to start with minimal frills.
131 146 user_ns,global_ns = IPython.ipapi.make_user_namespaces(ipnsdict(),dict())
132
133 IPython.Shell.IPShell(['--classic','--noterm_title'],
147 IPython.Shell.IPShell(['--colors=NoColor','--noterm_title'],
134 148 user_ns,global_ns)
135 149
136 150 # Deactivate the various python system hooks added by ipython for
137 151 # interactive convenience so we don't confuse the doctest system
138 152 sys.modules['__main__'] = _main
139 153 sys.displayhook = _displayhook
140 154 sys.excepthook = _excepthook
141 155
142 156 # So that ipython magics and aliases can be doctested (they work by making
143 157 # a call into a global _ip object)
144 158 _ip = IPython.ipapi.get()
145 159 __builtin__._ip = _ip
146 160
147 161 # Modify the IPython system call with one that uses getoutput, so that we
148 162 # can capture subcommands and print them to Python's stdout, otherwise the
149 163 # doctest machinery would miss them.
150 164 _ip.system = xsys
151 165
152 166 # Also patch our %run function in.
153 167 im = new.instancemethod(_run_ns_sync,_ip.IP, _ip.IP.__class__)
154 168 _ip.IP.magic_run_ori = _ip.IP.magic_run
155 169 _ip.IP.magic_run = im
156 170
157 171 # The start call MUST be made here. I'm not sure yet why it doesn't work if
158 172 # it is made later, at plugin initialization time, but in all my tests, that's
159 173 # the case.
160 174 start_ipython()
161 175
162 176 # *** END HACK ***
163 177 ###########################################################################
164 178
165 179 # Classes and functions
166 180
167 181 def is_extension_module(filename):
168 182 """Return whether the given filename is an extension module.
169 183
170 184 This simply checks that the extension is either .so or .pyd.
171 185 """
172 186 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
173 187
174 188
175 189 class nodoc(object):
176 190 def __init__(self,obj):
177 191 self.obj = obj
178 192
179 193 def __getattribute__(self,key):
180 194 if key == '__doc__':
181 195 return None
182 196 else:
183 197 return getattr(object.__getattribute__(self,'obj'),key)
184 198
185 199 # Modified version of the one in the stdlib, that fixes a python bug (doctests
186 200 # not found in extension modules, http://bugs.python.org/issue3158)
187 201 class DocTestFinder(doctest.DocTestFinder):
188 202
189 203 def _from_module(self, module, object):
190 204 """
191 205 Return true if the given object is defined in the given
192 206 module.
193 207 """
194 208 if module is None:
195 209 return True
196 210 elif inspect.isfunction(object):
197 211 return module.__dict__ is object.func_globals
198 212 elif inspect.isbuiltin(object):
199 213 return module.__name__ == object.__module__
200 214 elif inspect.isclass(object):
201 215 return module.__name__ == object.__module__
202 216 elif inspect.ismethod(object):
203 217 # This one may be a bug in cython that fails to correctly set the
204 218 # __module__ attribute of methods, but since the same error is easy
205 219 # to make by extension code writers, having this safety in place
206 220 # isn't such a bad idea
207 221 return module.__name__ == object.im_class.__module__
208 222 elif inspect.getmodule(object) is not None:
209 223 return module is inspect.getmodule(object)
210 224 elif hasattr(object, '__module__'):
211 225 return module.__name__ == object.__module__
212 226 elif isinstance(object, property):
213 227 return True # [XX] no way not be sure.
214 228 else:
215 229 raise ValueError("object must be a class or function")
216 230
217 231 def _find(self, tests, obj, name, module, source_lines, globs, seen):
218 232 """
219 233 Find tests for the given object and any contained objects, and
220 234 add them to `tests`.
221 235 """
222 236
223 237 if hasattr(obj,"skip_doctest"):
224 238 #print 'SKIPPING DOCTEST FOR:',obj # dbg
225 239 obj = nodoc(obj)
226 240
227 241 doctest.DocTestFinder._find(self,tests, obj, name, module,
228 242 source_lines, globs, seen)
229 243
230 244 # Below we re-run pieces of the above method with manual modifications,
231 245 # because the original code is buggy and fails to correctly identify
232 246 # doctests in extension modules.
233 247
234 248 # Local shorthands
235 249 from inspect import isroutine, isclass, ismodule
236 250
237 251 # Look for tests in a module's contained objects.
238 252 if inspect.ismodule(obj) and self._recurse:
239 253 for valname, val in obj.__dict__.items():
240 254 valname1 = '%s.%s' % (name, valname)
241 255 if ( (isroutine(val) or isclass(val))
242 256 and self._from_module(module, val) ):
243 257
244 258 self._find(tests, val, valname1, module, source_lines,
245 259 globs, seen)
246 260
247 261 # Look for tests in a class's contained objects.
248 262 if inspect.isclass(obj) and self._recurse:
249 263 #print 'RECURSE into class:',obj # dbg
250 264 for valname, val in obj.__dict__.items():
251 265 # Special handling for staticmethod/classmethod.
252 266 if isinstance(val, staticmethod):
253 267 val = getattr(obj, valname)
254 268 if isinstance(val, classmethod):
255 269 val = getattr(obj, valname).im_func
256 270
257 271 # Recurse to methods, properties, and nested classes.
258 272 if ((inspect.isfunction(val) or inspect.isclass(val) or
259 273 inspect.ismethod(val) or
260 274 isinstance(val, property)) and
261 275 self._from_module(module, val)):
262 276 valname = '%s.%s' % (name, valname)
263 277 self._find(tests, val, valname, module, source_lines,
264 278 globs, seen)
265 279
266 280
267 281 class IPDoctestOutputChecker(doctest.OutputChecker):
268 282 """Second-chance checker with support for random tests.
269 283
270 284 If the default comparison doesn't pass, this checker looks in the expected
271 285 output string for flags that tell us to ignore the output.
272 286 """
273 287
274 288 random_re = re.compile(r'#\s*random\s+')
275 289
276 290 def check_output(self, want, got, optionflags):
277 291 """Check output, accepting special markers embedded in the output.
278 292
279 293 If the output didn't pass the default validation but the special string
280 294 '#random' is included, we accept it."""
281 295
282 296 # Let the original tester verify first, in case people have valid tests
283 297 # that happen to have a comment saying '#random' embedded in.
284 298 ret = doctest.OutputChecker.check_output(self, want, got,
285 299 optionflags)
286 300 if not ret and self.random_re.search(want):
287 301 #print >> sys.stderr, 'RANDOM OK:',want # dbg
288 302 return True
289 303
290 304 return ret
291 305
292 306
293 307 class DocTestCase(doctests.DocTestCase):
294 308 """Proxy for DocTestCase: provides an address() method that
295 309 returns the correct address for the doctest case. Otherwise
296 310 acts as a proxy to the test case. To provide hints for address(),
297 311 an obj may also be passed -- this will be used as the test object
298 312 for purposes of determining the test address, if it is provided.
299 313 """
300 314
301 315 # Note: this method was taken from numpy's nosetester module.
302 316
303 317 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
304 318 # its constructor that blocks non-default arguments from being passed
305 319 # down into doctest.DocTestCase
306 320
307 321 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
308 322 checker=None, obj=None, result_var='_'):
309 323 self._result_var = result_var
310 324 doctests.DocTestCase.__init__(self, test,
311 325 optionflags=optionflags,
312 326 setUp=setUp, tearDown=tearDown,
313 327 checker=checker)
314 328 # Now we must actually copy the original constructor from the stdlib
315 329 # doctest class, because we can't call it directly and a bug in nose
316 330 # means it never gets passed the right arguments.
317 331
318 332 self._dt_optionflags = optionflags
319 333 self._dt_checker = checker
320 334 self._dt_test = test
321 335 self._dt_setUp = setUp
322 336 self._dt_tearDown = tearDown
323 337
324 338 # XXX - store this runner once in the object!
325 339 runner = IPDocTestRunner(optionflags=optionflags,
326 340 checker=checker, verbose=False)
327 341 self._dt_runner = runner
328 342
329 343
330 344 # Each doctest should remember what directory it was loaded from...
331 345 self._ori_dir = os.getcwd()
332 346
333 347 # Modified runTest from the default stdlib
334 348 def runTest(self):
335 349 test = self._dt_test
336 350 runner = self._dt_runner
337 351
338 352 old = sys.stdout
339 353 new = StringIO()
340 354 optionflags = self._dt_optionflags
341 355
342 356 if not (optionflags & REPORTING_FLAGS):
343 357 # The option flags don't include any reporting flags,
344 358 # so add the default reporting flags
345 359 optionflags |= _unittest_reportflags
346 360
347 361 try:
348 362 # Save our current directory and switch out to the one where the
349 363 # test was originally created, in case another doctest did a
350 364 # directory change. We'll restore this in the finally clause.
351 365 curdir = os.getcwd()
352 366 os.chdir(self._ori_dir)
353 367
354 368 runner.DIVIDER = "-"*70
355 369 failures, tries = runner.run(test,out=new.write,
356 370 clear_globs=False)
357 371 finally:
358 372 sys.stdout = old
359 373 os.chdir(curdir)
360 374
361 375 if failures:
362 376 raise self.failureException(self.format_failure(new.getvalue()))
363 377
364 378 def setUp(self):
365 379 """Modified test setup that syncs with ipython namespace"""
366 380
367 381 if isinstance(self._dt_test.examples[0],IPExample):
368 382 # for IPython examples *only*, we swap the globals with the ipython
369 383 # namespace, after updating it with the globals (which doctest
370 384 # fills with the necessary info from the module being tested).
371 385 _ip.IP.user_ns.update(self._dt_test.globs)
372 386 self._dt_test.globs = _ip.IP.user_ns
373 387
374 388 doctests.DocTestCase.setUp(self)
375 389
376 390
377 391
378 392 # A simple subclassing of the original with a different class name, so we can
379 393 # distinguish and treat differently IPython examples from pure python ones.
380 394 class IPExample(doctest.Example): pass
381 395
382 396
383 397 class IPExternalExample(doctest.Example):
384 398 """Doctest examples to be run in an external process."""
385 399
386 400 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
387 401 options=None):
388 402 # Parent constructor
389 403 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
390 404
391 405 # An EXTRA newline is needed to prevent pexpect hangs
392 406 self.source += '\n'
393 407
394 408
395 409 class IPDocTestParser(doctest.DocTestParser):
396 410 """
397 411 A class used to parse strings containing doctest examples.
398 412
399 413 Note: This is a version modified to properly recognize IPython input and
400 414 convert any IPython examples into valid Python ones.
401 415 """
402 416 # This regular expression is used to find doctest examples in a
403 417 # string. It defines three groups: `source` is the source code
404 418 # (including leading indentation and prompts); `indent` is the
405 419 # indentation of the first (PS1) line of the source code; and
406 420 # `want` is the expected output (including leading indentation).
407 421
408 422 # Classic Python prompts or default IPython ones
409 423 _PS1_PY = r'>>>'
410 424 _PS2_PY = r'\.\.\.'
411 425
412 426 _PS1_IP = r'In\ \[\d+\]:'
413 427 _PS2_IP = r'\ \ \ \.\.\.+:'
414 428
415 429 _RE_TPL = r'''
416 430 # Source consists of a PS1 line followed by zero or more PS2 lines.
417 431 (?P<source>
418 432 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
419 433 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
420 434 \n? # a newline
421 435 # Want consists of any non-blank lines that do not start with PS1.
422 436 (?P<want> (?:(?![ ]*$) # Not a blank line
423 437 (?![ ]*%s) # Not a line starting with PS1
424 438 (?![ ]*%s) # Not a line starting with PS2
425 439 .*$\n? # But any other line
426 440 )*)
427 441 '''
428 442
429 443 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
430 444 re.MULTILINE | re.VERBOSE)
431 445
432 446 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
433 447 re.MULTILINE | re.VERBOSE)
434 448
435 449 # Mark a test as being fully random. In this case, we simply append the
436 450 # random marker ('#random') to each individual example's output. This way
437 451 # we don't need to modify any other code.
438 452 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
439 453
440 454 # Mark tests to be executed in an external process - currently unsupported.
441 455 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
442 456
443 457 def ip2py(self,source):
444 458 """Convert input IPython source into valid Python."""
445 459 out = []
446 460 newline = out.append
447 461 for lnum,line in enumerate(source.splitlines()):
448 462 newline(_ip.IP.prefilter(line,lnum>0))
449 463 newline('') # ensure a closing newline, needed by doctest
450 464 #print "PYSRC:", '\n'.join(out) # dbg
451 465 return '\n'.join(out)
452 466
453 467 def parse(self, string, name='<string>'):
454 468 """
455 469 Divide the given string into examples and intervening text,
456 470 and return them as a list of alternating Examples and strings.
457 471 Line numbers for the Examples are 0-based. The optional
458 472 argument `name` is a name identifying this string, and is only
459 473 used for error messages.
460 474 """
461 475
462 476 #print 'Parse string:\n',string # dbg
463 477
464 478 string = string.expandtabs()
465 479 # If all lines begin with the same indentation, then strip it.
466 480 min_indent = self._min_indent(string)
467 481 if min_indent > 0:
468 482 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
469 483
470 484 output = []
471 485 charno, lineno = 0, 0
472 486
473 487 # We make 'all random' tests by adding the '# random' mark to every
474 488 # block of output in the test.
475 489 if self._RANDOM_TEST.search(string):
476 490 random_marker = '\n# random'
477 491 else:
478 492 random_marker = ''
479 493
480 494 # Whether to convert the input from ipython to python syntax
481 495 ip2py = False
482 496 # Find all doctest examples in the string. First, try them as Python
483 497 # examples, then as IPython ones
484 498 terms = list(self._EXAMPLE_RE_PY.finditer(string))
485 499 if terms:
486 500 # Normal Python example
487 501 #print '-'*70 # dbg
488 502 #print 'PyExample, Source:\n',string # dbg
489 503 #print '-'*70 # dbg
490 504 Example = doctest.Example
491 505 else:
492 506 # It's an ipython example. Note that IPExamples are run
493 507 # in-process, so their syntax must be turned into valid python.
494 508 # IPExternalExamples are run out-of-process (via pexpect) so they
495 509 # don't need any filtering (a real ipython will be executing them).
496 510 terms = list(self._EXAMPLE_RE_IP.finditer(string))
497 511 if self._EXTERNAL_IP.search(string):
498 512 #print '-'*70 # dbg
499 513 #print 'IPExternalExample, Source:\n',string # dbg
500 514 #print '-'*70 # dbg
501 515 Example = IPExternalExample
502 516 else:
503 517 #print '-'*70 # dbg
504 518 #print 'IPExample, Source:\n',string # dbg
505 519 #print '-'*70 # dbg
506 520 Example = IPExample
507 521 ip2py = True
508 522
509 523 for m in terms:
510 524 # Add the pre-example text to `output`.
511 525 output.append(string[charno:m.start()])
512 526 # Update lineno (lines before this example)
513 527 lineno += string.count('\n', charno, m.start())
514 528 # Extract info from the regexp match.
515 529 (source, options, want, exc_msg) = \
516 530 self._parse_example(m, name, lineno,ip2py)
517 531
518 532 # Append the random-output marker (it defaults to empty in most
519 533 # cases, it's only non-empty for 'all-random' tests):
520 534 want += random_marker
521 535
522 536 if Example is IPExternalExample:
523 537 options[doctest.NORMALIZE_WHITESPACE] = True
524 538 want += '\n'
525 539
526 540 # Create an Example, and add it to the list.
527 541 if not self._IS_BLANK_OR_COMMENT(source):
528 542 output.append(Example(source, want, exc_msg,
529 543 lineno=lineno,
530 544 indent=min_indent+len(m.group('indent')),
531 545 options=options))
532 546 # Update lineno (lines inside this example)
533 547 lineno += string.count('\n', m.start(), m.end())
534 548 # Update charno.
535 549 charno = m.end()
536 550 # Add any remaining post-example text to `output`.
537 551 output.append(string[charno:])
538 552 return output
539 553
540 554 def _parse_example(self, m, name, lineno,ip2py=False):
541 555 """
542 556 Given a regular expression match from `_EXAMPLE_RE` (`m`),
543 557 return a pair `(source, want)`, where `source` is the matched
544 558 example's source code (with prompts and indentation stripped);
545 559 and `want` is the example's expected output (with indentation
546 560 stripped).
547 561
548 562 `name` is the string's name, and `lineno` is the line number
549 563 where the example starts; both are used for error messages.
550 564
551 565 Optional:
552 566 `ip2py`: if true, filter the input via IPython to convert the syntax
553 567 into valid python.
554 568 """
555 569
556 570 # Get the example's indentation level.
557 571 indent = len(m.group('indent'))
558 572
559 573 # Divide source into lines; check that they're properly
560 574 # indented; and then strip their indentation & prompts.
561 575 source_lines = m.group('source').split('\n')
562 576
563 577 # We're using variable-length input prompts
564 578 ps1 = m.group('ps1')
565 579 ps2 = m.group('ps2')
566 580 ps1_len = len(ps1)
567 581
568 582 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
569 583 if ps2:
570 584 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
571 585
572 586 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
573 587
574 588 if ip2py:
575 589 # Convert source input from IPython into valid Python syntax
576 590 source = self.ip2py(source)
577 591
578 592 # Divide want into lines; check that it's properly indented; and
579 593 # then strip the indentation. Spaces before the last newline should
580 594 # be preserved, so plain rstrip() isn't good enough.
581 595 want = m.group('want')
582 596 want_lines = want.split('\n')
583 597 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
584 598 del want_lines[-1] # forget final newline & spaces after it
585 599 self._check_prefix(want_lines, ' '*indent, name,
586 600 lineno + len(source_lines))
587 601
588 602 # Remove ipython output prompt that might be present in the first line
589 603 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
590 604
591 605 want = '\n'.join([wl[indent:] for wl in want_lines])
592 606
593 607 # If `want` contains a traceback message, then extract it.
594 608 m = self._EXCEPTION_RE.match(want)
595 609 if m:
596 610 exc_msg = m.group('msg')
597 611 else:
598 612 exc_msg = None
599 613
600 614 # Extract options from the source.
601 615 options = self._find_options(source, name, lineno)
602 616
603 617 return source, options, want, exc_msg
604 618
605 619 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
606 620 """
607 621 Given the lines of a source string (including prompts and
608 622 leading indentation), check to make sure that every prompt is
609 623 followed by a space character. If any line is not followed by
610 624 a space character, then raise ValueError.
611 625
612 626 Note: IPython-modified version which takes the input prompt length as a
613 627 parameter, so that prompts of variable length can be dealt with.
614 628 """
615 629 space_idx = indent+ps1_len
616 630 min_len = space_idx+1
617 631 for i, line in enumerate(lines):
618 632 if len(line) >= min_len and line[space_idx] != ' ':
619 633 raise ValueError('line %r of the docstring for %s '
620 634 'lacks blank after %s: %r' %
621 635 (lineno+i+1, name,
622 636 line[indent:space_idx], line))
623 637
624 638
625 639 SKIP = doctest.register_optionflag('SKIP')
626 640
627 641
628 642 class IPDocTestRunner(doctest.DocTestRunner,object):
629 643 """Test runner that synchronizes the IPython namespace with test globals.
630 644 """
631 645
632 646 def run(self, test, compileflags=None, out=None, clear_globs=True):
633 647
634 648 # Hack: ipython needs access to the execution context of the example,
635 649 # so that it can propagate user variables loaded by %run into
636 650 # test.globs. We put them here into our modified %run as a function
637 651 # attribute. Our new %run will then only make the namespace update
638 652 # when called (rather than unconconditionally updating test.globs here
639 653 # for all examples, most of which won't be calling %run anyway).
640 654 _run_ns_sync.test_globs = test.globs
641
655 _run_ns_sync.test_filename = test.filename
656
642 657 return super(IPDocTestRunner,self).run(test,
643 658 compileflags,out,clear_globs)
644 659
645 660
646 661 class DocFileCase(doctest.DocFileCase):
647 662 """Overrides to provide filename
648 663 """
649 664 def address(self):
650 665 return (self._dt_test.filename, None, None)
651 666
652 667
653 668 class ExtensionDoctest(doctests.Doctest):
654 669 """Nose Plugin that supports doctests in extension modules.
655 670 """
656 671 name = 'extdoctest' # call nosetests with --with-extdoctest
657 672 enabled = True
658 673
659 674 def options(self, parser, env=os.environ):
660 675 Plugin.options(self, parser, env)
661 676 parser.add_option('--doctest-tests', action='store_true',
662 677 dest='doctest_tests',
663 678 default=env.get('NOSE_DOCTEST_TESTS',True),
664 679 help="Also look for doctests in test modules. "
665 680 "Note that classes, methods and functions should "
666 681 "have either doctests or non-doctest tests, "
667 682 "not both. [NOSE_DOCTEST_TESTS]")
668 683 parser.add_option('--doctest-extension', action="append",
669 684 dest="doctestExtension",
670 685 help="Also look for doctests in files with "
671 686 "this extension [NOSE_DOCTEST_EXTENSION]")
672 687 # Set the default as a list, if given in env; otherwise
673 688 # an additional value set on the command line will cause
674 689 # an error.
675 690 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
676 691 if env_setting is not None:
677 692 parser.set_defaults(doctestExtension=tolist(env_setting))
678 693
679 694
680 695 def configure(self, options, config):
681 696 Plugin.configure(self, options, config)
682 697 self.doctest_tests = options.doctest_tests
683 698 self.extension = tolist(options.doctestExtension)
684 699
685 700 self.parser = doctest.DocTestParser()
686 701 self.finder = DocTestFinder()
687 702 self.checker = IPDoctestOutputChecker()
688 703 self.globs = None
689 704 self.extraglobs = None
690 705
691 706
692 707 def loadTestsFromExtensionModule(self,filename):
693 708 bpath,mod = os.path.split(filename)
694 709 modname = os.path.splitext(mod)[0]
695 710 try:
696 711 sys.path.append(bpath)
697 712 module = __import__(modname)
698 713 tests = list(self.loadTestsFromModule(module))
699 714 finally:
700 715 sys.path.pop()
701 716 return tests
702 717
703 718 # NOTE: the method below is almost a copy of the original one in nose, with
704 719 # a few modifications to control output checking.
705 720
706 721 def loadTestsFromModule(self, module):
707 722 #print '*** ipdoctest - lTM',module # dbg
708 723
709 724 if not self.matches(module.__name__):
710 725 log.debug("Doctest doesn't want module %s", module)
711 726 return
712 727
713 728 tests = self.finder.find(module,globs=self.globs,
714 729 extraglobs=self.extraglobs)
715 730 if not tests:
716 731 return
717 732
718 733 # always use whitespace and ellipsis options
719 734 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
720 735
721 736 tests.sort()
722 737 module_file = module.__file__
723 738 if module_file[-4:] in ('.pyc', '.pyo'):
724 739 module_file = module_file[:-1]
725 740 for test in tests:
726 741 if not test.examples:
727 742 continue
728 743 if not test.filename:
729 744 test.filename = module_file
730 745
731 746 yield DocTestCase(test,
732 747 optionflags=optionflags,
733 748 checker=self.checker)
734 749
735 750
736 751 def loadTestsFromFile(self, filename):
737 752 if is_extension_module(filename):
738 753 for t in self.loadTestsFromExtensionModule(filename):
739 754 yield t
740 755 else:
741 756 if self.extension and anyp(filename.endswith, self.extension):
742 757 name = os.path.basename(filename)
743 758 dh = open(filename)
744 759 try:
745 760 doc = dh.read()
746 761 finally:
747 762 dh.close()
748 763 test = self.parser.get_doctest(
749 764 doc, globs={'__file__': filename}, name=name,
750 765 filename=filename, lineno=0)
751 766 if test.examples:
752 767 #print 'FileCase:',test.examples # dbg
753 768 yield DocFileCase(test)
754 769 else:
755 770 yield False # no tests to load
756 771
757 772 def wantFile(self,filename):
758 773 """Return whether the given filename should be scanned for tests.
759 774
760 775 Modified version that accepts extension modules as valid containers for
761 776 doctests.
762 777 """
763 778 #print '*** ipdoctest- wantFile:',filename # dbg
764 779
765 780 # XXX - temporarily hardcoded list, will move to driver later
766 781 exclude = ['IPython/external/',
767 782 'IPython/platutils_win32',
768 783 'IPython/frontend/cocoa',
769 784 'IPython_doctest_plugin',
770 785 'IPython/Gnuplot',
771 786 'IPython/Extensions/ipy_',
772 787 'IPython/Extensions/PhysicalQIn',
773 788 'IPython/Extensions/scitedirector',
774 789 'IPython/testing/plugin',
775 790 ]
776 791
777 792 for fex in exclude:
778 793 if fex in filename: # substring
779 794 #print '###>>> SKIP:',filename # dbg
780 795 return False
781 796
782 797 if is_extension_module(filename):
783 798 return True
784 799 else:
785 800 return doctests.Doctest.wantFile(self,filename)
786 801
787 802
788 803 class IPythonDoctest(ExtensionDoctest):
789 804 """Nose Plugin that supports doctests in extension modules.
790 805 """
791 806 name = 'ipdoctest' # call nosetests with --with-ipdoctest
792 807 enabled = True
793 808
794 809 def configure(self, options, config):
795 810
796 811 Plugin.configure(self, options, config)
797 812 self.doctest_tests = options.doctest_tests
798 813 self.extension = tolist(options.doctestExtension)
799 814
800 815 self.parser = IPDocTestParser()
801 816 self.finder = DocTestFinder(parser=self.parser)
802 817 self.checker = IPDoctestOutputChecker()
803 818 self.globs = None
804 819 self.extraglobs = None
805 820
@@ -1,21 +1,92 b''
1 1 """ Tests for various magic functions
2 2
3 3 Needs to be run by nose (to make ipython session available)
4
5 4 """
5
6 from IPython.testing import decorators as dec
7
6 8 def test_rehashx():
7 9 # clear up everything
8 10 _ip.IP.alias_table.clear()
9 11 del _ip.db['syscmdlist']
10 12
11 13 _ip.magic('rehashx')
12 14 # Practically ALL ipython development systems will have more than 10 aliases
13 15
14 16 assert len(_ip.IP.alias_table) > 10
15 17 for key, val in _ip.IP.alias_table.items():
16 18 # we must strip dots from alias names
17 19 assert '.' not in key
18 20
19 21 # rehashx must fill up syscmdlist
20 22 scoms = _ip.db['syscmdlist']
21 23 assert len(scoms) > 10
24
25
26 def doctest_run_ns():
27 """Classes declared %run scripts must be instantiable afterwards.
28
29 In [3]: run tclass.py
30
31 In [4]: f()
32 """
33 pass # doctest only
34
35
36 def doctest_hist_f():
37 """Test %hist -f with temporary filename.
38
39 In [9]: import tempfile
40
41 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
42
43 In [11]: %history -n -f $tfile 3
44 """
45
46
47 def doctest_hist_r():
48 """Test %hist -r
49
50 XXX - This test is not recording the output correctly. Not sure why...
51
52 In [6]: x=1
53
54 In [7]: hist -n -r 2
55 x=1 # random
56 hist -n -r 2 # random
57 """
58
59
60 def test_shist():
61 # Simple tests of ShadowHist class
62 import os, shutil, tempfile
63 import nose.tools as nt
64
65 from IPython.Extensions import pickleshare
66 from IPython.history import ShadowHist
67
68
69 tfile = tempfile.mktemp('','tmp-ipython-')
70
71 db = pickleshare.PickleShareDB(tfile)
72 s = ShadowHist(db)
73 s.add('hello')
74 s.add('world')
75 s.add('hello')
76 s.add('hello')
77 s.add('karhu')
78
79 yield nt.assert_equals,s.all(),[(1, 'hello'), (2, 'world'), (3, 'karhu')]
80
81 yield nt.assert_equal,s.get(2),'world'
82
83 shutil.rmtree(tfile)
84
85
86 @dec.skip_numpy_not_avail
87 def doctest_clear_array():
88 """Check that array clearing works.
89
90 >>> 1/0
91 """
92 pass # doctest only
General Comments 0
You need to be logged in to leave comments. Login now