~ipython-contrib/ipython/qt-frontend

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