##// END OF EJS Templates
Tests for prefiltering, contributed by Dan Milstein.
fperez -
Show More
@@ -0,0 +1,381 b''
1 """
2 Test which prefilter transformations get called for various input lines.
3 Note that this does *not* test the transformations themselves -- it's just
4 verifying that a particular combination of, e.g. config options and escape
5 chars trigger the proper handle_X transform of the input line.
6
7 Usage: run from the command line with *normal* python, not ipython:
8 > python test_prefilter.py
9
10 Fairly quiet output by default. Pass in -v to get everyone's favorite dots.
11 """
12
13 # The prefilter always ends in a call to some self.handle_X method. We swap
14 # all of those out so that we can capture which one was called.
15
16 import sys
17 import IPython
18 import IPython.ipapi
19 import sys
20
21 verbose = False
22 if len(sys.argv) > 1:
23 if sys.argv[1] == '-v':
24 sys.argv = sys.argv[:-1] # IPython is confused by -v, apparently
25 verbose = True
26
27 IPython.Shell.start()
28
29 ip = IPython.ipapi.get()
30
31 # Collect failed tests + stats and print them at the end
32 failures = []
33 num_tests = 0
34
35 # Store the results in module vars as we go
36 last_line = None
37 handler_called = None
38 def install_mock_handler(name):
39 """Swap out one of the IP.handle_x methods with a function which can
40 record which handler was called and what line was produced. The mock
41 handler func always returns '', which causes ipython to cease handling
42 the string immediately. That way, that it doesn't echo output, raise
43 exceptions, etc. But do note that testing multiline strings thus gets
44 a bit hard."""
45 def mock_handler(self, line, continue_prompt=None,
46 pre=None,iFun=None,theRest=None,
47 obj=None):
48 #print "Inside %s with '%s'" % (name, line)
49 global last_line, handler_called
50 last_line = line
51 handler_called = name
52 return ''
53 mock_handler.name = name
54 setattr(IPython.iplib.InteractiveShell, name, mock_handler)
55
56 install_mock_handler('handle_normal')
57 install_mock_handler('handle_auto')
58 install_mock_handler('handle_magic')
59 install_mock_handler('handle_help')
60 install_mock_handler('handle_shell_escape')
61 install_mock_handler('handle_alias')
62 install_mock_handler('handle_emacs')
63
64
65 def reset_esc_handlers():
66 """The escape handlers are stored in a hash (as an attribute of the
67 InteractiveShell *instance*), so we have to rebuild that hash to get our
68 new handlers in there."""
69 s = ip.IP
70 s.esc_handlers = {s.ESC_PAREN : s.handle_auto,
71 s.ESC_QUOTE : s.handle_auto,
72 s.ESC_QUOTE2 : s.handle_auto,
73 s.ESC_MAGIC : s.handle_magic,
74 s.ESC_HELP : s.handle_help,
75 s.ESC_SHELL : s.handle_shell_escape,
76 }
77 reset_esc_handlers()
78
79 # This is so I don't have to quote over and over. Gotta be a better way.
80 handle_normal = 'handle_normal'
81 handle_auto = 'handle_auto'
82 handle_magic = 'handle_magic'
83 handle_help = 'handle_help'
84 handle_shell_escape = 'handle_shell_escape'
85 handle_alias = 'handle_alias'
86 handle_emacs = 'handle_emacs'
87
88 def check(assertion, failure_msg):
89 """Check a boolean assertion and fail with a message if necessary. Store
90 an error essage in module-level failures list in case of failure. Print
91 '.' or 'F' if module var Verbose is true.
92 """
93 global num_tests
94 num_tests += 1
95 if assertion:
96 if verbose:
97 sys.stdout.write('.')
98 sys.stdout.flush()
99 else:
100 if verbose:
101 sys.stdout.write('F')
102 sys.stdout.flush()
103 failures.append(failure_msg)
104
105
106 def check_handler(expected_handler, line):
107 """Verify that the expected hander was called (for the given line,
108 passed in for failure reporting).
109
110 Pulled out to its own function so that tests which don't use
111 run_handler_tests can still take advantage of it."""
112 check(handler_called == expected_handler,
113 "Expected %s to be called for %s, "
114 "instead %s called" % (expected_handler,
115 repr(line),
116 handler_called))
117
118
119 def run_handler_tests(h_tests):
120 """Loop through a series of (input_line, handler_name) pairs, verifying
121 that, for each ip calls the given handler for the given line.
122
123 The verbose complaint includes the line passed in, so if that line can
124 include enough info to find the error, the tests are modestly
125 self-documenting.
126 """
127 for ln, expected_handler in h_tests:
128 global handler_called
129 handler_called = None
130 ip.runlines(ln)
131 check_handler(expected_handler, ln)
132
133 def run_one_test(ln, expected_handler):
134 run_handler_tests([(ln, expected_handler)])
135
136
137 # =========================================
138 # Tests
139 # =========================================
140
141
142 # Fundamental escape characters + whitespace & misc
143 # =================================================
144 esc_handler_tests = [
145 ( '?thing', handle_help, ),
146 ( 'thing?', handle_help ), # '?' can trail...
147 ( 'thing!', handle_normal), # but only '?' can trail
148 ( '!thing?', handle_help), # trailing '?' wins if more than one
149 ( ' ?thing', handle_help), # ignore leading whitespace
150 ( '!ls', handle_shell_escape ),
151 ( '%magic', handle_magic),
152 # Possibly, add test for /,; once those are unhooked from %autocall
153 ( 'emacs_mode # PYTHON-MODE', handle_emacs ),
154 ( ' ', handle_normal),
155 ]
156 run_handler_tests(esc_handler_tests)
157
158
159
160 # Shell Escapes in Multi-line statements
161 # ======================================
162 #
163 # We can't test this via runlines, since the hacked over-handlers all
164 # return None, so continue_prompt never becomes true. Instead we drop
165 # into prefilter directly and pass in continue_prompt.
166
167 old_mls = ip.options.multi_line_specials
168 ln = '!ls $f multi_line_specials on'
169 ignore = ip.IP.prefilter(ln, continue_prompt=True)
170 check_handler(handle_shell_escape, ln)
171
172 ip.options.multi_line_specials = 0
173 ln = '!ls $f multi_line_specials off'
174 ignore = ip.IP.prefilter(ln, continue_prompt=True)
175 check_handler(handle_normal, ln)
176
177 ip.options.multi_line_specials = old_mls
178
179
180 # Automagic
181 # =========
182
183 # Pick one magic fun and one non_magic fun, make sure both exist
184 assert hasattr(ip.IP, "magic_cpaste")
185 assert not hasattr(ip.IP, "magic_does_not_exist")
186 ip.options.automagic = 0
187 run_handler_tests([
188 # Without automagic, only shows up with explicit escape
189 ( 'cpaste', handle_normal),
190 ( '%cpaste', handle_magic),
191 ( '%does_not_exist', handle_magic)
192 ])
193 ip.options.automagic = 1
194 run_handler_tests([
195 ( 'cpaste', handle_magic),
196 ( '%cpaste', handle_magic),
197 ( 'does_not_exist', handle_normal),
198 ( '%does_not_exist', handle_magic)])
199
200 # If next elt starts with anything that could be an assignment, func call,
201 # etc, we don't call the magic func, unless explicitly escaped to do so.
202 magic_killing_tests = []
203 for c in list('!=()<>,'):
204 magic_killing_tests.append(('cpaste %s killed_automagic' % c, handle_normal))
205 magic_killing_tests.append(('%%cpaste %s escaped_magic' % c, handle_magic))
206 run_handler_tests(magic_killing_tests)
207
208 # magic on indented continuation lines -- on iff multi_line_specials == 1
209 ip.options.multi_line_specials = 0
210 ln = 'cpaste multi_line off kills magic'
211 ignore = ip.IP.prefilter(ln, continue_prompt=True)
212 check_handler(handle_normal, ln)
213
214 ip.options.multi_line_specials = 1
215 ln = 'cpaste multi_line on enables magic'
216 ignore = ip.IP.prefilter(ln, continue_prompt=True)
217 check_handler(handle_magic, ln)
218
219 # user namespace shadows the magic one unless shell escaped
220 ip.user_ns['cpaste'] = 'user_ns'
221 run_handler_tests([
222 ( 'cpaste', handle_normal),
223 ( '%cpaste', handle_magic)])
224 del ip.user_ns['cpaste']
225
226
227 # Check for !=() turning off .ofind
228 # =================================
229 class AttributeMutator(object):
230 """A class which will be modified on attribute access, to test ofind"""
231 def __init__(self):
232 self.called = False
233
234 def getFoo(self): self.called = True
235 foo = property(getFoo)
236
237 attr_mutator = AttributeMutator()
238 ip.to_user_ns('attr_mutator')
239
240 ip.options.autocall = 1
241
242 run_one_test('attr_mutator.foo should mutate', handle_normal)
243 check(attr_mutator.called, 'ofind should be called in absence of assign characters')
244
245 for c in list('!=()'): # XXX What about <> -- they *are* important above
246 attr_mutator.called = False
247 run_one_test('attr_mutator.foo %s should *not* mutate' % c, handle_normal)
248 check(not attr_mutator.called,
249 'ofind should not be called near character %s' % c)
250
251
252
253 # Alias expansion
254 # ===============
255
256 # With autocall on or off, aliases should be shadowed by user, internal and
257 # __builtin__ namespaces
258 #
259 # XXX Can aliases have '.' in their name? With autocall off, that works,
260 # with autocall on, it doesn't. Hmmm.
261 import __builtin__
262 for ac_state in [0,1]:
263 ip.options.autocall = ac_state
264 ip.IP.alias_table['alias_cmd'] = 'alias_result'
265 ip.IP.alias_table['alias_head.with_dot'] = 'alias_result'
266 run_handler_tests([
267 ("alias_cmd", handle_alias),
268 # XXX See note above
269 #("alias_head.with_dot unshadowed, autocall=%s" % ac_state, handle_alias),
270 ("alias_cmd.something aliases must match whole expr", handle_normal),
271 ])
272
273 for ns in [ip.user_ns, ip.IP.internal_ns, __builtin__.__dict__ ]:
274 ns['alias_cmd'] = 'a user value'
275 ns['alias_head'] = 'a user value'
276 run_handler_tests([
277 ("alias_cmd", handle_normal),
278 ("alias_head.with_dot", handle_normal)])
279 del ns['alias_cmd']
280 del ns['alias_head']
281
282 ip.options.autocall = 1
283
284
285
286
287 # Autocall
288 # ========
289
290 # First, with autocalling fully off
291 ip.options.autocall = 0
292 run_handler_tests( [
293 # Since len is callable, these *should* get auto-called
294
295 # XXX Except, at the moment, they're *not*, because the code is wrong
296 # XXX So I'm commenting 'em out to keep the tests quiet
297
298 #( '/len autocall_0', handle_auto),
299 #( ',len autocall_0 b0', handle_auto),
300 #( ';len autocall_0 b0', handle_auto),
301
302 # But these, since fun is not a callable, should *not* get auto-called
303 ( '/fun autocall_0', handle_normal),
304 ( ',fun autocall_0 b0', handle_normal),
305 ( ';fun autocall_0 b0', handle_normal),
306
307 # With no escapes, no autocalling should happen, callable or not
308 ( 'len autocall_0', handle_normal),
309 ( 'fun autocall_0', handle_normal),
310 ])
311
312
313 # Now, with autocall in default, 'smart' mode
314 ip.options.autocall = 1
315 run_handler_tests( [
316 # Since len is callable, these *do* get auto-called
317 ( '/len a1', handle_auto),
318 ( ',len a1 b1', handle_auto),
319 ( ';len a1 b1', handle_auto),
320 # But these, since fun is not a callable, should *not* get auto-called
321 ( '/fun a1', handle_normal),
322 ( ',fun a1 b1', handle_normal),
323 ( ';fun a1 b1', handle_normal),
324 # Autocalls without escapes
325 ( 'len a1', handle_auto),
326 ( 'fun a1', handle_normal), # Not callable -> no add
327 # Autocalls only happen on things which look like funcs, even if
328 # explicitly requested. Which, in this case means they look like a
329 # sequence of identifiers and . attribute references. So the second
330 # test should pass, but it's not at the moment (meaning, IPython is
331 # attempting to run an autocall). Though it does blow up in ipython
332 # later (because of how lines are split, I think).
333 ( '"abc".join range(4)', handle_normal),
334 # XXX ( '/"abc".join range(4)', handle_normal),
335 ])
336
337
338 # No tests for autocall = 2, since the extra magic there happens inside the
339 # handle_auto function, which our test doesn't examine.
340
341 # Note that we leave autocall in default, 1, 'smart' mode
342
343
344 # Autocall / Binary operators
345 # ==========================
346
347 # Even with autocall on, 'len in thing' won't transform.
348 # But ';len in thing' will
349
350 # Note, the tests below don't check for multi-char ops. It could.
351
352 # XXX % is a binary op and should be in the list, too, but fails
353 bin_ops = list(r'<>,&^|*/+-') + 'is not in and or'.split()
354 bin_tests = []
355 for b in bin_ops:
356 bin_tests.append(('len %s binop_autocall' % b, handle_normal))
357 bin_tests.append((';len %s binop_autocall' % b, handle_auto))
358 bin_tests.append((',len %s binop_autocall' % b, handle_auto))
359 bin_tests.append(('/len %s binop_autocall' % b, handle_auto))
360
361 # Who loves auto-generating tests?
362 run_handler_tests(bin_tests)
363
364
365 # Possibly add tests for namespace shadowing (really ofind's business?).
366 #
367 # user > ipython internal > python builtin > alias > magic
368
369
370 # ============
371 # Test Summary
372 # ============
373 num_f = len(failures)
374 if verbose:
375 print
376 print "%s tests run, %s failure%s" % (num_tests,
377 num_f,
378 num_f != 1 and "s" or "")
379 for f in failures:
380 print f
381
General Comments 0
You need to be logged in to leave comments. Login now