~ellisonbg/ipython/bugfixes0411409

« back to all changes in this revision

Viewing changes to test/test_prefilter.py

  • Committer: ville
  • Date: 2008-02-16 09:50:47 UTC
  • mto: (0.12.1 ipython_main)
  • mto: This revision was merged to the branch mainline in revision 990.
  • Revision ID: ville@ville-pc-20080216095047-500x6dluki1iz40o
initialization (no svn history)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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