##// END OF EJS Templates
Python 3.11 fixes
Nikita Kniazev -
Show More
@@ -1,55 +1,59 b''
1 1 name: Run tests
2 2
3 3 on:
4 4 push:
5 5 pull_request:
6 6 # Run weekly on Monday at 1:23 UTC
7 7 schedule:
8 8 - cron: '23 1 * * 1'
9 9 workflow_dispatch:
10 10
11 11
12 12 jobs:
13 13 test:
14 14 runs-on: ${{ matrix.os }}
15 15 strategy:
16 16 matrix:
17 17 os: [ubuntu-latest]
18 18 python-version: ["3.7", "3.8", "3.9", "3.10"]
19 19 deps: [test_extra]
20 20 # Test all on ubuntu, test ends on macos
21 21 include:
22 22 - os: macos-latest
23 23 python-version: "3.7"
24 24 deps: test_extra
25 25 - os: macos-latest
26 26 python-version: "3.10"
27 27 deps: test_extra
28 28 # Tests minimal dependencies set
29 29 - os: ubuntu-latest
30 30 python-version: "3.10"
31 31 deps: test
32 # Tests latest development Python version
33 - os: ubuntu-latest
34 python-version: "3.11-dev"
35 deps: test
32 36
33 37 steps:
34 38 - uses: actions/checkout@v2
35 39 - name: Set up Python ${{ matrix.python-version }}
36 40 uses: actions/setup-python@v2
37 41 with:
38 42 python-version: ${{ matrix.python-version }}
39 43 - name: Install latex
40 44 if: runner.os == 'Linux' && matrix.deps == 'test_extra'
41 45 run: sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng
42 46 - name: Install and update Python dependencies
43 47 run: |
44 48 python -m pip install --upgrade pip setuptools wheel
45 49 python -m pip install --upgrade -e .[${{ matrix.deps }}]
46 50 python -m pip install --upgrade check-manifest pytest-cov
47 51 - name: Check manifest
48 52 run: check-manifest
49 53 - name: pytest
50 54 env:
51 55 COLUMNS: 120
52 56 run: |
53 57 pytest --color=yes -v --cov --cov-report=xml
54 58 - name: Upload coverage to Codecov
55 59 uses: codecov/codecov-action@v2
@@ -1,1284 +1,1289 b''
1 1 # encoding: utf-8
2 2 """Tests for the IPython tab-completion machinery."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import os
8 8 import pytest
9 9 import sys
10 10 import textwrap
11 11 import unittest
12 12
13 13 from contextlib import contextmanager
14 14
15 15 from traitlets.config.loader import Config
16 16 from IPython import get_ipython
17 17 from IPython.core import completer
18 18 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
19 19 from IPython.utils.generics import complete_object
20 20 from IPython.testing import decorators as dec
21 21
22 22 from IPython.core.completer import (
23 23 Completion,
24 24 provisionalcompleter,
25 25 match_dict_keys,
26 26 _deduplicate_completions,
27 27 )
28 28
29 29 if sys.version_info >= (3, 10):
30 30 import jedi
31 31 from pkg_resources import parse_version
32 32
33 33 # Requires https://github.com/davidhalter/jedi/pull/1795
34 34 jedi_issue = parse_version(jedi.__version__) <= parse_version("0.18.0")
35 35 else:
36 36 jedi_issue = False
37 37
38 38 # -----------------------------------------------------------------------------
39 39 # Test functions
40 40 # -----------------------------------------------------------------------------
41 41
42 42 def recompute_unicode_ranges():
43 43 """
44 44 utility to recompute the largest unicode range without any characters
45 45
46 46 use to recompute the gap in the global _UNICODE_RANGES of completer.py
47 47 """
48 48 import itertools
49 49 import unicodedata
50 50 valid = []
51 51 for c in range(0,0x10FFFF + 1):
52 52 try:
53 53 unicodedata.name(chr(c))
54 54 except ValueError:
55 55 continue
56 56 valid.append(c)
57 57
58 58 def ranges(i):
59 59 for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
60 60 b = list(b)
61 61 yield b[0][1], b[-1][1]
62 62
63 63 rg = list(ranges(valid))
64 64 lens = []
65 65 gap_lens = []
66 66 pstart, pstop = 0,0
67 67 for start, stop in rg:
68 68 lens.append(stop-start)
69 69 gap_lens.append((start - pstop, hex(pstop), hex(start), f'{round((start - pstop)/0xe01f0*100)}%'))
70 70 pstart, pstop = start, stop
71 71
72 72 return sorted(gap_lens)[-1]
73 73
74 74
75 75
76 76 def test_unicode_range():
77 77 """
78 78 Test that the ranges we test for unicode names give the same number of
79 79 results than testing the full length.
80 80 """
81 81 from IPython.core.completer import _unicode_name_compute, _UNICODE_RANGES
82 82
83 83 expected_list = _unicode_name_compute([(0, 0x110000)])
84 84 test = _unicode_name_compute(_UNICODE_RANGES)
85 85 len_exp = len(expected_list)
86 86 len_test = len(test)
87 87
88 88 # do not inline the len() or on error pytest will try to print the 130 000 +
89 89 # elements.
90 90 message = None
91 91 if len_exp != len_test or len_exp > 131808:
92 92 size, start, stop, prct = recompute_unicode_ranges()
93 93 message = f"""_UNICODE_RANGES likely wrong and need updating. This is
94 94 likely due to a new release of Python. We've find that the biggest gap
95 95 in unicode characters has reduces in size to be {size} characters
96 96 ({prct}), from {start}, to {stop}. In completer.py likely update to
97 97
98 98 _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)]
99 99
100 100 And update the assertion below to use
101 101
102 102 len_exp <= {len_exp}
103 103 """
104 104 assert len_exp == len_test, message
105 105
106 106 # fail if new unicode symbols have been added.
107 assert len_exp <= 137714, message
107 assert len_exp <= 138552, message
108 108
109 109
110 110 @contextmanager
111 111 def greedy_completion():
112 112 ip = get_ipython()
113 113 greedy_original = ip.Completer.greedy
114 114 try:
115 115 ip.Completer.greedy = True
116 116 yield
117 117 finally:
118 118 ip.Completer.greedy = greedy_original
119 119
120 120
121 121 def test_protect_filename():
122 122 if sys.platform == "win32":
123 123 pairs = [
124 124 ("abc", "abc"),
125 125 (" abc", '" abc"'),
126 126 ("a bc", '"a bc"'),
127 127 ("a bc", '"a bc"'),
128 128 (" bc", '" bc"'),
129 129 ]
130 130 else:
131 131 pairs = [
132 132 ("abc", "abc"),
133 133 (" abc", r"\ abc"),
134 134 ("a bc", r"a\ bc"),
135 135 ("a bc", r"a\ \ bc"),
136 136 (" bc", r"\ \ bc"),
137 137 # On posix, we also protect parens and other special characters.
138 138 ("a(bc", r"a\(bc"),
139 139 ("a)bc", r"a\)bc"),
140 140 ("a( )bc", r"a\(\ \)bc"),
141 141 ("a[1]bc", r"a\[1\]bc"),
142 142 ("a{1}bc", r"a\{1\}bc"),
143 143 ("a#bc", r"a\#bc"),
144 144 ("a?bc", r"a\?bc"),
145 145 ("a=bc", r"a\=bc"),
146 146 ("a\\bc", r"a\\bc"),
147 147 ("a|bc", r"a\|bc"),
148 148 ("a;bc", r"a\;bc"),
149 149 ("a:bc", r"a\:bc"),
150 150 ("a'bc", r"a\'bc"),
151 151 ("a*bc", r"a\*bc"),
152 152 ('a"bc', r"a\"bc"),
153 153 ("a^bc", r"a\^bc"),
154 154 ("a&bc", r"a\&bc"),
155 155 ]
156 156 # run the actual tests
157 157 for s1, s2 in pairs:
158 158 s1p = completer.protect_filename(s1)
159 159 assert s1p == s2
160 160
161 161
162 162 def check_line_split(splitter, test_specs):
163 163 for part1, part2, split in test_specs:
164 164 cursor_pos = len(part1)
165 165 line = part1 + part2
166 166 out = splitter.split_line(line, cursor_pos)
167 167 assert out == split
168 168
169 169
170 170 def test_line_split():
171 171 """Basic line splitter test with default specs."""
172 172 sp = completer.CompletionSplitter()
173 173 # The format of the test specs is: part1, part2, expected answer. Parts 1
174 174 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
175 175 # was at the end of part1. So an empty part2 represents someone hitting
176 176 # tab at the end of the line, the most common case.
177 177 t = [
178 178 ("run some/scrip", "", "some/scrip"),
179 179 ("run scripts/er", "ror.py foo", "scripts/er"),
180 180 ("echo $HOM", "", "HOM"),
181 181 ("print sys.pa", "", "sys.pa"),
182 182 ("print(sys.pa", "", "sys.pa"),
183 183 ("execfile('scripts/er", "", "scripts/er"),
184 184 ("a[x.", "", "x."),
185 185 ("a[x.", "y", "x."),
186 186 ('cd "some_file/', "", "some_file/"),
187 187 ]
188 188 check_line_split(sp, t)
189 189 # Ensure splitting works OK with unicode by re-running the tests with
190 190 # all inputs turned into unicode
191 191 check_line_split(sp, [map(str, p) for p in t])
192 192
193 193
194 194 class NamedInstanceMetaclass(type):
195 195 def __getitem__(cls, item):
196 196 return cls.get_instance(item)
197 197
198 198
199 199 class NamedInstanceClass(metaclass=NamedInstanceMetaclass):
200 200 def __init__(self, name):
201 201 if not hasattr(self.__class__, "instances"):
202 202 self.__class__.instances = {}
203 203 self.__class__.instances[name] = self
204 204
205 205 @classmethod
206 206 def _ipython_key_completions_(cls):
207 207 return cls.instances.keys()
208 208
209 209 @classmethod
210 210 def get_instance(cls, name):
211 211 return cls.instances[name]
212 212
213 213
214 214 class KeyCompletable:
215 215 def __init__(self, things=()):
216 216 self.things = things
217 217
218 218 def _ipython_key_completions_(self):
219 219 return list(self.things)
220 220
221 221
222 @pytest.mark.xfail(
223 sys.version_info >= (3, 11),
224 reason="parso does not support 3.11 yet",
225 raises=NotImplementedError,
226 )
222 227 class TestCompleter(unittest.TestCase):
223 228 def setUp(self):
224 229 """
225 230 We want to silence all PendingDeprecationWarning when testing the completer
226 231 """
227 232 self._assertwarns = self.assertWarns(PendingDeprecationWarning)
228 233 self._assertwarns.__enter__()
229 234
230 235 def tearDown(self):
231 236 try:
232 237 self._assertwarns.__exit__(None, None, None)
233 238 except AssertionError:
234 239 pass
235 240
236 241 def test_custom_completion_error(self):
237 242 """Test that errors from custom attribute completers are silenced."""
238 243 ip = get_ipython()
239 244
240 245 class A:
241 246 pass
242 247
243 248 ip.user_ns["x"] = A()
244 249
245 250 @complete_object.register(A)
246 251 def complete_A(a, existing_completions):
247 252 raise TypeError("this should be silenced")
248 253
249 254 ip.complete("x.")
250 255
251 256 def test_custom_completion_ordering(self):
252 257 """Test that errors from custom attribute completers are silenced."""
253 258 ip = get_ipython()
254 259
255 260 _, matches = ip.complete('in')
256 261 assert matches.index('input') < matches.index('int')
257 262
258 263 def complete_example(a):
259 264 return ['example2', 'example1']
260 265
261 266 ip.Completer.custom_completers.add_re('ex*', complete_example)
262 267 _, matches = ip.complete('ex')
263 268 assert matches.index('example2') < matches.index('example1')
264 269
265 270 def test_unicode_completions(self):
266 271 ip = get_ipython()
267 272 # Some strings that trigger different types of completion. Check them both
268 273 # in str and unicode forms
269 274 s = ["ru", "%ru", "cd /", "floa", "float(x)/"]
270 275 for t in s + list(map(str, s)):
271 276 # We don't need to check exact completion values (they may change
272 277 # depending on the state of the namespace, but at least no exceptions
273 278 # should be thrown and the return value should be a pair of text, list
274 279 # values.
275 280 text, matches = ip.complete(t)
276 281 self.assertIsInstance(text, str)
277 282 self.assertIsInstance(matches, list)
278 283
279 284 def test_latex_completions(self):
280 285 from IPython.core.latex_symbols import latex_symbols
281 286 import random
282 287
283 288 ip = get_ipython()
284 289 # Test some random unicode symbols
285 keys = random.sample(latex_symbols.keys(), 10)
290 keys = random.sample(sorted(latex_symbols), 10)
286 291 for k in keys:
287 292 text, matches = ip.complete(k)
288 293 self.assertEqual(text, k)
289 294 self.assertEqual(matches, [latex_symbols[k]])
290 295 # Test a more complex line
291 296 text, matches = ip.complete("print(\\alpha")
292 297 self.assertEqual(text, "\\alpha")
293 298 self.assertEqual(matches[0], latex_symbols["\\alpha"])
294 299 # Test multiple matching latex symbols
295 300 text, matches = ip.complete("\\al")
296 301 self.assertIn("\\alpha", matches)
297 302 self.assertIn("\\aleph", matches)
298 303
299 304 def test_latex_no_results(self):
300 305 """
301 306 forward latex should really return nothing in either field if nothing is found.
302 307 """
303 308 ip = get_ipython()
304 309 text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing")
305 310 self.assertEqual(text, "")
306 311 self.assertEqual(matches, ())
307 312
308 313 def test_back_latex_completion(self):
309 314 ip = get_ipython()
310 315
311 316 # do not return more than 1 matches for \beta, only the latex one.
312 317 name, matches = ip.complete("\\β")
313 318 self.assertEqual(matches, ["\\beta"])
314 319
315 320 def test_back_unicode_completion(self):
316 321 ip = get_ipython()
317 322
318 323 name, matches = ip.complete("\\Ⅴ")
319 324 self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
320 325
321 326 def test_forward_unicode_completion(self):
322 327 ip = get_ipython()
323 328
324 329 name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
325 330 self.assertEqual(matches, ["Ⅴ"]) # This is not a V
326 331 self.assertEqual(matches, ["\u2164"]) # same as above but explicit.
327 332
328 333 def test_delim_setting(self):
329 334 sp = completer.CompletionSplitter()
330 335 sp.delims = " "
331 336 self.assertEqual(sp.delims, " ")
332 337 self.assertEqual(sp._delim_expr, r"[\ ]")
333 338
334 339 def test_spaces(self):
335 340 """Test with only spaces as split chars."""
336 341 sp = completer.CompletionSplitter()
337 342 sp.delims = " "
338 343 t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")]
339 344 check_line_split(sp, t)
340 345
341 346 def test_has_open_quotes1(self):
342 347 for s in ["'", "'''", "'hi' '"]:
343 348 self.assertEqual(completer.has_open_quotes(s), "'")
344 349
345 350 def test_has_open_quotes2(self):
346 351 for s in ['"', '"""', '"hi" "']:
347 352 self.assertEqual(completer.has_open_quotes(s), '"')
348 353
349 354 def test_has_open_quotes3(self):
350 355 for s in ["''", "''' '''", "'hi' 'ipython'"]:
351 356 self.assertFalse(completer.has_open_quotes(s))
352 357
353 358 def test_has_open_quotes4(self):
354 359 for s in ['""', '""" """', '"hi" "ipython"']:
355 360 self.assertFalse(completer.has_open_quotes(s))
356 361
357 362 @pytest.mark.xfail(
358 363 sys.platform == "win32", reason="abspath completions fail on Windows"
359 364 )
360 365 def test_abspath_file_completions(self):
361 366 ip = get_ipython()
362 367 with TemporaryDirectory() as tmpdir:
363 368 prefix = os.path.join(tmpdir, "foo")
364 369 suffixes = ["1", "2"]
365 370 names = [prefix + s for s in suffixes]
366 371 for n in names:
367 372 open(n, "w").close()
368 373
369 374 # Check simple completion
370 375 c = ip.complete(prefix)[1]
371 376 self.assertEqual(c, names)
372 377
373 378 # Now check with a function call
374 379 cmd = 'a = f("%s' % prefix
375 380 c = ip.complete(prefix, cmd)[1]
376 381 comp = [prefix + s for s in suffixes]
377 382 self.assertEqual(c, comp)
378 383
379 384 def test_local_file_completions(self):
380 385 ip = get_ipython()
381 386 with TemporaryWorkingDirectory():
382 387 prefix = "./foo"
383 388 suffixes = ["1", "2"]
384 389 names = [prefix + s for s in suffixes]
385 390 for n in names:
386 391 open(n, "w").close()
387 392
388 393 # Check simple completion
389 394 c = ip.complete(prefix)[1]
390 395 self.assertEqual(c, names)
391 396
392 397 # Now check with a function call
393 398 cmd = 'a = f("%s' % prefix
394 399 c = ip.complete(prefix, cmd)[1]
395 400 comp = {prefix + s for s in suffixes}
396 401 self.assertTrue(comp.issubset(set(c)))
397 402
398 403 def test_quoted_file_completions(self):
399 404 ip = get_ipython()
400 405 with TemporaryWorkingDirectory():
401 406 name = "foo'bar"
402 407 open(name, "w").close()
403 408
404 409 # Don't escape Windows
405 410 escaped = name if sys.platform == "win32" else "foo\\'bar"
406 411
407 412 # Single quote matches embedded single quote
408 413 text = "open('foo"
409 414 c = ip.Completer._complete(
410 415 cursor_line=0, cursor_pos=len(text), full_text=text
411 416 )[1]
412 417 self.assertEqual(c, [escaped])
413 418
414 419 # Double quote requires no escape
415 420 text = 'open("foo'
416 421 c = ip.Completer._complete(
417 422 cursor_line=0, cursor_pos=len(text), full_text=text
418 423 )[1]
419 424 self.assertEqual(c, [name])
420 425
421 426 # No quote requires an escape
422 427 text = "%ls foo"
423 428 c = ip.Completer._complete(
424 429 cursor_line=0, cursor_pos=len(text), full_text=text
425 430 )[1]
426 431 self.assertEqual(c, [escaped])
427 432
428 433 def test_all_completions_dups(self):
429 434 """
430 435 Make sure the output of `IPCompleter.all_completions` does not have
431 436 duplicated prefixes.
432 437 """
433 438 ip = get_ipython()
434 439 c = ip.Completer
435 440 ip.ex("class TestClass():\n\ta=1\n\ta1=2")
436 441 for jedi_status in [True, False]:
437 442 with provisionalcompleter():
438 443 ip.Completer.use_jedi = jedi_status
439 444 matches = c.all_completions("TestCl")
440 445 assert matches == ['TestClass'], jedi_status
441 446 matches = c.all_completions("TestClass.")
442 447 if jedi_status and jedi_issue:
443 448 continue
444 449 assert len(matches) > 2, jedi_status
445 450 matches = c.all_completions("TestClass.a")
446 451 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
447 452
448 453 def test_jedi(self):
449 454 """
450 455 A couple of issue we had with Jedi
451 456 """
452 457 ip = get_ipython()
453 458
454 459 def _test_complete(reason, s, comp, start=None, end=None):
455 460 l = len(s)
456 461 start = start if start is not None else l
457 462 end = end if end is not None else l
458 463 with provisionalcompleter():
459 464 ip.Completer.use_jedi = True
460 465 completions = set(ip.Completer.completions(s, l))
461 466 ip.Completer.use_jedi = False
462 467 assert Completion(start, end, comp) in completions, reason
463 468
464 469 def _test_not_complete(reason, s, comp):
465 470 l = len(s)
466 471 with provisionalcompleter():
467 472 ip.Completer.use_jedi = True
468 473 completions = set(ip.Completer.completions(s, l))
469 474 ip.Completer.use_jedi = False
470 475 assert Completion(l, l, comp) not in completions, reason
471 476
472 477 import jedi
473 478
474 479 jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3])
475 480 if jedi_version > (0, 10):
476 481 _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real")
477 482 _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real")
478 483 _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize")
479 484 _test_complete("cover duplicate completions", "im", "import", 0, 2)
480 485
481 486 _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize")
482 487
483 488 def test_completion_have_signature(self):
484 489 """
485 490 Lets make sure jedi is capable of pulling out the signature of the function we are completing.
486 491 """
487 492 ip = get_ipython()
488 493 with provisionalcompleter():
489 494 ip.Completer.use_jedi = True
490 495 completions = ip.Completer.completions("ope", 3)
491 496 c = next(completions) # should be `open`
492 497 ip.Completer.use_jedi = False
493 498 assert "file" in c.signature, "Signature of function was not found by completer"
494 499 assert (
495 500 "encoding" in c.signature
496 501 ), "Signature of function was not found by completer"
497 502
498 503 @pytest.mark.xfail(jedi_issue, reason="Known failure on jedi<=0.18.0")
499 504 def test_deduplicate_completions(self):
500 505 """
501 506 Test that completions are correctly deduplicated (even if ranges are not the same)
502 507 """
503 508 ip = get_ipython()
504 509 ip.ex(
505 510 textwrap.dedent(
506 511 """
507 512 class Z:
508 513 zoo = 1
509 514 """
510 515 )
511 516 )
512 517 with provisionalcompleter():
513 518 ip.Completer.use_jedi = True
514 519 l = list(
515 520 _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3))
516 521 )
517 522 ip.Completer.use_jedi = False
518 523
519 524 assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l
520 525 assert l[0].text == "zoo" # and not `it.accumulate`
521 526
522 527 def test_greedy_completions(self):
523 528 """
524 529 Test the capability of the Greedy completer.
525 530
526 531 Most of the test here does not really show off the greedy completer, for proof
527 532 each of the text below now pass with Jedi. The greedy completer is capable of more.
528 533
529 534 See the :any:`test_dict_key_completion_contexts`
530 535
531 536 """
532 537 ip = get_ipython()
533 538 ip.ex("a=list(range(5))")
534 539 _, c = ip.complete(".", line="a[0].")
535 540 self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c)
536 541
537 542 def _(line, cursor_pos, expect, message, completion):
538 543 with greedy_completion(), provisionalcompleter():
539 544 ip.Completer.use_jedi = False
540 545 _, c = ip.complete(".", line=line, cursor_pos=cursor_pos)
541 546 self.assertIn(expect, c, message % c)
542 547
543 548 ip.Completer.use_jedi = True
544 549 with provisionalcompleter():
545 550 completions = ip.Completer.completions(line, cursor_pos)
546 551 self.assertIn(completion, completions)
547 552
548 553 with provisionalcompleter():
549 554 _(
550 555 "a[0].",
551 556 5,
552 557 "a[0].real",
553 558 "Should have completed on a[0].: %s",
554 559 Completion(5, 5, "real"),
555 560 )
556 561 _(
557 562 "a[0].r",
558 563 6,
559 564 "a[0].real",
560 565 "Should have completed on a[0].r: %s",
561 566 Completion(5, 6, "real"),
562 567 )
563 568
564 569 _(
565 570 "a[0].from_",
566 571 10,
567 572 "a[0].from_bytes",
568 573 "Should have completed on a[0].from_: %s",
569 574 Completion(5, 10, "from_bytes"),
570 575 )
571 576
572 577 def test_omit__names(self):
573 578 # also happens to test IPCompleter as a configurable
574 579 ip = get_ipython()
575 580 ip._hidden_attr = 1
576 581 ip._x = {}
577 582 c = ip.Completer
578 583 ip.ex("ip=get_ipython()")
579 584 cfg = Config()
580 585 cfg.IPCompleter.omit__names = 0
581 586 c.update_config(cfg)
582 587 with provisionalcompleter():
583 588 c.use_jedi = False
584 589 s, matches = c.complete("ip.")
585 590 self.assertIn("ip.__str__", matches)
586 591 self.assertIn("ip._hidden_attr", matches)
587 592
588 593 # c.use_jedi = True
589 594 # completions = set(c.completions('ip.', 3))
590 595 # self.assertIn(Completion(3, 3, '__str__'), completions)
591 596 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
592 597
593 598 cfg = Config()
594 599 cfg.IPCompleter.omit__names = 1
595 600 c.update_config(cfg)
596 601 with provisionalcompleter():
597 602 c.use_jedi = False
598 603 s, matches = c.complete("ip.")
599 604 self.assertNotIn("ip.__str__", matches)
600 605 # self.assertIn('ip._hidden_attr', matches)
601 606
602 607 # c.use_jedi = True
603 608 # completions = set(c.completions('ip.', 3))
604 609 # self.assertNotIn(Completion(3,3,'__str__'), completions)
605 610 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
606 611
607 612 cfg = Config()
608 613 cfg.IPCompleter.omit__names = 2
609 614 c.update_config(cfg)
610 615 with provisionalcompleter():
611 616 c.use_jedi = False
612 617 s, matches = c.complete("ip.")
613 618 self.assertNotIn("ip.__str__", matches)
614 619 self.assertNotIn("ip._hidden_attr", matches)
615 620
616 621 # c.use_jedi = True
617 622 # completions = set(c.completions('ip.', 3))
618 623 # self.assertNotIn(Completion(3,3,'__str__'), completions)
619 624 # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions)
620 625
621 626 with provisionalcompleter():
622 627 c.use_jedi = False
623 628 s, matches = c.complete("ip._x.")
624 629 self.assertIn("ip._x.keys", matches)
625 630
626 631 # c.use_jedi = True
627 632 # completions = set(c.completions('ip._x.', 6))
628 633 # self.assertIn(Completion(6,6, "keys"), completions)
629 634
630 635 del ip._hidden_attr
631 636 del ip._x
632 637
633 638 def test_limit_to__all__False_ok(self):
634 639 """
635 640 Limit to all is deprecated, once we remove it this test can go away.
636 641 """
637 642 ip = get_ipython()
638 643 c = ip.Completer
639 644 c.use_jedi = False
640 645 ip.ex("class D: x=24")
641 646 ip.ex("d=D()")
642 647 cfg = Config()
643 648 cfg.IPCompleter.limit_to__all__ = False
644 649 c.update_config(cfg)
645 650 s, matches = c.complete("d.")
646 651 self.assertIn("d.x", matches)
647 652
648 653 def test_get__all__entries_ok(self):
649 654 class A:
650 655 __all__ = ["x", 1]
651 656
652 657 words = completer.get__all__entries(A())
653 658 self.assertEqual(words, ["x"])
654 659
655 660 def test_get__all__entries_no__all__ok(self):
656 661 class A:
657 662 pass
658 663
659 664 words = completer.get__all__entries(A())
660 665 self.assertEqual(words, [])
661 666
662 667 def test_func_kw_completions(self):
663 668 ip = get_ipython()
664 669 c = ip.Completer
665 670 c.use_jedi = False
666 671 ip.ex("def myfunc(a=1,b=2): return a+b")
667 672 s, matches = c.complete(None, "myfunc(1,b")
668 673 self.assertIn("b=", matches)
669 674 # Simulate completing with cursor right after b (pos==10):
670 675 s, matches = c.complete(None, "myfunc(1,b)", 10)
671 676 self.assertIn("b=", matches)
672 677 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
673 678 self.assertIn("b=", matches)
674 679 # builtin function
675 680 s, matches = c.complete(None, "min(k, k")
676 681 self.assertIn("key=", matches)
677 682
678 683 def test_default_arguments_from_docstring(self):
679 684 ip = get_ipython()
680 685 c = ip.Completer
681 686 kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value")
682 687 self.assertEqual(kwd, ["key"])
683 688 # with cython type etc
684 689 kwd = c._default_arguments_from_docstring(
685 690 "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
686 691 )
687 692 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
688 693 # white spaces
689 694 kwd = c._default_arguments_from_docstring(
690 695 "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
691 696 )
692 697 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
693 698
694 699 def test_line_magics(self):
695 700 ip = get_ipython()
696 701 c = ip.Completer
697 702 s, matches = c.complete(None, "lsmag")
698 703 self.assertIn("%lsmagic", matches)
699 704 s, matches = c.complete(None, "%lsmag")
700 705 self.assertIn("%lsmagic", matches)
701 706
702 707 def test_cell_magics(self):
703 708 from IPython.core.magic import register_cell_magic
704 709
705 710 @register_cell_magic
706 711 def _foo_cellm(line, cell):
707 712 pass
708 713
709 714 ip = get_ipython()
710 715 c = ip.Completer
711 716
712 717 s, matches = c.complete(None, "_foo_ce")
713 718 self.assertIn("%%_foo_cellm", matches)
714 719 s, matches = c.complete(None, "%%_foo_ce")
715 720 self.assertIn("%%_foo_cellm", matches)
716 721
717 722 def test_line_cell_magics(self):
718 723 from IPython.core.magic import register_line_cell_magic
719 724
720 725 @register_line_cell_magic
721 726 def _bar_cellm(line, cell):
722 727 pass
723 728
724 729 ip = get_ipython()
725 730 c = ip.Completer
726 731
727 732 # The policy here is trickier, see comments in completion code. The
728 733 # returned values depend on whether the user passes %% or not explicitly,
729 734 # and this will show a difference if the same name is both a line and cell
730 735 # magic.
731 736 s, matches = c.complete(None, "_bar_ce")
732 737 self.assertIn("%_bar_cellm", matches)
733 738 self.assertIn("%%_bar_cellm", matches)
734 739 s, matches = c.complete(None, "%_bar_ce")
735 740 self.assertIn("%_bar_cellm", matches)
736 741 self.assertIn("%%_bar_cellm", matches)
737 742 s, matches = c.complete(None, "%%_bar_ce")
738 743 self.assertNotIn("%_bar_cellm", matches)
739 744 self.assertIn("%%_bar_cellm", matches)
740 745
741 746 def test_magic_completion_order(self):
742 747 ip = get_ipython()
743 748 c = ip.Completer
744 749
745 750 # Test ordering of line and cell magics.
746 751 text, matches = c.complete("timeit")
747 752 self.assertEqual(matches, ["%timeit", "%%timeit"])
748 753
749 754 def test_magic_completion_shadowing(self):
750 755 ip = get_ipython()
751 756 c = ip.Completer
752 757 c.use_jedi = False
753 758
754 759 # Before importing matplotlib, %matplotlib magic should be the only option.
755 760 text, matches = c.complete("mat")
756 761 self.assertEqual(matches, ["%matplotlib"])
757 762
758 763 # The newly introduced name should shadow the magic.
759 764 ip.run_cell("matplotlib = 1")
760 765 text, matches = c.complete("mat")
761 766 self.assertEqual(matches, ["matplotlib"])
762 767
763 768 # After removing matplotlib from namespace, the magic should again be
764 769 # the only option.
765 770 del ip.user_ns["matplotlib"]
766 771 text, matches = c.complete("mat")
767 772 self.assertEqual(matches, ["%matplotlib"])
768 773
769 774 def test_magic_completion_shadowing_explicit(self):
770 775 """
771 776 If the user try to complete a shadowed magic, and explicit % start should
772 777 still return the completions.
773 778 """
774 779 ip = get_ipython()
775 780 c = ip.Completer
776 781
777 782 # Before importing matplotlib, %matplotlib magic should be the only option.
778 783 text, matches = c.complete("%mat")
779 784 self.assertEqual(matches, ["%matplotlib"])
780 785
781 786 ip.run_cell("matplotlib = 1")
782 787
783 788 # After removing matplotlib from namespace, the magic should still be
784 789 # the only option.
785 790 text, matches = c.complete("%mat")
786 791 self.assertEqual(matches, ["%matplotlib"])
787 792
788 793 def test_magic_config(self):
789 794 ip = get_ipython()
790 795 c = ip.Completer
791 796
792 797 s, matches = c.complete(None, "conf")
793 798 self.assertIn("%config", matches)
794 799 s, matches = c.complete(None, "conf")
795 800 self.assertNotIn("AliasManager", matches)
796 801 s, matches = c.complete(None, "config ")
797 802 self.assertIn("AliasManager", matches)
798 803 s, matches = c.complete(None, "%config ")
799 804 self.assertIn("AliasManager", matches)
800 805 s, matches = c.complete(None, "config Ali")
801 806 self.assertListEqual(["AliasManager"], matches)
802 807 s, matches = c.complete(None, "%config Ali")
803 808 self.assertListEqual(["AliasManager"], matches)
804 809 s, matches = c.complete(None, "config AliasManager")
805 810 self.assertListEqual(["AliasManager"], matches)
806 811 s, matches = c.complete(None, "%config AliasManager")
807 812 self.assertListEqual(["AliasManager"], matches)
808 813 s, matches = c.complete(None, "config AliasManager.")
809 814 self.assertIn("AliasManager.default_aliases", matches)
810 815 s, matches = c.complete(None, "%config AliasManager.")
811 816 self.assertIn("AliasManager.default_aliases", matches)
812 817 s, matches = c.complete(None, "config AliasManager.de")
813 818 self.assertListEqual(["AliasManager.default_aliases"], matches)
814 819 s, matches = c.complete(None, "config AliasManager.de")
815 820 self.assertListEqual(["AliasManager.default_aliases"], matches)
816 821
817 822 def test_magic_color(self):
818 823 ip = get_ipython()
819 824 c = ip.Completer
820 825
821 826 s, matches = c.complete(None, "colo")
822 827 self.assertIn("%colors", matches)
823 828 s, matches = c.complete(None, "colo")
824 829 self.assertNotIn("NoColor", matches)
825 830 s, matches = c.complete(None, "%colors") # No trailing space
826 831 self.assertNotIn("NoColor", matches)
827 832 s, matches = c.complete(None, "colors ")
828 833 self.assertIn("NoColor", matches)
829 834 s, matches = c.complete(None, "%colors ")
830 835 self.assertIn("NoColor", matches)
831 836 s, matches = c.complete(None, "colors NoCo")
832 837 self.assertListEqual(["NoColor"], matches)
833 838 s, matches = c.complete(None, "%colors NoCo")
834 839 self.assertListEqual(["NoColor"], matches)
835 840
836 841 def test_match_dict_keys(self):
837 842 """
838 843 Test that match_dict_keys works on a couple of use case does return what
839 844 expected, and does not crash
840 845 """
841 846 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
842 847
843 848 keys = ["foo", b"far"]
844 849 assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"])
845 850 assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"])
846 851 assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"])
847 852 assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
848 853
849 854 assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"])
850 855 assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"])
851 856 assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"])
852 857 assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"])
853 858
854 859 match_dict_keys
855 860
856 861 def test_match_dict_keys_tuple(self):
857 862 """
858 863 Test that match_dict_keys called with extra prefix works on a couple of use case,
859 864 does return what expected, and does not crash.
860 865 """
861 866 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
862 867
863 868 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
864 869
865 870 # Completion on first key == "foo"
866 871 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
867 872 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
868 873 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
869 874 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
870 875 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
871 876 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
872 877 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
873 878 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
874 879
875 880 # No Completion
876 881 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
877 882 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
878 883
879 884 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
880 885 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
881 886 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
882 887 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
883 888 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
884 889
885 890 def test_dict_key_completion_string(self):
886 891 """Test dictionary key completion for string keys"""
887 892 ip = get_ipython()
888 893 complete = ip.Completer.complete
889 894
890 895 ip.user_ns["d"] = {"abc": None}
891 896
892 897 # check completion at different stages
893 898 _, matches = complete(line_buffer="d[")
894 899 self.assertIn("'abc'", matches)
895 900 self.assertNotIn("'abc']", matches)
896 901
897 902 _, matches = complete(line_buffer="d['")
898 903 self.assertIn("abc", matches)
899 904 self.assertNotIn("abc']", matches)
900 905
901 906 _, matches = complete(line_buffer="d['a")
902 907 self.assertIn("abc", matches)
903 908 self.assertNotIn("abc']", matches)
904 909
905 910 # check use of different quoting
906 911 _, matches = complete(line_buffer='d["')
907 912 self.assertIn("abc", matches)
908 913 self.assertNotIn('abc"]', matches)
909 914
910 915 _, matches = complete(line_buffer='d["a')
911 916 self.assertIn("abc", matches)
912 917 self.assertNotIn('abc"]', matches)
913 918
914 919 # check sensitivity to following context
915 920 _, matches = complete(line_buffer="d[]", cursor_pos=2)
916 921 self.assertIn("'abc'", matches)
917 922
918 923 _, matches = complete(line_buffer="d['']", cursor_pos=3)
919 924 self.assertIn("abc", matches)
920 925 self.assertNotIn("abc'", matches)
921 926 self.assertNotIn("abc']", matches)
922 927
923 928 # check multiple solutions are correctly returned and that noise is not
924 929 ip.user_ns["d"] = {
925 930 "abc": None,
926 931 "abd": None,
927 932 "bad": None,
928 933 object(): None,
929 934 5: None,
930 935 ("abe", None): None,
931 936 (None, "abf"): None
932 937 }
933 938
934 939 _, matches = complete(line_buffer="d['a")
935 940 self.assertIn("abc", matches)
936 941 self.assertIn("abd", matches)
937 942 self.assertNotIn("bad", matches)
938 943 self.assertNotIn("abe", matches)
939 944 self.assertNotIn("abf", matches)
940 945 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
941 946
942 947 # check escaping and whitespace
943 948 ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None}
944 949 _, matches = complete(line_buffer="d['a")
945 950 self.assertIn("a\\nb", matches)
946 951 self.assertIn("a\\'b", matches)
947 952 self.assertIn('a"b', matches)
948 953 self.assertIn("a word", matches)
949 954 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
950 955
951 956 # - can complete on non-initial word of the string
952 957 _, matches = complete(line_buffer="d['a w")
953 958 self.assertIn("word", matches)
954 959
955 960 # - understands quote escaping
956 961 _, matches = complete(line_buffer="d['a\\'")
957 962 self.assertIn("b", matches)
958 963
959 964 # - default quoting should work like repr
960 965 _, matches = complete(line_buffer="d[")
961 966 self.assertIn('"a\'b"', matches)
962 967
963 968 # - when opening quote with ", possible to match with unescaped apostrophe
964 969 _, matches = complete(line_buffer="d[\"a'")
965 970 self.assertIn("b", matches)
966 971
967 972 # need to not split at delims that readline won't split at
968 973 if "-" not in ip.Completer.splitter.delims:
969 974 ip.user_ns["d"] = {"before-after": None}
970 975 _, matches = complete(line_buffer="d['before-af")
971 976 self.assertIn("before-after", matches)
972 977
973 978 # check completion on tuple-of-string keys at different stage - on first key
974 979 ip.user_ns["d"] = {('foo', 'bar'): None}
975 980 _, matches = complete(line_buffer="d[")
976 981 self.assertIn("'foo'", matches)
977 982 self.assertNotIn("'foo']", matches)
978 983 self.assertNotIn("'bar'", matches)
979 984 self.assertNotIn("foo", matches)
980 985 self.assertNotIn("bar", matches)
981 986
982 987 # - match the prefix
983 988 _, matches = complete(line_buffer="d['f")
984 989 self.assertIn("foo", matches)
985 990 self.assertNotIn("foo']", matches)
986 991 self.assertNotIn('foo"]', matches)
987 992 _, matches = complete(line_buffer="d['foo")
988 993 self.assertIn("foo", matches)
989 994
990 995 # - can complete on second key
991 996 _, matches = complete(line_buffer="d['foo', ")
992 997 self.assertIn("'bar'", matches)
993 998 _, matches = complete(line_buffer="d['foo', 'b")
994 999 self.assertIn("bar", matches)
995 1000 self.assertNotIn("foo", matches)
996 1001
997 1002 # - does not propose missing keys
998 1003 _, matches = complete(line_buffer="d['foo', 'f")
999 1004 self.assertNotIn("bar", matches)
1000 1005 self.assertNotIn("foo", matches)
1001 1006
1002 1007 # check sensitivity to following context
1003 1008 _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
1004 1009 self.assertIn("'bar'", matches)
1005 1010 self.assertNotIn("bar", matches)
1006 1011 self.assertNotIn("'foo'", matches)
1007 1012 self.assertNotIn("foo", matches)
1008 1013
1009 1014 _, matches = complete(line_buffer="d['']", cursor_pos=3)
1010 1015 self.assertIn("foo", matches)
1011 1016 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1012 1017
1013 1018 _, matches = complete(line_buffer='d[""]', cursor_pos=3)
1014 1019 self.assertIn("foo", matches)
1015 1020 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1016 1021
1017 1022 _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
1018 1023 self.assertIn("bar", matches)
1019 1024 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1020 1025
1021 1026 _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1022 1027 self.assertIn("'bar'", matches)
1023 1028 self.assertNotIn("bar", matches)
1024 1029
1025 1030 # Can complete with longer tuple keys
1026 1031 ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1027 1032
1028 1033 # - can complete second key
1029 1034 _, matches = complete(line_buffer="d['foo', 'b")
1030 1035 self.assertIn("bar", matches)
1031 1036 self.assertNotIn("foo", matches)
1032 1037 self.assertNotIn("foobar", matches)
1033 1038
1034 1039 # - can complete third key
1035 1040 _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1036 1041 self.assertIn("foobar", matches)
1037 1042 self.assertNotIn("foo", matches)
1038 1043 self.assertNotIn("bar", matches)
1039 1044
1040 1045 def test_dict_key_completion_contexts(self):
1041 1046 """Test expression contexts in which dict key completion occurs"""
1042 1047 ip = get_ipython()
1043 1048 complete = ip.Completer.complete
1044 1049 d = {"abc": None}
1045 1050 ip.user_ns["d"] = d
1046 1051
1047 1052 class C:
1048 1053 data = d
1049 1054
1050 1055 ip.user_ns["C"] = C
1051 1056 ip.user_ns["get"] = lambda: d
1052 1057
1053 1058 def assert_no_completion(**kwargs):
1054 1059 _, matches = complete(**kwargs)
1055 1060 self.assertNotIn("abc", matches)
1056 1061 self.assertNotIn("abc'", matches)
1057 1062 self.assertNotIn("abc']", matches)
1058 1063 self.assertNotIn("'abc'", matches)
1059 1064 self.assertNotIn("'abc']", matches)
1060 1065
1061 1066 def assert_completion(**kwargs):
1062 1067 _, matches = complete(**kwargs)
1063 1068 self.assertIn("'abc'", matches)
1064 1069 self.assertNotIn("'abc']", matches)
1065 1070
1066 1071 # no completion after string closed, even if reopened
1067 1072 assert_no_completion(line_buffer="d['a'")
1068 1073 assert_no_completion(line_buffer='d["a"')
1069 1074 assert_no_completion(line_buffer="d['a' + ")
1070 1075 assert_no_completion(line_buffer="d['a' + '")
1071 1076
1072 1077 # completion in non-trivial expressions
1073 1078 assert_completion(line_buffer="+ d[")
1074 1079 assert_completion(line_buffer="(d[")
1075 1080 assert_completion(line_buffer="C.data[")
1076 1081
1077 1082 # greedy flag
1078 1083 def assert_completion(**kwargs):
1079 1084 _, matches = complete(**kwargs)
1080 1085 self.assertIn("get()['abc']", matches)
1081 1086
1082 1087 assert_no_completion(line_buffer="get()[")
1083 1088 with greedy_completion():
1084 1089 assert_completion(line_buffer="get()[")
1085 1090 assert_completion(line_buffer="get()['")
1086 1091 assert_completion(line_buffer="get()['a")
1087 1092 assert_completion(line_buffer="get()['ab")
1088 1093 assert_completion(line_buffer="get()['abc")
1089 1094
1090 1095 def test_dict_key_completion_bytes(self):
1091 1096 """Test handling of bytes in dict key completion"""
1092 1097 ip = get_ipython()
1093 1098 complete = ip.Completer.complete
1094 1099
1095 1100 ip.user_ns["d"] = {"abc": None, b"abd": None}
1096 1101
1097 1102 _, matches = complete(line_buffer="d[")
1098 1103 self.assertIn("'abc'", matches)
1099 1104 self.assertIn("b'abd'", matches)
1100 1105
1101 1106 if False: # not currently implemented
1102 1107 _, matches = complete(line_buffer="d[b")
1103 1108 self.assertIn("b'abd'", matches)
1104 1109 self.assertNotIn("b'abc'", matches)
1105 1110
1106 1111 _, matches = complete(line_buffer="d[b'")
1107 1112 self.assertIn("abd", matches)
1108 1113 self.assertNotIn("abc", matches)
1109 1114
1110 1115 _, matches = complete(line_buffer="d[B'")
1111 1116 self.assertIn("abd", matches)
1112 1117 self.assertNotIn("abc", matches)
1113 1118
1114 1119 _, matches = complete(line_buffer="d['")
1115 1120 self.assertIn("abc", matches)
1116 1121 self.assertNotIn("abd", matches)
1117 1122
1118 1123 def test_dict_key_completion_unicode_py3(self):
1119 1124 """Test handling of unicode in dict key completion"""
1120 1125 ip = get_ipython()
1121 1126 complete = ip.Completer.complete
1122 1127
1123 1128 ip.user_ns["d"] = {"a\u05d0": None}
1124 1129
1125 1130 # query using escape
1126 1131 if sys.platform != "win32":
1127 1132 # Known failure on Windows
1128 1133 _, matches = complete(line_buffer="d['a\\u05d0")
1129 1134 self.assertIn("u05d0", matches) # tokenized after \\
1130 1135
1131 1136 # query using character
1132 1137 _, matches = complete(line_buffer="d['a\u05d0")
1133 1138 self.assertIn("a\u05d0", matches)
1134 1139
1135 1140 with greedy_completion():
1136 1141 # query using escape
1137 1142 _, matches = complete(line_buffer="d['a\\u05d0")
1138 1143 self.assertIn("d['a\\u05d0']", matches) # tokenized after \\
1139 1144
1140 1145 # query using character
1141 1146 _, matches = complete(line_buffer="d['a\u05d0")
1142 1147 self.assertIn("d['a\u05d0']", matches)
1143 1148
1144 1149 @dec.skip_without("numpy")
1145 1150 def test_struct_array_key_completion(self):
1146 1151 """Test dict key completion applies to numpy struct arrays"""
1147 1152 import numpy
1148 1153
1149 1154 ip = get_ipython()
1150 1155 complete = ip.Completer.complete
1151 1156 ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")])
1152 1157 _, matches = complete(line_buffer="d['")
1153 1158 self.assertIn("hello", matches)
1154 1159 self.assertIn("world", matches)
1155 1160 # complete on the numpy struct itself
1156 1161 dt = numpy.dtype(
1157 1162 [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)]
1158 1163 )
1159 1164 x = numpy.zeros(2, dtype=dt)
1160 1165 ip.user_ns["d"] = x[1]
1161 1166 _, matches = complete(line_buffer="d['")
1162 1167 self.assertIn("my_head", matches)
1163 1168 self.assertIn("my_data", matches)
1164 1169 # complete on a nested level
1165 1170 with greedy_completion():
1166 1171 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1167 1172 _, matches = complete(line_buffer="d[1]['my_head']['")
1168 1173 self.assertTrue(any(["my_dt" in m for m in matches]))
1169 1174 self.assertTrue(any(["my_df" in m for m in matches]))
1170 1175
1171 1176 @dec.skip_without("pandas")
1172 1177 def test_dataframe_key_completion(self):
1173 1178 """Test dict key completion applies to pandas DataFrames"""
1174 1179 import pandas
1175 1180
1176 1181 ip = get_ipython()
1177 1182 complete = ip.Completer.complete
1178 1183 ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]})
1179 1184 _, matches = complete(line_buffer="d['")
1180 1185 self.assertIn("hello", matches)
1181 1186 self.assertIn("world", matches)
1182 1187
1183 1188 def test_dict_key_completion_invalids(self):
1184 1189 """Smoke test cases dict key completion can't handle"""
1185 1190 ip = get_ipython()
1186 1191 complete = ip.Completer.complete
1187 1192
1188 1193 ip.user_ns["no_getitem"] = None
1189 1194 ip.user_ns["no_keys"] = []
1190 1195 ip.user_ns["cant_call_keys"] = dict
1191 1196 ip.user_ns["empty"] = {}
1192 1197 ip.user_ns["d"] = {"abc": 5}
1193 1198
1194 1199 _, matches = complete(line_buffer="no_getitem['")
1195 1200 _, matches = complete(line_buffer="no_keys['")
1196 1201 _, matches = complete(line_buffer="cant_call_keys['")
1197 1202 _, matches = complete(line_buffer="empty['")
1198 1203 _, matches = complete(line_buffer="name_error['")
1199 1204 _, matches = complete(line_buffer="d['\\") # incomplete escape
1200 1205
1201 1206 def test_object_key_completion(self):
1202 1207 ip = get_ipython()
1203 1208 ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"])
1204 1209
1205 1210 _, matches = ip.Completer.complete(line_buffer="key_completable['qw")
1206 1211 self.assertIn("qwerty", matches)
1207 1212 self.assertIn("qwick", matches)
1208 1213
1209 1214 def test_class_key_completion(self):
1210 1215 ip = get_ipython()
1211 1216 NamedInstanceClass("qwerty")
1212 1217 NamedInstanceClass("qwick")
1213 1218 ip.user_ns["named_instance_class"] = NamedInstanceClass
1214 1219
1215 1220 _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw")
1216 1221 self.assertIn("qwerty", matches)
1217 1222 self.assertIn("qwick", matches)
1218 1223
1219 1224 def test_tryimport(self):
1220 1225 """
1221 1226 Test that try-import don't crash on trailing dot, and import modules before
1222 1227 """
1223 1228 from IPython.core.completerlib import try_import
1224 1229
1225 1230 assert try_import("IPython.")
1226 1231
1227 1232 def test_aimport_module_completer(self):
1228 1233 ip = get_ipython()
1229 1234 _, matches = ip.complete("i", "%aimport i")
1230 1235 self.assertIn("io", matches)
1231 1236 self.assertNotIn("int", matches)
1232 1237
1233 1238 def test_nested_import_module_completer(self):
1234 1239 ip = get_ipython()
1235 1240 _, matches = ip.complete(None, "import IPython.co", 17)
1236 1241 self.assertIn("IPython.core", matches)
1237 1242 self.assertNotIn("import IPython.core", matches)
1238 1243 self.assertNotIn("IPython.display", matches)
1239 1244
1240 1245 def test_import_module_completer(self):
1241 1246 ip = get_ipython()
1242 1247 _, matches = ip.complete("i", "import i")
1243 1248 self.assertIn("io", matches)
1244 1249 self.assertNotIn("int", matches)
1245 1250
1246 1251 def test_from_module_completer(self):
1247 1252 ip = get_ipython()
1248 1253 _, matches = ip.complete("B", "from io import B", 16)
1249 1254 self.assertIn("BytesIO", matches)
1250 1255 self.assertNotIn("BaseException", matches)
1251 1256
1252 1257 def test_snake_case_completion(self):
1253 1258 ip = get_ipython()
1254 1259 ip.Completer.use_jedi = False
1255 1260 ip.user_ns["some_three"] = 3
1256 1261 ip.user_ns["some_four"] = 4
1257 1262 _, matches = ip.complete("s_", "print(s_f")
1258 1263 self.assertIn("some_three", matches)
1259 1264 self.assertIn("some_four", matches)
1260 1265
1261 1266 def test_mix_terms(self):
1262 1267 ip = get_ipython()
1263 1268 from textwrap import dedent
1264 1269
1265 1270 ip.Completer.use_jedi = False
1266 1271 ip.ex(
1267 1272 dedent(
1268 1273 """
1269 1274 class Test:
1270 1275 def meth(self, meth_arg1):
1271 1276 print("meth")
1272 1277
1273 1278 def meth_1(self, meth1_arg1, meth1_arg2):
1274 1279 print("meth1")
1275 1280
1276 1281 def meth_2(self, meth2_arg1, meth2_arg2):
1277 1282 print("meth2")
1278 1283 test = Test()
1279 1284 """
1280 1285 )
1281 1286 )
1282 1287 _, matches = ip.complete(None, "test.meth(")
1283 1288 self.assertIn("meth_arg1=", matches)
1284 1289 self.assertNotIn("meth2_arg1=", matches)
@@ -1,636 +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 import pytest
9 10 import sys
10 11
11 12 from IPython.core import inputsplitter as isp
12 13 from IPython.core.inputtransformer import InputTransformer
13 14 from IPython.core.tests.test_inputtransformer import syntax, syntax_ml
14 15 from IPython.testing import tools as tt
15 from IPython.testing.decorators import skipif
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Semi-complete examples (also used as tests)
19 19 #-----------------------------------------------------------------------------
20 20
21 21 # Note: at the bottom, there's a slightly more complete version of this that
22 22 # can be useful during development of code here.
23 23
24 24 def mini_interactive_loop(input_func):
25 25 """Minimal example of the logic of an interactive interpreter loop.
26 26
27 27 This serves as an example, and it is used by the test system with a fake
28 28 raw_input that simulates interactive input."""
29 29
30 30 from IPython.core.inputsplitter import InputSplitter
31 31
32 32 isp = InputSplitter()
33 33 # In practice, this input loop would be wrapped in an outside loop to read
34 34 # input indefinitely, until some exit/quit command was issued. Here we
35 35 # only illustrate the basic inner loop.
36 36 while isp.push_accepts_more():
37 37 indent = ' '*isp.get_indent_spaces()
38 38 prompt = '>>> ' + indent
39 39 line = indent + input_func(prompt)
40 40 isp.push(line)
41 41
42 42 # Here we just return input so we can use it in a test suite, but a real
43 43 # interpreter would instead send it for execution somewhere.
44 44 src = isp.source_reset()
45 45 #print 'Input source was:\n', src # dbg
46 46 return src
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Test utilities, just for local use
50 50 #-----------------------------------------------------------------------------
51 51
52 52
53 53 def pseudo_input(lines):
54 54 """Return a function that acts like raw_input but feeds the input list."""
55 55 ilines = iter(lines)
56 56 def raw_in(prompt):
57 57 try:
58 58 return next(ilines)
59 59 except StopIteration:
60 60 return ''
61 61 return raw_in
62 62
63 63 #-----------------------------------------------------------------------------
64 64 # Tests
65 65 #-----------------------------------------------------------------------------
66 66 def test_spaces():
67 67 tests = [('', 0),
68 68 (' ', 1),
69 69 ('\n', 0),
70 70 (' \n', 1),
71 71 ('x', 0),
72 72 (' x', 1),
73 73 (' x',2),
74 74 (' x',4),
75 75 # Note: tabs are counted as a single whitespace!
76 76 ('\tx', 1),
77 77 ('\t x', 2),
78 78 ]
79 79 tt.check_pairs(isp.num_ini_spaces, tests)
80 80
81 81
82 82 def test_remove_comments():
83 83 tests = [('text', 'text'),
84 84 ('text # comment', 'text '),
85 85 ('text # comment\n', 'text \n'),
86 86 ('text # comment \n', 'text \n'),
87 87 ('line # c \nline\n','line \nline\n'),
88 88 ('line # c \nline#c2 \nline\nline #c\n\n',
89 89 'line \nline\nline\nline \n\n'),
90 90 ]
91 91 tt.check_pairs(isp.remove_comments, tests)
92 92
93 93
94 94 def test_get_input_encoding():
95 95 encoding = isp.get_input_encoding()
96 96 assert isinstance(encoding, str)
97 97 # simple-minded check that at least encoding a simple string works with the
98 98 # encoding we got.
99 99 assert "test".encode(encoding) == b"test"
100 100
101 101
102 102 class NoInputEncodingTestCase(unittest.TestCase):
103 103 def setUp(self):
104 104 self.old_stdin = sys.stdin
105 105 class X: pass
106 106 fake_stdin = X()
107 107 sys.stdin = fake_stdin
108 108
109 109 def test(self):
110 110 # Verify that if sys.stdin has no 'encoding' attribute we do the right
111 111 # thing
112 112 enc = isp.get_input_encoding()
113 113 self.assertEqual(enc, 'ascii')
114 114
115 115 def tearDown(self):
116 116 sys.stdin = self.old_stdin
117 117
118 118
119 119 class InputSplitterTestCase(unittest.TestCase):
120 120 def setUp(self):
121 121 self.isp = isp.InputSplitter()
122 122
123 123 def test_reset(self):
124 124 isp = self.isp
125 125 isp.push('x=1')
126 126 isp.reset()
127 127 self.assertEqual(isp._buffer, [])
128 128 self.assertEqual(isp.get_indent_spaces(), 0)
129 129 self.assertEqual(isp.source, '')
130 130 self.assertEqual(isp.code, None)
131 131 self.assertEqual(isp._is_complete, False)
132 132
133 133 def test_source(self):
134 134 self.isp._store('1')
135 135 self.isp._store('2')
136 136 self.assertEqual(self.isp.source, '1\n2\n')
137 137 self.assertEqual(len(self.isp._buffer)>0, True)
138 138 self.assertEqual(self.isp.source_reset(), '1\n2\n')
139 139 self.assertEqual(self.isp._buffer, [])
140 140 self.assertEqual(self.isp.source, '')
141 141
142 142 def test_indent(self):
143 143 isp = self.isp # shorthand
144 144 isp.push('x=1')
145 145 self.assertEqual(isp.get_indent_spaces(), 0)
146 146 isp.push('if 1:\n x=1')
147 147 self.assertEqual(isp.get_indent_spaces(), 4)
148 148 isp.push('y=2\n')
149 149 self.assertEqual(isp.get_indent_spaces(), 0)
150 150
151 151 def test_indent2(self):
152 152 isp = self.isp
153 153 isp.push('if 1:')
154 154 self.assertEqual(isp.get_indent_spaces(), 4)
155 155 isp.push(' x=1')
156 156 self.assertEqual(isp.get_indent_spaces(), 4)
157 157 # Blank lines shouldn't change the indent level
158 158 isp.push(' '*2)
159 159 self.assertEqual(isp.get_indent_spaces(), 4)
160 160
161 161 def test_indent3(self):
162 162 isp = self.isp
163 163 # When a multiline statement contains parens or multiline strings, we
164 164 # shouldn't get confused.
165 165 isp.push("if 1:")
166 166 isp.push(" x = (1+\n 2)")
167 167 self.assertEqual(isp.get_indent_spaces(), 4)
168 168
169 169 def test_indent4(self):
170 170 isp = self.isp
171 171 # whitespace after ':' should not screw up indent level
172 172 isp.push('if 1: \n x=1')
173 173 self.assertEqual(isp.get_indent_spaces(), 4)
174 174 isp.push('y=2\n')
175 175 self.assertEqual(isp.get_indent_spaces(), 0)
176 176 isp.push('if 1:\t\n x=1')
177 177 self.assertEqual(isp.get_indent_spaces(), 4)
178 178 isp.push('y=2\n')
179 179 self.assertEqual(isp.get_indent_spaces(), 0)
180 180
181 181 def test_dedent_pass(self):
182 182 isp = self.isp # shorthand
183 183 # should NOT cause dedent
184 184 isp.push('if 1:\n passes = 5')
185 185 self.assertEqual(isp.get_indent_spaces(), 4)
186 186 isp.push('if 1:\n pass')
187 187 self.assertEqual(isp.get_indent_spaces(), 0)
188 188 isp.push('if 1:\n pass ')
189 189 self.assertEqual(isp.get_indent_spaces(), 0)
190 190
191 191 def test_dedent_break(self):
192 192 isp = self.isp # shorthand
193 193 # should NOT cause dedent
194 194 isp.push('while 1:\n breaks = 5')
195 195 self.assertEqual(isp.get_indent_spaces(), 4)
196 196 isp.push('while 1:\n break')
197 197 self.assertEqual(isp.get_indent_spaces(), 0)
198 198 isp.push('while 1:\n break ')
199 199 self.assertEqual(isp.get_indent_spaces(), 0)
200 200
201 201 def test_dedent_continue(self):
202 202 isp = self.isp # shorthand
203 203 # should NOT cause dedent
204 204 isp.push('while 1:\n continues = 5')
205 205 self.assertEqual(isp.get_indent_spaces(), 4)
206 206 isp.push('while 1:\n continue')
207 207 self.assertEqual(isp.get_indent_spaces(), 0)
208 208 isp.push('while 1:\n continue ')
209 209 self.assertEqual(isp.get_indent_spaces(), 0)
210 210
211 211 def test_dedent_raise(self):
212 212 isp = self.isp # shorthand
213 213 # should NOT cause dedent
214 214 isp.push('if 1:\n raised = 4')
215 215 self.assertEqual(isp.get_indent_spaces(), 4)
216 216 isp.push('if 1:\n raise TypeError()')
217 217 self.assertEqual(isp.get_indent_spaces(), 0)
218 218 isp.push('if 1:\n raise')
219 219 self.assertEqual(isp.get_indent_spaces(), 0)
220 220 isp.push('if 1:\n raise ')
221 221 self.assertEqual(isp.get_indent_spaces(), 0)
222 222
223 223 def test_dedent_return(self):
224 224 isp = self.isp # shorthand
225 225 # should NOT cause dedent
226 226 isp.push('if 1:\n returning = 4')
227 227 self.assertEqual(isp.get_indent_spaces(), 4)
228 228 isp.push('if 1:\n return 5 + 493')
229 229 self.assertEqual(isp.get_indent_spaces(), 0)
230 230 isp.push('if 1:\n return')
231 231 self.assertEqual(isp.get_indent_spaces(), 0)
232 232 isp.push('if 1:\n return ')
233 233 self.assertEqual(isp.get_indent_spaces(), 0)
234 234 isp.push('if 1:\n return(0)')
235 235 self.assertEqual(isp.get_indent_spaces(), 0)
236 236
237 237 def test_push(self):
238 238 isp = self.isp
239 239 self.assertEqual(isp.push('x=1'), True)
240 240
241 241 def test_push2(self):
242 242 isp = self.isp
243 243 self.assertEqual(isp.push('if 1:'), False)
244 244 for line in [' x=1', '# a comment', ' y=2']:
245 245 print(line)
246 246 self.assertEqual(isp.push(line), True)
247 247
248 248 def test_push3(self):
249 249 isp = self.isp
250 250 isp.push('if True:')
251 251 isp.push(' a = 1')
252 252 self.assertEqual(isp.push('b = [1,'), False)
253 253
254 254 def test_push_accepts_more(self):
255 255 isp = self.isp
256 256 isp.push('x=1')
257 257 self.assertEqual(isp.push_accepts_more(), False)
258 258
259 259 def test_push_accepts_more2(self):
260 260 isp = self.isp
261 261 isp.push('if 1:')
262 262 self.assertEqual(isp.push_accepts_more(), True)
263 263 isp.push(' x=1')
264 264 self.assertEqual(isp.push_accepts_more(), True)
265 265 isp.push('')
266 266 self.assertEqual(isp.push_accepts_more(), False)
267 267
268 268 def test_push_accepts_more3(self):
269 269 isp = self.isp
270 270 isp.push("x = (2+\n3)")
271 271 self.assertEqual(isp.push_accepts_more(), False)
272 272
273 273 def test_push_accepts_more4(self):
274 274 isp = self.isp
275 275 # When a multiline statement contains parens or multiline strings, we
276 276 # shouldn't get confused.
277 277 # FIXME: we should be able to better handle de-dents in statements like
278 278 # multiline strings and multiline expressions (continued with \ or
279 279 # parens). Right now we aren't handling the indentation tracking quite
280 280 # correctly with this, though in practice it may not be too much of a
281 281 # problem. We'll need to see.
282 282 isp.push("if 1:")
283 283 isp.push(" x = (2+")
284 284 isp.push(" 3)")
285 285 self.assertEqual(isp.push_accepts_more(), True)
286 286 isp.push(" y = 3")
287 287 self.assertEqual(isp.push_accepts_more(), True)
288 288 isp.push('')
289 289 self.assertEqual(isp.push_accepts_more(), False)
290 290
291 291 def test_push_accepts_more5(self):
292 292 isp = self.isp
293 293 isp.push('try:')
294 294 isp.push(' a = 5')
295 295 isp.push('except:')
296 296 isp.push(' raise')
297 297 # We want to be able to add an else: block at this point, so it should
298 298 # wait for a blank line.
299 299 self.assertEqual(isp.push_accepts_more(), True)
300 300
301 301 def test_continuation(self):
302 302 isp = self.isp
303 303 isp.push("import os, \\")
304 304 self.assertEqual(isp.push_accepts_more(), True)
305 305 isp.push("sys")
306 306 self.assertEqual(isp.push_accepts_more(), False)
307 307
308 308 def test_syntax_error(self):
309 309 isp = self.isp
310 310 # Syntax errors immediately produce a 'ready' block, so the invalid
311 311 # Python can be sent to the kernel for evaluation with possible ipython
312 312 # special-syntax conversion.
313 313 isp.push('run foo')
314 314 self.assertEqual(isp.push_accepts_more(), False)
315 315
316 316 def test_unicode(self):
317 317 self.isp.push(u"Pérez")
318 318 self.isp.push(u'\xc3\xa9')
319 319 self.isp.push(u"u'\xc3\xa9'")
320 320
321 @skipif(sys.version_info[:3] == (3, 9, 8))
321 @pytest.mark.xfail(
322 reason="Bug in python 3.9.8 – bpo 45738",
323 condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
324 raises=SystemError,
325 strict=True,
326 )
322 327 def test_line_continuation(self):
323 328 """ Test issue #2108."""
324 329 isp = self.isp
325 330 # A blank line after a line continuation should not accept more
326 331 isp.push("1 \\\n\n")
327 332 self.assertEqual(isp.push_accepts_more(), False)
328 333 # Whitespace after a \ is a SyntaxError. The only way to test that
329 334 # here is to test that push doesn't accept more (as with
330 335 # test_syntax_error() above).
331 336 isp.push(r"1 \ ")
332 337 self.assertEqual(isp.push_accepts_more(), False)
333 338 # Even if the line is continuable (c.f. the regular Python
334 339 # interpreter)
335 340 isp.push(r"(1 \ ")
336 341 self.assertEqual(isp.push_accepts_more(), False)
337 342
338 343 def test_check_complete(self):
339 344 isp = self.isp
340 345 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
341 346 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
342 347 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
343 348 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
344 349 self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None))
345 350
346 351 class InteractiveLoopTestCase(unittest.TestCase):
347 352 """Tests for an interactive loop like a python shell.
348 353 """
349 354 def check_ns(self, lines, ns):
350 355 """Validate that the given input lines produce the resulting namespace.
351 356
352 357 Note: the input lines are given exactly as they would be typed in an
353 358 auto-indenting environment, as mini_interactive_loop above already does
354 359 auto-indenting and prepends spaces to the input.
355 360 """
356 361 src = mini_interactive_loop(pseudo_input(lines))
357 362 test_ns = {}
358 363 exec(src, test_ns)
359 364 # We can't check that the provided ns is identical to the test_ns,
360 365 # because Python fills test_ns with extra keys (copyright, etc). But
361 366 # we can check that the given dict is *contained* in test_ns
362 367 for k,v in ns.items():
363 368 self.assertEqual(test_ns[k], v)
364 369
365 370 def test_simple(self):
366 371 self.check_ns(['x=1'], dict(x=1))
367 372
368 373 def test_simple2(self):
369 374 self.check_ns(['if 1:', 'x=2'], dict(x=2))
370 375
371 376 def test_xy(self):
372 377 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
373 378
374 379 def test_abc(self):
375 380 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
376 381
377 382 def test_multi(self):
378 383 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
379 384
380 385
381 386 class IPythonInputTestCase(InputSplitterTestCase):
382 387 """By just creating a new class whose .isp is a different instance, we
383 388 re-run the same test battery on the new input splitter.
384 389
385 390 In addition, this runs the tests over the syntax and syntax_ml dicts that
386 391 were tested by individual functions, as part of the OO interface.
387 392
388 393 It also makes some checks on the raw buffer storage.
389 394 """
390 395
391 396 def setUp(self):
392 397 self.isp = isp.IPythonInputSplitter()
393 398
394 399 def test_syntax(self):
395 400 """Call all single-line syntax tests from the main object"""
396 401 isp = self.isp
397 402 for example in syntax.values():
398 403 for raw, out_t in example:
399 404 if raw.startswith(' '):
400 405 continue
401 406
402 407 isp.push(raw+'\n')
403 408 out_raw = isp.source_raw
404 409 out = isp.source_reset()
405 410 self.assertEqual(out.rstrip(), out_t,
406 411 tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
407 412 self.assertEqual(out_raw.rstrip(), raw.rstrip())
408 413
409 414 def test_syntax_multiline(self):
410 415 isp = self.isp
411 416 for example in syntax_ml.values():
412 417 for line_pairs in example:
413 418 out_t_parts = []
414 419 raw_parts = []
415 420 for lraw, out_t_part in line_pairs:
416 421 if out_t_part is not None:
417 422 out_t_parts.append(out_t_part)
418 423
419 424 if lraw is not None:
420 425 isp.push(lraw)
421 426 raw_parts.append(lraw)
422 427
423 428 out_raw = isp.source_raw
424 429 out = isp.source_reset()
425 430 out_t = '\n'.join(out_t_parts).rstrip()
426 431 raw = '\n'.join(raw_parts).rstrip()
427 432 self.assertEqual(out.rstrip(), out_t)
428 433 self.assertEqual(out_raw.rstrip(), raw)
429 434
430 435 def test_syntax_multiline_cell(self):
431 436 isp = self.isp
432 437 for example in syntax_ml.values():
433 438
434 439 out_t_parts = []
435 440 for line_pairs in example:
436 441 raw = '\n'.join(r for r, _ in line_pairs if r is not None)
437 442 out_t = '\n'.join(t for _,t in line_pairs if t is not None)
438 443 out = isp.transform_cell(raw)
439 444 # Match ignoring trailing whitespace
440 445 self.assertEqual(out.rstrip(), out_t.rstrip())
441 446
442 447 def test_cellmagic_preempt(self):
443 448 isp = self.isp
444 449 for raw, name, line, cell in [
445 450 ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
446 451 ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
447 452 (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
448 453 ("%%cellm \n>>> hi", u'cellm', u'', u'>>> hi'),
449 454 ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
450 455 ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
451 456 ]:
452 457 expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
453 458 name, line, cell
454 459 )
455 460 out = isp.transform_cell(raw)
456 461 self.assertEqual(out.rstrip(), expected.rstrip())
457 462
458 463 def test_multiline_passthrough(self):
459 464 isp = self.isp
460 465 class CommentTransformer(InputTransformer):
461 466 def __init__(self):
462 467 self._lines = []
463 468
464 469 def push(self, line):
465 470 self._lines.append(line + '#')
466 471
467 472 def reset(self):
468 473 text = '\n'.join(self._lines)
469 474 self._lines = []
470 475 return text
471 476
472 477 isp.physical_line_transforms.insert(0, CommentTransformer())
473 478
474 479 for raw, expected in [
475 480 ("a=5", "a=5#"),
476 481 ("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')),
477 482 ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().run_line_magic(%r, %r)" % (
478 483 u'ls foo#', u'ls', u'bar#'
479 484 )),
480 485 ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().run_line_magic(%r, %r)\n4#\n5#" % (u'ls', u'foo#')),
481 486 ]:
482 487 out = isp.transform_cell(raw)
483 488 self.assertEqual(out.rstrip(), expected.rstrip())
484 489
485 490 #-----------------------------------------------------------------------------
486 491 # Main - use as a script, mostly for developer experiments
487 492 #-----------------------------------------------------------------------------
488 493
489 494 if __name__ == '__main__':
490 495 # A simple demo for interactive experimentation. This code will not get
491 496 # picked up by any test suite.
492 497 from IPython.core.inputsplitter import IPythonInputSplitter
493 498
494 499 # configure here the syntax to use, prompt and whether to autoindent
495 500 #isp, start_prompt = InputSplitter(), '>>> '
496 501 isp, start_prompt = IPythonInputSplitter(), 'In> '
497 502
498 503 autoindent = True
499 504 #autoindent = False
500 505
501 506 try:
502 507 while True:
503 508 prompt = start_prompt
504 509 while isp.push_accepts_more():
505 510 indent = ' '*isp.get_indent_spaces()
506 511 if autoindent:
507 512 line = indent + input(prompt+indent)
508 513 else:
509 514 line = input(prompt)
510 515 isp.push(line)
511 516 prompt = '... '
512 517
513 518 # Here we just return input so we can use it in a test suite, but a
514 519 # real interpreter would instead send it for execution somewhere.
515 520 #src = isp.source; raise EOFError # dbg
516 521 raw = isp.source_raw
517 522 src = isp.source_reset()
518 523 print('Input source was:\n', src)
519 524 print('Raw source was:\n', raw)
520 525 except EOFError:
521 526 print('Bye')
522 527
523 528 # Tests for cell magics support
524 529
525 530 def test_last_blank():
526 531 assert isp.last_blank("") is False
527 532 assert isp.last_blank("abc") is False
528 533 assert isp.last_blank("abc\n") is False
529 534 assert isp.last_blank("abc\na") is False
530 535
531 536 assert isp.last_blank("\n") is True
532 537 assert isp.last_blank("\n ") is True
533 538 assert isp.last_blank("abc\n ") is True
534 539 assert isp.last_blank("abc\n\n") is True
535 540 assert isp.last_blank("abc\nd\n\n") is True
536 541 assert isp.last_blank("abc\nd\ne\n\n") is True
537 542 assert isp.last_blank("abc \n \n \n\n") is True
538 543
539 544
540 545 def test_last_two_blanks():
541 546 assert isp.last_two_blanks("") is False
542 547 assert isp.last_two_blanks("abc") is False
543 548 assert isp.last_two_blanks("abc\n") is False
544 549 assert isp.last_two_blanks("abc\n\na") is False
545 550 assert isp.last_two_blanks("abc\n \n") is False
546 551 assert isp.last_two_blanks("abc\n\n") is False
547 552
548 553 assert isp.last_two_blanks("\n\n") is True
549 554 assert isp.last_two_blanks("\n\n ") is True
550 555 assert isp.last_two_blanks("\n \n") is True
551 556 assert isp.last_two_blanks("abc\n\n ") is True
552 557 assert isp.last_two_blanks("abc\n\n\n") is True
553 558 assert isp.last_two_blanks("abc\n\n \n") is True
554 559 assert isp.last_two_blanks("abc\n\n \n ") is True
555 560 assert isp.last_two_blanks("abc\n\n \n \n") is True
556 561 assert isp.last_two_blanks("abc\nd\n\n\n") is True
557 562 assert isp.last_two_blanks("abc\nd\ne\nf\n\n\n") is True
558 563
559 564
560 565 class CellMagicsCommon(object):
561 566
562 567 def test_whole_cell(self):
563 568 src = "%%cellm line\nbody\n"
564 569 out = self.sp.transform_cell(src)
565 570 ref = "get_ipython().run_cell_magic('cellm', 'line', 'body')\n"
566 571 assert out == ref
567 572
568 573 def test_cellmagic_help(self):
569 574 self.sp.push('%%cellm?')
570 575 assert self.sp.push_accepts_more() is False
571 576
572 577 def tearDown(self):
573 578 self.sp.reset()
574 579
575 580
576 581 class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
577 582 sp = isp.IPythonInputSplitter(line_input_checker=False)
578 583
579 584 def test_incremental(self):
580 585 sp = self.sp
581 586 sp.push("%%cellm firstline\n")
582 587 assert sp.push_accepts_more() is True # 1
583 588 sp.push("line2\n")
584 589 assert sp.push_accepts_more() is True # 2
585 590 sp.push("\n")
586 591 # This should accept a blank line and carry on until the cell is reset
587 592 assert sp.push_accepts_more() is True # 3
588 593
589 594 def test_no_strip_coding(self):
590 595 src = '\n'.join([
591 596 '%%writefile foo.py',
592 597 '# coding: utf-8',
593 598 'print(u"üñîçø∂é")',
594 599 ])
595 600 out = self.sp.transform_cell(src)
596 601 assert "# coding: utf-8" in out
597 602
598 603
599 604 class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
600 605 sp = isp.IPythonInputSplitter(line_input_checker=True)
601 606
602 607 def test_incremental(self):
603 608 sp = self.sp
604 609 sp.push("%%cellm line2\n")
605 610 assert sp.push_accepts_more() is True # 1
606 611 sp.push("\n")
607 612 # In this case, a blank line should end the cell magic
608 613 assert sp.push_accepts_more() is False # 2
609 614
610 615
611 616 indentation_samples = [
612 617 ('a = 1', 0),
613 618 ('for a in b:', 4),
614 619 ('def f():', 4),
615 620 ('def f(): #comment', 4),
616 621 ('a = ":#not a comment"', 0),
617 622 ('def f():\n a = 1', 4),
618 623 ('def f():\n return 1', 0),
619 624 ('for a in b:\n'
620 625 ' if a < 0:'
621 626 ' continue', 3),
622 627 ('a = {', 4),
623 628 ('a = {\n'
624 629 ' 1,', 5),
625 630 ('b = """123', 0),
626 631 ('', 0),
627 632 ('def f():\n pass', 0),
628 633 ('class Bar:\n def f():\n pass', 4),
629 634 ('class Bar:\n def f():\n raise', 4),
630 635 ]
631 636
632 637 def test_find_next_indent():
633 638 for code, exp in indentation_samples:
634 639 res = isp.find_next_indent(code)
635 640 msg = "{!r} != {!r} (expected)\n Code: {!r}".format(res, exp, code)
636 641 assert res == exp, msg
@@ -1,388 +1,391 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 8 import sys
9 9 from textwrap import dedent
10 10
11 11 import pytest
12 12
13 13 from IPython.core import inputtransformer2 as ipt2
14 14 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line
15 15 from IPython.testing.decorators import skip_iptest_but_not_pytest
16 16
17 17 MULTILINE_MAGIC = ("""\
18 18 a = f()
19 19 %foo \\
20 20 bar
21 21 g()
22 22 """.splitlines(keepends=True), (2, 0), """\
23 23 a = f()
24 24 get_ipython().run_line_magic('foo', ' bar')
25 25 g()
26 26 """.splitlines(keepends=True))
27 27
28 28 INDENTED_MAGIC = ("""\
29 29 for a in range(5):
30 30 %ls
31 31 """.splitlines(keepends=True), (2, 4), """\
32 32 for a in range(5):
33 33 get_ipython().run_line_magic('ls', '')
34 34 """.splitlines(keepends=True))
35 35
36 36 CRLF_MAGIC = ([
37 37 "a = f()\n",
38 38 "%ls\r\n",
39 39 "g()\n"
40 40 ], (2, 0), [
41 41 "a = f()\n",
42 42 "get_ipython().run_line_magic('ls', '')\n",
43 43 "g()\n"
44 44 ])
45 45
46 46 MULTILINE_MAGIC_ASSIGN = ("""\
47 47 a = f()
48 48 b = %foo \\
49 49 bar
50 50 g()
51 51 """.splitlines(keepends=True), (2, 4), """\
52 52 a = f()
53 53 b = get_ipython().run_line_magic('foo', ' bar')
54 54 g()
55 55 """.splitlines(keepends=True))
56 56
57 57 MULTILINE_SYSTEM_ASSIGN = ("""\
58 58 a = f()
59 59 b = !foo \\
60 60 bar
61 61 g()
62 62 """.splitlines(keepends=True), (2, 4), """\
63 63 a = f()
64 64 b = get_ipython().getoutput('foo bar')
65 65 g()
66 66 """.splitlines(keepends=True))
67 67
68 68 #####
69 69
70 70 MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\
71 71 def test():
72 72 for i in range(1):
73 73 print(i)
74 74 res =! ls
75 75 """.splitlines(keepends=True), (4, 7), '''\
76 76 def test():
77 77 for i in range(1):
78 78 print(i)
79 79 res =get_ipython().getoutput(\' ls\')
80 80 '''.splitlines(keepends=True))
81 81
82 82 ######
83 83
84 84 AUTOCALL_QUOTE = (
85 85 [",f 1 2 3\n"], (1, 0),
86 86 ['f("1", "2", "3")\n']
87 87 )
88 88
89 89 AUTOCALL_QUOTE2 = (
90 90 [";f 1 2 3\n"], (1, 0),
91 91 ['f("1 2 3")\n']
92 92 )
93 93
94 94 AUTOCALL_PAREN = (
95 95 ["/f 1 2 3\n"], (1, 0),
96 96 ['f(1, 2, 3)\n']
97 97 )
98 98
99 99 SIMPLE_HELP = (
100 100 ["foo?\n"], (1, 0),
101 101 ["get_ipython().run_line_magic('pinfo', 'foo')\n"]
102 102 )
103 103
104 104 DETAILED_HELP = (
105 105 ["foo??\n"], (1, 0),
106 106 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"]
107 107 )
108 108
109 109 MAGIC_HELP = (
110 110 ["%foo?\n"], (1, 0),
111 111 ["get_ipython().run_line_magic('pinfo', '%foo')\n"]
112 112 )
113 113
114 114 HELP_IN_EXPR = (
115 115 ["a = b + c?\n"], (1, 0),
116 116 ["get_ipython().set_next_input('a = b + c');"
117 117 "get_ipython().run_line_magic('pinfo', 'c')\n"]
118 118 )
119 119
120 120 HELP_CONTINUED_LINE = ("""\
121 121 a = \\
122 122 zip?
123 123 """.splitlines(keepends=True), (1, 0),
124 124 [r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
125 125 )
126 126
127 127 HELP_MULTILINE = ("""\
128 128 (a,
129 129 b) = zip?
130 130 """.splitlines(keepends=True), (1, 0),
131 131 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
132 132 )
133 133
134 134 HELP_UNICODE = (
135 135 ["π.foo?\n"], (1, 0),
136 136 ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"]
137 137 )
138 138
139 139
140 140 def null_cleanup_transformer(lines):
141 141 """
142 142 A cleanup transform that returns an empty list.
143 143 """
144 144 return []
145 145
146 146
147 147 def test_check_make_token_by_line_never_ends_empty():
148 148 """
149 149 Check that not sequence of single or double characters ends up leading to en empty list of tokens
150 150 """
151 151 from string import printable
152 152 for c in printable:
153 153 assert make_tokens_by_line(c)[-1] != []
154 154 for k in printable:
155 155 assert make_tokens_by_line(c + k)[-1] != []
156 156
157 157
158 158 def check_find(transformer, case, match=True):
159 159 sample, expected_start, _ = case
160 160 tbl = make_tokens_by_line(sample)
161 161 res = transformer.find(tbl)
162 162 if match:
163 163 # start_line is stored 0-indexed, expected values are 1-indexed
164 164 assert (res.start_line + 1, res.start_col) == expected_start
165 165 return res
166 166 else:
167 167 assert res is None
168 168
169 169 def check_transform(transformer_cls, case):
170 170 lines, start, expected = case
171 171 transformer = transformer_cls(start)
172 172 assert transformer.transform(lines) == expected
173 173
174 174 def test_continued_line():
175 175 lines = MULTILINE_MAGIC_ASSIGN[0]
176 176 assert ipt2.find_end_of_continued_line(lines, 1) == 2
177 177
178 178 assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar"
179 179
180 180 def test_find_assign_magic():
181 181 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
182 182 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
183 183 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
184 184
185 185 def test_transform_assign_magic():
186 186 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
187 187
188 188 def test_find_assign_system():
189 189 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
190 190 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
191 191 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
192 192 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
193 193 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
194 194
195 195 def test_transform_assign_system():
196 196 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
197 197 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
198 198
199 199 def test_find_magic_escape():
200 200 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
201 201 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
202 202 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
203 203
204 204 def test_transform_magic_escape():
205 205 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
206 206 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
207 207 check_transform(ipt2.EscapedCommand, CRLF_MAGIC)
208 208
209 209 def test_find_autocalls():
210 210 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
211 211 print("Testing %r" % case[0])
212 212 check_find(ipt2.EscapedCommand, case)
213 213
214 214 def test_transform_autocall():
215 215 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
216 216 print("Testing %r" % case[0])
217 217 check_transform(ipt2.EscapedCommand, case)
218 218
219 219 def test_find_help():
220 220 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
221 221 check_find(ipt2.HelpEnd, case)
222 222
223 223 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
224 224 assert tf.q_line == 1
225 225 assert tf.q_col == 3
226 226
227 227 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
228 228 assert tf.q_line == 1
229 229 assert tf.q_col == 8
230 230
231 231 # ? in a comment does not trigger help
232 232 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
233 233 # Nor in a string
234 234 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
235 235
236 236 def test_transform_help():
237 237 tf = ipt2.HelpEnd((1, 0), (1, 9))
238 238 assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2]
239 239
240 240 tf = ipt2.HelpEnd((1, 0), (2, 3))
241 241 assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2]
242 242
243 243 tf = ipt2.HelpEnd((1, 0), (2, 8))
244 244 assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2]
245 245
246 246 tf = ipt2.HelpEnd((1, 0), (1, 0))
247 247 assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2]
248 248
249 249 def test_find_assign_op_dedent():
250 250 """
251 251 be careful that empty token like dedent are not counted as parens
252 252 """
253 253 class Tk:
254 254 def __init__(self, s):
255 255 self.string = s
256 256
257 257 assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2
258 258 assert (
259 259 _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6
260 260 )
261 261
262 262
263 263 examples = [
264 264 pytest.param("a = 1", "complete", None),
265 265 pytest.param("for a in range(5):", "incomplete", 4),
266 266 pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8),
267 267 pytest.param("raise = 2", "invalid", None),
268 268 pytest.param("a = [1,\n2,", "incomplete", 0),
269 269 pytest.param("(\n))", "incomplete", 0),
270 270 pytest.param("\\\r\n", "incomplete", 0),
271 271 pytest.param("a = '''\n hi", "incomplete", 3),
272 272 pytest.param("def a():\n x=1\n global x", "invalid", None),
273 273 pytest.param(
274 274 "a \\ ",
275 275 "invalid",
276 276 None,
277 277 marks=pytest.mark.xfail(
278 278 reason="Bug in python 3.9.8 – bpo 45738",
279 condition=sys.version_info[:3] == (3, 9, 8),
279 condition=sys.version_info
280 in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
280 281 raises=SystemError,
281 282 strict=True,
282 283 ),
283 284 ), # Nothing allowed after backslash,
284 285 pytest.param("1\\\n+2", "complete", None),
285 286 ]
286 287
287 288
288 289 @skip_iptest_but_not_pytest
289 290 @pytest.mark.parametrize("code, expected, number", examples)
290 291 def test_check_complete_param(code, expected, number):
291 292 cc = ipt2.TransformerManager().check_complete
292 293 assert cc(code) == (expected, number)
293 294
294 295
295 296 @skip_iptest_but_not_pytest
296 297 @pytest.mark.xfail(
297 298 reason="Bug in python 3.9.8 – bpo 45738",
298 condition=sys.version_info[:3] == (3, 9, 8),
299 condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
300 raises=SystemError,
301 strict=True,
299 302 )
300 303 def test_check_complete():
301 304 cc = ipt2.TransformerManager().check_complete
302 305
303 306 example = dedent("""
304 307 if True:
305 308 a=1""" )
306 309
307 310 assert cc(example) == ("incomplete", 4)
308 311 assert cc(example + "\n") == ("complete", None)
309 312 assert cc(example + "\n ") == ("complete", None)
310 313
311 314 # no need to loop on all the letters/numbers.
312 315 short = '12abAB'+string.printable[62:]
313 316 for c in short:
314 317 # test does not raise:
315 318 cc(c)
316 319 for k in short:
317 320 cc(c+k)
318 321
319 322 assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2)
320 323
321 324
322 325 def test_check_complete_II():
323 326 """
324 327 Test that multiple line strings are properly handled.
325 328
326 329 Separate test function for convenience
327 330
328 331 """
329 332 cc = ipt2.TransformerManager().check_complete
330 333 assert cc('''def foo():\n """''') == ("incomplete", 4)
331 334
332 335
333 336 def test_check_complete_invalidates_sunken_brackets():
334 337 """
335 338 Test that a single line with more closing brackets than the opening ones is
336 339 interpreted as invalid
337 340 """
338 341 cc = ipt2.TransformerManager().check_complete
339 342 assert cc(")") == ("invalid", None)
340 343 assert cc("]") == ("invalid", None)
341 344 assert cc("}") == ("invalid", None)
342 345 assert cc(")(") == ("invalid", None)
343 346 assert cc("][") == ("invalid", None)
344 347 assert cc("}{") == ("invalid", None)
345 348 assert cc("]()(") == ("invalid", None)
346 349 assert cc("())(") == ("invalid", None)
347 350 assert cc(")[](") == ("invalid", None)
348 351 assert cc("()](") == ("invalid", None)
349 352
350 353
351 354 def test_null_cleanup_transformer():
352 355 manager = ipt2.TransformerManager()
353 356 manager.cleanup_transforms.insert(0, null_cleanup_transformer)
354 357 assert manager.transform_cell("") == ""
355 358
356 359
357 360
358 361
359 362 def test_side_effects_I():
360 363 count = 0
361 364 def counter(lines):
362 365 nonlocal count
363 366 count += 1
364 367 return lines
365 368
366 369 counter.has_side_effects = True
367 370
368 371 manager = ipt2.TransformerManager()
369 372 manager.cleanup_transforms.insert(0, counter)
370 373 assert manager.check_complete("a=1\n") == ('complete', None)
371 374 assert count == 0
372 375
373 376
374 377
375 378
376 379 def test_side_effects_II():
377 380 count = 0
378 381 def counter(lines):
379 382 nonlocal count
380 383 count += 1
381 384 return lines
382 385
383 386 counter.has_side_effects = True
384 387
385 388 manager = ipt2.TransformerManager()
386 389 manager.line_transforms.insert(0, counter)
387 390 assert manager.check_complete("b=1\n") == ('complete', None)
388 391 assert count == 0
@@ -1,454 +1,453 b''
1 1 """Tests for the object inspection functionality.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from inspect import signature, Signature, Parameter
9 9 import os
10 10 import re
11 11
12 12 from .. import oinspect
13 13
14 14 from decorator import decorator
15 15
16 16 from IPython.testing.tools import AssertPrints, AssertNotPrints
17 17 from IPython.utils.path import compress_user
18 18
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Globals and constants
22 22 #-----------------------------------------------------------------------------
23 23
24 24 inspector = None
25 25
26 26 def setup_module():
27 27 global inspector
28 28 inspector = oinspect.Inspector()
29 29
30 30
31 31 #-----------------------------------------------------------------------------
32 32 # Local utilities
33 33 #-----------------------------------------------------------------------------
34 34
35 35 # WARNING: since this test checks the line number where a function is
36 36 # defined, if any code is inserted above, the following line will need to be
37 37 # updated. Do NOT insert any whitespace between the next line and the function
38 38 # definition below.
39 39 THIS_LINE_NUMBER = 39 # Put here the actual number of this line
40 40
41 41 from unittest import TestCase
42 42
43 43 class Test(TestCase):
44 44
45 45 def test_find_source_lines(self):
46 46 self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines),
47 47 THIS_LINE_NUMBER+6)
48 48
49 49
50 50 # A couple of utilities to ensure these tests work the same from a source or a
51 51 # binary install
52 52 def pyfile(fname):
53 53 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
54 54
55 55
56 56 def match_pyfiles(f1, f2):
57 57 assert pyfile(f1) == pyfile(f2)
58 58
59 59
60 60 def test_find_file():
61 61 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
62 62
63 63
64 64 def test_find_file_decorated1():
65 65
66 66 @decorator
67 67 def noop1(f):
68 68 def wrapper(*a, **kw):
69 69 return f(*a, **kw)
70 70 return wrapper
71 71
72 72 @noop1
73 73 def f(x):
74 74 "My docstring"
75 75
76 76 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
77 77 assert f.__doc__ == "My docstring"
78 78
79 79
80 80 def test_find_file_decorated2():
81 81
82 82 @decorator
83 83 def noop2(f, *a, **kw):
84 84 return f(*a, **kw)
85 85
86 86 @noop2
87 87 @noop2
88 88 @noop2
89 89 def f(x):
90 90 "My docstring 2"
91 91
92 92 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
93 93 assert f.__doc__ == "My docstring 2"
94 94
95 95
96 96 def test_find_file_magic():
97 97 run = ip.find_line_magic('run')
98 98 assert oinspect.find_file(run) is not None
99 99
100 100
101 101 # A few generic objects we can then inspect in the tests below
102 102
103 103 class Call(object):
104 104 """This is the class docstring."""
105 105
106 106 def __init__(self, x, y=1):
107 107 """This is the constructor docstring."""
108 108
109 109 def __call__(self, *a, **kw):
110 110 """This is the call docstring."""
111 111
112 112 def method(self, x, z=2):
113 113 """Some method's docstring"""
114 114
115 115 class HasSignature(object):
116 116 """This is the class docstring."""
117 117 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
118 118
119 119 def __init__(self, *args):
120 120 """This is the init docstring"""
121 121
122 122
123 123 class SimpleClass(object):
124 124 def method(self, x, z=2):
125 125 """Some method's docstring"""
126 126
127 127
128 128 class Awkward(object):
129 129 def __getattr__(self, name):
130 130 raise Exception(name)
131 131
132 132 class NoBoolCall:
133 133 """
134 134 callable with `__bool__` raising should still be inspect-able.
135 135 """
136 136
137 137 def __call__(self):
138 138 """does nothing"""
139 139 pass
140 140
141 141 def __bool__(self):
142 142 """just raise NotImplemented"""
143 143 raise NotImplementedError('Must be implemented')
144 144
145 145
146 146 class SerialLiar(object):
147 147 """Attribute accesses always get another copy of the same class.
148 148
149 149 unittest.mock.call does something similar, but it's not ideal for testing
150 150 as the failure mode is to eat all your RAM. This gives up after 10k levels.
151 151 """
152 152 def __init__(self, max_fibbing_twig, lies_told=0):
153 153 if lies_told > 10000:
154 154 raise RuntimeError('Nose too long, honesty is the best policy')
155 155 self.max_fibbing_twig = max_fibbing_twig
156 156 self.lies_told = lies_told
157 157 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
158 158
159 159 def __getattr__(self, item):
160 160 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
161 161
162 162 #-----------------------------------------------------------------------------
163 163 # Tests
164 164 #-----------------------------------------------------------------------------
165 165
166 166 def test_info():
167 167 "Check that Inspector.info fills out various fields as expected."
168 168 i = inspector.info(Call, oname="Call")
169 169 assert i["type_name"] == "type"
170 170 expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
171 171 assert i["base_class"] == expected_class
172 172 assert re.search(
173 173 "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>",
174 174 i["string_form"],
175 175 )
176 176 fname = __file__
177 177 if fname.endswith(".pyc"):
178 178 fname = fname[:-1]
179 179 # case-insensitive comparison needed on some filesystems
180 180 # e.g. Windows:
181 181 assert i["file"].lower() == compress_user(fname).lower()
182 182 assert i["definition"] == None
183 183 assert i["docstring"] == Call.__doc__
184 184 assert i["source"] == None
185 185 assert i["isclass"] is True
186 186 assert i["init_definition"] == "Call(x, y=1)"
187 187 assert i["init_docstring"] == Call.__init__.__doc__
188 188
189 189 i = inspector.info(Call, detail_level=1)
190 190 assert i["source"] is not None
191 191 assert i["docstring"] == None
192 192
193 193 c = Call(1)
194 194 c.__doc__ = "Modified instance docstring"
195 195 i = inspector.info(c)
196 196 assert i["type_name"] == "Call"
197 197 assert i["docstring"] == "Modified instance docstring"
198 198 assert i["class_docstring"] == Call.__doc__
199 199 assert i["init_docstring"] == Call.__init__.__doc__
200 200 assert i["call_docstring"] == Call.__call__.__doc__
201 201
202 202
203 203 def test_class_signature():
204 204 info = inspector.info(HasSignature, "HasSignature")
205 205 assert info["init_definition"] == "HasSignature(test)"
206 206 assert info["init_docstring"] == HasSignature.__init__.__doc__
207 207
208 208
209 209 def test_info_awkward():
210 210 # Just test that this doesn't throw an error.
211 211 inspector.info(Awkward())
212 212
213 213 def test_bool_raise():
214 214 inspector.info(NoBoolCall())
215 215
216 216 def test_info_serialliar():
217 217 fib_tracker = [0]
218 218 inspector.info(SerialLiar(fib_tracker))
219 219
220 220 # Nested attribute access should be cut off at 100 levels deep to avoid
221 221 # infinite loops: https://github.com/ipython/ipython/issues/9122
222 222 assert fib_tracker[0] < 9000
223 223
224 224 def support_function_one(x, y=2, *a, **kw):
225 225 """A simple function."""
226 226
227 227 def test_calldef_none():
228 228 # We should ignore __call__ for all of these.
229 229 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
230 230 i = inspector.info(obj)
231 231 assert i["call_def"] is None
232 232
233 233
234 234 def f_kwarg(pos, *, kwonly):
235 235 pass
236 236
237 237 def test_definition_kwonlyargs():
238 238 i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore
239 239 assert i["definition"] == "f_kwarg(pos, *, kwonly)"
240 240
241 241
242 242 def test_getdoc():
243 243 class A(object):
244 244 """standard docstring"""
245 245 pass
246 246
247 247 class B(object):
248 248 """standard docstring"""
249 249 def getdoc(self):
250 250 return "custom docstring"
251 251
252 252 class C(object):
253 253 """standard docstring"""
254 254 def getdoc(self):
255 255 return None
256 256
257 257 a = A()
258 258 b = B()
259 259 c = C()
260 260
261 261 assert oinspect.getdoc(a) == "standard docstring"
262 262 assert oinspect.getdoc(b) == "custom docstring"
263 263 assert oinspect.getdoc(c) == "standard docstring"
264 264
265 265
266 266 def test_empty_property_has_no_source():
267 267 i = inspector.info(property(), detail_level=1)
268 268 assert i["source"] is None
269 269
270 270
271 271 def test_property_sources():
272 import posixpath
273 272 # A simple adder whose source and signature stays
274 273 # the same across Python distributions
275 274 def simple_add(a, b):
276 275 "Adds two numbers"
277 276 return a + b
278 277
279 278 class A(object):
280 279 @property
281 280 def foo(self):
282 281 return 'bar'
283 282
284 283 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
285 284
286 dname = property(posixpath.dirname)
285 dname = property(oinspect.getdoc)
287 286 adder = property(simple_add)
288 287
289 288 i = inspector.info(A.foo, detail_level=1)
290 289 assert "def foo(self):" in i["source"]
291 290 assert "lambda self, v:" in i["source"]
292 291
293 292 i = inspector.info(A.dname, detail_level=1)
294 assert "def dirname(p)" in i["source"]
293 assert "def getdoc(obj)" in i["source"]
295 294
296 295 i = inspector.info(A.adder, detail_level=1)
297 296 assert "def simple_add(a, b)" in i["source"]
298 297
299 298
300 299 def test_property_docstring_is_in_info_for_detail_level_0():
301 300 class A(object):
302 301 @property
303 302 def foobar(self):
304 303 """This is `foobar` property."""
305 304 pass
306 305
307 306 ip.user_ns["a_obj"] = A()
308 307 assert (
309 308 "This is `foobar` property."
310 309 == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"]
311 310 )
312 311
313 312 ip.user_ns["a_cls"] = A
314 313 assert (
315 314 "This is `foobar` property."
316 315 == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"]
317 316 )
318 317
319 318
320 319 def test_pdef():
321 320 # See gh-1914
322 321 def foo(): pass
323 322 inspector.pdef(foo, 'foo')
324 323
325 324
326 325 def test_pinfo_nonascii():
327 326 # See gh-1177
328 327 from . import nonascii2
329 328 ip.user_ns['nonascii2'] = nonascii2
330 329 ip._inspect('pinfo', 'nonascii2', detail_level=1)
331 330
332 331 def test_pinfo_type():
333 332 """
334 333 type can fail in various edge case, for example `type.__subclass__()`
335 334 """
336 335 ip._inspect('pinfo', 'type')
337 336
338 337
339 338 def test_pinfo_docstring_no_source():
340 339 """Docstring should be included with detail_level=1 if there is no source"""
341 340 with AssertPrints('Docstring:'):
342 341 ip._inspect('pinfo', 'str.format', detail_level=0)
343 342 with AssertPrints('Docstring:'):
344 343 ip._inspect('pinfo', 'str.format', detail_level=1)
345 344
346 345
347 346 def test_pinfo_no_docstring_if_source():
348 347 """Docstring should not be included with detail_level=1 if source is found"""
349 348 def foo():
350 349 """foo has a docstring"""
351 350
352 351 ip.user_ns['foo'] = foo
353 352
354 353 with AssertPrints('Docstring:'):
355 354 ip._inspect('pinfo', 'foo', detail_level=0)
356 355 with AssertPrints('Source:'):
357 356 ip._inspect('pinfo', 'foo', detail_level=1)
358 357 with AssertNotPrints('Docstring:'):
359 358 ip._inspect('pinfo', 'foo', detail_level=1)
360 359
361 360
362 361 def test_pinfo_docstring_if_detail_and_no_source():
363 362 """ Docstring should be displayed if source info not available """
364 363 obj_def = '''class Foo(object):
365 364 """ This is a docstring for Foo """
366 365 def bar(self):
367 366 """ This is a docstring for Foo.bar """
368 367 pass
369 368 '''
370 369
371 370 ip.run_cell(obj_def)
372 371 ip.run_cell('foo = Foo()')
373 372
374 373 with AssertNotPrints("Source:"):
375 374 with AssertPrints('Docstring:'):
376 375 ip._inspect('pinfo', 'foo', detail_level=0)
377 376 with AssertPrints('Docstring:'):
378 377 ip._inspect('pinfo', 'foo', detail_level=1)
379 378 with AssertPrints('Docstring:'):
380 379 ip._inspect('pinfo', 'foo.bar', detail_level=0)
381 380
382 381 with AssertNotPrints('Docstring:'):
383 382 with AssertPrints('Source:'):
384 383 ip._inspect('pinfo', 'foo.bar', detail_level=1)
385 384
386 385
387 386 def test_pinfo_magic():
388 387 with AssertPrints('Docstring:'):
389 388 ip._inspect('pinfo', 'lsmagic', detail_level=0)
390 389
391 390 with AssertPrints('Source:'):
392 391 ip._inspect('pinfo', 'lsmagic', detail_level=1)
393 392
394 393
395 394 def test_init_colors():
396 395 # ensure colors are not present in signature info
397 396 info = inspector.info(HasSignature)
398 397 init_def = info["init_definition"]
399 398 assert "[0m" not in init_def
400 399
401 400
402 401 def test_builtin_init():
403 402 info = inspector.info(list)
404 403 init_def = info['init_definition']
405 404 assert init_def is not None
406 405
407 406
408 407 def test_render_signature_short():
409 408 def short_fun(a=1): pass
410 409 sig = oinspect._render_signature(
411 410 signature(short_fun),
412 411 short_fun.__name__,
413 412 )
414 413 assert sig == "short_fun(a=1)"
415 414
416 415
417 416 def test_render_signature_long():
418 417 from typing import Optional
419 418
420 419 def long_function(
421 420 a_really_long_parameter: int,
422 421 and_another_long_one: bool = False,
423 422 let_us_make_sure_this_is_looong: Optional[str] = None,
424 423 ) -> bool: pass
425 424
426 425 sig = oinspect._render_signature(
427 426 signature(long_function),
428 427 long_function.__name__,
429 428 )
430 429 assert sig in [
431 430 # Python >=3.9
432 431 '''\
433 432 long_function(
434 433 a_really_long_parameter: int,
435 434 and_another_long_one: bool = False,
436 435 let_us_make_sure_this_is_looong: Optional[str] = None,
437 436 ) -> bool\
438 437 ''',
439 438 # Python >=3.7
440 439 '''\
441 440 long_function(
442 441 a_really_long_parameter: int,
443 442 and_another_long_one: bool = False,
444 443 let_us_make_sure_this_is_looong: Union[str, NoneType] = None,
445 444 ) -> bool\
446 445 ''', # Python <=3.6
447 446 '''\
448 447 long_function(
449 448 a_really_long_parameter:int,
450 449 and_another_long_one:bool=False,
451 450 let_us_make_sure_this_is_looong:Union[str, NoneType]=None,
452 451 ) -> bool\
453 452 ''',
454 453 ]
General Comments 0
You need to be logged in to leave comments. Login now