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