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