0.1.1
by ville
initialization (no svn history) |
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 |
sys.path.append('..') |
|
18 |
import IPython |
|
19 |
import IPython.ipapi |
|
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 |
s.ESC_SH_CAP : s.handle_shell_escape, |
|
77 |
}
|
|
78 |
reset_esc_handlers() |
|
79 |
||
80 |
# This is so I don't have to quote over and over. Gotta be a better way.
|
|
81 |
handle_normal = 'handle_normal' |
|
82 |
handle_auto = 'handle_auto' |
|
83 |
handle_magic = 'handle_magic' |
|
84 |
handle_help = 'handle_help' |
|
85 |
handle_shell_escape = 'handle_shell_escape' |
|
86 |
handle_alias = 'handle_alias' |
|
87 |
handle_emacs = 'handle_emacs' |
|
88 |
||
89 |
def check(assertion, failure_msg): |
|
90 |
"""Check a boolean assertion and fail with a message if necessary. Store
|
|
91 |
an error essage in module-level failures list in case of failure. Print
|
|
92 |
'.' or 'F' if module var Verbose is true.
|
|
93 |
"""
|
|
94 |
global num_tests |
|
95 |
num_tests += 1 |
|
96 |
if assertion: |
|
97 |
if verbose: |
|
98 |
sys.stdout.write('.') |
|
99 |
sys.stdout.flush() |
|
100 |
else: |
|
101 |
if verbose: |
|
102 |
sys.stdout.write('F') |
|
103 |
sys.stdout.flush() |
|
104 |
failures.append(failure_msg) |
|
105 |
||
106 |
||
107 |
def check_handler(expected_handler, line): |
|
108 |
"""Verify that the expected hander was called (for the given line,
|
|
109 |
passed in for failure reporting).
|
|
110 |
|
|
111 |
Pulled out to its own function so that tests which don't use
|
|
112 |
run_handler_tests can still take advantage of it."""
|
|
113 |
check(handler_called == expected_handler, |
|
114 |
"Expected %s to be called for %s, " |
|
115 |
"instead %s called" % (expected_handler, |
|
116 |
repr(line), |
|
117 |
handler_called)) |
|
118 |
||
119 |
||
120 |
def run_handler_tests(h_tests): |
|
121 |
"""Loop through a series of (input_line, handler_name) pairs, verifying
|
|
122 |
that, for each ip calls the given handler for the given line.
|
|
123 |
||
124 |
The verbose complaint includes the line passed in, so if that line can
|
|
125 |
include enough info to find the error, the tests are modestly
|
|
126 |
self-documenting.
|
|
127 |
"""
|
|
128 |
for ln, expected_handler in h_tests: |
|
129 |
global handler_called |
|
130 |
handler_called = None |
|
131 |
ip.runlines(ln) |
|
132 |
check_handler(expected_handler, ln) |
|
133 |
||
134 |
def run_one_test(ln, expected_handler): |
|
135 |
run_handler_tests([(ln, expected_handler)]) |
|
136 |
||
137 |
||
138 |
# =========================================
|
|
139 |
# Tests
|
|
140 |
# =========================================
|
|
141 |
||
142 |
||
143 |
# Fundamental escape characters + whitespace & misc
|
|
144 |
# =================================================
|
|
145 |
esc_handler_tests = [ |
|
146 |
( '?thing', handle_help, ), |
|
147 |
( 'thing?', handle_help ), # '?' can trail... |
|
148 |
( 'thing!', handle_normal), # but only '?' can trail |
|
149 |
( ' ?thing', handle_normal), # leading whitespace turns off esc chars |
|
150 |
( '!ls', handle_shell_escape), |
|
151 |
( '! true', handle_shell_escape), |
|
152 |
( '!! true', handle_shell_escape), |
|
153 |
( '%magic', handle_magic), |
|
154 |
# XXX Possibly, add test for /,; once those are unhooked from %autocall
|
|
155 |
( 'emacs_mode # PYTHON-MODE', handle_emacs ), |
|
156 |
( ' ', handle_normal), |
|
157 |
||
158 |
# Trailing qmark combos. Odd special cases abound
|
|
159 |
||
160 |
# ! always takes priority!
|
|
161 |
( '!thing?', handle_shell_escape), |
|
162 |
( '!thing arg?', handle_shell_escape), |
|
163 |
( '!!thing?', handle_shell_escape), |
|
164 |
( '!!thing arg?', handle_shell_escape), |
|
165 |
( ' !!thing arg?', handle_shell_escape), |
|
166 |
||
167 |
# For all other leading esc chars, we always trigger help
|
|
168 |
( '%cmd?', handle_help), |
|
169 |
( '%cmd ?', handle_help), |
|
170 |
( '/cmd?', handle_help), |
|
171 |
( '/cmd ?', handle_help), |
|
172 |
( ';cmd?', handle_help), |
|
173 |
( ',cmd?', handle_help), |
|
174 |
]
|
|
175 |
run_handler_tests(esc_handler_tests) |
|
176 |
||
177 |
||
178 |
||
179 |
# Shell Escapes in Multi-line statements
|
|
180 |
# ======================================
|
|
181 |
#
|
|
182 |
# We can't test this via runlines, since the hacked-over-for-testing
|
|
183 |
# handlers all return None, so continue_prompt never becomes true. Instead
|
|
184 |
# we drop into prefilter directly and pass in continue_prompt.
|
|
185 |
||
186 |
old_mls = ip.options.multi_line_specials |
|
187 |
for ln in [ ' !ls $f multi_line_specials %s', |
|
188 |
' !!ls $f multi_line_specials %s', # !! escapes work on mls |
|
189 |
# Trailing ? doesn't trigger help:
|
|
190 |
' !ls $f multi_line_specials %s ?', |
|
191 |
' !!ls $f multi_line_specials %s ?', |
|
192 |
]:
|
|
193 |
ip.options.multi_line_specials = 1 |
|
194 |
on_ln = ln % 'on' |
|
195 |
ignore = ip.IP.prefilter(on_ln, continue_prompt=True) |
|
196 |
check_handler(handle_shell_escape, on_ln) |
|
197 |
||
198 |
ip.options.multi_line_specials = 0 |
|
199 |
off_ln = ln % 'off' |
|
200 |
ignore = ip.IP.prefilter(off_ln, continue_prompt=True) |
|
201 |
check_handler(handle_normal, off_ln) |
|
202 |
||
203 |
ip.options.multi_line_specials = old_mls |
|
204 |
||
205 |
||
206 |
# Automagic
|
|
207 |
# =========
|
|
208 |
||
209 |
# Pick one magic fun and one non_magic fun, make sure both exist
|
|
210 |
assert hasattr(ip.IP, "magic_cpaste") |
|
211 |
assert not hasattr(ip.IP, "magic_does_not_exist") |
|
212 |
ip.options.autocall = 0 # gotta have this off to get handle_normal |
|
213 |
ip.options.automagic = 0 |
|
214 |
run_handler_tests([ |
|
215 |
# Without automagic, only shows up with explicit escape
|
|
216 |
( 'cpaste', handle_normal), |
|
217 |
( '%cpaste', handle_magic), |
|
218 |
( '%does_not_exist', handle_magic), |
|
219 |
])
|
|
220 |
ip.options.automagic = 1 |
|
221 |
run_handler_tests([ |
|
222 |
( 'cpaste', handle_magic), |
|
223 |
( '%cpaste', handle_magic), |
|
224 |
( 'does_not_exist', handle_normal), |
|
225 |
( '%does_not_exist', handle_magic), |
|
226 |
( 'cd /', handle_magic), |
|
227 |
( 'cd = 2', handle_normal), |
|
228 |
( 'r', handle_magic), |
|
229 |
( 'r thing', handle_magic), |
|
230 |
( 'r"str"', handle_normal), |
|
231 |
])
|
|
232 |
||
233 |
# If next elt starts with anything that could be an assignment, func call,
|
|
234 |
# etc, we don't call the magic func, unless explicitly escaped to do so.
|
|
235 |
#magic_killing_tests = []
|
|
236 |
#for c in list('!=()<>,'):
|
|
237 |
# magic_killing_tests.append(('cpaste %s killed_automagic' % c, handle_normal))
|
|
238 |
# magic_killing_tests.append(('%%cpaste %s escaped_magic' % c, handle_magic))
|
|
239 |
#run_handler_tests(magic_killing_tests)
|
|
240 |
||
241 |
# magic on indented continuation lines -- on iff multi_line_specials == 1
|
|
242 |
ip.options.multi_line_specials = 0 |
|
243 |
ln = ' cpaste multi_line off kills magic' |
|
244 |
ignore = ip.IP.prefilter(ln, continue_prompt=True) |
|
245 |
check_handler(handle_normal, ln) |
|
246 |
||
247 |
ip.options.multi_line_specials = 1 |
|
248 |
ln = ' cpaste multi_line on enables magic' |
|
249 |
ignore = ip.IP.prefilter(ln, continue_prompt=True) |
|
250 |
check_handler(handle_magic, ln) |
|
251 |
||
252 |
# user namespace shadows the magic one unless shell escaped
|
|
253 |
ip.user_ns['cpaste'] = 'user_ns' |
|
254 |
run_handler_tests([ |
|
255 |
( 'cpaste', handle_normal), |
|
256 |
( '%cpaste', handle_magic)]) |
|
257 |
del ip.user_ns['cpaste'] |
|
258 |
||
259 |
||
260 |
||
261 |
# Check for !=() turning off .ofind
|
|
262 |
# =================================
|
|
263 |
class AttributeMutator(object): |
|
264 |
"""A class which will be modified on attribute access, to test ofind"""
|
|
265 |
def __init__(self): |
|
266 |
self.called = False |
|
267 |
||
268 |
def getFoo(self): self.called = True |
|
269 |
foo = property(getFoo) |
|
270 |
||
271 |
attr_mutator = AttributeMutator() |
|
272 |
ip.to_user_ns('attr_mutator') |
|
273 |
||
274 |
ip.options.autocall = 1 |
|
275 |
||
276 |
run_one_test('attr_mutator.foo should mutate', handle_normal) |
|
277 |
check(attr_mutator.called, 'ofind should be called in absence of assign characters') |
|
278 |
||
279 |
for c in list('!=()<>+*/%^&|'): |
|
280 |
attr_mutator.called = False |
|
281 |
run_one_test('attr_mutator.foo %s should *not* mutate' % c, handle_normal) |
|
282 |
run_one_test('attr_mutator.foo%s should *not* mutate' % c, handle_normal) |
|
283 |
||
284 |
check(not attr_mutator.called, |
|
285 |
'ofind should not be called near character %s' % c) |
|
286 |
||
287 |
||
288 |
||
289 |
# Alias expansion
|
|
290 |
# ===============
|
|
291 |
||
292 |
# With autocall on or off, aliases should be shadowed by user, internal and
|
|
293 |
# __builtin__ namespaces
|
|
294 |
#
|
|
295 |
# XXX Can aliases have '.' in their name? With autocall off, that works,
|
|
296 |
# with autocall on, it doesn't. Hmmm.
|
|
297 |
import __builtin__ |
|
298 |
for ac_state in [0,1]: |
|
299 |
ip.options.autocall = ac_state |
|
300 |
ip.IP.alias_table['alias_cmd'] = 'alias_result' |
|
301 |
ip.IP.alias_table['alias_head.with_dot'] = 'alias_result' |
|
302 |
run_handler_tests([ |
|
303 |
("alias_cmd", handle_alias), |
|
304 |
# XXX See note above
|
|
305 |
#("alias_head.with_dot unshadowed, autocall=%s" % ac_state, handle_alias),
|
|
306 |
("alias_cmd.something aliases must match whole expr", handle_normal), |
|
307 |
("alias_cmd /", handle_alias), |
|
308 |
])
|
|
309 |
||
310 |
for ns in [ip.user_ns, ip.IP.internal_ns, __builtin__.__dict__ ]: |
|
311 |
ns['alias_cmd'] = 'a user value' |
|
312 |
ns['alias_head'] = 'a user value' |
|
313 |
run_handler_tests([ |
|
314 |
("alias_cmd", handle_normal), |
|
315 |
("alias_head.with_dot", handle_normal)]) |
|
316 |
del ns['alias_cmd'] |
|
317 |
del ns['alias_head'] |
|
318 |
||
319 |
ip.options.autocall = 1 |
|
320 |
||
321 |
||
322 |
||
323 |
||
324 |
# Autocall
|
|
325 |
# ========
|
|
326 |
||
327 |
# For all the tests below, 'len' is callable / 'thing' is not
|
|
328 |
||
329 |
# Objects which are instances of IPyAutocall are *always* autocalled
|
|
330 |
import IPython.ipapi |
|
331 |
class Autocallable(IPython.ipapi.IPyAutocall): |
|
332 |
def __call__(self): |
|
333 |
return "called" |
|
334 |
||
335 |
autocallable = Autocallable() |
|
336 |
ip.to_user_ns('autocallable') |
|
337 |
||
338 |
||
339 |
# First, with autocalling fully off
|
|
340 |
ip.options.autocall = 0 |
|
341 |
run_handler_tests( [ |
|
342 |
# With no escapes, no autocalling expansions happen, callable or not,
|
|
343 |
# unless the obj extends IPyAutocall
|
|
344 |
( 'len autocall_0', handle_normal), |
|
345 |
( 'thing autocall_0', handle_normal), |
|
346 |
( 'autocallable', handle_auto), |
|
347 |
||
348 |
# With explicit escapes, callable and non-callables both get expanded,
|
|
349 |
# regardless of the %autocall setting:
|
|
350 |
( '/len autocall_0', handle_auto), |
|
351 |
( ',len autocall_0 b0', handle_auto), |
|
352 |
( ';len autocall_0 b0', handle_auto), |
|
353 |
||
354 |
( '/thing autocall_0', handle_auto), |
|
355 |
( ',thing autocall_0 b0', handle_auto), |
|
356 |
( ';thing autocall_0 b0', handle_auto), |
|
357 |
||
358 |
# Explicit autocall should not trigger if there is leading whitespace
|
|
359 |
( ' /len autocall_0', handle_normal), |
|
360 |
( ' ;len autocall_0', handle_normal), |
|
361 |
( ' ,len autocall_0', handle_normal), |
|
362 |
( ' / len autocall_0', handle_normal), |
|
363 |
||
364 |
# But should work if the whitespace comes after the esc char
|
|
365 |
( '/ len autocall_0', handle_auto), |
|
366 |
( '; len autocall_0', handle_auto), |
|
367 |
( ', len autocall_0', handle_auto), |
|
368 |
( '/ len autocall_0', handle_auto), |
|
369 |
])
|
|
370 |
||
371 |
||
372 |
# Now, with autocall in default, 'smart' mode
|
|
373 |
ip.options.autocall = 1 |
|
374 |
run_handler_tests( [ |
|
375 |
# Autocalls without escapes -- only expand if it's callable
|
|
376 |
( 'len a1', handle_auto), |
|
377 |
( 'thing a1', handle_normal), |
|
378 |
( 'autocallable', handle_auto), |
|
379 |
||
380 |
# As above, all explicit escapes generate auto-calls, callable or not
|
|
381 |
( '/len a1', handle_auto), |
|
382 |
( ',len a1 b1', handle_auto), |
|
383 |
( ';len a1 b1', handle_auto), |
|
384 |
( '/thing a1', handle_auto), |
|
385 |
( ',thing a1 b1', handle_auto), |
|
386 |
( ';thing a1 b1', handle_auto), |
|
387 |
||
388 |
# Autocalls only happen on things which look like funcs, even if
|
|
389 |
# explicitly requested. Which, in this case means they look like a
|
|
390 |
# sequence of identifiers and . attribute references. Possibly the
|
|
391 |
# second of these two should trigger handle_auto. But not for now.
|
|
392 |
( '"abc".join range(4)', handle_normal), |
|
393 |
( '/"abc".join range(4)', handle_normal), |
|
394 |
])
|
|
395 |
||
396 |
||
397 |
# No tests for autocall = 2, since the extra magic there happens inside the
|
|
398 |
# handle_auto function, which our test doesn't examine.
|
|
399 |
||
400 |
# Note that we leave autocall in default, 1, 'smart' mode
|
|
401 |
||
402 |
||
403 |
# Autocall / Binary operators
|
|
404 |
# ==========================
|
|
405 |
||
406 |
# Even with autocall on, 'len in thing' won't transform.
|
|
407 |
# But ';len in thing' will
|
|
408 |
||
409 |
# Note, the tests below don't check for multi-char ops. It could.
|
|
410 |
||
411 |
# XXX % is a binary op and should be in the list, too, but fails
|
|
412 |
bin_ops = list(r'<>,&^|*/+-') + 'is not in and or'.split() |
|
413 |
bin_tests = [] |
|
414 |
for b in bin_ops: |
|
415 |
bin_tests.append(('len %s binop_autocall' % b, handle_normal)) |
|
416 |
bin_tests.append((';len %s binop_autocall' % b, handle_auto)) |
|
417 |
bin_tests.append((',len %s binop_autocall' % b, handle_auto)) |
|
418 |
bin_tests.append(('/len %s binop_autocall' % b, handle_auto)) |
|
419 |
||
420 |
# Who loves auto-generating tests?
|
|
421 |
run_handler_tests(bin_tests) |
|
422 |
||
423 |
||
424 |
# Possibly add tests for namespace shadowing (really ofind's business?).
|
|
425 |
#
|
|
426 |
# user > ipython internal > python builtin > alias > magic
|
|
427 |
||
428 |
||
429 |
# ============
|
|
430 |
# Test Summary
|
|
431 |
# ============
|
|
432 |
num_f = len(failures) |
|
433 |
if verbose: |
|
434 |
print
|
|
435 |
print "%s tests run, %s failure%s" % (num_tests, |
|
436 |
num_f, |
|
437 |
num_f != 1 and "s" or "") |
|
438 |
for f in failures: |
|
439 |
print f |
|
440 |