##// END OF EJS Templates
Add Xfail test on 3.9.8...
Matthias Bussonnier -
Show More
@@ -1,638 +1,640 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for the inputsplitter module."""
3 3
4 4
5 5 # Copyright (c) IPython Development Team.
6 6 # Distributed under the terms of the Modified BSD License.
7 7
8 8 import unittest
9 9 import sys
10 10
11 11 from IPython.core import inputsplitter as isp
12 12 from IPython.core.inputtransformer import InputTransformer
13 13 from IPython.core.tests.test_inputtransformer import syntax, syntax_ml
14 14 from IPython.testing import tools as tt
15 from IPython.testing.decorators import skipif
15 16
16 17 #-----------------------------------------------------------------------------
17 18 # Semi-complete examples (also used as tests)
18 19 #-----------------------------------------------------------------------------
19 20
20 21 # Note: at the bottom, there's a slightly more complete version of this that
21 22 # can be useful during development of code here.
22 23
23 24 def mini_interactive_loop(input_func):
24 25 """Minimal example of the logic of an interactive interpreter loop.
25 26
26 27 This serves as an example, and it is used by the test system with a fake
27 28 raw_input that simulates interactive input."""
28 29
29 30 from IPython.core.inputsplitter import InputSplitter
30 31
31 32 isp = InputSplitter()
32 33 # In practice, this input loop would be wrapped in an outside loop to read
33 34 # input indefinitely, until some exit/quit command was issued. Here we
34 35 # only illustrate the basic inner loop.
35 36 while isp.push_accepts_more():
36 37 indent = ' '*isp.get_indent_spaces()
37 38 prompt = '>>> ' + indent
38 39 line = indent + input_func(prompt)
39 40 isp.push(line)
40 41
41 42 # Here we just return input so we can use it in a test suite, but a real
42 43 # interpreter would instead send it for execution somewhere.
43 44 src = isp.source_reset()
44 45 #print 'Input source was:\n', src # dbg
45 46 return src
46 47
47 48 #-----------------------------------------------------------------------------
48 49 # Test utilities, just for local use
49 50 #-----------------------------------------------------------------------------
50 51
51 52 def assemble(block):
52 53 """Assemble a block into multi-line sub-blocks."""
53 54 return ['\n'.join(sub_block)+'\n' for sub_block in block]
54 55
55 56
56 57 def pseudo_input(lines):
57 58 """Return a function that acts like raw_input but feeds the input list."""
58 59 ilines = iter(lines)
59 60 def raw_in(prompt):
60 61 try:
61 62 return next(ilines)
62 63 except StopIteration:
63 64 return ''
64 65 return raw_in
65 66
66 67 #-----------------------------------------------------------------------------
67 68 # Tests
68 69 #-----------------------------------------------------------------------------
69 70 def test_spaces():
70 71 tests = [('', 0),
71 72 (' ', 1),
72 73 ('\n', 0),
73 74 (' \n', 1),
74 75 ('x', 0),
75 76 (' x', 1),
76 77 (' x',2),
77 78 (' x',4),
78 79 # Note: tabs are counted as a single whitespace!
79 80 ('\tx', 1),
80 81 ('\t x', 2),
81 82 ]
82 83 tt.check_pairs(isp.num_ini_spaces, tests)
83 84
84 85
85 86 def test_remove_comments():
86 87 tests = [('text', 'text'),
87 88 ('text # comment', 'text '),
88 89 ('text # comment\n', 'text \n'),
89 90 ('text # comment \n', 'text \n'),
90 91 ('line # c \nline\n','line \nline\n'),
91 92 ('line # c \nline#c2 \nline\nline #c\n\n',
92 93 'line \nline\nline\nline \n\n'),
93 94 ]
94 95 tt.check_pairs(isp.remove_comments, tests)
95 96
96 97
97 98 def test_get_input_encoding():
98 99 encoding = isp.get_input_encoding()
99 100 assert isinstance(encoding, str)
100 101 # simple-minded check that at least encoding a simple string works with the
101 102 # encoding we got.
102 103 assert "test".encode(encoding) == b"test"
103 104
104 105
105 106 class NoInputEncodingTestCase(unittest.TestCase):
106 107 def setUp(self):
107 108 self.old_stdin = sys.stdin
108 109 class X: pass
109 110 fake_stdin = X()
110 111 sys.stdin = fake_stdin
111 112
112 113 def test(self):
113 114 # Verify that if sys.stdin has no 'encoding' attribute we do the right
114 115 # thing
115 116 enc = isp.get_input_encoding()
116 117 self.assertEqual(enc, 'ascii')
117 118
118 119 def tearDown(self):
119 120 sys.stdin = self.old_stdin
120 121
121 122
122 123 class InputSplitterTestCase(unittest.TestCase):
123 124 def setUp(self):
124 125 self.isp = isp.InputSplitter()
125 126
126 127 def test_reset(self):
127 128 isp = self.isp
128 129 isp.push('x=1')
129 130 isp.reset()
130 131 self.assertEqual(isp._buffer, [])
131 132 self.assertEqual(isp.get_indent_spaces(), 0)
132 133 self.assertEqual(isp.source, '')
133 134 self.assertEqual(isp.code, None)
134 135 self.assertEqual(isp._is_complete, False)
135 136
136 137 def test_source(self):
137 138 self.isp._store('1')
138 139 self.isp._store('2')
139 140 self.assertEqual(self.isp.source, '1\n2\n')
140 141 self.assertEqual(len(self.isp._buffer)>0, True)
141 142 self.assertEqual(self.isp.source_reset(), '1\n2\n')
142 143 self.assertEqual(self.isp._buffer, [])
143 144 self.assertEqual(self.isp.source, '')
144 145
145 146 def test_indent(self):
146 147 isp = self.isp # shorthand
147 148 isp.push('x=1')
148 149 self.assertEqual(isp.get_indent_spaces(), 0)
149 150 isp.push('if 1:\n x=1')
150 151 self.assertEqual(isp.get_indent_spaces(), 4)
151 152 isp.push('y=2\n')
152 153 self.assertEqual(isp.get_indent_spaces(), 0)
153 154
154 155 def test_indent2(self):
155 156 isp = self.isp
156 157 isp.push('if 1:')
157 158 self.assertEqual(isp.get_indent_spaces(), 4)
158 159 isp.push(' x=1')
159 160 self.assertEqual(isp.get_indent_spaces(), 4)
160 161 # Blank lines shouldn't change the indent level
161 162 isp.push(' '*2)
162 163 self.assertEqual(isp.get_indent_spaces(), 4)
163 164
164 165 def test_indent3(self):
165 166 isp = self.isp
166 167 # When a multiline statement contains parens or multiline strings, we
167 168 # shouldn't get confused.
168 169 isp.push("if 1:")
169 170 isp.push(" x = (1+\n 2)")
170 171 self.assertEqual(isp.get_indent_spaces(), 4)
171 172
172 173 def test_indent4(self):
173 174 isp = self.isp
174 175 # whitespace after ':' should not screw up indent level
175 176 isp.push('if 1: \n x=1')
176 177 self.assertEqual(isp.get_indent_spaces(), 4)
177 178 isp.push('y=2\n')
178 179 self.assertEqual(isp.get_indent_spaces(), 0)
179 180 isp.push('if 1:\t\n x=1')
180 181 self.assertEqual(isp.get_indent_spaces(), 4)
181 182 isp.push('y=2\n')
182 183 self.assertEqual(isp.get_indent_spaces(), 0)
183 184
184 185 def test_dedent_pass(self):
185 186 isp = self.isp # shorthand
186 187 # should NOT cause dedent
187 188 isp.push('if 1:\n passes = 5')
188 189 self.assertEqual(isp.get_indent_spaces(), 4)
189 190 isp.push('if 1:\n pass')
190 191 self.assertEqual(isp.get_indent_spaces(), 0)
191 192 isp.push('if 1:\n pass ')
192 193 self.assertEqual(isp.get_indent_spaces(), 0)
193 194
194 195 def test_dedent_break(self):
195 196 isp = self.isp # shorthand
196 197 # should NOT cause dedent
197 198 isp.push('while 1:\n breaks = 5')
198 199 self.assertEqual(isp.get_indent_spaces(), 4)
199 200 isp.push('while 1:\n break')
200 201 self.assertEqual(isp.get_indent_spaces(), 0)
201 202 isp.push('while 1:\n break ')
202 203 self.assertEqual(isp.get_indent_spaces(), 0)
203 204
204 205 def test_dedent_continue(self):
205 206 isp = self.isp # shorthand
206 207 # should NOT cause dedent
207 208 isp.push('while 1:\n continues = 5')
208 209 self.assertEqual(isp.get_indent_spaces(), 4)
209 210 isp.push('while 1:\n continue')
210 211 self.assertEqual(isp.get_indent_spaces(), 0)
211 212 isp.push('while 1:\n continue ')
212 213 self.assertEqual(isp.get_indent_spaces(), 0)
213 214
214 215 def test_dedent_raise(self):
215 216 isp = self.isp # shorthand
216 217 # should NOT cause dedent
217 218 isp.push('if 1:\n raised = 4')
218 219 self.assertEqual(isp.get_indent_spaces(), 4)
219 220 isp.push('if 1:\n raise TypeError()')
220 221 self.assertEqual(isp.get_indent_spaces(), 0)
221 222 isp.push('if 1:\n raise')
222 223 self.assertEqual(isp.get_indent_spaces(), 0)
223 224 isp.push('if 1:\n raise ')
224 225 self.assertEqual(isp.get_indent_spaces(), 0)
225 226
226 227 def test_dedent_return(self):
227 228 isp = self.isp # shorthand
228 229 # should NOT cause dedent
229 230 isp.push('if 1:\n returning = 4')
230 231 self.assertEqual(isp.get_indent_spaces(), 4)
231 232 isp.push('if 1:\n return 5 + 493')
232 233 self.assertEqual(isp.get_indent_spaces(), 0)
233 234 isp.push('if 1:\n return')
234 235 self.assertEqual(isp.get_indent_spaces(), 0)
235 236 isp.push('if 1:\n return ')
236 237 self.assertEqual(isp.get_indent_spaces(), 0)
237 238 isp.push('if 1:\n return(0)')
238 239 self.assertEqual(isp.get_indent_spaces(), 0)
239 240
240 241 def test_push(self):
241 242 isp = self.isp
242 243 self.assertEqual(isp.push('x=1'), True)
243 244
244 245 def test_push2(self):
245 246 isp = self.isp
246 247 self.assertEqual(isp.push('if 1:'), False)
247 248 for line in [' x=1', '# a comment', ' y=2']:
248 249 print(line)
249 250 self.assertEqual(isp.push(line), True)
250 251
251 252 def test_push3(self):
252 253 isp = self.isp
253 254 isp.push('if True:')
254 255 isp.push(' a = 1')
255 256 self.assertEqual(isp.push('b = [1,'), False)
256 257
257 258 def test_push_accepts_more(self):
258 259 isp = self.isp
259 260 isp.push('x=1')
260 261 self.assertEqual(isp.push_accepts_more(), False)
261 262
262 263 def test_push_accepts_more2(self):
263 264 isp = self.isp
264 265 isp.push('if 1:')
265 266 self.assertEqual(isp.push_accepts_more(), True)
266 267 isp.push(' x=1')
267 268 self.assertEqual(isp.push_accepts_more(), True)
268 269 isp.push('')
269 270 self.assertEqual(isp.push_accepts_more(), False)
270 271
271 272 def test_push_accepts_more3(self):
272 273 isp = self.isp
273 274 isp.push("x = (2+\n3)")
274 275 self.assertEqual(isp.push_accepts_more(), False)
275 276
276 277 def test_push_accepts_more4(self):
277 278 isp = self.isp
278 279 # When a multiline statement contains parens or multiline strings, we
279 280 # shouldn't get confused.
280 281 # FIXME: we should be able to better handle de-dents in statements like
281 282 # multiline strings and multiline expressions (continued with \ or
282 283 # parens). Right now we aren't handling the indentation tracking quite
283 284 # correctly with this, though in practice it may not be too much of a
284 285 # problem. We'll need to see.
285 286 isp.push("if 1:")
286 287 isp.push(" x = (2+")
287 288 isp.push(" 3)")
288 289 self.assertEqual(isp.push_accepts_more(), True)
289 290 isp.push(" y = 3")
290 291 self.assertEqual(isp.push_accepts_more(), True)
291 292 isp.push('')
292 293 self.assertEqual(isp.push_accepts_more(), False)
293 294
294 295 def test_push_accepts_more5(self):
295 296 isp = self.isp
296 297 isp.push('try:')
297 298 isp.push(' a = 5')
298 299 isp.push('except:')
299 300 isp.push(' raise')
300 301 # We want to be able to add an else: block at this point, so it should
301 302 # wait for a blank line.
302 303 self.assertEqual(isp.push_accepts_more(), True)
303 304
304 305 def test_continuation(self):
305 306 isp = self.isp
306 307 isp.push("import os, \\")
307 308 self.assertEqual(isp.push_accepts_more(), True)
308 309 isp.push("sys")
309 310 self.assertEqual(isp.push_accepts_more(), False)
310 311
311 312 def test_syntax_error(self):
312 313 isp = self.isp
313 314 # Syntax errors immediately produce a 'ready' block, so the invalid
314 315 # Python can be sent to the kernel for evaluation with possible ipython
315 316 # special-syntax conversion.
316 317 isp.push('run foo')
317 318 self.assertEqual(isp.push_accepts_more(), False)
318 319
319 320 def test_unicode(self):
320 321 self.isp.push(u"Pérez")
321 322 self.isp.push(u'\xc3\xa9')
322 323 self.isp.push(u"u'\xc3\xa9'")
323 324
325 @skipif(sys.version_info[:3] == (3, 9, 8))
324 326 def test_line_continuation(self):
325 327 """ Test issue #2108."""
326 328 isp = self.isp
327 329 # A blank line after a line continuation should not accept more
328 330 isp.push("1 \\\n\n")
329 331 self.assertEqual(isp.push_accepts_more(), False)
330 332 # Whitespace after a \ is a SyntaxError. The only way to test that
331 333 # here is to test that push doesn't accept more (as with
332 334 # test_syntax_error() above).
333 335 isp.push(r"1 \ ")
334 336 self.assertEqual(isp.push_accepts_more(), False)
335 337 # Even if the line is continuable (c.f. the regular Python
336 338 # interpreter)
337 339 isp.push(r"(1 \ ")
338 340 self.assertEqual(isp.push_accepts_more(), False)
339 341
340 342 def test_check_complete(self):
341 343 isp = self.isp
342 344 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
343 345 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
344 346 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
345 347 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
346 348 self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None))
347 349
348 350 class InteractiveLoopTestCase(unittest.TestCase):
349 351 """Tests for an interactive loop like a python shell.
350 352 """
351 353 def check_ns(self, lines, ns):
352 354 """Validate that the given input lines produce the resulting namespace.
353 355
354 356 Note: the input lines are given exactly as they would be typed in an
355 357 auto-indenting environment, as mini_interactive_loop above already does
356 358 auto-indenting and prepends spaces to the input.
357 359 """
358 360 src = mini_interactive_loop(pseudo_input(lines))
359 361 test_ns = {}
360 362 exec(src, test_ns)
361 363 # We can't check that the provided ns is identical to the test_ns,
362 364 # because Python fills test_ns with extra keys (copyright, etc). But
363 365 # we can check that the given dict is *contained* in test_ns
364 366 for k,v in ns.items():
365 367 self.assertEqual(test_ns[k], v)
366 368
367 369 def test_simple(self):
368 370 self.check_ns(['x=1'], dict(x=1))
369 371
370 372 def test_simple2(self):
371 373 self.check_ns(['if 1:', 'x=2'], dict(x=2))
372 374
373 375 def test_xy(self):
374 376 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
375 377
376 378 def test_abc(self):
377 379 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
378 380
379 381 def test_multi(self):
380 382 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
381 383
382 384
383 385 class IPythonInputTestCase(InputSplitterTestCase):
384 386 """By just creating a new class whose .isp is a different instance, we
385 387 re-run the same test battery on the new input splitter.
386 388
387 389 In addition, this runs the tests over the syntax and syntax_ml dicts that
388 390 were tested by individual functions, as part of the OO interface.
389 391
390 392 It also makes some checks on the raw buffer storage.
391 393 """
392 394
393 395 def setUp(self):
394 396 self.isp = isp.IPythonInputSplitter()
395 397
396 398 def test_syntax(self):
397 399 """Call all single-line syntax tests from the main object"""
398 400 isp = self.isp
399 401 for example in syntax.values():
400 402 for raw, out_t in example:
401 403 if raw.startswith(' '):
402 404 continue
403 405
404 406 isp.push(raw+'\n')
405 407 out_raw = isp.source_raw
406 408 out = isp.source_reset()
407 409 self.assertEqual(out.rstrip(), out_t,
408 410 tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
409 411 self.assertEqual(out_raw.rstrip(), raw.rstrip())
410 412
411 413 def test_syntax_multiline(self):
412 414 isp = self.isp
413 415 for example in syntax_ml.values():
414 416 for line_pairs in example:
415 417 out_t_parts = []
416 418 raw_parts = []
417 419 for lraw, out_t_part in line_pairs:
418 420 if out_t_part is not None:
419 421 out_t_parts.append(out_t_part)
420 422
421 423 if lraw is not None:
422 424 isp.push(lraw)
423 425 raw_parts.append(lraw)
424 426
425 427 out_raw = isp.source_raw
426 428 out = isp.source_reset()
427 429 out_t = '\n'.join(out_t_parts).rstrip()
428 430 raw = '\n'.join(raw_parts).rstrip()
429 431 self.assertEqual(out.rstrip(), out_t)
430 432 self.assertEqual(out_raw.rstrip(), raw)
431 433
432 434 def test_syntax_multiline_cell(self):
433 435 isp = self.isp
434 436 for example in syntax_ml.values():
435 437
436 438 out_t_parts = []
437 439 for line_pairs in example:
438 440 raw = '\n'.join(r for r, _ in line_pairs if r is not None)
439 441 out_t = '\n'.join(t for _,t in line_pairs if t is not None)
440 442 out = isp.transform_cell(raw)
441 443 # Match ignoring trailing whitespace
442 444 self.assertEqual(out.rstrip(), out_t.rstrip())
443 445
444 446 def test_cellmagic_preempt(self):
445 447 isp = self.isp
446 448 for raw, name, line, cell in [
447 449 ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
448 450 ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
449 451 (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
450 452 ("%%cellm \n>>> hi", u'cellm', u'', u'>>> hi'),
451 453 ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
452 454 ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
453 455 ]:
454 456 expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
455 457 name, line, cell
456 458 )
457 459 out = isp.transform_cell(raw)
458 460 self.assertEqual(out.rstrip(), expected.rstrip())
459 461
460 462 def test_multiline_passthrough(self):
461 463 isp = self.isp
462 464 class CommentTransformer(InputTransformer):
463 465 def __init__(self):
464 466 self._lines = []
465 467
466 468 def push(self, line):
467 469 self._lines.append(line + '#')
468 470
469 471 def reset(self):
470 472 text = '\n'.join(self._lines)
471 473 self._lines = []
472 474 return text
473 475
474 476 isp.physical_line_transforms.insert(0, CommentTransformer())
475 477
476 478 for raw, expected in [
477 479 ("a=5", "a=5#"),
478 480 ("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')),
479 481 ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().run_line_magic(%r, %r)" % (
480 482 u'ls foo#', u'ls', u'bar#'
481 483 )),
482 484 ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().run_line_magic(%r, %r)\n4#\n5#" % (u'ls', u'foo#')),
483 485 ]:
484 486 out = isp.transform_cell(raw)
485 487 self.assertEqual(out.rstrip(), expected.rstrip())
486 488
487 489 #-----------------------------------------------------------------------------
488 490 # Main - use as a script, mostly for developer experiments
489 491 #-----------------------------------------------------------------------------
490 492
491 493 if __name__ == '__main__':
492 494 # A simple demo for interactive experimentation. This code will not get
493 495 # picked up by any test suite.
494 496 from IPython.core.inputsplitter import IPythonInputSplitter
495 497
496 498 # configure here the syntax to use, prompt and whether to autoindent
497 499 #isp, start_prompt = InputSplitter(), '>>> '
498 500 isp, start_prompt = IPythonInputSplitter(), 'In> '
499 501
500 502 autoindent = True
501 503 #autoindent = False
502 504
503 505 try:
504 506 while True:
505 507 prompt = start_prompt
506 508 while isp.push_accepts_more():
507 509 indent = ' '*isp.get_indent_spaces()
508 510 if autoindent:
509 511 line = indent + input(prompt+indent)
510 512 else:
511 513 line = input(prompt)
512 514 isp.push(line)
513 515 prompt = '... '
514 516
515 517 # Here we just return input so we can use it in a test suite, but a
516 518 # real interpreter would instead send it for execution somewhere.
517 519 #src = isp.source; raise EOFError # dbg
518 520 raw = isp.source_raw
519 521 src = isp.source_reset()
520 522 print('Input source was:\n', src)
521 523 print('Raw source was:\n', raw)
522 524 except EOFError:
523 525 print('Bye')
524 526
525 527 # Tests for cell magics support
526 528
527 529 def test_last_blank():
528 530 assert isp.last_blank("") is False
529 531 assert isp.last_blank("abc") is False
530 532 assert isp.last_blank("abc\n") is False
531 533 assert isp.last_blank("abc\na") is False
532 534
533 535 assert isp.last_blank("\n") is True
534 536 assert isp.last_blank("\n ") is True
535 537 assert isp.last_blank("abc\n ") is True
536 538 assert isp.last_blank("abc\n\n") is True
537 539 assert isp.last_blank("abc\nd\n\n") is True
538 540 assert isp.last_blank("abc\nd\ne\n\n") is True
539 541 assert isp.last_blank("abc \n \n \n\n") is True
540 542
541 543
542 544 def test_last_two_blanks():
543 545 assert isp.last_two_blanks("") is False
544 546 assert isp.last_two_blanks("abc") is False
545 547 assert isp.last_two_blanks("abc\n") is False
546 548 assert isp.last_two_blanks("abc\n\na") is False
547 549 assert isp.last_two_blanks("abc\n \n") is False
548 550 assert isp.last_two_blanks("abc\n\n") is False
549 551
550 552 assert isp.last_two_blanks("\n\n") is True
551 553 assert isp.last_two_blanks("\n\n ") is True
552 554 assert isp.last_two_blanks("\n \n") is True
553 555 assert isp.last_two_blanks("abc\n\n ") is True
554 556 assert isp.last_two_blanks("abc\n\n\n") is True
555 557 assert isp.last_two_blanks("abc\n\n \n") is True
556 558 assert isp.last_two_blanks("abc\n\n \n ") is True
557 559 assert isp.last_two_blanks("abc\n\n \n \n") is True
558 560 assert isp.last_two_blanks("abc\nd\n\n\n") is True
559 561 assert isp.last_two_blanks("abc\nd\ne\nf\n\n\n") is True
560 562
561 563
562 564 class CellMagicsCommon(object):
563 565
564 566 def test_whole_cell(self):
565 567 src = "%%cellm line\nbody\n"
566 568 out = self.sp.transform_cell(src)
567 569 ref = "get_ipython().run_cell_magic('cellm', 'line', 'body')\n"
568 570 assert out == ref
569 571
570 572 def test_cellmagic_help(self):
571 573 self.sp.push('%%cellm?')
572 574 assert self.sp.push_accepts_more() is False
573 575
574 576 def tearDown(self):
575 577 self.sp.reset()
576 578
577 579
578 580 class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
579 581 sp = isp.IPythonInputSplitter(line_input_checker=False)
580 582
581 583 def test_incremental(self):
582 584 sp = self.sp
583 585 sp.push("%%cellm firstline\n")
584 586 assert sp.push_accepts_more() is True # 1
585 587 sp.push("line2\n")
586 588 assert sp.push_accepts_more() is True # 2
587 589 sp.push("\n")
588 590 # This should accept a blank line and carry on until the cell is reset
589 591 assert sp.push_accepts_more() is True # 3
590 592
591 593 def test_no_strip_coding(self):
592 594 src = '\n'.join([
593 595 '%%writefile foo.py',
594 596 '# coding: utf-8',
595 597 'print(u"üñîçø∂é")',
596 598 ])
597 599 out = self.sp.transform_cell(src)
598 600 assert "# coding: utf-8" in out
599 601
600 602
601 603 class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
602 604 sp = isp.IPythonInputSplitter(line_input_checker=True)
603 605
604 606 def test_incremental(self):
605 607 sp = self.sp
606 608 sp.push("%%cellm line2\n")
607 609 assert sp.push_accepts_more() is True # 1
608 610 sp.push("\n")
609 611 # In this case, a blank line should end the cell magic
610 612 assert sp.push_accepts_more() is False # 2
611 613
612 614
613 615 indentation_samples = [
614 616 ('a = 1', 0),
615 617 ('for a in b:', 4),
616 618 ('def f():', 4),
617 619 ('def f(): #comment', 4),
618 620 ('a = ":#not a comment"', 0),
619 621 ('def f():\n a = 1', 4),
620 622 ('def f():\n return 1', 0),
621 623 ('for a in b:\n'
622 624 ' if a < 0:'
623 625 ' continue', 3),
624 626 ('a = {', 4),
625 627 ('a = {\n'
626 628 ' 1,', 5),
627 629 ('b = """123', 0),
628 630 ('', 0),
629 631 ('def f():\n pass', 0),
630 632 ('class Bar:\n def f():\n pass', 4),
631 633 ('class Bar:\n def f():\n raise', 4),
632 634 ]
633 635
634 636 def test_find_next_indent():
635 637 for code, exp in indentation_samples:
636 638 res = isp.find_next_indent(code)
637 639 msg = "{!r} != {!r} (expected)\n Code: {!r}".format(res, exp, code)
638 640 assert res == exp, msg
@@ -1,359 +1,387 b''
1 1 """Tests for the token-based transformers in IPython.core.inputtransformer2
2 2
3 3 Line-based transformers are the simpler ones; token-based transformers are
4 4 more complex. See test_inputtransformer2_line for tests for line-based
5 5 transformations.
6 6 """
7 7 import string
8 import sys
9 from textwrap import dedent
8 10
9 from IPython.core import inputtransformer2 as ipt2
10 from IPython.core.inputtransformer2 import make_tokens_by_line, _find_assign_op
11 import pytest
11 12
12 from textwrap import dedent
13 from IPython.core import inputtransformer2 as ipt2
14 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line
15 from IPython.testing.decorators import skip_iptest_but_not_pytest
13 16
14 17 MULTILINE_MAGIC = ("""\
15 18 a = f()
16 19 %foo \\
17 20 bar
18 21 g()
19 22 """.splitlines(keepends=True), (2, 0), """\
20 23 a = f()
21 24 get_ipython().run_line_magic('foo', ' bar')
22 25 g()
23 26 """.splitlines(keepends=True))
24 27
25 28 INDENTED_MAGIC = ("""\
26 29 for a in range(5):
27 30 %ls
28 31 """.splitlines(keepends=True), (2, 4), """\
29 32 for a in range(5):
30 33 get_ipython().run_line_magic('ls', '')
31 34 """.splitlines(keepends=True))
32 35
33 36 CRLF_MAGIC = ([
34 37 "a = f()\n",
35 38 "%ls\r\n",
36 39 "g()\n"
37 40 ], (2, 0), [
38 41 "a = f()\n",
39 42 "get_ipython().run_line_magic('ls', '')\n",
40 43 "g()\n"
41 44 ])
42 45
43 46 MULTILINE_MAGIC_ASSIGN = ("""\
44 47 a = f()
45 48 b = %foo \\
46 49 bar
47 50 g()
48 51 """.splitlines(keepends=True), (2, 4), """\
49 52 a = f()
50 53 b = get_ipython().run_line_magic('foo', ' bar')
51 54 g()
52 55 """.splitlines(keepends=True))
53 56
54 57 MULTILINE_SYSTEM_ASSIGN = ("""\
55 58 a = f()
56 59 b = !foo \\
57 60 bar
58 61 g()
59 62 """.splitlines(keepends=True), (2, 4), """\
60 63 a = f()
61 64 b = get_ipython().getoutput('foo bar')
62 65 g()
63 66 """.splitlines(keepends=True))
64 67
65 68 #####
66 69
67 70 MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\
68 71 def test():
69 72 for i in range(1):
70 73 print(i)
71 74 res =! ls
72 75 """.splitlines(keepends=True), (4, 7), '''\
73 76 def test():
74 77 for i in range(1):
75 78 print(i)
76 79 res =get_ipython().getoutput(\' ls\')
77 80 '''.splitlines(keepends=True))
78 81
79 82 ######
80 83
81 84 AUTOCALL_QUOTE = (
82 85 [",f 1 2 3\n"], (1, 0),
83 86 ['f("1", "2", "3")\n']
84 87 )
85 88
86 89 AUTOCALL_QUOTE2 = (
87 90 [";f 1 2 3\n"], (1, 0),
88 91 ['f("1 2 3")\n']
89 92 )
90 93
91 94 AUTOCALL_PAREN = (
92 95 ["/f 1 2 3\n"], (1, 0),
93 96 ['f(1, 2, 3)\n']
94 97 )
95 98
96 99 SIMPLE_HELP = (
97 100 ["foo?\n"], (1, 0),
98 101 ["get_ipython().run_line_magic('pinfo', 'foo')\n"]
99 102 )
100 103
101 104 DETAILED_HELP = (
102 105 ["foo??\n"], (1, 0),
103 106 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"]
104 107 )
105 108
106 109 MAGIC_HELP = (
107 110 ["%foo?\n"], (1, 0),
108 111 ["get_ipython().run_line_magic('pinfo', '%foo')\n"]
109 112 )
110 113
111 114 HELP_IN_EXPR = (
112 115 ["a = b + c?\n"], (1, 0),
113 116 ["get_ipython().set_next_input('a = b + c');"
114 117 "get_ipython().run_line_magic('pinfo', 'c')\n"]
115 118 )
116 119
117 120 HELP_CONTINUED_LINE = ("""\
118 121 a = \\
119 122 zip?
120 123 """.splitlines(keepends=True), (1, 0),
121 124 [r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
122 125 )
123 126
124 127 HELP_MULTILINE = ("""\
125 128 (a,
126 129 b) = zip?
127 130 """.splitlines(keepends=True), (1, 0),
128 131 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
129 132 )
130 133
131 134 HELP_UNICODE = (
132 135 ["π.foo?\n"], (1, 0),
133 136 ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"]
134 137 )
135 138
136 139
137 140 def null_cleanup_transformer(lines):
138 141 """
139 142 A cleanup transform that returns an empty list.
140 143 """
141 144 return []
142 145
143 146 def check_make_token_by_line_never_ends_empty():
144 147 """
145 148 Check that not sequence of single or double characters ends up leading to en empty list of tokens
146 149 """
147 150 from string import printable
148 151 for c in printable:
149 152 assert make_tokens_by_line(c)[-1] != []
150 153 for k in printable:
151 154 assert make_tokens_by_line(c + k)[-1] != []
152 155
153 156
154 157 def check_find(transformer, case, match=True):
155 158 sample, expected_start, _ = case
156 159 tbl = make_tokens_by_line(sample)
157 160 res = transformer.find(tbl)
158 161 if match:
159 162 # start_line is stored 0-indexed, expected values are 1-indexed
160 163 assert (res.start_line + 1, res.start_col) == expected_start
161 164 return res
162 165 else:
163 166 assert res is None
164 167
165 168 def check_transform(transformer_cls, case):
166 169 lines, start, expected = case
167 170 transformer = transformer_cls(start)
168 171 assert transformer.transform(lines) == expected
169 172
170 173 def test_continued_line():
171 174 lines = MULTILINE_MAGIC_ASSIGN[0]
172 175 assert ipt2.find_end_of_continued_line(lines, 1) == 2
173 176
174 177 assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar"
175 178
176 179 def test_find_assign_magic():
177 180 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
178 181 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
179 182 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
180 183
181 184 def test_transform_assign_magic():
182 185 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
183 186
184 187 def test_find_assign_system():
185 188 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
186 189 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
187 190 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
188 191 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
189 192 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
190 193
191 194 def test_transform_assign_system():
192 195 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
193 196 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
194 197
195 198 def test_find_magic_escape():
196 199 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
197 200 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
198 201 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
199 202
200 203 def test_transform_magic_escape():
201 204 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
202 205 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
203 206 check_transform(ipt2.EscapedCommand, CRLF_MAGIC)
204 207
205 208 def test_find_autocalls():
206 209 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
207 210 print("Testing %r" % case[0])
208 211 check_find(ipt2.EscapedCommand, case)
209 212
210 213 def test_transform_autocall():
211 214 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
212 215 print("Testing %r" % case[0])
213 216 check_transform(ipt2.EscapedCommand, case)
214 217
215 218 def test_find_help():
216 219 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
217 220 check_find(ipt2.HelpEnd, case)
218 221
219 222 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
220 223 assert tf.q_line == 1
221 224 assert tf.q_col == 3
222 225
223 226 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
224 227 assert tf.q_line == 1
225 228 assert tf.q_col == 8
226 229
227 230 # ? in a comment does not trigger help
228 231 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
229 232 # Nor in a string
230 233 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
231 234
232 235 def test_transform_help():
233 236 tf = ipt2.HelpEnd((1, 0), (1, 9))
234 237 assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2]
235 238
236 239 tf = ipt2.HelpEnd((1, 0), (2, 3))
237 240 assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2]
238 241
239 242 tf = ipt2.HelpEnd((1, 0), (2, 8))
240 243 assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2]
241 244
242 245 tf = ipt2.HelpEnd((1, 0), (1, 0))
243 246 assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2]
244 247
245 248 def test_find_assign_op_dedent():
246 249 """
247 250 be careful that empty token like dedent are not counted as parens
248 251 """
249 252 class Tk:
250 253 def __init__(self, s):
251 254 self.string = s
252 255
253 256 assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2
254 257 assert (
255 258 _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6
256 259 )
257 260
258 261
262 examples = [
263 pytest.param("a = 1", "complete", None),
264 pytest.param("for a in range(5):", "incomplete", 4),
265 pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8),
266 pytest.param("raise = 2", "invalid", None),
267 pytest.param("a = [1,\n2,", "incomplete", 0),
268 pytest.param("(\n))", "incomplete", 0),
269 pytest.param("\\\r\n", "incomplete", 0),
270 pytest.param("a = '''\n hi", "incomplete", 3),
271 pytest.param("def a():\n x=1\n global x", "invalid", None),
272 pytest.param(
273 "a \\ ",
274 "invalid",
275 None,
276 marks=pytest.mark.xfail(
277 reason="Bug in python 3.9.8 – bpo 45738",
278 condition=sys.version_info[:3] == (3, 9, 8),
279 raises=SystemError,
280 strict=True,
281 ),
282 ), # Nothing allowed after backslash,
283 pytest.param("1\\\n+2", "complete", None),
284 ]
285
286
287 @skip_iptest_but_not_pytest
288 @pytest.mark.parametrize("code, expected, number", examples)
289 def test_check_complete_param(code, expected, number):
290 cc = ipt2.TransformerManager().check_complete
291 assert cc(code) == (expected, number)
292
293
294 @skip_iptest_but_not_pytest
295 @pytest.mark.xfail(
296 reason="Bug in python 3.9.8 – bpo 45738",
297 condition=sys.version_info[:3] == (3, 9, 8),
298 )
259 299 def test_check_complete():
260 300 cc = ipt2.TransformerManager().check_complete
261 assert cc("a = 1") == ("complete", None)
262 assert cc("for a in range(5):") == ("incomplete", 4)
263 assert cc("for a in range(5):\n if a > 0:") == ("incomplete", 8)
264 assert cc("raise = 2") == ("invalid", None)
265 assert cc("a = [1,\n2,") == ("incomplete", 0)
266 assert cc("(\n))") == ("incomplete", 0)
267 assert cc("\\\r\n") == ("incomplete", 0)
268 assert cc("a = '''\n hi") == ("incomplete", 3)
269 assert cc("def a():\n x=1\n global x") == ("invalid", None)
270 assert cc("a \\ ") == ("invalid", None) # Nothing allowed after backslash
271 assert cc("1\\\n+2") == ("complete", None)
272 assert cc("exit") == ("complete", None)
273 301
274 302 example = dedent("""
275 303 if True:
276 304 a=1""" )
277 305
278 306 assert cc(example) == ("incomplete", 4)
279 307 assert cc(example + "\n") == ("complete", None)
280 308 assert cc(example + "\n ") == ("complete", None)
281 309
282 310 # no need to loop on all the letters/numbers.
283 311 short = '12abAB'+string.printable[62:]
284 312 for c in short:
285 313 # test does not raise:
286 314 cc(c)
287 315 for k in short:
288 316 cc(c+k)
289 317
290 318 assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2)
291 319
292 320
293 321 def test_check_complete_II():
294 322 """
295 323 Test that multiple line strings are properly handled.
296 324
297 325 Separate test function for convenience
298 326
299 327 """
300 328 cc = ipt2.TransformerManager().check_complete
301 329 assert cc('''def foo():\n """''') == ("incomplete", 4)
302 330
303 331
304 332 def test_check_complete_invalidates_sunken_brackets():
305 333 """
306 334 Test that a single line with more closing brackets than the opening ones is
307 335 interpreted as invalid
308 336 """
309 337 cc = ipt2.TransformerManager().check_complete
310 338 assert cc(")") == ("invalid", None)
311 339 assert cc("]") == ("invalid", None)
312 340 assert cc("}") == ("invalid", None)
313 341 assert cc(")(") == ("invalid", None)
314 342 assert cc("][") == ("invalid", None)
315 343 assert cc("}{") == ("invalid", None)
316 344 assert cc("]()(") == ("invalid", None)
317 345 assert cc("())(") == ("invalid", None)
318 346 assert cc(")[](") == ("invalid", None)
319 347 assert cc("()](") == ("invalid", None)
320 348
321 349
322 350 def test_null_cleanup_transformer():
323 351 manager = ipt2.TransformerManager()
324 352 manager.cleanup_transforms.insert(0, null_cleanup_transformer)
325 353 assert manager.transform_cell("") == ""
326 354
327 355
328 356
329 357
330 358 def test_side_effects_I():
331 359 count = 0
332 360 def counter(lines):
333 361 nonlocal count
334 362 count += 1
335 363 return lines
336 364
337 365 counter.has_side_effects = True
338 366
339 367 manager = ipt2.TransformerManager()
340 368 manager.cleanup_transforms.insert(0, counter)
341 369 assert manager.check_complete("a=1\n") == ('complete', None)
342 370 assert count == 0
343 371
344 372
345 373
346 374
347 375 def test_side_effects_II():
348 376 count = 0
349 377 def counter(lines):
350 378 nonlocal count
351 379 count += 1
352 380 return lines
353 381
354 382 counter.has_side_effects = True
355 383
356 384 manager = ipt2.TransformerManager()
357 385 manager.line_transforms.insert(0, counter)
358 386 assert manager.check_complete("b=1\n") == ('complete', None)
359 387 assert count == 0
General Comments 0
You need to be logged in to leave comments. Login now