##// END OF EJS Templates
Add line number next to the file in the traceback...
Matthias Bussonnier -
r27356:e023dcb2 merge
parent child Browse files
Show More
@@ -1,1263 +1,1263 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 # -----------------------------------------------------------------------------
30 30 # Test functions
31 31 # -----------------------------------------------------------------------------
32 32
33 33 def recompute_unicode_ranges():
34 34 """
35 35 utility to recompute the largest unicode range without any characters
36 36
37 37 use to recompute the gap in the global _UNICODE_RANGES of completer.py
38 38 """
39 39 import itertools
40 40 import unicodedata
41 41 valid = []
42 42 for c in range(0,0x10FFFF + 1):
43 43 try:
44 44 unicodedata.name(chr(c))
45 45 except ValueError:
46 46 continue
47 47 valid.append(c)
48 48
49 49 def ranges(i):
50 50 for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
51 51 b = list(b)
52 52 yield b[0][1], b[-1][1]
53 53
54 54 rg = list(ranges(valid))
55 55 lens = []
56 56 gap_lens = []
57 57 pstart, pstop = 0,0
58 58 for start, stop in rg:
59 59 lens.append(stop-start)
60 60 gap_lens.append((start - pstop, hex(pstop), hex(start), f'{round((start - pstop)/0xe01f0*100)}%'))
61 61 pstart, pstop = start, stop
62 62
63 63 return sorted(gap_lens)[-1]
64 64
65 65
66 66
67 67 def test_unicode_range():
68 68 """
69 69 Test that the ranges we test for unicode names give the same number of
70 70 results than testing the full length.
71 71 """
72 72 from IPython.core.completer import _unicode_name_compute, _UNICODE_RANGES
73 73
74 74 expected_list = _unicode_name_compute([(0, 0x110000)])
75 75 test = _unicode_name_compute(_UNICODE_RANGES)
76 76 len_exp = len(expected_list)
77 77 len_test = len(test)
78 78
79 79 # do not inline the len() or on error pytest will try to print the 130 000 +
80 80 # elements.
81 81 message = None
82 82 if len_exp != len_test or len_exp > 131808:
83 83 size, start, stop, prct = recompute_unicode_ranges()
84 84 message = f"""_UNICODE_RANGES likely wrong and need updating. This is
85 85 likely due to a new release of Python. We've find that the biggest gap
86 86 in unicode characters has reduces in size to be {size} characters
87 87 ({prct}), from {start}, to {stop}. In completer.py likely update to
88 88
89 89 _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)]
90 90
91 91 And update the assertion below to use
92 92
93 93 len_exp <= {len_exp}
94 94 """
95 95 assert len_exp == len_test, message
96 96
97 97 # fail if new unicode symbols have been added.
98 98 assert len_exp <= 138552, message
99 99
100 100
101 101 @contextmanager
102 102 def greedy_completion():
103 103 ip = get_ipython()
104 104 greedy_original = ip.Completer.greedy
105 105 try:
106 106 ip.Completer.greedy = True
107 107 yield
108 108 finally:
109 109 ip.Completer.greedy = greedy_original
110 110
111 111
112 112 def test_protect_filename():
113 113 if sys.platform == "win32":
114 114 pairs = [
115 115 ("abc", "abc"),
116 116 (" abc", '" abc"'),
117 117 ("a bc", '"a bc"'),
118 118 ("a bc", '"a bc"'),
119 119 (" bc", '" bc"'),
120 120 ]
121 121 else:
122 122 pairs = [
123 123 ("abc", "abc"),
124 124 (" abc", r"\ abc"),
125 125 ("a bc", r"a\ bc"),
126 126 ("a bc", r"a\ \ bc"),
127 127 (" bc", r"\ \ bc"),
128 128 # On posix, we also protect parens and other special characters.
129 129 ("a(bc", r"a\(bc"),
130 130 ("a)bc", r"a\)bc"),
131 131 ("a( )bc", r"a\(\ \)bc"),
132 132 ("a[1]bc", r"a\[1\]bc"),
133 133 ("a{1}bc", r"a\{1\}bc"),
134 134 ("a#bc", r"a\#bc"),
135 135 ("a?bc", r"a\?bc"),
136 136 ("a=bc", r"a\=bc"),
137 137 ("a\\bc", r"a\\bc"),
138 138 ("a|bc", r"a\|bc"),
139 139 ("a;bc", r"a\;bc"),
140 140 ("a:bc", r"a\:bc"),
141 141 ("a'bc", r"a\'bc"),
142 142 ("a*bc", r"a\*bc"),
143 143 ('a"bc', r"a\"bc"),
144 144 ("a^bc", r"a\^bc"),
145 145 ("a&bc", r"a\&bc"),
146 146 ]
147 147 # run the actual tests
148 148 for s1, s2 in pairs:
149 149 s1p = completer.protect_filename(s1)
150 150 assert s1p == s2
151 151
152 152
153 153 def check_line_split(splitter, test_specs):
154 154 for part1, part2, split in test_specs:
155 155 cursor_pos = len(part1)
156 156 line = part1 + part2
157 157 out = splitter.split_line(line, cursor_pos)
158 158 assert out == split
159 159
160 160
161 161 def test_line_split():
162 162 """Basic line splitter test with default specs."""
163 163 sp = completer.CompletionSplitter()
164 164 # The format of the test specs is: part1, part2, expected answer. Parts 1
165 165 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
166 166 # was at the end of part1. So an empty part2 represents someone hitting
167 167 # tab at the end of the line, the most common case.
168 168 t = [
169 169 ("run some/scrip", "", "some/scrip"),
170 170 ("run scripts/er", "ror.py foo", "scripts/er"),
171 171 ("echo $HOM", "", "HOM"),
172 172 ("print sys.pa", "", "sys.pa"),
173 173 ("print(sys.pa", "", "sys.pa"),
174 174 ("execfile('scripts/er", "", "scripts/er"),
175 175 ("a[x.", "", "x."),
176 176 ("a[x.", "y", "x."),
177 177 ('cd "some_file/', "", "some_file/"),
178 178 ]
179 179 check_line_split(sp, t)
180 180 # Ensure splitting works OK with unicode by re-running the tests with
181 181 # all inputs turned into unicode
182 182 check_line_split(sp, [map(str, p) for p in t])
183 183
184 184
185 185 class NamedInstanceClass:
186 186 instances = {}
187 187
188 188 def __init__(self, name):
189 189 self.instances[name] = self
190 190
191 191 @classmethod
192 192 def _ipython_key_completions_(cls):
193 193 return cls.instances.keys()
194 194
195 195
196 196 class KeyCompletable:
197 197 def __init__(self, things=()):
198 198 self.things = things
199 199
200 200 def _ipython_key_completions_(self):
201 201 return list(self.things)
202 202
203 203
204 204 class TestCompleter(unittest.TestCase):
205 205 def setUp(self):
206 206 """
207 207 We want to silence all PendingDeprecationWarning when testing the completer
208 208 """
209 209 self._assertwarns = self.assertWarns(PendingDeprecationWarning)
210 210 self._assertwarns.__enter__()
211 211
212 212 def tearDown(self):
213 213 try:
214 214 self._assertwarns.__exit__(None, None, None)
215 215 except AssertionError:
216 216 pass
217 217
218 218 def test_custom_completion_error(self):
219 219 """Test that errors from custom attribute completers are silenced."""
220 220 ip = get_ipython()
221 221
222 222 class A:
223 223 pass
224 224
225 225 ip.user_ns["x"] = A()
226 226
227 227 @complete_object.register(A)
228 228 def complete_A(a, existing_completions):
229 229 raise TypeError("this should be silenced")
230 230
231 231 ip.complete("x.")
232 232
233 233 def test_custom_completion_ordering(self):
234 234 """Test that errors from custom attribute completers are silenced."""
235 235 ip = get_ipython()
236 236
237 237 _, matches = ip.complete('in')
238 238 assert matches.index('input') < matches.index('int')
239 239
240 240 def complete_example(a):
241 241 return ['example2', 'example1']
242 242
243 243 ip.Completer.custom_completers.add_re('ex*', complete_example)
244 244 _, matches = ip.complete('ex')
245 245 assert matches.index('example2') < matches.index('example1')
246 246
247 247 def test_unicode_completions(self):
248 248 ip = get_ipython()
249 249 # Some strings that trigger different types of completion. Check them both
250 250 # in str and unicode forms
251 251 s = ["ru", "%ru", "cd /", "floa", "float(x)/"]
252 252 for t in s + list(map(str, s)):
253 253 # We don't need to check exact completion values (they may change
254 254 # depending on the state of the namespace, but at least no exceptions
255 255 # should be thrown and the return value should be a pair of text, list
256 256 # values.
257 257 text, matches = ip.complete(t)
258 258 self.assertIsInstance(text, str)
259 259 self.assertIsInstance(matches, list)
260 260
261 261 def test_latex_completions(self):
262 262 from IPython.core.latex_symbols import latex_symbols
263 263 import random
264 264
265 265 ip = get_ipython()
266 266 # Test some random unicode symbols
267 267 keys = random.sample(sorted(latex_symbols), 10)
268 268 for k in keys:
269 269 text, matches = ip.complete(k)
270 270 self.assertEqual(text, k)
271 271 self.assertEqual(matches, [latex_symbols[k]])
272 272 # Test a more complex line
273 273 text, matches = ip.complete("print(\\alpha")
274 274 self.assertEqual(text, "\\alpha")
275 275 self.assertEqual(matches[0], latex_symbols["\\alpha"])
276 276 # Test multiple matching latex symbols
277 277 text, matches = ip.complete("\\al")
278 278 self.assertIn("\\alpha", matches)
279 279 self.assertIn("\\aleph", matches)
280 280
281 281 def test_latex_no_results(self):
282 282 """
283 283 forward latex should really return nothing in either field if nothing is found.
284 284 """
285 285 ip = get_ipython()
286 286 text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing")
287 287 self.assertEqual(text, "")
288 288 self.assertEqual(matches, ())
289 289
290 290 def test_back_latex_completion(self):
291 291 ip = get_ipython()
292 292
293 293 # do not return more than 1 matches for \beta, only the latex one.
294 294 name, matches = ip.complete("\\Ξ²")
295 295 self.assertEqual(matches, ["\\beta"])
296 296
297 297 def test_back_unicode_completion(self):
298 298 ip = get_ipython()
299 299
300 300 name, matches = ip.complete("\\β…€")
301 301 self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
302 302
303 303 def test_forward_unicode_completion(self):
304 304 ip = get_ipython()
305 305
306 306 name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
307 307 self.assertEqual(matches, ["β…€"]) # This is not a V
308 308 self.assertEqual(matches, ["\u2164"]) # same as above but explicit.
309 309
310 310 def test_delim_setting(self):
311 311 sp = completer.CompletionSplitter()
312 312 sp.delims = " "
313 313 self.assertEqual(sp.delims, " ")
314 314 self.assertEqual(sp._delim_expr, r"[\ ]")
315 315
316 316 def test_spaces(self):
317 317 """Test with only spaces as split chars."""
318 318 sp = completer.CompletionSplitter()
319 319 sp.delims = " "
320 320 t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")]
321 321 check_line_split(sp, t)
322 322
323 323 def test_has_open_quotes1(self):
324 324 for s in ["'", "'''", "'hi' '"]:
325 325 self.assertEqual(completer.has_open_quotes(s), "'")
326 326
327 327 def test_has_open_quotes2(self):
328 328 for s in ['"', '"""', '"hi" "']:
329 329 self.assertEqual(completer.has_open_quotes(s), '"')
330 330
331 331 def test_has_open_quotes3(self):
332 332 for s in ["''", "''' '''", "'hi' 'ipython'"]:
333 333 self.assertFalse(completer.has_open_quotes(s))
334 334
335 335 def test_has_open_quotes4(self):
336 336 for s in ['""', '""" """', '"hi" "ipython"']:
337 337 self.assertFalse(completer.has_open_quotes(s))
338 338
339 339 @pytest.mark.xfail(
340 340 sys.platform == "win32", reason="abspath completions fail on Windows"
341 341 )
342 342 def test_abspath_file_completions(self):
343 343 ip = get_ipython()
344 344 with TemporaryDirectory() as tmpdir:
345 345 prefix = os.path.join(tmpdir, "foo")
346 346 suffixes = ["1", "2"]
347 347 names = [prefix + s for s in suffixes]
348 348 for n in names:
349 349 open(n, "w").close()
350 350
351 351 # Check simple completion
352 352 c = ip.complete(prefix)[1]
353 353 self.assertEqual(c, names)
354 354
355 355 # Now check with a function call
356 356 cmd = 'a = f("%s' % prefix
357 357 c = ip.complete(prefix, cmd)[1]
358 358 comp = [prefix + s for s in suffixes]
359 359 self.assertEqual(c, comp)
360 360
361 361 def test_local_file_completions(self):
362 362 ip = get_ipython()
363 363 with TemporaryWorkingDirectory():
364 364 prefix = "./foo"
365 365 suffixes = ["1", "2"]
366 366 names = [prefix + s for s in suffixes]
367 367 for n in names:
368 368 open(n, "w").close()
369 369
370 370 # Check simple completion
371 371 c = ip.complete(prefix)[1]
372 372 self.assertEqual(c, names)
373 373
374 374 # Now check with a function call
375 375 cmd = 'a = f("%s' % prefix
376 376 c = ip.complete(prefix, cmd)[1]
377 377 comp = {prefix + s for s in suffixes}
378 378 self.assertTrue(comp.issubset(set(c)))
379 379
380 380 def test_quoted_file_completions(self):
381 381 ip = get_ipython()
382 382 with TemporaryWorkingDirectory():
383 383 name = "foo'bar"
384 384 open(name, "w").close()
385 385
386 386 # Don't escape Windows
387 387 escaped = name if sys.platform == "win32" else "foo\\'bar"
388 388
389 389 # Single quote matches embedded single quote
390 390 text = "open('foo"
391 391 c = ip.Completer._complete(
392 392 cursor_line=0, cursor_pos=len(text), full_text=text
393 393 )[1]
394 394 self.assertEqual(c, [escaped])
395 395
396 396 # Double quote requires no escape
397 397 text = 'open("foo'
398 398 c = ip.Completer._complete(
399 399 cursor_line=0, cursor_pos=len(text), full_text=text
400 400 )[1]
401 401 self.assertEqual(c, [name])
402 402
403 403 # No quote requires an escape
404 404 text = "%ls foo"
405 405 c = ip.Completer._complete(
406 406 cursor_line=0, cursor_pos=len(text), full_text=text
407 407 )[1]
408 408 self.assertEqual(c, [escaped])
409 409
410 410 def test_all_completions_dups(self):
411 411 """
412 412 Make sure the output of `IPCompleter.all_completions` does not have
413 413 duplicated prefixes.
414 414 """
415 415 ip = get_ipython()
416 416 c = ip.Completer
417 417 ip.ex("class TestClass():\n\ta=1\n\ta1=2")
418 418 for jedi_status in [True, False]:
419 419 with provisionalcompleter():
420 420 ip.Completer.use_jedi = jedi_status
421 421 matches = c.all_completions("TestCl")
422 assert matches == ['TestClass'], jedi_status
422 assert matches == ["TestClass"], (jedi_status, matches)
423 423 matches = c.all_completions("TestClass.")
424 assert len(matches) > 2, jedi_status
424 assert len(matches) > 2, (jedi_status, matches)
425 425 matches = c.all_completions("TestClass.a")
426 426 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
427 427
428 428 def test_jedi(self):
429 429 """
430 430 A couple of issue we had with Jedi
431 431 """
432 432 ip = get_ipython()
433 433
434 434 def _test_complete(reason, s, comp, start=None, end=None):
435 435 l = len(s)
436 436 start = start if start is not None else l
437 437 end = end if end is not None else l
438 438 with provisionalcompleter():
439 439 ip.Completer.use_jedi = True
440 440 completions = set(ip.Completer.completions(s, l))
441 441 ip.Completer.use_jedi = False
442 442 assert Completion(start, end, comp) in completions, reason
443 443
444 444 def _test_not_complete(reason, s, comp):
445 445 l = len(s)
446 446 with provisionalcompleter():
447 447 ip.Completer.use_jedi = True
448 448 completions = set(ip.Completer.completions(s, l))
449 449 ip.Completer.use_jedi = False
450 450 assert Completion(l, l, comp) not in completions, reason
451 451
452 452 import jedi
453 453
454 454 jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3])
455 455 if jedi_version > (0, 10):
456 456 _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real")
457 457 _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real")
458 458 _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize")
459 459 _test_complete("cover duplicate completions", "im", "import", 0, 2)
460 460
461 461 _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize")
462 462
463 463 def test_completion_have_signature(self):
464 464 """
465 465 Lets make sure jedi is capable of pulling out the signature of the function we are completing.
466 466 """
467 467 ip = get_ipython()
468 468 with provisionalcompleter():
469 469 ip.Completer.use_jedi = True
470 470 completions = ip.Completer.completions("ope", 3)
471 471 c = next(completions) # should be `open`
472 472 ip.Completer.use_jedi = False
473 473 assert "file" in c.signature, "Signature of function was not found by completer"
474 474 assert (
475 475 "encoding" in c.signature
476 476 ), "Signature of function was not found by completer"
477 477
478 478 def test_deduplicate_completions(self):
479 479 """
480 480 Test that completions are correctly deduplicated (even if ranges are not the same)
481 481 """
482 482 ip = get_ipython()
483 483 ip.ex(
484 484 textwrap.dedent(
485 485 """
486 486 class Z:
487 487 zoo = 1
488 488 """
489 489 )
490 490 )
491 491 with provisionalcompleter():
492 492 ip.Completer.use_jedi = True
493 493 l = list(
494 494 _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3))
495 495 )
496 496 ip.Completer.use_jedi = False
497 497
498 498 assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l
499 499 assert l[0].text == "zoo" # and not `it.accumulate`
500 500
501 501 def test_greedy_completions(self):
502 502 """
503 503 Test the capability of the Greedy completer.
504 504
505 505 Most of the test here does not really show off the greedy completer, for proof
506 506 each of the text below now pass with Jedi. The greedy completer is capable of more.
507 507
508 508 See the :any:`test_dict_key_completion_contexts`
509 509
510 510 """
511 511 ip = get_ipython()
512 512 ip.ex("a=list(range(5))")
513 513 _, c = ip.complete(".", line="a[0].")
514 514 self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c)
515 515
516 516 def _(line, cursor_pos, expect, message, completion):
517 517 with greedy_completion(), provisionalcompleter():
518 518 ip.Completer.use_jedi = False
519 519 _, c = ip.complete(".", line=line, cursor_pos=cursor_pos)
520 520 self.assertIn(expect, c, message % c)
521 521
522 522 ip.Completer.use_jedi = True
523 523 with provisionalcompleter():
524 524 completions = ip.Completer.completions(line, cursor_pos)
525 525 self.assertIn(completion, completions)
526 526
527 527 with provisionalcompleter():
528 528 _(
529 529 "a[0].",
530 530 5,
531 531 "a[0].real",
532 532 "Should have completed on a[0].: %s",
533 533 Completion(5, 5, "real"),
534 534 )
535 535 _(
536 536 "a[0].r",
537 537 6,
538 538 "a[0].real",
539 539 "Should have completed on a[0].r: %s",
540 540 Completion(5, 6, "real"),
541 541 )
542 542
543 543 _(
544 544 "a[0].from_",
545 545 10,
546 546 "a[0].from_bytes",
547 547 "Should have completed on a[0].from_: %s",
548 548 Completion(5, 10, "from_bytes"),
549 549 )
550 550
551 551 def test_omit__names(self):
552 552 # also happens to test IPCompleter as a configurable
553 553 ip = get_ipython()
554 554 ip._hidden_attr = 1
555 555 ip._x = {}
556 556 c = ip.Completer
557 557 ip.ex("ip=get_ipython()")
558 558 cfg = Config()
559 559 cfg.IPCompleter.omit__names = 0
560 560 c.update_config(cfg)
561 561 with provisionalcompleter():
562 562 c.use_jedi = False
563 563 s, matches = c.complete("ip.")
564 564 self.assertIn("ip.__str__", matches)
565 565 self.assertIn("ip._hidden_attr", matches)
566 566
567 567 # c.use_jedi = True
568 568 # completions = set(c.completions('ip.', 3))
569 569 # self.assertIn(Completion(3, 3, '__str__'), completions)
570 570 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
571 571
572 572 cfg = Config()
573 573 cfg.IPCompleter.omit__names = 1
574 574 c.update_config(cfg)
575 575 with provisionalcompleter():
576 576 c.use_jedi = False
577 577 s, matches = c.complete("ip.")
578 578 self.assertNotIn("ip.__str__", matches)
579 579 # self.assertIn('ip._hidden_attr', matches)
580 580
581 581 # c.use_jedi = True
582 582 # completions = set(c.completions('ip.', 3))
583 583 # self.assertNotIn(Completion(3,3,'__str__'), completions)
584 584 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
585 585
586 586 cfg = Config()
587 587 cfg.IPCompleter.omit__names = 2
588 588 c.update_config(cfg)
589 589 with provisionalcompleter():
590 590 c.use_jedi = False
591 591 s, matches = c.complete("ip.")
592 592 self.assertNotIn("ip.__str__", matches)
593 593 self.assertNotIn("ip._hidden_attr", matches)
594 594
595 595 # c.use_jedi = True
596 596 # completions = set(c.completions('ip.', 3))
597 597 # self.assertNotIn(Completion(3,3,'__str__'), completions)
598 598 # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions)
599 599
600 600 with provisionalcompleter():
601 601 c.use_jedi = False
602 602 s, matches = c.complete("ip._x.")
603 603 self.assertIn("ip._x.keys", matches)
604 604
605 605 # c.use_jedi = True
606 606 # completions = set(c.completions('ip._x.', 6))
607 607 # self.assertIn(Completion(6,6, "keys"), completions)
608 608
609 609 del ip._hidden_attr
610 610 del ip._x
611 611
612 612 def test_limit_to__all__False_ok(self):
613 613 """
614 614 Limit to all is deprecated, once we remove it this test can go away.
615 615 """
616 616 ip = get_ipython()
617 617 c = ip.Completer
618 618 c.use_jedi = False
619 619 ip.ex("class D: x=24")
620 620 ip.ex("d=D()")
621 621 cfg = Config()
622 622 cfg.IPCompleter.limit_to__all__ = False
623 623 c.update_config(cfg)
624 624 s, matches = c.complete("d.")
625 625 self.assertIn("d.x", matches)
626 626
627 627 def test_get__all__entries_ok(self):
628 628 class A:
629 629 __all__ = ["x", 1]
630 630
631 631 words = completer.get__all__entries(A())
632 632 self.assertEqual(words, ["x"])
633 633
634 634 def test_get__all__entries_no__all__ok(self):
635 635 class A:
636 636 pass
637 637
638 638 words = completer.get__all__entries(A())
639 639 self.assertEqual(words, [])
640 640
641 641 def test_func_kw_completions(self):
642 642 ip = get_ipython()
643 643 c = ip.Completer
644 644 c.use_jedi = False
645 645 ip.ex("def myfunc(a=1,b=2): return a+b")
646 646 s, matches = c.complete(None, "myfunc(1,b")
647 647 self.assertIn("b=", matches)
648 648 # Simulate completing with cursor right after b (pos==10):
649 649 s, matches = c.complete(None, "myfunc(1,b)", 10)
650 650 self.assertIn("b=", matches)
651 651 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
652 652 self.assertIn("b=", matches)
653 653 # builtin function
654 654 s, matches = c.complete(None, "min(k, k")
655 655 self.assertIn("key=", matches)
656 656
657 657 def test_default_arguments_from_docstring(self):
658 658 ip = get_ipython()
659 659 c = ip.Completer
660 660 kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value")
661 661 self.assertEqual(kwd, ["key"])
662 662 # with cython type etc
663 663 kwd = c._default_arguments_from_docstring(
664 664 "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
665 665 )
666 666 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
667 667 # white spaces
668 668 kwd = c._default_arguments_from_docstring(
669 669 "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
670 670 )
671 671 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
672 672
673 673 def test_line_magics(self):
674 674 ip = get_ipython()
675 675 c = ip.Completer
676 676 s, matches = c.complete(None, "lsmag")
677 677 self.assertIn("%lsmagic", matches)
678 678 s, matches = c.complete(None, "%lsmag")
679 679 self.assertIn("%lsmagic", matches)
680 680
681 681 def test_cell_magics(self):
682 682 from IPython.core.magic import register_cell_magic
683 683
684 684 @register_cell_magic
685 685 def _foo_cellm(line, cell):
686 686 pass
687 687
688 688 ip = get_ipython()
689 689 c = ip.Completer
690 690
691 691 s, matches = c.complete(None, "_foo_ce")
692 692 self.assertIn("%%_foo_cellm", matches)
693 693 s, matches = c.complete(None, "%%_foo_ce")
694 694 self.assertIn("%%_foo_cellm", matches)
695 695
696 696 def test_line_cell_magics(self):
697 697 from IPython.core.magic import register_line_cell_magic
698 698
699 699 @register_line_cell_magic
700 700 def _bar_cellm(line, cell):
701 701 pass
702 702
703 703 ip = get_ipython()
704 704 c = ip.Completer
705 705
706 706 # The policy here is trickier, see comments in completion code. The
707 707 # returned values depend on whether the user passes %% or not explicitly,
708 708 # and this will show a difference if the same name is both a line and cell
709 709 # magic.
710 710 s, matches = c.complete(None, "_bar_ce")
711 711 self.assertIn("%_bar_cellm", matches)
712 712 self.assertIn("%%_bar_cellm", matches)
713 713 s, matches = c.complete(None, "%_bar_ce")
714 714 self.assertIn("%_bar_cellm", matches)
715 715 self.assertIn("%%_bar_cellm", matches)
716 716 s, matches = c.complete(None, "%%_bar_ce")
717 717 self.assertNotIn("%_bar_cellm", matches)
718 718 self.assertIn("%%_bar_cellm", matches)
719 719
720 720 def test_magic_completion_order(self):
721 721 ip = get_ipython()
722 722 c = ip.Completer
723 723
724 724 # Test ordering of line and cell magics.
725 725 text, matches = c.complete("timeit")
726 726 self.assertEqual(matches, ["%timeit", "%%timeit"])
727 727
728 728 def test_magic_completion_shadowing(self):
729 729 ip = get_ipython()
730 730 c = ip.Completer
731 731 c.use_jedi = False
732 732
733 733 # Before importing matplotlib, %matplotlib magic should be the only option.
734 734 text, matches = c.complete("mat")
735 735 self.assertEqual(matches, ["%matplotlib"])
736 736
737 737 # The newly introduced name should shadow the magic.
738 738 ip.run_cell("matplotlib = 1")
739 739 text, matches = c.complete("mat")
740 740 self.assertEqual(matches, ["matplotlib"])
741 741
742 742 # After removing matplotlib from namespace, the magic should again be
743 743 # the only option.
744 744 del ip.user_ns["matplotlib"]
745 745 text, matches = c.complete("mat")
746 746 self.assertEqual(matches, ["%matplotlib"])
747 747
748 748 def test_magic_completion_shadowing_explicit(self):
749 749 """
750 750 If the user try to complete a shadowed magic, and explicit % start should
751 751 still return the completions.
752 752 """
753 753 ip = get_ipython()
754 754 c = ip.Completer
755 755
756 756 # Before importing matplotlib, %matplotlib magic should be the only option.
757 757 text, matches = c.complete("%mat")
758 758 self.assertEqual(matches, ["%matplotlib"])
759 759
760 760 ip.run_cell("matplotlib = 1")
761 761
762 762 # After removing matplotlib from namespace, the magic should still be
763 763 # the only option.
764 764 text, matches = c.complete("%mat")
765 765 self.assertEqual(matches, ["%matplotlib"])
766 766
767 767 def test_magic_config(self):
768 768 ip = get_ipython()
769 769 c = ip.Completer
770 770
771 771 s, matches = c.complete(None, "conf")
772 772 self.assertIn("%config", matches)
773 773 s, matches = c.complete(None, "conf")
774 774 self.assertNotIn("AliasManager", matches)
775 775 s, matches = c.complete(None, "config ")
776 776 self.assertIn("AliasManager", matches)
777 777 s, matches = c.complete(None, "%config ")
778 778 self.assertIn("AliasManager", matches)
779 779 s, matches = c.complete(None, "config Ali")
780 780 self.assertListEqual(["AliasManager"], matches)
781 781 s, matches = c.complete(None, "%config Ali")
782 782 self.assertListEqual(["AliasManager"], matches)
783 783 s, matches = c.complete(None, "config AliasManager")
784 784 self.assertListEqual(["AliasManager"], matches)
785 785 s, matches = c.complete(None, "%config AliasManager")
786 786 self.assertListEqual(["AliasManager"], matches)
787 787 s, matches = c.complete(None, "config AliasManager.")
788 788 self.assertIn("AliasManager.default_aliases", matches)
789 789 s, matches = c.complete(None, "%config AliasManager.")
790 790 self.assertIn("AliasManager.default_aliases", matches)
791 791 s, matches = c.complete(None, "config AliasManager.de")
792 792 self.assertListEqual(["AliasManager.default_aliases"], matches)
793 793 s, matches = c.complete(None, "config AliasManager.de")
794 794 self.assertListEqual(["AliasManager.default_aliases"], matches)
795 795
796 796 def test_magic_color(self):
797 797 ip = get_ipython()
798 798 c = ip.Completer
799 799
800 800 s, matches = c.complete(None, "colo")
801 801 self.assertIn("%colors", matches)
802 802 s, matches = c.complete(None, "colo")
803 803 self.assertNotIn("NoColor", matches)
804 804 s, matches = c.complete(None, "%colors") # No trailing space
805 805 self.assertNotIn("NoColor", matches)
806 806 s, matches = c.complete(None, "colors ")
807 807 self.assertIn("NoColor", matches)
808 808 s, matches = c.complete(None, "%colors ")
809 809 self.assertIn("NoColor", matches)
810 810 s, matches = c.complete(None, "colors NoCo")
811 811 self.assertListEqual(["NoColor"], matches)
812 812 s, matches = c.complete(None, "%colors NoCo")
813 813 self.assertListEqual(["NoColor"], matches)
814 814
815 815 def test_match_dict_keys(self):
816 816 """
817 817 Test that match_dict_keys works on a couple of use case does return what
818 818 expected, and does not crash
819 819 """
820 820 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
821 821
822 822 keys = ["foo", b"far"]
823 823 assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"])
824 824 assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"])
825 825 assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"])
826 826 assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
827 827
828 828 assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"])
829 829 assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"])
830 830 assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"])
831 831 assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"])
832 832
833 833 match_dict_keys
834 834
835 835 def test_match_dict_keys_tuple(self):
836 836 """
837 837 Test that match_dict_keys called with extra prefix works on a couple of use case,
838 838 does return what expected, and does not crash.
839 839 """
840 840 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
841 841
842 842 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
843 843
844 844 # Completion on first key == "foo"
845 845 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
846 846 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
847 847 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
848 848 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
849 849 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
850 850 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
851 851 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
852 852 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
853 853
854 854 # No Completion
855 855 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
856 856 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
857 857
858 858 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
859 859 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
860 860 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
861 861 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
862 862 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
863 863
864 864 def test_dict_key_completion_string(self):
865 865 """Test dictionary key completion for string keys"""
866 866 ip = get_ipython()
867 867 complete = ip.Completer.complete
868 868
869 869 ip.user_ns["d"] = {"abc": None}
870 870
871 871 # check completion at different stages
872 872 _, matches = complete(line_buffer="d[")
873 873 self.assertIn("'abc'", matches)
874 874 self.assertNotIn("'abc']", matches)
875 875
876 876 _, matches = complete(line_buffer="d['")
877 877 self.assertIn("abc", matches)
878 878 self.assertNotIn("abc']", matches)
879 879
880 880 _, matches = complete(line_buffer="d['a")
881 881 self.assertIn("abc", matches)
882 882 self.assertNotIn("abc']", matches)
883 883
884 884 # check use of different quoting
885 885 _, matches = complete(line_buffer='d["')
886 886 self.assertIn("abc", matches)
887 887 self.assertNotIn('abc"]', matches)
888 888
889 889 _, matches = complete(line_buffer='d["a')
890 890 self.assertIn("abc", matches)
891 891 self.assertNotIn('abc"]', matches)
892 892
893 893 # check sensitivity to following context
894 894 _, matches = complete(line_buffer="d[]", cursor_pos=2)
895 895 self.assertIn("'abc'", matches)
896 896
897 897 _, matches = complete(line_buffer="d['']", cursor_pos=3)
898 898 self.assertIn("abc", matches)
899 899 self.assertNotIn("abc'", matches)
900 900 self.assertNotIn("abc']", matches)
901 901
902 902 # check multiple solutions are correctly returned and that noise is not
903 903 ip.user_ns["d"] = {
904 904 "abc": None,
905 905 "abd": None,
906 906 "bad": None,
907 907 object(): None,
908 908 5: None,
909 909 ("abe", None): None,
910 910 (None, "abf"): None
911 911 }
912 912
913 913 _, matches = complete(line_buffer="d['a")
914 914 self.assertIn("abc", matches)
915 915 self.assertIn("abd", matches)
916 916 self.assertNotIn("bad", matches)
917 917 self.assertNotIn("abe", matches)
918 918 self.assertNotIn("abf", matches)
919 919 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
920 920
921 921 # check escaping and whitespace
922 922 ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None}
923 923 _, matches = complete(line_buffer="d['a")
924 924 self.assertIn("a\\nb", matches)
925 925 self.assertIn("a\\'b", matches)
926 926 self.assertIn('a"b', matches)
927 927 self.assertIn("a word", matches)
928 928 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
929 929
930 930 # - can complete on non-initial word of the string
931 931 _, matches = complete(line_buffer="d['a w")
932 932 self.assertIn("word", matches)
933 933
934 934 # - understands quote escaping
935 935 _, matches = complete(line_buffer="d['a\\'")
936 936 self.assertIn("b", matches)
937 937
938 938 # - default quoting should work like repr
939 939 _, matches = complete(line_buffer="d[")
940 940 self.assertIn('"a\'b"', matches)
941 941
942 942 # - when opening quote with ", possible to match with unescaped apostrophe
943 943 _, matches = complete(line_buffer="d[\"a'")
944 944 self.assertIn("b", matches)
945 945
946 946 # need to not split at delims that readline won't split at
947 947 if "-" not in ip.Completer.splitter.delims:
948 948 ip.user_ns["d"] = {"before-after": None}
949 949 _, matches = complete(line_buffer="d['before-af")
950 950 self.assertIn("before-after", matches)
951 951
952 952 # check completion on tuple-of-string keys at different stage - on first key
953 953 ip.user_ns["d"] = {('foo', 'bar'): None}
954 954 _, matches = complete(line_buffer="d[")
955 955 self.assertIn("'foo'", matches)
956 956 self.assertNotIn("'foo']", matches)
957 957 self.assertNotIn("'bar'", matches)
958 958 self.assertNotIn("foo", matches)
959 959 self.assertNotIn("bar", matches)
960 960
961 961 # - match the prefix
962 962 _, matches = complete(line_buffer="d['f")
963 963 self.assertIn("foo", matches)
964 964 self.assertNotIn("foo']", matches)
965 965 self.assertNotIn('foo"]', matches)
966 966 _, matches = complete(line_buffer="d['foo")
967 967 self.assertIn("foo", matches)
968 968
969 969 # - can complete on second key
970 970 _, matches = complete(line_buffer="d['foo', ")
971 971 self.assertIn("'bar'", matches)
972 972 _, matches = complete(line_buffer="d['foo', 'b")
973 973 self.assertIn("bar", matches)
974 974 self.assertNotIn("foo", matches)
975 975
976 976 # - does not propose missing keys
977 977 _, matches = complete(line_buffer="d['foo', 'f")
978 978 self.assertNotIn("bar", matches)
979 979 self.assertNotIn("foo", matches)
980 980
981 981 # check sensitivity to following context
982 982 _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
983 983 self.assertIn("'bar'", matches)
984 984 self.assertNotIn("bar", matches)
985 985 self.assertNotIn("'foo'", matches)
986 986 self.assertNotIn("foo", matches)
987 987
988 988 _, matches = complete(line_buffer="d['']", cursor_pos=3)
989 989 self.assertIn("foo", matches)
990 990 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
991 991
992 992 _, matches = complete(line_buffer='d[""]', cursor_pos=3)
993 993 self.assertIn("foo", matches)
994 994 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
995 995
996 996 _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
997 997 self.assertIn("bar", matches)
998 998 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
999 999
1000 1000 _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1001 1001 self.assertIn("'bar'", matches)
1002 1002 self.assertNotIn("bar", matches)
1003 1003
1004 1004 # Can complete with longer tuple keys
1005 1005 ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1006 1006
1007 1007 # - can complete second key
1008 1008 _, matches = complete(line_buffer="d['foo', 'b")
1009 1009 self.assertIn("bar", matches)
1010 1010 self.assertNotIn("foo", matches)
1011 1011 self.assertNotIn("foobar", matches)
1012 1012
1013 1013 # - can complete third key
1014 1014 _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1015 1015 self.assertIn("foobar", matches)
1016 1016 self.assertNotIn("foo", matches)
1017 1017 self.assertNotIn("bar", matches)
1018 1018
1019 1019 def test_dict_key_completion_contexts(self):
1020 1020 """Test expression contexts in which dict key completion occurs"""
1021 1021 ip = get_ipython()
1022 1022 complete = ip.Completer.complete
1023 1023 d = {"abc": None}
1024 1024 ip.user_ns["d"] = d
1025 1025
1026 1026 class C:
1027 1027 data = d
1028 1028
1029 1029 ip.user_ns["C"] = C
1030 1030 ip.user_ns["get"] = lambda: d
1031 1031
1032 1032 def assert_no_completion(**kwargs):
1033 1033 _, matches = complete(**kwargs)
1034 1034 self.assertNotIn("abc", matches)
1035 1035 self.assertNotIn("abc'", matches)
1036 1036 self.assertNotIn("abc']", matches)
1037 1037 self.assertNotIn("'abc'", matches)
1038 1038 self.assertNotIn("'abc']", matches)
1039 1039
1040 1040 def assert_completion(**kwargs):
1041 1041 _, matches = complete(**kwargs)
1042 1042 self.assertIn("'abc'", matches)
1043 1043 self.assertNotIn("'abc']", matches)
1044 1044
1045 1045 # no completion after string closed, even if reopened
1046 1046 assert_no_completion(line_buffer="d['a'")
1047 1047 assert_no_completion(line_buffer='d["a"')
1048 1048 assert_no_completion(line_buffer="d['a' + ")
1049 1049 assert_no_completion(line_buffer="d['a' + '")
1050 1050
1051 1051 # completion in non-trivial expressions
1052 1052 assert_completion(line_buffer="+ d[")
1053 1053 assert_completion(line_buffer="(d[")
1054 1054 assert_completion(line_buffer="C.data[")
1055 1055
1056 1056 # greedy flag
1057 1057 def assert_completion(**kwargs):
1058 1058 _, matches = complete(**kwargs)
1059 1059 self.assertIn("get()['abc']", matches)
1060 1060
1061 1061 assert_no_completion(line_buffer="get()[")
1062 1062 with greedy_completion():
1063 1063 assert_completion(line_buffer="get()[")
1064 1064 assert_completion(line_buffer="get()['")
1065 1065 assert_completion(line_buffer="get()['a")
1066 1066 assert_completion(line_buffer="get()['ab")
1067 1067 assert_completion(line_buffer="get()['abc")
1068 1068
1069 1069 def test_dict_key_completion_bytes(self):
1070 1070 """Test handling of bytes in dict key completion"""
1071 1071 ip = get_ipython()
1072 1072 complete = ip.Completer.complete
1073 1073
1074 1074 ip.user_ns["d"] = {"abc": None, b"abd": None}
1075 1075
1076 1076 _, matches = complete(line_buffer="d[")
1077 1077 self.assertIn("'abc'", matches)
1078 1078 self.assertIn("b'abd'", matches)
1079 1079
1080 1080 if False: # not currently implemented
1081 1081 _, matches = complete(line_buffer="d[b")
1082 1082 self.assertIn("b'abd'", matches)
1083 1083 self.assertNotIn("b'abc'", matches)
1084 1084
1085 1085 _, matches = complete(line_buffer="d[b'")
1086 1086 self.assertIn("abd", matches)
1087 1087 self.assertNotIn("abc", matches)
1088 1088
1089 1089 _, matches = complete(line_buffer="d[B'")
1090 1090 self.assertIn("abd", matches)
1091 1091 self.assertNotIn("abc", matches)
1092 1092
1093 1093 _, matches = complete(line_buffer="d['")
1094 1094 self.assertIn("abc", matches)
1095 1095 self.assertNotIn("abd", matches)
1096 1096
1097 1097 def test_dict_key_completion_unicode_py3(self):
1098 1098 """Test handling of unicode in dict key completion"""
1099 1099 ip = get_ipython()
1100 1100 complete = ip.Completer.complete
1101 1101
1102 1102 ip.user_ns["d"] = {"a\u05d0": None}
1103 1103
1104 1104 # query using escape
1105 1105 if sys.platform != "win32":
1106 1106 # Known failure on Windows
1107 1107 _, matches = complete(line_buffer="d['a\\u05d0")
1108 1108 self.assertIn("u05d0", matches) # tokenized after \\
1109 1109
1110 1110 # query using character
1111 1111 _, matches = complete(line_buffer="d['a\u05d0")
1112 1112 self.assertIn("a\u05d0", matches)
1113 1113
1114 1114 with greedy_completion():
1115 1115 # query using escape
1116 1116 _, matches = complete(line_buffer="d['a\\u05d0")
1117 1117 self.assertIn("d['a\\u05d0']", matches) # tokenized after \\
1118 1118
1119 1119 # query using character
1120 1120 _, matches = complete(line_buffer="d['a\u05d0")
1121 1121 self.assertIn("d['a\u05d0']", matches)
1122 1122
1123 1123 @dec.skip_without("numpy")
1124 1124 def test_struct_array_key_completion(self):
1125 1125 """Test dict key completion applies to numpy struct arrays"""
1126 1126 import numpy
1127 1127
1128 1128 ip = get_ipython()
1129 1129 complete = ip.Completer.complete
1130 1130 ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")])
1131 1131 _, matches = complete(line_buffer="d['")
1132 1132 self.assertIn("hello", matches)
1133 1133 self.assertIn("world", matches)
1134 1134 # complete on the numpy struct itself
1135 1135 dt = numpy.dtype(
1136 1136 [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)]
1137 1137 )
1138 1138 x = numpy.zeros(2, dtype=dt)
1139 1139 ip.user_ns["d"] = x[1]
1140 1140 _, matches = complete(line_buffer="d['")
1141 1141 self.assertIn("my_head", matches)
1142 1142 self.assertIn("my_data", matches)
1143 1143 # complete on a nested level
1144 1144 with greedy_completion():
1145 1145 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1146 1146 _, matches = complete(line_buffer="d[1]['my_head']['")
1147 1147 self.assertTrue(any(["my_dt" in m for m in matches]))
1148 1148 self.assertTrue(any(["my_df" in m for m in matches]))
1149 1149
1150 1150 @dec.skip_without("pandas")
1151 1151 def test_dataframe_key_completion(self):
1152 1152 """Test dict key completion applies to pandas DataFrames"""
1153 1153 import pandas
1154 1154
1155 1155 ip = get_ipython()
1156 1156 complete = ip.Completer.complete
1157 1157 ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]})
1158 1158 _, matches = complete(line_buffer="d['")
1159 1159 self.assertIn("hello", matches)
1160 1160 self.assertIn("world", matches)
1161 1161
1162 1162 def test_dict_key_completion_invalids(self):
1163 1163 """Smoke test cases dict key completion can't handle"""
1164 1164 ip = get_ipython()
1165 1165 complete = ip.Completer.complete
1166 1166
1167 1167 ip.user_ns["no_getitem"] = None
1168 1168 ip.user_ns["no_keys"] = []
1169 1169 ip.user_ns["cant_call_keys"] = dict
1170 1170 ip.user_ns["empty"] = {}
1171 1171 ip.user_ns["d"] = {"abc": 5}
1172 1172
1173 1173 _, matches = complete(line_buffer="no_getitem['")
1174 1174 _, matches = complete(line_buffer="no_keys['")
1175 1175 _, matches = complete(line_buffer="cant_call_keys['")
1176 1176 _, matches = complete(line_buffer="empty['")
1177 1177 _, matches = complete(line_buffer="name_error['")
1178 1178 _, matches = complete(line_buffer="d['\\") # incomplete escape
1179 1179
1180 1180 def test_object_key_completion(self):
1181 1181 ip = get_ipython()
1182 1182 ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"])
1183 1183
1184 1184 _, matches = ip.Completer.complete(line_buffer="key_completable['qw")
1185 1185 self.assertIn("qwerty", matches)
1186 1186 self.assertIn("qwick", matches)
1187 1187
1188 1188 def test_class_key_completion(self):
1189 1189 ip = get_ipython()
1190 1190 NamedInstanceClass("qwerty")
1191 1191 NamedInstanceClass("qwick")
1192 1192 ip.user_ns["named_instance_class"] = NamedInstanceClass
1193 1193
1194 1194 _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw")
1195 1195 self.assertIn("qwerty", matches)
1196 1196 self.assertIn("qwick", matches)
1197 1197
1198 1198 def test_tryimport(self):
1199 1199 """
1200 1200 Test that try-import don't crash on trailing dot, and import modules before
1201 1201 """
1202 1202 from IPython.core.completerlib import try_import
1203 1203
1204 1204 assert try_import("IPython.")
1205 1205
1206 1206 def test_aimport_module_completer(self):
1207 1207 ip = get_ipython()
1208 1208 _, matches = ip.complete("i", "%aimport i")
1209 1209 self.assertIn("io", matches)
1210 1210 self.assertNotIn("int", matches)
1211 1211
1212 1212 def test_nested_import_module_completer(self):
1213 1213 ip = get_ipython()
1214 1214 _, matches = ip.complete(None, "import IPython.co", 17)
1215 1215 self.assertIn("IPython.core", matches)
1216 1216 self.assertNotIn("import IPython.core", matches)
1217 1217 self.assertNotIn("IPython.display", matches)
1218 1218
1219 1219 def test_import_module_completer(self):
1220 1220 ip = get_ipython()
1221 1221 _, matches = ip.complete("i", "import i")
1222 1222 self.assertIn("io", matches)
1223 1223 self.assertNotIn("int", matches)
1224 1224
1225 1225 def test_from_module_completer(self):
1226 1226 ip = get_ipython()
1227 1227 _, matches = ip.complete("B", "from io import B", 16)
1228 1228 self.assertIn("BytesIO", matches)
1229 1229 self.assertNotIn("BaseException", matches)
1230 1230
1231 1231 def test_snake_case_completion(self):
1232 1232 ip = get_ipython()
1233 1233 ip.Completer.use_jedi = False
1234 1234 ip.user_ns["some_three"] = 3
1235 1235 ip.user_ns["some_four"] = 4
1236 1236 _, matches = ip.complete("s_", "print(s_f")
1237 1237 self.assertIn("some_three", matches)
1238 1238 self.assertIn("some_four", matches)
1239 1239
1240 1240 def test_mix_terms(self):
1241 1241 ip = get_ipython()
1242 1242 from textwrap import dedent
1243 1243
1244 1244 ip.Completer.use_jedi = False
1245 1245 ip.ex(
1246 1246 dedent(
1247 1247 """
1248 1248 class Test:
1249 1249 def meth(self, meth_arg1):
1250 1250 print("meth")
1251 1251
1252 1252 def meth_1(self, meth1_arg1, meth1_arg2):
1253 1253 print("meth1")
1254 1254
1255 1255 def meth_2(self, meth2_arg1, meth2_arg2):
1256 1256 print("meth2")
1257 1257 test = Test()
1258 1258 """
1259 1259 )
1260 1260 )
1261 1261 _, matches = ip.complete(None, "test.meth(")
1262 1262 self.assertIn("meth_arg1=", matches)
1263 1263 self.assertNotIn("meth2_arg1=", matches)
@@ -1,249 +1,249 b''
1 1 """Tests for the key interactiveshell module, where the main ipython class is defined.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Module imports
5 5 #-----------------------------------------------------------------------------
6 6
7 7 # third party
8 8 import pytest
9 9
10 10 # our own packages
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Test functions
14 14 #-----------------------------------------------------------------------------
15 15
16 16 def test_reset():
17 17 """reset must clear most namespaces."""
18 18
19 19 # Check that reset runs without error
20 20 ip.reset()
21 21
22 22 # Once we've reset it (to clear of any junk that might have been there from
23 23 # other tests, we can count how many variables are in the user's namespace
24 24 nvars_user_ns = len(ip.user_ns)
25 25 nvars_hidden = len(ip.user_ns_hidden)
26 26
27 27 # Now add a few variables to user_ns, and check that reset clears them
28 28 ip.user_ns['x'] = 1
29 29 ip.user_ns['y'] = 1
30 30 ip.reset()
31 31
32 32 # Finally, check that all namespaces have only as many variables as we
33 33 # expect to find in them:
34 34 assert len(ip.user_ns) == nvars_user_ns
35 35 assert len(ip.user_ns_hidden) == nvars_hidden
36 36
37 37
38 38 # Tests for reporting of exceptions in various modes, handling of SystemExit,
39 39 # and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
40 40
41 41 def doctest_tb_plain():
42 42 """
43 43 In [18]: xmode plain
44 44 Exception reporting mode: Plain
45 45
46 46 In [19]: run simpleerr.py
47 47 Traceback (most recent call last):
48 ...line ..., in <module>
48 File ...:... in <module>
49 49 bar(mode)
50 ...line ..., in bar
50 File ...:... in bar
51 51 div0()
52 ...line ..., in div0
52 File ...:... in div0
53 53 x/y
54 54 ZeroDivisionError: ...
55 55 """
56 56
57 57
58 58 def doctest_tb_context():
59 59 """
60 60 In [3]: xmode context
61 61 Exception reporting mode: Context
62 62
63 63 In [4]: run simpleerr.py
64 64 ---------------------------------------------------------------------------
65 65 ZeroDivisionError Traceback (most recent call last)
66 66 <BLANKLINE>
67 67 ... in <module>
68 68 30 except IndexError:
69 69 31 mode = 'div'
70 70 ---> 33 bar(mode)
71 71 <BLANKLINE>
72 72 ... in bar(mode)
73 73 15 "bar"
74 74 16 if mode=='div':
75 75 ---> 17 div0()
76 76 18 elif mode=='exit':
77 77 19 try:
78 78 <BLANKLINE>
79 79 ... in div0()
80 80 6 x = 1
81 81 7 y = 0
82 82 ----> 8 x/y
83 83 <BLANKLINE>
84 84 ZeroDivisionError: ..."""
85 85
86 86
87 87 def doctest_tb_verbose():
88 88 """
89 89 In [5]: xmode verbose
90 90 Exception reporting mode: Verbose
91 91
92 92 In [6]: run simpleerr.py
93 93 ---------------------------------------------------------------------------
94 94 ZeroDivisionError Traceback (most recent call last)
95 95 <BLANKLINE>
96 96 ... in <module>
97 97 30 except IndexError:
98 98 31 mode = 'div'
99 99 ---> 33 bar(mode)
100 100 mode = 'div'
101 101 <BLANKLINE>
102 102 ... in bar(mode='div')
103 103 15 "bar"
104 104 16 if mode=='div':
105 105 ---> 17 div0()
106 106 18 elif mode=='exit':
107 107 19 try:
108 108 <BLANKLINE>
109 109 ... in div0()
110 110 6 x = 1
111 111 7 y = 0
112 112 ----> 8 x/y
113 113 x = 1
114 114 y = 0
115 115 <BLANKLINE>
116 116 ZeroDivisionError: ...
117 117 """
118 118
119 119
120 120 def doctest_tb_sysexit():
121 121 """
122 122 In [17]: %xmode plain
123 123 Exception reporting mode: Plain
124 124
125 125 In [18]: %run simpleerr.py exit
126 126 An exception has occurred, use %tb to see the full traceback.
127 127 SystemExit: (1, 'Mode = exit')
128 128
129 129 In [19]: %run simpleerr.py exit 2
130 130 An exception has occurred, use %tb to see the full traceback.
131 131 SystemExit: (2, 'Mode = exit')
132 132
133 133 In [20]: %tb
134 134 Traceback (most recent call last):
135 File ..., in execfile
135 File ...:... in execfile
136 136 exec(compiler(f.read(), fname, "exec"), glob, loc)
137 File ..., in <module>
137 File ...:... in <module>
138 138 bar(mode)
139 File ..., in bar
139 File ...:... in bar
140 140 sysexit(stat, mode)
141 File ..., in sysexit
141 File ...:... in sysexit
142 142 raise SystemExit(stat, f"Mode = {mode}")
143 143 SystemExit: (2, 'Mode = exit')
144 144
145 145 In [21]: %xmode context
146 146 Exception reporting mode: Context
147 147
148 148 In [22]: %tb
149 149 ---------------------------------------------------------------------------
150 150 SystemExit Traceback (most recent call last)
151 151 File ..., in execfile(fname, glob, loc, compiler)
152 152 ... with open(fname, "rb") as f:
153 153 ... compiler = compiler or compile
154 154 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
155 155 ...<module>
156 156 30 except IndexError:
157 157 31 mode = 'div'
158 158 ---> 33 bar(mode)
159 159 <BLANKLINE>
160 160 ...bar(mode)
161 161 21 except:
162 162 22 stat = 1
163 163 ---> 23 sysexit(stat, mode)
164 164 24 else:
165 165 25 raise ValueError('Unknown mode')
166 166 <BLANKLINE>
167 167 ...sysexit(stat, mode)
168 168 10 def sysexit(stat, mode):
169 169 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
170 170 <BLANKLINE>
171 171 SystemExit: (2, 'Mode = exit')
172 172 """
173 173
174 174
175 175 def doctest_tb_sysexit_verbose():
176 176 """
177 177 In [18]: %run simpleerr.py exit
178 178 An exception has occurred, use %tb to see the full traceback.
179 179 SystemExit: (1, 'Mode = exit')
180 180
181 181 In [19]: %run simpleerr.py exit 2
182 182 An exception has occurred, use %tb to see the full traceback.
183 183 SystemExit: (2, 'Mode = exit')
184 184
185 185 In [23]: %xmode verbose
186 186 Exception reporting mode: Verbose
187 187
188 188 In [24]: %tb
189 189 ---------------------------------------------------------------------------
190 190 SystemExit Traceback (most recent call last)
191 191 <BLANKLINE>
192 192 ... in <module>
193 193 30 except IndexError:
194 194 31 mode = 'div'
195 195 ---> 33 bar(mode)
196 196 mode = 'exit'
197 197 <BLANKLINE>
198 198 ... in bar(mode='exit')
199 199 ... except:
200 200 ... stat = 1
201 201 ---> ... sysexit(stat, mode)
202 202 mode = 'exit'
203 203 stat = 2
204 204 ... else:
205 205 ... raise ValueError('Unknown mode')
206 206 <BLANKLINE>
207 207 ... in sysexit(stat=2, mode='exit')
208 208 10 def sysexit(stat, mode):
209 209 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
210 210 stat = 2
211 211 <BLANKLINE>
212 212 SystemExit: (2, 'Mode = exit')
213 213 """
214 214
215 215
216 216 def test_run_cell():
217 217 import textwrap
218 218
219 219 ip.run_cell("a = 10\na+=1")
220 220 ip.run_cell("assert a == 11\nassert 1")
221 221
222 222 assert ip.user_ns["a"] == 11
223 223 complex = textwrap.dedent(
224 224 """
225 225 if 1:
226 226 print "hello"
227 227 if 1:
228 228 print "world"
229 229
230 230 if 2:
231 231 print "foo"
232 232
233 233 if 3:
234 234 print "bar"
235 235
236 236 if 4:
237 237 print "bar"
238 238
239 239 """)
240 240 # Simply verifies that this kind of input is run
241 241 ip.run_cell(complex)
242 242
243 243
244 244 def test_db():
245 245 """Test the internal database used for variable persistence."""
246 246 ip.db["__unittest_"] = 12
247 247 assert ip.db["__unittest_"] == 12
248 248 del ip.db["__unittest_"]
249 249 assert "__unittest_" not in ip.db
@@ -1,414 +1,410 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.core.ultratb
3 3 """
4 4 import io
5 5 import logging
6 6 import platform
7 7 import re
8 8 import sys
9 9 import os.path
10 10 from textwrap import dedent
11 11 import traceback
12 12 import unittest
13 13
14 14 from IPython.core.ultratb import ColorTB, VerboseTB
15 15
16 16
17 17 from IPython.testing import tools as tt
18 18 from IPython.testing.decorators import onlyif_unicode_paths
19 19 from IPython.utils.syspathcontext import prepended_to_syspath
20 20 from IPython.utils.tempdir import TemporaryDirectory
21 21
22 22 file_1 = """1
23 23 2
24 24 3
25 25 def f():
26 26 1/0
27 27 """
28 28
29 29 file_2 = """def f():
30 30 1/0
31 31 """
32 32
33 33
34 34 def recursionlimit(frames):
35 35 """
36 36 decorator to set the recursion limit temporarily
37 37 """
38 38
39 39 def inner(test_function):
40 40 def wrapper(*args, **kwargs):
41 41 rl = sys.getrecursionlimit()
42 42 sys.setrecursionlimit(frames)
43 43 try:
44 44 return test_function(*args, **kwargs)
45 45 finally:
46 46 sys.setrecursionlimit(rl)
47 47
48 48 return wrapper
49 49
50 50 return inner
51 51
52 52
53 53 class ChangedPyFileTest(unittest.TestCase):
54 54 def test_changing_py_file(self):
55 55 """Traceback produced if the line where the error occurred is missing?
56 56
57 57 https://github.com/ipython/ipython/issues/1456
58 58 """
59 59 with TemporaryDirectory() as td:
60 60 fname = os.path.join(td, "foo.py")
61 61 with open(fname, "w") as f:
62 62 f.write(file_1)
63 63
64 64 with prepended_to_syspath(td):
65 65 ip.run_cell("import foo")
66 66
67 67 with tt.AssertPrints("ZeroDivisionError"):
68 68 ip.run_cell("foo.f()")
69 69
70 70 # Make the file shorter, so the line of the error is missing.
71 71 with open(fname, "w") as f:
72 72 f.write(file_2)
73 73
74 74 # For some reason, this was failing on the *second* call after
75 75 # changing the file, so we call f() twice.
76 76 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
77 77 with tt.AssertPrints("ZeroDivisionError"):
78 78 ip.run_cell("foo.f()")
79 79 with tt.AssertPrints("ZeroDivisionError"):
80 80 ip.run_cell("foo.f()")
81 81
82 82 iso_8859_5_file = u'''# coding: iso-8859-5
83 83
84 84 def fail():
85 85 """Π΄Π±Π˜Π–"""
86 86 1/0 # Π΄Π±Π˜Π–
87 87 '''
88 88
89 89 class NonAsciiTest(unittest.TestCase):
90 90 @onlyif_unicode_paths
91 91 def test_nonascii_path(self):
92 92 # Non-ascii directory name as well.
93 93 with TemporaryDirectory(suffix=u'Γ©') as td:
94 94 fname = os.path.join(td, u"fooΓ©.py")
95 95 with open(fname, "w") as f:
96 96 f.write(file_1)
97 97
98 98 with prepended_to_syspath(td):
99 99 ip.run_cell("import foo")
100 100
101 101 with tt.AssertPrints("ZeroDivisionError"):
102 102 ip.run_cell("foo.f()")
103 103
104 104 def test_iso8859_5(self):
105 105 with TemporaryDirectory() as td:
106 106 fname = os.path.join(td, 'dfghjkl.py')
107 107
108 108 with io.open(fname, 'w', encoding='iso-8859-5') as f:
109 109 f.write(iso_8859_5_file)
110 110
111 111 with prepended_to_syspath(td):
112 112 ip.run_cell("from dfghjkl import fail")
113 113
114 114 with tt.AssertPrints("ZeroDivisionError"):
115 115 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
116 116 ip.run_cell('fail()')
117 117
118 118 def test_nonascii_msg(self):
119 119 cell = u"raise Exception('Γ©')"
120 120 expected = u"Exception('Γ©')"
121 121 ip.run_cell("%xmode plain")
122 122 with tt.AssertPrints(expected):
123 123 ip.run_cell(cell)
124 124
125 125 ip.run_cell("%xmode verbose")
126 126 with tt.AssertPrints(expected):
127 127 ip.run_cell(cell)
128 128
129 129 ip.run_cell("%xmode context")
130 130 with tt.AssertPrints(expected):
131 131 ip.run_cell(cell)
132 132
133 133 ip.run_cell("%xmode minimal")
134 134 with tt.AssertPrints(u"Exception: Γ©"):
135 135 ip.run_cell(cell)
136 136
137 137 # Put this back into Context mode for later tests.
138 138 ip.run_cell("%xmode context")
139 139
140 140 class NestedGenExprTestCase(unittest.TestCase):
141 141 """
142 142 Regression test for the following issues:
143 143 https://github.com/ipython/ipython/issues/8293
144 144 https://github.com/ipython/ipython/issues/8205
145 145 """
146 146 def test_nested_genexpr(self):
147 147 code = dedent(
148 148 """\
149 149 class SpecificException(Exception):
150 150 pass
151 151
152 152 def foo(x):
153 153 raise SpecificException("Success!")
154 154
155 155 sum(sum(foo(x) for _ in [0]) for x in [0])
156 156 """
157 157 )
158 158 with tt.AssertPrints('SpecificException: Success!', suppress=False):
159 159 ip.run_cell(code)
160 160
161 161
162 162 indentationerror_file = """if True:
163 163 zoon()
164 164 """
165 165
166 166 class IndentationErrorTest(unittest.TestCase):
167 167 def test_indentationerror_shows_line(self):
168 168 # See issue gh-2398
169 169 with tt.AssertPrints("IndentationError"):
170 170 with tt.AssertPrints("zoon()", suppress=False):
171 171 ip.run_cell(indentationerror_file)
172 172
173 173 with TemporaryDirectory() as td:
174 174 fname = os.path.join(td, "foo.py")
175 175 with open(fname, "w") as f:
176 176 f.write(indentationerror_file)
177 177
178 178 with tt.AssertPrints("IndentationError"):
179 179 with tt.AssertPrints("zoon()", suppress=False):
180 180 ip.magic('run %s' % fname)
181 181
182 182 se_file_1 = """1
183 183 2
184 184 7/
185 185 """
186 186
187 187 se_file_2 = """7/
188 188 """
189 189
190 190 class SyntaxErrorTest(unittest.TestCase):
191 def test_syntaxerror_without_lineno(self):
192 with tt.AssertNotPrints("TypeError"):
193 with tt.AssertPrints("line unknown"):
194 ip.run_cell("raise SyntaxError()")
195 191
196 192 def test_syntaxerror_no_stacktrace_at_compile_time(self):
197 193 syntax_error_at_compile_time = """
198 194 def foo():
199 195 ..
200 196 """
201 197 with tt.AssertPrints("SyntaxError"):
202 198 ip.run_cell(syntax_error_at_compile_time)
203 199
204 200 with tt.AssertNotPrints("foo()"):
205 201 ip.run_cell(syntax_error_at_compile_time)
206 202
207 203 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
208 204 syntax_error_at_runtime = """
209 205 def foo():
210 206 eval("..")
211 207
212 208 def bar():
213 209 foo()
214 210
215 211 bar()
216 212 """
217 213 with tt.AssertPrints("SyntaxError"):
218 214 ip.run_cell(syntax_error_at_runtime)
219 215 # Assert syntax error during runtime generate stacktrace
220 216 with tt.AssertPrints(["foo()", "bar()"]):
221 217 ip.run_cell(syntax_error_at_runtime)
222 218 del ip.user_ns['bar']
223 219 del ip.user_ns['foo']
224 220
225 221 def test_changing_py_file(self):
226 222 with TemporaryDirectory() as td:
227 223 fname = os.path.join(td, "foo.py")
228 224 with open(fname, 'w') as f:
229 225 f.write(se_file_1)
230 226
231 227 with tt.AssertPrints(["7/", "SyntaxError"]):
232 228 ip.magic("run " + fname)
233 229
234 230 # Modify the file
235 231 with open(fname, 'w') as f:
236 232 f.write(se_file_2)
237 233
238 234 # The SyntaxError should point to the correct line
239 235 with tt.AssertPrints(["7/", "SyntaxError"]):
240 236 ip.magic("run " + fname)
241 237
242 238 def test_non_syntaxerror(self):
243 239 # SyntaxTB may be called with an error other than a SyntaxError
244 240 # See e.g. gh-4361
245 241 try:
246 242 raise ValueError('QWERTY')
247 243 except ValueError:
248 244 with tt.AssertPrints('QWERTY'):
249 245 ip.showsyntaxerror()
250 246
251 247 import sys
252 248
253 249 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
254 250 """
255 251 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
256 252 """
257 253 class MemoryErrorTest(unittest.TestCase):
258 254 def test_memoryerror(self):
259 255 memoryerror_code = "(" * 200 + ")" * 200
260 256 with tt.AssertPrints("MemoryError"):
261 257 ip.run_cell(memoryerror_code)
262 258
263 259
264 260 class Python3ChainedExceptionsTest(unittest.TestCase):
265 261 DIRECT_CAUSE_ERROR_CODE = """
266 262 try:
267 263 x = 1 + 2
268 264 print(not_defined_here)
269 265 except Exception as e:
270 266 x += 55
271 267 x - 1
272 268 y = {}
273 269 raise KeyError('uh') from e
274 270 """
275 271
276 272 EXCEPTION_DURING_HANDLING_CODE = """
277 273 try:
278 274 x = 1 + 2
279 275 print(not_defined_here)
280 276 except Exception as e:
281 277 x += 55
282 278 x - 1
283 279 y = {}
284 280 raise KeyError('uh')
285 281 """
286 282
287 283 SUPPRESS_CHAINING_CODE = """
288 284 try:
289 285 1/0
290 286 except Exception:
291 287 raise ValueError("Yikes") from None
292 288 """
293 289
294 290 def test_direct_cause_error(self):
295 291 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
296 292 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
297 293
298 294 def test_exception_during_handling_error(self):
299 295 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
300 296 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
301 297
302 298 def test_suppress_exception_chaining(self):
303 299 with tt.AssertNotPrints("ZeroDivisionError"), \
304 300 tt.AssertPrints("ValueError", suppress=False):
305 301 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
306 302
307 303 def test_plain_direct_cause_error(self):
308 304 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
309 305 ip.run_cell("%xmode Plain")
310 306 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
311 307 ip.run_cell("%xmode Verbose")
312 308
313 309 def test_plain_exception_during_handling_error(self):
314 310 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
315 311 ip.run_cell("%xmode Plain")
316 312 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
317 313 ip.run_cell("%xmode Verbose")
318 314
319 315 def test_plain_suppress_exception_chaining(self):
320 316 with tt.AssertNotPrints("ZeroDivisionError"), \
321 317 tt.AssertPrints("ValueError", suppress=False):
322 318 ip.run_cell("%xmode Plain")
323 319 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
324 320 ip.run_cell("%xmode Verbose")
325 321
326 322
327 323 class RecursionTest(unittest.TestCase):
328 324 DEFINITIONS = """
329 325 def non_recurs():
330 326 1/0
331 327
332 328 def r1():
333 329 r1()
334 330
335 331 def r3a():
336 332 r3b()
337 333
338 334 def r3b():
339 335 r3c()
340 336
341 337 def r3c():
342 338 r3a()
343 339
344 340 def r3o1():
345 341 r3a()
346 342
347 343 def r3o2():
348 344 r3o1()
349 345 """
350 346 def setUp(self):
351 347 ip.run_cell(self.DEFINITIONS)
352 348
353 349 def test_no_recursion(self):
354 350 with tt.AssertNotPrints("skipping similar frames"):
355 351 ip.run_cell("non_recurs()")
356 352
357 353 @recursionlimit(200)
358 354 def test_recursion_one_frame(self):
359 355 with tt.AssertPrints(re.compile(
360 356 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
361 357 ):
362 358 ip.run_cell("r1()")
363 359
364 360 @recursionlimit(160)
365 361 def test_recursion_three_frames(self):
366 362 with tt.AssertPrints("[... skipping similar frames: "), \
367 363 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
368 364 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
369 365 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
370 366 ip.run_cell("r3o2()")
371 367
372 368
373 369 #----------------------------------------------------------------------------
374 370
375 371 # module testing (minimal)
376 372 def test_handlers():
377 373 def spam(c, d_e):
378 374 (d, e) = d_e
379 375 x = c + d
380 376 y = c * d
381 377 foo(x, y)
382 378
383 379 def foo(a, b, bar=1):
384 380 eggs(a, b + bar)
385 381
386 382 def eggs(f, g, z=globals()):
387 383 h = f + g
388 384 i = f - g
389 385 return h / i
390 386
391 387 buff = io.StringIO()
392 388
393 389 buff.write('')
394 390 buff.write('*** Before ***')
395 391 try:
396 392 buff.write(spam(1, (2, 3)))
397 393 except:
398 394 traceback.print_exc(file=buff)
399 395
400 396 handler = ColorTB(ostream=buff)
401 397 buff.write('*** ColorTB ***')
402 398 try:
403 399 buff.write(spam(1, (2, 3)))
404 400 except:
405 401 handler(*sys.exc_info())
406 402 buff.write('')
407 403
408 404 handler = VerboseTB(ostream=buff)
409 405 buff.write('*** VerboseTB ***')
410 406 try:
411 407 buff.write(spam(1, (2, 3)))
412 408 except:
413 409 handler(*sys.exc_info())
414 410 buff.write('')
@@ -1,1132 +1,1142 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Verbose and colourful traceback formatting.
4 4
5 5 **ColorTB**
6 6
7 7 I've always found it a bit hard to visually parse tracebacks in Python. The
8 8 ColorTB class is a solution to that problem. It colors the different parts of a
9 9 traceback in a manner similar to what you would expect from a syntax-highlighting
10 10 text editor.
11 11
12 12 Installation instructions for ColorTB::
13 13
14 14 import sys,ultratb
15 15 sys.excepthook = ultratb.ColorTB()
16 16
17 17 **VerboseTB**
18 18
19 19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
20 20 of useful info when a traceback occurs. Ping originally had it spit out HTML
21 21 and intended it for CGI programmers, but why should they have all the fun? I
22 22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
23 23 but kind of neat, and maybe useful for long-running programs that you believe
24 24 are bug-free. If a crash *does* occur in that type of program you want details.
25 25 Give it a shot--you'll love it or you'll hate it.
26 26
27 27 .. note::
28 28
29 29 The Verbose mode prints the variables currently visible where the exception
30 30 happened (shortening their strings if too long). This can potentially be
31 31 very slow, if you happen to have a huge data structure whose string
32 32 representation is complex to compute. Your computer may appear to freeze for
33 33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
34 34 with Ctrl-C (maybe hitting it more than once).
35 35
36 36 If you encounter this kind of situation often, you may want to use the
37 37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
38 38 variables (but otherwise includes the information and context given by
39 39 Verbose).
40 40
41 41 .. note::
42 42
43 43 The verbose mode print all variables in the stack, which means it can
44 44 potentially leak sensitive information like access keys, or unencrypted
45 45 password.
46 46
47 47 Installation instructions for VerboseTB::
48 48
49 49 import sys,ultratb
50 50 sys.excepthook = ultratb.VerboseTB()
51 51
52 52 Note: Much of the code in this module was lifted verbatim from the standard
53 53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
54 54
55 55 Color schemes
56 56 -------------
57 57
58 58 The colors are defined in the class TBTools through the use of the
59 59 ColorSchemeTable class. Currently the following exist:
60 60
61 61 - NoColor: allows all of this module to be used in any terminal (the color
62 62 escapes are just dummy blank strings).
63 63
64 64 - Linux: is meant to look good in a terminal like the Linux console (black
65 65 or very dark background).
66 66
67 67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
68 68 in light background terminals.
69 69
70 70 - Neutral: a neutral color scheme that should be readable on both light and
71 71 dark background
72 72
73 73 You can implement other color schemes easily, the syntax is fairly
74 74 self-explanatory. Please send back new schemes you develop to the author for
75 75 possible inclusion in future releases.
76 76
77 77 Inheritance diagram:
78 78
79 79 .. inheritance-diagram:: IPython.core.ultratb
80 80 :parts: 3
81 81 """
82 82
83 83 #*****************************************************************************
84 84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
85 85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
86 86 #
87 87 # Distributed under the terms of the BSD License. The full license is in
88 88 # the file COPYING, distributed as part of this software.
89 89 #*****************************************************************************
90 90
91 91
92 92 import inspect
93 93 import linecache
94 94 import pydoc
95 95 import sys
96 96 import time
97 97 import traceback
98 98
99 99 import stack_data
100 100 from pygments.formatters.terminal256 import Terminal256Formatter
101 101 from pygments.styles import get_style_by_name
102 102
103 103 # IPython's own modules
104 104 from IPython import get_ipython
105 105 from IPython.core import debugger
106 106 from IPython.core.display_trap import DisplayTrap
107 107 from IPython.core.excolors import exception_colors
108 108 from IPython.utils import path as util_path
109 109 from IPython.utils import py3compat
110 110 from IPython.utils.terminal import get_terminal_size
111 111
112 112 import IPython.utils.colorable as colorable
113 113
114 114 # Globals
115 115 # amount of space to put line numbers before verbose tracebacks
116 116 INDENT_SIZE = 8
117 117
118 118 # Default color scheme. This is used, for example, by the traceback
119 119 # formatter. When running in an actual IPython instance, the user's rc.colors
120 120 # value is used, but having a module global makes this functionality available
121 121 # to users of ultratb who are NOT running inside ipython.
122 122 DEFAULT_SCHEME = 'NoColor'
123 123
124 124 # ---------------------------------------------------------------------------
125 125 # Code begins
126 126
127 127 # Helper function -- largely belongs to VerboseTB, but we need the same
128 128 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
129 129 # can be recognized properly by ipython.el's py-traceback-line-re
130 130 # (SyntaxErrors have to be treated specially because they have no traceback)
131 131
132 132
133 133 def _format_traceback_lines(lines, Colors, has_colors, lvals):
134 134 """
135 135 Format tracebacks lines with pointing arrow, leading numbers...
136 136
137 137 Parameters
138 138 ----------
139 139 lines : list[Line]
140 140 Colors
141 141 ColorScheme used.
142 142 lvals : str
143 143 Values of local variables, already colored, to inject just after the error line.
144 144 """
145 145 numbers_width = INDENT_SIZE - 1
146 146 res = []
147 147
148 148 for stack_line in lines:
149 149 if stack_line is stack_data.LINE_GAP:
150 150 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
151 151 continue
152 152
153 153 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
154 154 lineno = stack_line.lineno
155 155 if stack_line.is_current:
156 156 # This is the line with the error
157 157 pad = numbers_width - len(str(lineno))
158 158 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
159 159 start_color = Colors.linenoEm
160 160 else:
161 161 num = '%*s' % (numbers_width, lineno)
162 162 start_color = Colors.lineno
163 163
164 164 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
165 165
166 166 res.append(line)
167 167 if lvals and stack_line.is_current:
168 168 res.append(lvals + '\n')
169 169 return res
170 170
171 171
172 def _format_filename(file, ColorFilename, ColorNormal):
172 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
173 173 """
174 174 Format filename lines with `In [n]` if it's the nth code cell or `File *.py` if it's a module.
175 175
176 176 Parameters
177 177 ----------
178 178 file : str
179 179 ColorFilename
180 180 ColorScheme's filename coloring to be used.
181 181 ColorNormal
182 182 ColorScheme's normal coloring to be used.
183 183 """
184 184 ipinst = get_ipython()
185 185
186 186 if ipinst is not None and file in ipinst.compile._filename_map:
187 187 file = "[%s]" % ipinst.compile._filename_map[file]
188 tpl_link = "Input %sIn %%s%s" % (ColorFilename, ColorNormal)
188 tpl_link = f"Input {ColorFilename}In {{file}}{ColorNormal}"
189 189 else:
190 190 file = util_path.compress_user(
191 191 py3compat.cast_unicode(file, util_path.fs_encoding)
192 192 )
193 tpl_link = "File %s%%s%s" % (ColorFilename, ColorNormal)
193 if lineno is None:
194 tpl_link = f"File {ColorFilename}{{file}}{ColorNormal}"
195 else:
196 tpl_link = f"File {ColorFilename}{{file}}:{{lineno}}{ColorNormal}"
194 197
195 return tpl_link % file
198 return tpl_link.format(file=file, lineno=lineno)
196 199
197 200 #---------------------------------------------------------------------------
198 201 # Module classes
199 202 class TBTools(colorable.Colorable):
200 203 """Basic tools used by all traceback printer classes."""
201 204
202 205 # Number of frames to skip when reporting tracebacks
203 206 tb_offset = 0
204 207
205 208 def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None, parent=None, config=None):
206 209 # Whether to call the interactive pdb debugger after printing
207 210 # tracebacks or not
208 211 super(TBTools, self).__init__(parent=parent, config=config)
209 212 self.call_pdb = call_pdb
210 213
211 214 # Output stream to write to. Note that we store the original value in
212 215 # a private attribute and then make the public ostream a property, so
213 216 # that we can delay accessing sys.stdout until runtime. The way
214 217 # things are written now, the sys.stdout object is dynamically managed
215 218 # so a reference to it should NEVER be stored statically. This
216 219 # property approach confines this detail to a single location, and all
217 220 # subclasses can simply access self.ostream for writing.
218 221 self._ostream = ostream
219 222
220 223 # Create color table
221 224 self.color_scheme_table = exception_colors()
222 225
223 226 self.set_colors(color_scheme)
224 227 self.old_scheme = color_scheme # save initial value for toggles
225 228
226 229 if call_pdb:
227 230 self.pdb = debugger.Pdb()
228 231 else:
229 232 self.pdb = None
230 233
231 234 def _get_ostream(self):
232 235 """Output stream that exceptions are written to.
233 236
234 237 Valid values are:
235 238
236 239 - None: the default, which means that IPython will dynamically resolve
237 240 to sys.stdout. This ensures compatibility with most tools, including
238 241 Windows (where plain stdout doesn't recognize ANSI escapes).
239 242
240 243 - Any object with 'write' and 'flush' attributes.
241 244 """
242 245 return sys.stdout if self._ostream is None else self._ostream
243 246
244 247 def _set_ostream(self, val):
245 248 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
246 249 self._ostream = val
247 250
248 251 ostream = property(_get_ostream, _set_ostream)
249 252
250 253 def get_parts_of_chained_exception(self, evalue):
251 254 def get_chained_exception(exception_value):
252 255 cause = getattr(exception_value, '__cause__', None)
253 256 if cause:
254 257 return cause
255 258 if getattr(exception_value, '__suppress_context__', False):
256 259 return None
257 260 return getattr(exception_value, '__context__', None)
258 261
259 262 chained_evalue = get_chained_exception(evalue)
260 263
261 264 if chained_evalue:
262 265 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
263 266
264 267 def prepare_chained_exception_message(self, cause):
265 268 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
266 269 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
267 270
268 271 if cause:
269 272 message = [[direct_cause]]
270 273 else:
271 274 message = [[exception_during_handling]]
272 275 return message
273 276
274 277 @property
275 278 def has_colors(self):
276 279 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
277 280
278 281 def set_colors(self, *args, **kw):
279 282 """Shorthand access to the color table scheme selector method."""
280 283
281 284 # Set own color table
282 285 self.color_scheme_table.set_active_scheme(*args, **kw)
283 286 # for convenience, set Colors to the active scheme
284 287 self.Colors = self.color_scheme_table.active_colors
285 288 # Also set colors of debugger
286 289 if hasattr(self, 'pdb') and self.pdb is not None:
287 290 self.pdb.set_colors(*args, **kw)
288 291
289 292 def color_toggle(self):
290 293 """Toggle between the currently active color scheme and NoColor."""
291 294
292 295 if self.color_scheme_table.active_scheme_name == 'NoColor':
293 296 self.color_scheme_table.set_active_scheme(self.old_scheme)
294 297 self.Colors = self.color_scheme_table.active_colors
295 298 else:
296 299 self.old_scheme = self.color_scheme_table.active_scheme_name
297 300 self.color_scheme_table.set_active_scheme('NoColor')
298 301 self.Colors = self.color_scheme_table.active_colors
299 302
300 303 def stb2text(self, stb):
301 304 """Convert a structured traceback (a list) to a string."""
302 305 return '\n'.join(stb)
303 306
304 307 def text(self, etype, value, tb, tb_offset=None, context=5):
305 308 """Return formatted traceback.
306 309
307 310 Subclasses may override this if they add extra arguments.
308 311 """
309 312 tb_list = self.structured_traceback(etype, value, tb,
310 313 tb_offset, context)
311 314 return self.stb2text(tb_list)
312 315
313 316 def structured_traceback(self, etype, evalue, tb, tb_offset=None,
314 317 context=5, mode=None):
315 318 """Return a list of traceback frames.
316 319
317 320 Must be implemented by each class.
318 321 """
319 322 raise NotImplementedError()
320 323
321 324
322 325 #---------------------------------------------------------------------------
323 326 class ListTB(TBTools):
324 327 """Print traceback information from a traceback list, with optional color.
325 328
326 329 Calling requires 3 arguments: (etype, evalue, elist)
327 330 as would be obtained by::
328 331
329 332 etype, evalue, tb = sys.exc_info()
330 333 if tb:
331 334 elist = traceback.extract_tb(tb)
332 335 else:
333 336 elist = None
334 337
335 338 It can thus be used by programs which need to process the traceback before
336 339 printing (such as console replacements based on the code module from the
337 340 standard library).
338 341
339 342 Because they are meant to be called without a full traceback (only a
340 343 list), instances of this class can't call the interactive pdb debugger."""
341 344
342 345 def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None, parent=None, config=None):
343 346 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
344 347 ostream=ostream, parent=parent,config=config)
345 348
346 349 def __call__(self, etype, value, elist):
347 350 self.ostream.flush()
348 351 self.ostream.write(self.text(etype, value, elist))
349 352 self.ostream.write('\n')
350 353
351 354 def _extract_tb(self, tb):
352 355 if tb:
353 356 return traceback.extract_tb(tb)
354 357 else:
355 358 return None
356 359
357 360 def structured_traceback(self, etype, evalue, etb=None, tb_offset=None,
358 361 context=5):
359 362 """Return a color formatted string with the traceback info.
360 363
361 364 Parameters
362 365 ----------
363 366 etype : exception type
364 367 Type of the exception raised.
365 368 evalue : object
366 369 Data stored in the exception
367 370 etb : object
368 371 If list: List of frames, see class docstring for details.
369 372 If Traceback: Traceback of the exception.
370 373 tb_offset : int, optional
371 374 Number of frames in the traceback to skip. If not given, the
372 375 instance evalue is used (set in constructor).
373 376 context : int, optional
374 377 Number of lines of context information to print.
375 378
376 379 Returns
377 380 -------
378 381 String with formatted exception.
379 382 """
380 383 # This is a workaround to get chained_exc_ids in recursive calls
381 384 # etb should not be a tuple if structured_traceback is not recursive
382 385 if isinstance(etb, tuple):
383 386 etb, chained_exc_ids = etb
384 387 else:
385 388 chained_exc_ids = set()
386 389
387 390 if isinstance(etb, list):
388 391 elist = etb
389 392 elif etb is not None:
390 393 elist = self._extract_tb(etb)
391 394 else:
392 395 elist = []
393 396 tb_offset = self.tb_offset if tb_offset is None else tb_offset
394 397 Colors = self.Colors
395 398 out_list = []
396 399 if elist:
397 400
398 401 if tb_offset and len(elist) > tb_offset:
399 402 elist = elist[tb_offset:]
400 403
401 404 out_list.append('Traceback %s(most recent call last)%s:' %
402 405 (Colors.normalEm, Colors.Normal) + '\n')
403 406 out_list.extend(self._format_list(elist))
404 407 # The exception info should be a single entry in the list.
405 408 lines = ''.join(self._format_exception_only(etype, evalue))
406 409 out_list.append(lines)
407 410
408 411 exception = self.get_parts_of_chained_exception(evalue)
409 412
410 413 if exception and not id(exception[1]) in chained_exc_ids:
411 414 chained_exception_message = self.prepare_chained_exception_message(
412 415 evalue.__cause__)[0]
413 416 etype, evalue, etb = exception
414 417 # Trace exception to avoid infinite 'cause' loop
415 418 chained_exc_ids.add(id(exception[1]))
416 419 chained_exceptions_tb_offset = 0
417 420 out_list = (
418 421 self.structured_traceback(
419 422 etype, evalue, (etb, chained_exc_ids),
420 423 chained_exceptions_tb_offset, context)
421 424 + chained_exception_message
422 425 + out_list)
423 426
424 427 return out_list
425 428
426 429 def _format_list(self, extracted_list):
427 430 """Format a list of traceback entry tuples for printing.
428 431
429 432 Given a list of tuples as returned by extract_tb() or
430 433 extract_stack(), return a list of strings ready for printing.
431 434 Each string in the resulting list corresponds to the item with the
432 435 same index in the argument list. Each string ends in a newline;
433 436 the strings may contain internal newlines as well, for those items
434 437 whose source text line is not None.
435 438
436 439 Lifted almost verbatim from traceback.py
437 440 """
438 441
439 442 Colors = self.Colors
440 443 list = []
441 444 for filename, lineno, name, line in extracted_list[:-1]:
442 item = " %s, line %s%d%s, in %s%s%s\n" % (
443 _format_filename(filename, Colors.filename, Colors.Normal),
444 Colors.lineno,
445 lineno,
446 Colors.Normal,
445 item = " %s in %s%s%s\n" % (
446 _format_filename(
447 filename, Colors.filename, Colors.Normal, lineno=lineno
448 ),
447 449 Colors.name,
448 450 name,
449 451 Colors.Normal,
450 452 )
451 453 if line:
452 454 item += ' %s\n' % line.strip()
453 455 list.append(item)
454 456 # Emphasize the last entry
455 457 filename, lineno, name, line = extracted_list[-1]
456 item = "%s %s, line %s%d%s, in %s%s%s%s\n" % (
457 Colors.normalEm,
458 _format_filename(filename, Colors.filenameEm, Colors.normalEm),
459 Colors.linenoEm,
460 lineno,
458 item = "%s %s in %s%s%s%s\n" % (
461 459 Colors.normalEm,
460 _format_filename(
461 filename, Colors.filenameEm, Colors.normalEm, lineno=lineno
462 ),
462 463 Colors.nameEm,
463 464 name,
464 465 Colors.normalEm,
465 466 Colors.Normal,
466 467 )
467 468 if line:
468 469 item += '%s %s%s\n' % (Colors.line, line.strip(),
469 470 Colors.Normal)
470 471 list.append(item)
471 472 return list
472 473
473 474 def _format_exception_only(self, etype, value):
474 475 """Format the exception part of a traceback.
475 476
476 477 The arguments are the exception type and value such as given by
477 478 sys.exc_info()[:2]. The return value is a list of strings, each ending
478 479 in a newline. Normally, the list contains a single string; however,
479 480 for SyntaxError exceptions, it contains several lines that (when
480 481 printed) display detailed information about where the syntax error
481 482 occurred. The message indicating which exception occurred is the
482 483 always last string in the list.
483 484
484 485 Also lifted nearly verbatim from traceback.py
485 486 """
486 487 have_filedata = False
487 488 Colors = self.Colors
488 489 list = []
489 490 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
490 491 if value is None:
491 492 # Not sure if this can still happen in Python 2.6 and above
492 493 list.append(stype + '\n')
493 494 else:
494 495 if issubclass(etype, SyntaxError):
495 496 have_filedata = True
496 497 if not value.filename: value.filename = "<string>"
497 498 if value.lineno:
498 499 lineno = value.lineno
499 500 textline = linecache.getline(value.filename, value.lineno)
500 501 else:
501 502 lineno = "unknown"
502 503 textline = ""
503 504 list.append(
504 "%s %s, line %s%s%s\n"
505 "%s %s%s\n"
505 506 % (
506 507 Colors.normalEm,
507 508 _format_filename(
508 value.filename, Colors.filenameEm, Colors.normalEm
509 value.filename,
510 Colors.filenameEm,
511 Colors.normalEm,
512 lineno=(None if lineno == "unknown" else lineno),
509 513 ),
510 Colors.linenoEm,
511 lineno,
512 514 Colors.Normal,
513 515 )
514 516 )
515 517 if textline == "":
516 518 textline = py3compat.cast_unicode(value.text, "utf-8")
517 519
518 520 if textline is not None:
519 521 i = 0
520 522 while i < len(textline) and textline[i].isspace():
521 523 i += 1
522 524 list.append('%s %s%s\n' % (Colors.line,
523 525 textline.strip(),
524 526 Colors.Normal))
525 527 if value.offset is not None:
526 528 s = ' '
527 529 for c in textline[i:value.offset - 1]:
528 530 if c.isspace():
529 531 s += c
530 532 else:
531 533 s += ' '
532 534 list.append('%s%s^%s\n' % (Colors.caret, s,
533 535 Colors.Normal))
534 536
535 537 try:
536 538 s = value.msg
537 539 except Exception:
538 540 s = self._some_str(value)
539 541 if s:
540 542 list.append('%s%s:%s %s\n' % (stype, Colors.excName,
541 543 Colors.Normal, s))
542 544 else:
543 545 list.append('%s\n' % stype)
544 546
545 547 # sync with user hooks
546 548 if have_filedata:
547 549 ipinst = get_ipython()
548 550 if ipinst is not None:
549 551 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
550 552
551 553 return list
552 554
553 555 def get_exception_only(self, etype, value):
554 556 """Only print the exception type and message, without a traceback.
555 557
556 558 Parameters
557 559 ----------
558 560 etype : exception type
559 561 value : exception value
560 562 """
561 563 return ListTB.structured_traceback(self, etype, value)
562 564
563 565 def show_exception_only(self, etype, evalue):
564 566 """Only print the exception type and message, without a traceback.
565 567
566 568 Parameters
567 569 ----------
568 570 etype : exception type
569 571 evalue : exception value
570 572 """
571 573 # This method needs to use __call__ from *this* class, not the one from
572 574 # a subclass whose signature or behavior may be different
573 575 ostream = self.ostream
574 576 ostream.flush()
575 577 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
576 578 ostream.flush()
577 579
578 580 def _some_str(self, value):
579 581 # Lifted from traceback.py
580 582 try:
581 583 return py3compat.cast_unicode(str(value))
582 584 except:
583 585 return u'<unprintable %s object>' % type(value).__name__
584 586
585 587
586 588 #----------------------------------------------------------------------------
587 589 class VerboseTB(TBTools):
588 590 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
589 591 of HTML. Requires inspect and pydoc. Crazy, man.
590 592
591 593 Modified version which optionally strips the topmost entries from the
592 594 traceback, to be used with alternate interpreters (because their own code
593 595 would appear in the traceback)."""
594 596
595 597 def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None,
596 598 tb_offset=0, long_header=False, include_vars=True,
597 599 check_cache=None, debugger_cls = None,
598 600 parent=None, config=None):
599 601 """Specify traceback offset, headers and color scheme.
600 602
601 603 Define how many frames to drop from the tracebacks. Calling it with
602 604 tb_offset=1 allows use of this handler in interpreters which will have
603 605 their own code at the top of the traceback (VerboseTB will first
604 606 remove that frame before printing the traceback info)."""
605 607 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
606 608 ostream=ostream, parent=parent, config=config)
607 609 self.tb_offset = tb_offset
608 610 self.long_header = long_header
609 611 self.include_vars = include_vars
610 612 # By default we use linecache.checkcache, but the user can provide a
611 613 # different check_cache implementation. This is used by the IPython
612 614 # kernel to provide tracebacks for interactive code that is cached,
613 615 # by a compiler instance that flushes the linecache but preserves its
614 616 # own code cache.
615 617 if check_cache is None:
616 618 check_cache = linecache.checkcache
617 619 self.check_cache = check_cache
618 620
619 621 self.debugger_cls = debugger_cls or debugger.Pdb
620 622 self.skip_hidden = True
621 623
622 624 def format_record(self, frame_info):
623 625 """Format a single stack frame"""
624 626 Colors = self.Colors # just a shorthand + quicker name lookup
625 627 ColorsNormal = Colors.Normal # used a lot
626 628
627 629 if isinstance(frame_info, stack_data.RepeatedFrames):
628 630 return ' %s[... skipping similar frames: %s]%s\n' % (
629 631 Colors.excName, frame_info.description, ColorsNormal)
630 632
631 indent = ' ' * INDENT_SIZE
632 em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal)
633 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
634 ColorsNormal)
635 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
636 (Colors.vName, Colors.valEm, ColorsNormal)
637 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
633 indent = " " * INDENT_SIZE
634 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
635 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
636 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
637 Colors.vName,
638 Colors.valEm,
639 ColorsNormal,
640 )
641 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
638 642
639 link = _format_filename(frame_info.filename, Colors.filenameEm, ColorsNormal)
643 link = _format_filename(
644 frame_info.filename,
645 Colors.filenameEm,
646 ColorsNormal,
647 lineno=frame_info.lineno,
648 )
640 649 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
641 650
642 651 func = frame_info.executing.code_qualname()
643 if func == '<module>':
644 call = tpl_call % (func, '')
652 if func == "<module>":
653 call = tpl_call.format(file=func, scope="")
645 654 else:
646 655 # Decide whether to include variable details or not
647 656 var_repr = eqrepr if self.include_vars else nullrepr
648 657 try:
649 call = tpl_call % (func, inspect.formatargvalues(args,
650 varargs, varkw,
651 locals_, formatvalue=var_repr))
658 scope = inspect.formatargvalues(
659 args, varargs, varkw, locals_, formatvalue=var_repr
660 )
661 call = tpl_call.format(file=func, scope=scope)
652 662 except KeyError:
653 663 # This happens in situations like errors inside generator
654 664 # expressions, where local variables are listed in the
655 665 # line, but can't be extracted from the frame. I'm not
656 666 # 100% sure this isn't actually a bug in inspect itself,
657 667 # but since there's no info for us to compute with, the
658 668 # best we can do is report the failure and move on. Here
659 669 # we must *not* call any traceback construction again,
660 670 # because that would mess up use of %debug later on. So we
661 671 # simply report the failure and move on. The only
662 672 # limitation will be that this frame won't have locals
663 673 # listed in the call signature. Quite subtle problem...
664 674 # I can't think of a good way to validate this in a unit
665 675 # test, but running a script consisting of:
666 676 # dict( (k,v.strip()) for (k,v) in range(10) )
667 677 # will illustrate the error, if this exception catch is
668 678 # disabled.
669 679 call = tpl_call_fail % func
670 680
671 681 lvals = ''
672 682 lvals_list = []
673 683 if self.include_vars:
674 684 try:
675 685 # we likely want to fix stackdata at some point, but
676 686 # still need a workaround.
677 687 fibp = frame_info.variables_in_executing_piece
678 688 for var in fibp:
679 689 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
680 690 except Exception:
681 691 lvals_list.append(
682 692 "Exception trying to inspect frame. No more locals available."
683 693 )
684 694 if lvals_list:
685 695 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
686 696
687 697 result = "%s, %s\n" % (link, call)
688 698
689 699 result += ''.join(_format_traceback_lines(frame_info.lines, Colors, self.has_colors, lvals))
690 700 return result
691 701
692 702 def prepare_header(self, etype, long_version=False):
693 703 colors = self.Colors # just a shorthand + quicker name lookup
694 704 colorsnormal = colors.Normal # used a lot
695 705 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
696 706 width = min(75, get_terminal_size()[0])
697 707 if long_version:
698 708 # Header with the exception type, python version, and date
699 709 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
700 710 date = time.ctime(time.time())
701 711
702 712 head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * width, colorsnormal,
703 713 exc, ' ' * (width - len(str(etype)) - len(pyver)),
704 714 pyver, date.rjust(width) )
705 715 head += "\nA problem occurred executing Python code. Here is the sequence of function" \
706 716 "\ncalls leading up to the error, with the most recent (innermost) call last."
707 717 else:
708 718 # Simplified header
709 719 head = '%s%s' % (exc, 'Traceback (most recent call last)'. \
710 720 rjust(width - len(str(etype))) )
711 721
712 722 return head
713 723
714 724 def format_exception(self, etype, evalue):
715 725 colors = self.Colors # just a shorthand + quicker name lookup
716 726 colorsnormal = colors.Normal # used a lot
717 727 # Get (safely) a string form of the exception info
718 728 try:
719 729 etype_str, evalue_str = map(str, (etype, evalue))
720 730 except:
721 731 # User exception is improperly defined.
722 732 etype, evalue = str, sys.exc_info()[:2]
723 733 etype_str, evalue_str = map(str, (etype, evalue))
724 734 # ... and format it
725 735 return ['%s%s%s: %s' % (colors.excName, etype_str,
726 736 colorsnormal, py3compat.cast_unicode(evalue_str))]
727 737
728 738 def format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_context, tb_offset):
729 739 """Formats the header, traceback and exception message for a single exception.
730 740
731 741 This may be called multiple times by Python 3 exception chaining
732 742 (PEP 3134).
733 743 """
734 744 # some locals
735 745 orig_etype = etype
736 746 try:
737 747 etype = etype.__name__
738 748 except AttributeError:
739 749 pass
740 750
741 751 tb_offset = self.tb_offset if tb_offset is None else tb_offset
742 752 head = self.prepare_header(etype, self.long_header)
743 753 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
744 754
745 755 frames = []
746 756 skipped = 0
747 757 lastrecord = len(records) - 1
748 758 for i, r in enumerate(records):
749 759 if not isinstance(r, stack_data.RepeatedFrames) and self.skip_hidden:
750 760 if r.frame.f_locals.get("__tracebackhide__", 0) and i != lastrecord:
751 761 skipped += 1
752 762 continue
753 763 if skipped:
754 764 Colors = self.Colors # just a shorthand + quicker name lookup
755 765 ColorsNormal = Colors.Normal # used a lot
756 766 frames.append(
757 767 " %s[... skipping hidden %s frame]%s\n"
758 768 % (Colors.excName, skipped, ColorsNormal)
759 769 )
760 770 skipped = 0
761 771 frames.append(self.format_record(r))
762 772 if skipped:
763 773 Colors = self.Colors # just a shorthand + quicker name lookup
764 774 ColorsNormal = Colors.Normal # used a lot
765 775 frames.append(
766 776 " %s[... skipping hidden %s frame]%s\n"
767 777 % (Colors.excName, skipped, ColorsNormal)
768 778 )
769 779
770 780 formatted_exception = self.format_exception(etype, evalue)
771 781 if records:
772 782 frame_info = records[-1]
773 783 ipinst = get_ipython()
774 784 if ipinst is not None:
775 785 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
776 786
777 787 return [[head] + frames + [''.join(formatted_exception[0])]]
778 788
779 789 def get_records(self, etb, number_of_lines_of_context, tb_offset):
780 790 context = number_of_lines_of_context - 1
781 791 after = context // 2
782 792 before = context - after
783 793 if self.has_colors:
784 794 style = get_style_by_name('default')
785 795 style = stack_data.style_with_executing_node(style, 'bg:#00005f')
786 796 formatter = Terminal256Formatter(style=style)
787 797 else:
788 798 formatter = None
789 799 options = stack_data.Options(
790 800 before=before,
791 801 after=after,
792 802 pygments_formatter=formatter,
793 803 )
794 804 return list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
795 805
796 806 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
797 807 number_of_lines_of_context=5):
798 808 """Return a nice text document describing the traceback."""
799 809
800 810 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
801 811 tb_offset)
802 812
803 813 colors = self.Colors # just a shorthand + quicker name lookup
804 814 colorsnormal = colors.Normal # used a lot
805 815 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
806 816 structured_traceback_parts = [head]
807 817 chained_exceptions_tb_offset = 0
808 818 lines_of_context = 3
809 819 formatted_exceptions = formatted_exception
810 820 exception = self.get_parts_of_chained_exception(evalue)
811 821 if exception:
812 822 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
813 823 etype, evalue, etb = exception
814 824 else:
815 825 evalue = None
816 826 chained_exc_ids = set()
817 827 while evalue:
818 828 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
819 829 chained_exceptions_tb_offset)
820 830 exception = self.get_parts_of_chained_exception(evalue)
821 831
822 832 if exception and not id(exception[1]) in chained_exc_ids:
823 833 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
824 834 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
825 835 etype, evalue, etb = exception
826 836 else:
827 837 evalue = None
828 838
829 839 # we want to see exceptions in a reversed order:
830 840 # the first exception should be on top
831 841 for formatted_exception in reversed(formatted_exceptions):
832 842 structured_traceback_parts += formatted_exception
833 843
834 844 return structured_traceback_parts
835 845
836 846 def debugger(self, force=False):
837 847 """Call up the pdb debugger if desired, always clean up the tb
838 848 reference.
839 849
840 850 Keywords:
841 851
842 852 - force(False): by default, this routine checks the instance call_pdb
843 853 flag and does not actually invoke the debugger if the flag is false.
844 854 The 'force' option forces the debugger to activate even if the flag
845 855 is false.
846 856
847 857 If the call_pdb flag is set, the pdb interactive debugger is
848 858 invoked. In all cases, the self.tb reference to the current traceback
849 859 is deleted to prevent lingering references which hamper memory
850 860 management.
851 861
852 862 Note that each call to pdb() does an 'import readline', so if your app
853 863 requires a special setup for the readline completers, you'll have to
854 864 fix that by hand after invoking the exception handler."""
855 865
856 866 if force or self.call_pdb:
857 867 if self.pdb is None:
858 868 self.pdb = self.debugger_cls()
859 869 # the system displayhook may have changed, restore the original
860 870 # for pdb
861 871 display_trap = DisplayTrap(hook=sys.__displayhook__)
862 872 with display_trap:
863 873 self.pdb.reset()
864 874 # Find the right frame so we don't pop up inside ipython itself
865 875 if hasattr(self, 'tb') and self.tb is not None:
866 876 etb = self.tb
867 877 else:
868 878 etb = self.tb = sys.last_traceback
869 879 while self.tb is not None and self.tb.tb_next is not None:
870 880 self.tb = self.tb.tb_next
871 881 if etb and etb.tb_next:
872 882 etb = etb.tb_next
873 883 self.pdb.botframe = etb.tb_frame
874 884 self.pdb.interaction(None, etb)
875 885
876 886 if hasattr(self, 'tb'):
877 887 del self.tb
878 888
879 889 def handler(self, info=None):
880 890 (etype, evalue, etb) = info or sys.exc_info()
881 891 self.tb = etb
882 892 ostream = self.ostream
883 893 ostream.flush()
884 894 ostream.write(self.text(etype, evalue, etb))
885 895 ostream.write('\n')
886 896 ostream.flush()
887 897
888 898 # Changed so an instance can just be called as VerboseTB_inst() and print
889 899 # out the right info on its own.
890 900 def __call__(self, etype=None, evalue=None, etb=None):
891 901 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
892 902 if etb is None:
893 903 self.handler()
894 904 else:
895 905 self.handler((etype, evalue, etb))
896 906 try:
897 907 self.debugger()
898 908 except KeyboardInterrupt:
899 909 print("\nKeyboardInterrupt")
900 910
901 911
902 912 #----------------------------------------------------------------------------
903 913 class FormattedTB(VerboseTB, ListTB):
904 914 """Subclass ListTB but allow calling with a traceback.
905 915
906 916 It can thus be used as a sys.excepthook for Python > 2.1.
907 917
908 918 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
909 919
910 920 Allows a tb_offset to be specified. This is useful for situations where
911 921 one needs to remove a number of topmost frames from the traceback (such as
912 922 occurs with python programs that themselves execute other python code,
913 923 like Python shells). """
914 924
915 925 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
916 926 ostream=None,
917 927 tb_offset=0, long_header=False, include_vars=False,
918 928 check_cache=None, debugger_cls=None,
919 929 parent=None, config=None):
920 930
921 931 # NEVER change the order of this list. Put new modes at the end:
922 932 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
923 933 self.verbose_modes = self.valid_modes[1:3]
924 934
925 935 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
926 936 ostream=ostream, tb_offset=tb_offset,
927 937 long_header=long_header, include_vars=include_vars,
928 938 check_cache=check_cache, debugger_cls=debugger_cls,
929 939 parent=parent, config=config)
930 940
931 941 # Different types of tracebacks are joined with different separators to
932 942 # form a single string. They are taken from this dict
933 943 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
934 944 Minimal='')
935 945 # set_mode also sets the tb_join_char attribute
936 946 self.set_mode(mode)
937 947
938 948 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
939 949 tb_offset = self.tb_offset if tb_offset is None else tb_offset
940 950 mode = self.mode
941 951 if mode in self.verbose_modes:
942 952 # Verbose modes need a full traceback
943 953 return VerboseTB.structured_traceback(
944 954 self, etype, value, tb, tb_offset, number_of_lines_of_context
945 955 )
946 956 elif mode == 'Minimal':
947 957 return ListTB.get_exception_only(self, etype, value)
948 958 else:
949 959 # We must check the source cache because otherwise we can print
950 960 # out-of-date source code.
951 961 self.check_cache()
952 962 # Now we can extract and format the exception
953 963 return ListTB.structured_traceback(
954 964 self, etype, value, tb, tb_offset, number_of_lines_of_context
955 965 )
956 966
957 967 def stb2text(self, stb):
958 968 """Convert a structured traceback (a list) to a string."""
959 969 return self.tb_join_char.join(stb)
960 970
961 971
962 972 def set_mode(self, mode=None):
963 973 """Switch to the desired mode.
964 974
965 975 If mode is not specified, cycles through the available modes."""
966 976
967 977 if not mode:
968 978 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
969 979 len(self.valid_modes)
970 980 self.mode = self.valid_modes[new_idx]
971 981 elif mode not in self.valid_modes:
972 982 raise ValueError('Unrecognized mode in FormattedTB: <' + mode + '>\n'
973 983 'Valid modes: ' + str(self.valid_modes))
974 984 else:
975 985 self.mode = mode
976 986 # include variable details only in 'Verbose' mode
977 987 self.include_vars = (self.mode == self.valid_modes[2])
978 988 # Set the join character for generating text tracebacks
979 989 self.tb_join_char = self._join_chars[self.mode]
980 990
981 991 # some convenient shortcuts
982 992 def plain(self):
983 993 self.set_mode(self.valid_modes[0])
984 994
985 995 def context(self):
986 996 self.set_mode(self.valid_modes[1])
987 997
988 998 def verbose(self):
989 999 self.set_mode(self.valid_modes[2])
990 1000
991 1001 def minimal(self):
992 1002 self.set_mode(self.valid_modes[3])
993 1003
994 1004
995 1005 #----------------------------------------------------------------------------
996 1006 class AutoFormattedTB(FormattedTB):
997 1007 """A traceback printer which can be called on the fly.
998 1008
999 1009 It will find out about exceptions by itself.
1000 1010
1001 1011 A brief example::
1002 1012
1003 1013 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1004 1014 try:
1005 1015 ...
1006 1016 except:
1007 1017 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1008 1018 """
1009 1019
1010 1020 def __call__(self, etype=None, evalue=None, etb=None,
1011 1021 out=None, tb_offset=None):
1012 1022 """Print out a formatted exception traceback.
1013 1023
1014 1024 Optional arguments:
1015 1025 - out: an open file-like object to direct output to.
1016 1026
1017 1027 - tb_offset: the number of frames to skip over in the stack, on a
1018 1028 per-call basis (this overrides temporarily the instance's tb_offset
1019 1029 given at initialization time."""
1020 1030
1021 1031 if out is None:
1022 1032 out = self.ostream
1023 1033 out.flush()
1024 1034 out.write(self.text(etype, evalue, etb, tb_offset))
1025 1035 out.write('\n')
1026 1036 out.flush()
1027 1037 # FIXME: we should remove the auto pdb behavior from here and leave
1028 1038 # that to the clients.
1029 1039 try:
1030 1040 self.debugger()
1031 1041 except KeyboardInterrupt:
1032 1042 print("\nKeyboardInterrupt")
1033 1043
1034 1044 def structured_traceback(self, etype=None, value=None, tb=None,
1035 1045 tb_offset=None, number_of_lines_of_context=5):
1036 1046 if etype is None:
1037 1047 etype, value, tb = sys.exc_info()
1038 1048 if isinstance(tb, tuple):
1039 1049 # tb is a tuple if this is a chained exception.
1040 1050 self.tb = tb[0]
1041 1051 else:
1042 1052 self.tb = tb
1043 1053 return FormattedTB.structured_traceback(
1044 1054 self, etype, value, tb, tb_offset, number_of_lines_of_context)
1045 1055
1046 1056
1047 1057 #---------------------------------------------------------------------------
1048 1058
1049 1059 # A simple class to preserve Nathan's original functionality.
1050 1060 class ColorTB(FormattedTB):
1051 1061 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1052 1062
1053 1063 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1054 1064 FormattedTB.__init__(self, color_scheme=color_scheme,
1055 1065 call_pdb=call_pdb, **kwargs)
1056 1066
1057 1067
1058 1068 class SyntaxTB(ListTB):
1059 1069 """Extension which holds some state: the last exception value"""
1060 1070
1061 1071 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1062 1072 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1063 1073 self.last_syntax_error = None
1064 1074
1065 1075 def __call__(self, etype, value, elist):
1066 1076 self.last_syntax_error = value
1067 1077
1068 1078 ListTB.__call__(self, etype, value, elist)
1069 1079
1070 1080 def structured_traceback(self, etype, value, elist, tb_offset=None,
1071 1081 context=5):
1072 1082 # If the source file has been edited, the line in the syntax error can
1073 1083 # be wrong (retrieved from an outdated cache). This replaces it with
1074 1084 # the current value.
1075 1085 if isinstance(value, SyntaxError) \
1076 1086 and isinstance(value.filename, str) \
1077 1087 and isinstance(value.lineno, int):
1078 1088 linecache.checkcache(value.filename)
1079 1089 newtext = linecache.getline(value.filename, value.lineno)
1080 1090 if newtext:
1081 1091 value.text = newtext
1082 1092 self.last_syntax_error = value
1083 1093 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1084 1094 tb_offset=tb_offset, context=context)
1085 1095
1086 1096 def clear_err_state(self):
1087 1097 """Return the current error state and clear it"""
1088 1098 e = self.last_syntax_error
1089 1099 self.last_syntax_error = None
1090 1100 return e
1091 1101
1092 1102 def stb2text(self, stb):
1093 1103 """Convert a structured traceback (a list) to a string."""
1094 1104 return ''.join(stb)
1095 1105
1096 1106
1097 1107 # some internal-use functions
1098 1108 def text_repr(value):
1099 1109 """Hopefully pretty robust repr equivalent."""
1100 1110 # this is pretty horrible but should always return *something*
1101 1111 try:
1102 1112 return pydoc.text.repr(value)
1103 1113 except KeyboardInterrupt:
1104 1114 raise
1105 1115 except:
1106 1116 try:
1107 1117 return repr(value)
1108 1118 except KeyboardInterrupt:
1109 1119 raise
1110 1120 except:
1111 1121 try:
1112 1122 # all still in an except block so we catch
1113 1123 # getattr raising
1114 1124 name = getattr(value, '__name__', None)
1115 1125 if name:
1116 1126 # ick, recursion
1117 1127 return text_repr(name)
1118 1128 klass = getattr(value, '__class__', None)
1119 1129 if klass:
1120 1130 return '%s instance' % text_repr(klass)
1121 1131 except KeyboardInterrupt:
1122 1132 raise
1123 1133 except:
1124 1134 return 'UNRECOVERABLE REPR FAILURE'
1125 1135
1126 1136
1127 1137 def eqrepr(value, repr=text_repr):
1128 1138 return '=%s' % repr(value)
1129 1139
1130 1140
1131 1141 def nullrepr(value, repr=text_repr):
1132 1142 return ''
@@ -1,786 +1,786 b''
1 1 # Based on Pytest doctest.py
2 2 # Original license:
3 3 # The MIT License (MIT)
4 4 #
5 5 # Copyright (c) 2004-2021 Holger Krekel and others
6 6 """Discover and run ipdoctests in modules and test files."""
7 7 import builtins
8 8 import bdb
9 9 import inspect
10 10 import os
11 11 import platform
12 12 import sys
13 13 import traceback
14 14 import types
15 15 import warnings
16 16 from contextlib import contextmanager
17 17 from typing import Any
18 18 from typing import Callable
19 19 from typing import Dict
20 20 from typing import Generator
21 21 from typing import Iterable
22 22 from typing import List
23 23 from typing import Optional
24 24 from typing import Pattern
25 25 from typing import Sequence
26 26 from typing import Tuple
27 27 from typing import Type
28 28 from typing import TYPE_CHECKING
29 29 from typing import Union
30 30
31 31 import py.path
32 32
33 33 import pytest
34 34 from _pytest import outcomes
35 35 from _pytest._code.code import ExceptionInfo
36 36 from _pytest._code.code import ReprFileLocation
37 37 from _pytest._code.code import TerminalRepr
38 38 from _pytest._io import TerminalWriter
39 39 from _pytest.compat import safe_getattr
40 40 from _pytest.config import Config
41 41 from _pytest.config.argparsing import Parser
42 42 from _pytest.fixtures import FixtureRequest
43 43 from _pytest.nodes import Collector
44 44 from _pytest.outcomes import OutcomeException
45 45 from _pytest.pathlib import import_path
46 46 from _pytest.python_api import approx
47 47 from _pytest.warning_types import PytestWarning
48 48
49 49 if TYPE_CHECKING:
50 50 import doctest
51 51
52 52 DOCTEST_REPORT_CHOICE_NONE = "none"
53 53 DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
54 54 DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
55 55 DOCTEST_REPORT_CHOICE_UDIFF = "udiff"
56 56 DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure"
57 57
58 58 DOCTEST_REPORT_CHOICES = (
59 59 DOCTEST_REPORT_CHOICE_NONE,
60 60 DOCTEST_REPORT_CHOICE_CDIFF,
61 61 DOCTEST_REPORT_CHOICE_NDIFF,
62 62 DOCTEST_REPORT_CHOICE_UDIFF,
63 63 DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
64 64 )
65 65
66 66 # Lazy definition of runner class
67 67 RUNNER_CLASS = None
68 68 # Lazy definition of output checker class
69 69 CHECKER_CLASS: Optional[Type["IPDoctestOutputChecker"]] = None
70 70
71 71
72 72 def pytest_addoption(parser: Parser) -> None:
73 73 parser.addini(
74 74 "ipdoctest_optionflags",
75 75 "option flags for ipdoctests",
76 76 type="args",
77 77 default=["ELLIPSIS"],
78 78 )
79 79 parser.addini(
80 80 "ipdoctest_encoding", "encoding used for ipdoctest files", default="utf-8"
81 81 )
82 82 group = parser.getgroup("collect")
83 83 group.addoption(
84 84 "--ipdoctest-modules",
85 85 action="store_true",
86 86 default=False,
87 87 help="run ipdoctests in all .py modules",
88 88 dest="ipdoctestmodules",
89 89 )
90 90 group.addoption(
91 91 "--ipdoctest-report",
92 92 type=str.lower,
93 93 default="udiff",
94 94 help="choose another output format for diffs on ipdoctest failure",
95 95 choices=DOCTEST_REPORT_CHOICES,
96 96 dest="ipdoctestreport",
97 97 )
98 98 group.addoption(
99 99 "--ipdoctest-glob",
100 100 action="append",
101 101 default=[],
102 102 metavar="pat",
103 103 help="ipdoctests file matching pattern, default: test*.txt",
104 104 dest="ipdoctestglob",
105 105 )
106 106 group.addoption(
107 107 "--ipdoctest-ignore-import-errors",
108 108 action="store_true",
109 109 default=False,
110 110 help="ignore ipdoctest ImportErrors",
111 111 dest="ipdoctest_ignore_import_errors",
112 112 )
113 113 group.addoption(
114 114 "--ipdoctest-continue-on-failure",
115 115 action="store_true",
116 116 default=False,
117 117 help="for a given ipdoctest, continue to run after the first failure",
118 118 dest="ipdoctest_continue_on_failure",
119 119 )
120 120
121 121
122 122 def pytest_unconfigure() -> None:
123 123 global RUNNER_CLASS
124 124
125 125 RUNNER_CLASS = None
126 126
127 127
128 128 def pytest_collect_file(
129 129 path: py.path.local,
130 130 parent: Collector,
131 131 ) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]:
132 132 config = parent.config
133 133 if path.ext == ".py":
134 134 if config.option.ipdoctestmodules and not _is_setup_py(path):
135 135 mod: IPDoctestModule = IPDoctestModule.from_parent(parent, fspath=path)
136 136 return mod
137 137 elif _is_ipdoctest(config, path, parent):
138 138 txt: IPDoctestTextfile = IPDoctestTextfile.from_parent(parent, fspath=path)
139 139 return txt
140 140 return None
141 141
142 142
143 143 def _is_setup_py(path: py.path.local) -> bool:
144 144 if path.basename != "setup.py":
145 145 return False
146 146 contents = path.read_binary()
147 147 return b"setuptools" in contents or b"distutils" in contents
148 148
149 149
150 150 def _is_ipdoctest(config: Config, path: py.path.local, parent) -> bool:
151 151 if path.ext in (".txt", ".rst") and parent.session.isinitpath(path):
152 152 return True
153 153 globs = config.getoption("ipdoctestglob") or ["test*.txt"]
154 154 for glob in globs:
155 155 if path.check(fnmatch=glob):
156 156 return True
157 157 return False
158 158
159 159
160 160 class ReprFailDoctest(TerminalRepr):
161 161 def __init__(
162 162 self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]]
163 163 ) -> None:
164 164 self.reprlocation_lines = reprlocation_lines
165 165
166 166 def toterminal(self, tw: TerminalWriter) -> None:
167 167 for reprlocation, lines in self.reprlocation_lines:
168 168 for line in lines:
169 169 tw.line(line)
170 170 reprlocation.toterminal(tw)
171 171
172 172
173 173 class MultipleDoctestFailures(Exception):
174 174 def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None:
175 175 super().__init__()
176 176 self.failures = failures
177 177
178 178
179 179 def _init_runner_class() -> Type["IPDocTestRunner"]:
180 180 import doctest
181 181 from .ipdoctest import IPDocTestRunner
182 182
183 183 class PytestDoctestRunner(IPDocTestRunner):
184 184 """Runner to collect failures.
185 185
186 186 Note that the out variable in this case is a list instead of a
187 187 stdout-like object.
188 188 """
189 189
190 190 def __init__(
191 191 self,
192 192 checker: Optional["IPDoctestOutputChecker"] = None,
193 193 verbose: Optional[bool] = None,
194 194 optionflags: int = 0,
195 195 continue_on_failure: bool = True,
196 196 ) -> None:
197 197 super().__init__(checker=checker, verbose=verbose, optionflags=optionflags)
198 198 self.continue_on_failure = continue_on_failure
199 199
200 200 def report_failure(
201 201 self,
202 202 out,
203 203 test: "doctest.DocTest",
204 204 example: "doctest.Example",
205 205 got: str,
206 206 ) -> None:
207 207 failure = doctest.DocTestFailure(test, example, got)
208 208 if self.continue_on_failure:
209 209 out.append(failure)
210 210 else:
211 211 raise failure
212 212
213 213 def report_unexpected_exception(
214 214 self,
215 215 out,
216 216 test: "doctest.DocTest",
217 217 example: "doctest.Example",
218 218 exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType],
219 219 ) -> None:
220 220 if isinstance(exc_info[1], OutcomeException):
221 221 raise exc_info[1]
222 222 if isinstance(exc_info[1], bdb.BdbQuit):
223 223 outcomes.exit("Quitting debugger")
224 224 failure = doctest.UnexpectedException(test, example, exc_info)
225 225 if self.continue_on_failure:
226 226 out.append(failure)
227 227 else:
228 228 raise failure
229 229
230 230 return PytestDoctestRunner
231 231
232 232
233 233 def _get_runner(
234 234 checker: Optional["IPDoctestOutputChecker"] = None,
235 235 verbose: Optional[bool] = None,
236 236 optionflags: int = 0,
237 237 continue_on_failure: bool = True,
238 238 ) -> "IPDocTestRunner":
239 239 # We need this in order to do a lazy import on doctest
240 240 global RUNNER_CLASS
241 241 if RUNNER_CLASS is None:
242 242 RUNNER_CLASS = _init_runner_class()
243 243 # Type ignored because the continue_on_failure argument is only defined on
244 244 # PytestDoctestRunner, which is lazily defined so can't be used as a type.
245 245 return RUNNER_CLASS( # type: ignore
246 246 checker=checker,
247 247 verbose=verbose,
248 248 optionflags=optionflags,
249 249 continue_on_failure=continue_on_failure,
250 250 )
251 251
252 252
253 253 class IPDoctestItem(pytest.Item):
254 254 def __init__(
255 255 self,
256 256 name: str,
257 257 parent: "Union[IPDoctestTextfile, IPDoctestModule]",
258 258 runner: Optional["IPDocTestRunner"] = None,
259 259 dtest: Optional["doctest.DocTest"] = None,
260 260 ) -> None:
261 261 super().__init__(name, parent)
262 262 self.runner = runner
263 263 self.dtest = dtest
264 264 self.obj = None
265 265 self.fixture_request: Optional[FixtureRequest] = None
266 266
267 267 @classmethod
268 268 def from_parent( # type: ignore
269 269 cls,
270 270 parent: "Union[IPDoctestTextfile, IPDoctestModule]",
271 271 *,
272 272 name: str,
273 273 runner: "IPDocTestRunner",
274 274 dtest: "doctest.DocTest",
275 275 ):
276 276 # incompatible signature due to to imposed limits on sublcass
277 277 """The public named constructor."""
278 278 return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
279 279
280 280 def setup(self) -> None:
281 281 if self.dtest is not None:
282 282 self.fixture_request = _setup_fixtures(self)
283 283 globs = dict(getfixture=self.fixture_request.getfixturevalue)
284 284 for name, value in self.fixture_request.getfixturevalue(
285 285 "ipdoctest_namespace"
286 286 ).items():
287 287 globs[name] = value
288 288 self.dtest.globs.update(globs)
289 289
290 290 from .ipdoctest import IPExample
291 291
292 292 if isinstance(self.dtest.examples[0], IPExample):
293 293 # for IPython examples *only*, we swap the globals with the ipython
294 294 # namespace, after updating it with the globals (which doctest
295 295 # fills with the necessary info from the module being tested).
296 296 self._user_ns_orig = {}
297 297 self._user_ns_orig.update(_ip.user_ns)
298 298 _ip.user_ns.update(self.dtest.globs)
299 299 # We must remove the _ key in the namespace, so that Python's
300 300 # doctest code sets it naturally
301 301 _ip.user_ns.pop("_", None)
302 302 _ip.user_ns["__builtins__"] = builtins
303 303 self.dtest.globs = _ip.user_ns
304 304
305 305 def teardown(self) -> None:
306 306 from .ipdoctest import IPExample
307 307
308 308 # Undo the test.globs reassignment we made
309 309 if isinstance(self.dtest.examples[0], IPExample):
310 310 self.dtest.globs = {}
311 311 _ip.user_ns.clear()
312 312 _ip.user_ns.update(self._user_ns_orig)
313 313 del self._user_ns_orig
314 314
315 315 self.dtest.globs.clear()
316 316
317 317 def runtest(self) -> None:
318 318 assert self.dtest is not None
319 319 assert self.runner is not None
320 320 _check_all_skipped(self.dtest)
321 321 self._disable_output_capturing_for_darwin()
322 322 failures: List["doctest.DocTestFailure"] = []
323 323
324 324 # exec(compile(..., "single", ...), ...) puts result in builtins._
325 325 had_underscore_value = hasattr(builtins, "_")
326 326 underscore_original_value = getattr(builtins, "_", None)
327 327
328 328 # Save our current directory and switch out to the one where the
329 329 # test was originally created, in case another doctest did a
330 330 # directory change. We'll restore this in the finally clause.
331 331 curdir = os.getcwd()
332 332 os.chdir(self.fspath.dirname)
333 333 try:
334 334 # Type ignored because we change the type of `out` from what
335 335 # ipdoctest expects.
336 336 self.runner.run(self.dtest, out=failures, clear_globs=False) # type: ignore[arg-type]
337 337 finally:
338 338 os.chdir(curdir)
339 339 if had_underscore_value:
340 340 setattr(builtins, "_", underscore_original_value)
341 341 elif hasattr(builtins, "_"):
342 342 delattr(builtins, "_")
343 343
344 344 if failures:
345 345 raise MultipleDoctestFailures(failures)
346 346
347 347 def _disable_output_capturing_for_darwin(self) -> None:
348 348 """Disable output capturing. Otherwise, stdout is lost to ipdoctest (pytest#985)."""
349 349 if platform.system() != "Darwin":
350 350 return
351 351 capman = self.config.pluginmanager.getplugin("capturemanager")
352 352 if capman:
353 353 capman.suspend_global_capture(in_=True)
354 354 out, err = capman.read_global_capture()
355 355 sys.stdout.write(out)
356 356 sys.stderr.write(err)
357 357
358 358 # TODO: Type ignored -- breaks Liskov Substitution.
359 359 def repr_failure( # type: ignore[override]
360 360 self,
361 361 excinfo: ExceptionInfo[BaseException],
362 362 ) -> Union[str, TerminalRepr]:
363 363 import doctest
364 364
365 365 failures: Optional[
366 366 Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
367 367 ] = None
368 368 if isinstance(
369 369 excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
370 370 ):
371 371 failures = [excinfo.value]
372 372 elif isinstance(excinfo.value, MultipleDoctestFailures):
373 373 failures = excinfo.value.failures
374 374
375 375 if failures is not None:
376 376 reprlocation_lines = []
377 377 for failure in failures:
378 378 example = failure.example
379 379 test = failure.test
380 380 filename = test.filename
381 381 if test.lineno is None:
382 382 lineno = None
383 383 else:
384 384 lineno = test.lineno + example.lineno + 1
385 385 message = type(failure).__name__
386 386 # TODO: ReprFileLocation doesn't expect a None lineno.
387 387 reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type]
388 388 checker = _get_checker()
389 389 report_choice = _get_report_choice(
390 390 self.config.getoption("ipdoctestreport")
391 391 )
392 392 if lineno is not None:
393 393 assert failure.test.docstring is not None
394 394 lines = failure.test.docstring.splitlines(False)
395 395 # add line numbers to the left of the error message
396 396 assert test.lineno is not None
397 397 lines = [
398 398 "%03d %s" % (i + test.lineno + 1, x)
399 399 for (i, x) in enumerate(lines)
400 400 ]
401 401 # trim docstring error lines to 10
402 402 lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
403 403 else:
404 404 lines = [
405 405 "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
406 406 ]
407 407 indent = ">>>"
408 408 for line in example.source.splitlines():
409 409 lines.append(f"??? {indent} {line}")
410 410 indent = "..."
411 411 if isinstance(failure, doctest.DocTestFailure):
412 412 lines += checker.output_difference(
413 413 example, failure.got, report_choice
414 414 ).split("\n")
415 415 else:
416 416 inner_excinfo = ExceptionInfo(failure.exc_info)
417 417 lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
418 418 lines += [
419 419 x.strip("\n")
420 420 for x in traceback.format_exception(*failure.exc_info)
421 421 ]
422 422 reprlocation_lines.append((reprlocation, lines))
423 423 return ReprFailDoctest(reprlocation_lines)
424 424 else:
425 425 return super().repr_failure(excinfo)
426 426
427 427 def reportinfo(self):
428 428 assert self.dtest is not None
429 429 return self.fspath, self.dtest.lineno, "[ipdoctest] %s" % self.name
430 430
431 431
432 432 def _get_flag_lookup() -> Dict[str, int]:
433 433 import doctest
434 434
435 435 return dict(
436 436 DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
437 437 DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
438 438 NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
439 439 ELLIPSIS=doctest.ELLIPSIS,
440 440 IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
441 441 COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
442 442 ALLOW_UNICODE=_get_allow_unicode_flag(),
443 443 ALLOW_BYTES=_get_allow_bytes_flag(),
444 444 NUMBER=_get_number_flag(),
445 445 )
446 446
447 447
448 448 def get_optionflags(parent):
449 449 optionflags_str = parent.config.getini("ipdoctest_optionflags")
450 450 flag_lookup_table = _get_flag_lookup()
451 451 flag_acc = 0
452 452 for flag in optionflags_str:
453 453 flag_acc |= flag_lookup_table[flag]
454 454 return flag_acc
455 455
456 456
457 457 def _get_continue_on_failure(config):
458 458 continue_on_failure = config.getvalue("ipdoctest_continue_on_failure")
459 459 if continue_on_failure:
460 460 # We need to turn off this if we use pdb since we should stop at
461 461 # the first failure.
462 462 if config.getvalue("usepdb"):
463 463 continue_on_failure = False
464 464 return continue_on_failure
465 465
466 466
467 467 class IPDoctestTextfile(pytest.Module):
468 468 obj = None
469 469
470 470 def collect(self) -> Iterable[IPDoctestItem]:
471 471 import doctest
472 472 from .ipdoctest import IPDocTestParser
473 473
474 474 # Inspired by doctest.testfile; ideally we would use it directly,
475 475 # but it doesn't support passing a custom checker.
476 476 encoding = self.config.getini("ipdoctest_encoding")
477 477 text = self.fspath.read_text(encoding)
478 478 filename = str(self.fspath)
479 479 name = self.fspath.basename
480 480 globs = {"__name__": "__main__"}
481 481
482 482 optionflags = get_optionflags(self)
483 483
484 484 runner = _get_runner(
485 485 verbose=False,
486 486 optionflags=optionflags,
487 487 checker=_get_checker(),
488 488 continue_on_failure=_get_continue_on_failure(self.config),
489 489 )
490 490
491 491 parser = IPDocTestParser()
492 492 test = parser.get_doctest(text, globs, name, filename, 0)
493 493 if test.examples:
494 494 yield IPDoctestItem.from_parent(
495 495 self, name=test.name, runner=runner, dtest=test
496 496 )
497 497
498 498
499 499 def _check_all_skipped(test: "doctest.DocTest") -> None:
500 500 """Raise pytest.skip() if all examples in the given DocTest have the SKIP
501 501 option set."""
502 502 import doctest
503 503
504 504 all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
505 505 if all_skipped:
506 pytest.skip("all tests skipped by +SKIP option")
506 pytest.skip("all docstests skipped by +SKIP option")
507 507
508 508
509 509 def _is_mocked(obj: object) -> bool:
510 510 """Return if an object is possibly a mock object by checking the
511 511 existence of a highly improbable attribute."""
512 512 return (
513 513 safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None)
514 514 is not None
515 515 )
516 516
517 517
518 518 @contextmanager
519 519 def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
520 520 """Context manager which replaces ``inspect.unwrap`` with a version
521 521 that's aware of mock objects and doesn't recurse into them."""
522 522 real_unwrap = inspect.unwrap
523 523
524 524 def _mock_aware_unwrap(
525 525 func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None
526 526 ) -> Any:
527 527 try:
528 528 if stop is None or stop is _is_mocked:
529 529 return real_unwrap(func, stop=_is_mocked)
530 530 _stop = stop
531 531 return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func))
532 532 except Exception as e:
533 533 warnings.warn(
534 534 "Got %r when unwrapping %r. This is usually caused "
535 535 "by a violation of Python's object protocol; see e.g. "
536 536 "https://github.com/pytest-dev/pytest/issues/5080" % (e, func),
537 537 PytestWarning,
538 538 )
539 539 raise
540 540
541 541 inspect.unwrap = _mock_aware_unwrap
542 542 try:
543 543 yield
544 544 finally:
545 545 inspect.unwrap = real_unwrap
546 546
547 547
548 548 class IPDoctestModule(pytest.Module):
549 549 def collect(self) -> Iterable[IPDoctestItem]:
550 550 import doctest
551 551 from .ipdoctest import DocTestFinder, IPDocTestParser
552 552
553 553 class MockAwareDocTestFinder(DocTestFinder):
554 554 """A hackish ipdoctest finder that overrides stdlib internals to fix a stdlib bug.
555 555
556 556 https://github.com/pytest-dev/pytest/issues/3456
557 557 https://bugs.python.org/issue25532
558 558 """
559 559
560 560 def _find_lineno(self, obj, source_lines):
561 561 """Doctest code does not take into account `@property`, this
562 562 is a hackish way to fix it.
563 563
564 564 https://bugs.python.org/issue17446
565 565 """
566 566 if isinstance(obj, property):
567 567 obj = getattr(obj, "fget", obj)
568 568 # Type ignored because this is a private function.
569 569 return DocTestFinder._find_lineno( # type: ignore
570 570 self,
571 571 obj,
572 572 source_lines,
573 573 )
574 574
575 575 def _find(
576 576 self, tests, obj, name, module, source_lines, globs, seen
577 577 ) -> None:
578 578 if _is_mocked(obj):
579 579 return
580 580 with _patch_unwrap_mock_aware():
581 581
582 582 # Type ignored because this is a private function.
583 583 DocTestFinder._find( # type: ignore
584 584 self, tests, obj, name, module, source_lines, globs, seen
585 585 )
586 586
587 587 if self.fspath.basename == "conftest.py":
588 588 module = self.config.pluginmanager._importconftest(
589 589 self.fspath, self.config.getoption("importmode")
590 590 )
591 591 else:
592 592 try:
593 593 module = import_path(self.fspath)
594 594 except ImportError:
595 595 if self.config.getvalue("ipdoctest_ignore_import_errors"):
596 596 pytest.skip("unable to import module %r" % self.fspath)
597 597 else:
598 598 raise
599 599 # Uses internal doctest module parsing mechanism.
600 600 finder = MockAwareDocTestFinder(parser=IPDocTestParser())
601 601 optionflags = get_optionflags(self)
602 602 runner = _get_runner(
603 603 verbose=False,
604 604 optionflags=optionflags,
605 605 checker=_get_checker(),
606 606 continue_on_failure=_get_continue_on_failure(self.config),
607 607 )
608 608
609 609 for test in finder.find(module, module.__name__):
610 610 if test.examples: # skip empty ipdoctests
611 611 yield IPDoctestItem.from_parent(
612 612 self, name=test.name, runner=runner, dtest=test
613 613 )
614 614
615 615
616 616 def _setup_fixtures(doctest_item: IPDoctestItem) -> FixtureRequest:
617 617 """Used by IPDoctestTextfile and IPDoctestItem to setup fixture information."""
618 618
619 619 def func() -> None:
620 620 pass
621 621
622 622 doctest_item.funcargs = {} # type: ignore[attr-defined]
623 623 fm = doctest_item.session._fixturemanager
624 624 doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined]
625 625 node=doctest_item, func=func, cls=None, funcargs=False
626 626 )
627 627 fixture_request = FixtureRequest(doctest_item, _ispytest=True)
628 628 fixture_request._fillfixtures()
629 629 return fixture_request
630 630
631 631
632 632 def _init_checker_class() -> Type["IPDoctestOutputChecker"]:
633 633 import doctest
634 634 import re
635 635 from .ipdoctest import IPDoctestOutputChecker
636 636
637 637 class LiteralsOutputChecker(IPDoctestOutputChecker):
638 638 # Based on doctest_nose_plugin.py from the nltk project
639 639 # (https://github.com/nltk/nltk) and on the "numtest" doctest extension
640 640 # by Sebastien Boisgerault (https://github.com/boisgera/numtest).
641 641
642 642 _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
643 643 _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
644 644 _number_re = re.compile(
645 645 r"""
646 646 (?P<number>
647 647 (?P<mantissa>
648 648 (?P<integer1> [+-]?\d*)\.(?P<fraction>\d+)
649 649 |
650 650 (?P<integer2> [+-]?\d+)\.
651 651 )
652 652 (?:
653 653 [Ee]
654 654 (?P<exponent1> [+-]?\d+)
655 655 )?
656 656 |
657 657 (?P<integer3> [+-]?\d+)
658 658 (?:
659 659 [Ee]
660 660 (?P<exponent2> [+-]?\d+)
661 661 )
662 662 )
663 663 """,
664 664 re.VERBOSE,
665 665 )
666 666
667 667 def check_output(self, want: str, got: str, optionflags: int) -> bool:
668 668 if IPDoctestOutputChecker.check_output(self, want, got, optionflags):
669 669 return True
670 670
671 671 allow_unicode = optionflags & _get_allow_unicode_flag()
672 672 allow_bytes = optionflags & _get_allow_bytes_flag()
673 673 allow_number = optionflags & _get_number_flag()
674 674
675 675 if not allow_unicode and not allow_bytes and not allow_number:
676 676 return False
677 677
678 678 def remove_prefixes(regex: Pattern[str], txt: str) -> str:
679 679 return re.sub(regex, r"\1\2", txt)
680 680
681 681 if allow_unicode:
682 682 want = remove_prefixes(self._unicode_literal_re, want)
683 683 got = remove_prefixes(self._unicode_literal_re, got)
684 684
685 685 if allow_bytes:
686 686 want = remove_prefixes(self._bytes_literal_re, want)
687 687 got = remove_prefixes(self._bytes_literal_re, got)
688 688
689 689 if allow_number:
690 690 got = self._remove_unwanted_precision(want, got)
691 691
692 692 return IPDoctestOutputChecker.check_output(self, want, got, optionflags)
693 693
694 694 def _remove_unwanted_precision(self, want: str, got: str) -> str:
695 695 wants = list(self._number_re.finditer(want))
696 696 gots = list(self._number_re.finditer(got))
697 697 if len(wants) != len(gots):
698 698 return got
699 699 offset = 0
700 700 for w, g in zip(wants, gots):
701 701 fraction: Optional[str] = w.group("fraction")
702 702 exponent: Optional[str] = w.group("exponent1")
703 703 if exponent is None:
704 704 exponent = w.group("exponent2")
705 705 if fraction is None:
706 706 precision = 0
707 707 else:
708 708 precision = len(fraction)
709 709 if exponent is not None:
710 710 precision -= int(exponent)
711 711 if float(w.group()) == approx(float(g.group()), abs=10 ** -precision):
712 712 # They're close enough. Replace the text we actually
713 713 # got with the text we want, so that it will match when we
714 714 # check the string literally.
715 715 got = (
716 716 got[: g.start() + offset] + w.group() + got[g.end() + offset :]
717 717 )
718 718 offset += w.end() - w.start() - (g.end() - g.start())
719 719 return got
720 720
721 721 return LiteralsOutputChecker
722 722
723 723
724 724 def _get_checker() -> "IPDoctestOutputChecker":
725 725 """Return a IPDoctestOutputChecker subclass that supports some
726 726 additional options:
727 727
728 728 * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
729 729 prefixes (respectively) in string literals. Useful when the same
730 730 ipdoctest should run in Python 2 and Python 3.
731 731
732 732 * NUMBER to ignore floating-point differences smaller than the
733 733 precision of the literal number in the ipdoctest.
734 734
735 735 An inner class is used to avoid importing "ipdoctest" at the module
736 736 level.
737 737 """
738 738 global CHECKER_CLASS
739 739 if CHECKER_CLASS is None:
740 740 CHECKER_CLASS = _init_checker_class()
741 741 return CHECKER_CLASS()
742 742
743 743
744 744 def _get_allow_unicode_flag() -> int:
745 745 """Register and return the ALLOW_UNICODE flag."""
746 746 import doctest
747 747
748 748 return doctest.register_optionflag("ALLOW_UNICODE")
749 749
750 750
751 751 def _get_allow_bytes_flag() -> int:
752 752 """Register and return the ALLOW_BYTES flag."""
753 753 import doctest
754 754
755 755 return doctest.register_optionflag("ALLOW_BYTES")
756 756
757 757
758 758 def _get_number_flag() -> int:
759 759 """Register and return the NUMBER flag."""
760 760 import doctest
761 761
762 762 return doctest.register_optionflag("NUMBER")
763 763
764 764
765 765 def _get_report_choice(key: str) -> int:
766 766 """Return the actual `ipdoctest` module flag value.
767 767
768 768 We want to do it as late as possible to avoid importing `ipdoctest` and all
769 769 its dependencies when parsing options, as it adds overhead and breaks tests.
770 770 """
771 771 import doctest
772 772
773 773 return {
774 774 DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF,
775 775 DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF,
776 776 DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF,
777 777 DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE,
778 778 DOCTEST_REPORT_CHOICE_NONE: 0,
779 779 }[key]
780 780
781 781
782 782 @pytest.fixture(scope="session")
783 783 def ipdoctest_namespace() -> Dict[str, Any]:
784 784 """Fixture that returns a :py:class:`dict` that will be injected into the
785 785 namespace of ipdoctests."""
786 786 return dict()
General Comments 0
You need to be logged in to leave comments. Login now