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