##// END OF EJS Templates
Backport PR #12359: Implement understanding on __tracebackhide__
Matthias Bussonnier -
Show More
@@ -0,0 +1,11 b''
1 The default tracebackmode will now skip frames that are marked with
2 ``__tracebackhide__ = True`` and show how many traceback frames have been
3 skipped. This can be toggled by using :magic:`xmode` with the ``--show`` or
4 ``--hide`` attribute. It will have no effect on non verbose traceback modes.
5
6 The ipython debugger also now understand ``__tracebackhide__`` as well and will
7 skip hidden frames when displaying. Movement up and down the stack will skip the
8 hidden frames and will show how many frames were hidden. Internal IPython frames
9 are also now hidden by default. The behavior can be changed with the
10 ``skip_hidden`` command and accepts "yes", "no", "true" and "false" case
11 insensitive parameters.
@@ -280,26 +280,31 b' class Pdb(OldPdb):'
280
280
281 # Set the prompt - the default prompt is '(Pdb)'
281 # Set the prompt - the default prompt is '(Pdb)'
282 self.prompt = prompt
282 self.prompt = prompt
283 self.skip_hidden = True
283
284
284 def set_colors(self, scheme):
285 def set_colors(self, scheme):
285 """Shorthand access to the color table scheme selector method."""
286 """Shorthand access to the color table scheme selector method."""
286 self.color_scheme_table.set_active_scheme(scheme)
287 self.color_scheme_table.set_active_scheme(scheme)
287 self.parser.style = scheme
288 self.parser.style = scheme
288
289
290
291 def hidden_frames(self, stack):
292 """
293 Given an index in the stack return wether it should be skipped.
294
295 This is used in up/down and where to skip frames.
296 """
297 ip_hide = [s[0].f_locals.get("__tracebackhide__", False) for s in stack]
298 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
299 if ip_start:
300 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
301 return ip_hide
302
289 def interaction(self, frame, traceback):
303 def interaction(self, frame, traceback):
290 try:
304 try:
291 OldPdb.interaction(self, frame, traceback)
305 OldPdb.interaction(self, frame, traceback)
292 except KeyboardInterrupt:
306 except KeyboardInterrupt:
293 self.stdout.write('\n' + self.shell.get_exception_only())
307 self.stdout.write("\n" + self.shell.get_exception_only())
294
295 def new_do_up(self, arg):
296 OldPdb.do_up(self, arg)
297 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
298
299 def new_do_down(self, arg):
300 OldPdb.do_down(self, arg)
301
302 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
303
308
304 def new_do_frame(self, arg):
309 def new_do_frame(self, arg):
305 OldPdb.do_frame(self, arg)
310 OldPdb.do_frame(self, arg)
@@ -320,6 +325,8 b' class Pdb(OldPdb):'
320 return self.do_quit(arg)
325 return self.do_quit(arg)
321
326
322 def print_stack_trace(self, context=None):
327 def print_stack_trace(self, context=None):
328 Colors = self.color_scheme_table.active_colors
329 ColorsNormal = Colors.Normal
323 if context is None:
330 if context is None:
324 context = self.context
331 context = self.context
325 try:
332 try:
@@ -329,8 +336,21 b' class Pdb(OldPdb):'
329 except (TypeError, ValueError):
336 except (TypeError, ValueError):
330 raise ValueError("Context must be a positive integer")
337 raise ValueError("Context must be a positive integer")
331 try:
338 try:
332 for frame_lineno in self.stack:
339 skipped = 0
340 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
341 if hidden and self.skip_hidden:
342 skipped += 1
343 continue
344 if skipped:
345 print(
346 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
347 )
348 skipped = 0
333 self.print_stack_entry(frame_lineno, context=context)
349 self.print_stack_entry(frame_lineno, context=context)
350 if skipped:
351 print(
352 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
353 )
334 except KeyboardInterrupt:
354 except KeyboardInterrupt:
335 pass
355 pass
336
356
@@ -487,6 +507,16 b' class Pdb(OldPdb):'
487 except KeyboardInterrupt:
507 except KeyboardInterrupt:
488 pass
508 pass
489
509
510 def do_skip_hidden(self, arg):
511 """
512 Change whether or not we should skip frames with the
513 __tracebackhide__ attribute.
514 """
515 if arg.strip().lower() in ("true", "yes"):
516 self.skip_hidden = True
517 elif arg.strip().lower() in ("false", "no"):
518 self.skip_hidden = False
519
490 def do_list(self, arg):
520 def do_list(self, arg):
491 """Print lines of code from the current stack frame
521 """Print lines of code from the current stack frame
492 """
522 """
@@ -633,6 +663,109 b' class Pdb(OldPdb):'
633
663
634 do_w = do_where
664 do_w = do_where
635
665
666 def stop_here(self, frame):
667 hidden = False
668 if self.skip_hidden:
669 hidden = frame.f_locals.get("__tracebackhide__", False)
670 if hidden:
671 Colors = self.color_scheme_table.active_colors
672 ColorsNormal = Colors.Normal
673 print(f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n")
674
675 return super().stop_here(frame)
676
677 def do_up(self, arg):
678 """u(p) [count]
679 Move the current frame count (default one) levels up in the
680 stack trace (to an older frame).
681
682 Will skip hidden frames.
683 """
684 ## modified version of upstream that skips
685 # frames with __tracebackide__
686 if self.curindex == 0:
687 self.error("Oldest frame")
688 return
689 try:
690 count = int(arg or 1)
691 except ValueError:
692 self.error("Invalid frame count (%s)" % arg)
693 return
694 skipped = 0
695 if count < 0:
696 _newframe = 0
697 else:
698 _newindex = self.curindex
699 counter = 0
700 hidden_frames = self.hidden_frames(self.stack)
701 for i in range(self.curindex - 1, -1, -1):
702 frame = self.stack[i][0]
703 if hidden_frames[i] and self.skip_hidden:
704 skipped += 1
705 continue
706 counter += 1
707 if counter >= count:
708 break
709 else:
710 # if no break occured.
711 self.error("all frames above hidden")
712 return
713
714 Colors = self.color_scheme_table.active_colors
715 ColorsNormal = Colors.Normal
716 _newframe = i
717 self._select_frame(_newframe)
718 if skipped:
719 print(
720 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
721 )
722
723 def do_down(self, arg):
724 """d(own) [count]
725 Move the current frame count (default one) levels down in the
726 stack trace (to a newer frame).
727
728 Will skip hidden frames.
729 """
730 if self.curindex + 1 == len(self.stack):
731 self.error("Newest frame")
732 return
733 try:
734 count = int(arg or 1)
735 except ValueError:
736 self.error("Invalid frame count (%s)" % arg)
737 return
738 if count < 0:
739 _newframe = len(self.stack) - 1
740 else:
741 _newindex = self.curindex
742 counter = 0
743 skipped = 0
744 hidden_frames = self.hidden_frames(self.stack)
745 for i in range(self.curindex + 1, len(self.stack)):
746 frame = self.stack[i][0]
747 if hidden_frames[i] and self.skip_hidden:
748 skipped += 1
749 continue
750 counter += 1
751 if counter >= count:
752 break
753 else:
754 self.error("all frames bellow hidden")
755 return
756
757 Colors = self.color_scheme_table.active_colors
758 ColorsNormal = Colors.Normal
759 if skipped:
760 print(
761 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
762 )
763 _newframe = i
764
765 self._select_frame(_newframe)
766
767 do_d = do_down
768 do_u = do_up
636
769
637 class InterruptiblePdb(Pdb):
770 class InterruptiblePdb(Pdb):
638 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
771 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
@@ -2211,11 +2211,20 b' class InteractiveShell(SingletonConfigurable):'
2211 with self.builtin_trap:
2211 with self.builtin_trap:
2212 return self.Completer.complete(text, line, cursor_pos)
2212 return self.Completer.complete(text, line, cursor_pos)
2213
2213
2214 def set_custom_completer(self, completer, pos=0):
2214 def set_custom_completer(self, completer, pos=0) -> None:
2215 """Adds a new custom completer function.
2215 """Adds a new custom completer function.
2216
2216
2217 The position argument (defaults to 0) is the index in the completers
2217 The position argument (defaults to 0) is the index in the completers
2218 list where you want the completer to be inserted."""
2218 list where you want the completer to be inserted.
2219
2220 `completer` should have the following signature::
2221
2222 def completion(self: Completer, text: string) -> List[str]:
2223 raise NotImplementedError
2224
2225 It will be bound to the current Completer instance and pass some text
2226 and return a list with current completions to suggest to the user.
2227 """
2219
2228
2220 newcomp = types.MethodType(completer, self.Completer)
2229 newcomp = types.MethodType(completer, self.Completer)
2221 self.Completer.custom_matchers.insert(pos,newcomp)
2230 self.Completer.custom_matchers.insert(pos,newcomp)
@@ -3310,6 +3319,9 b' class InteractiveShell(SingletonConfigurable):'
3310 False : successful execution.
3319 False : successful execution.
3311 True : an error occurred.
3320 True : an error occurred.
3312 """
3321 """
3322 # special value to say that anything above is IPython and should be
3323 # hidden.
3324 __tracebackhide__ = "__ipython_bottom__"
3313 # Set our own excepthook in case the user code tries to call it
3325 # Set our own excepthook in case the user code tries to call it
3314 # directly, so that the IPython crash handler doesn't get triggered
3326 # directly, so that the IPython crash handler doesn't get triggered
3315 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
3327 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
@@ -364,13 +364,25 b' Currently the magic system has the following functions:""",'
364
364
365 Valid modes: Plain, Context, Verbose, and Minimal.
365 Valid modes: Plain, Context, Verbose, and Minimal.
366
366
367 If called without arguments, acts as a toggle."""
367 If called without arguments, acts as a toggle.
368
369 When in verbose mode the value --show (and --hide)
370 will respectively show (or hide) frames with ``__tracebackhide__ =
371 True`` value set.
372 """
368
373
369 def xmode_switch_err(name):
374 def xmode_switch_err(name):
370 warn('Error changing %s exception modes.\n%s' %
375 warn('Error changing %s exception modes.\n%s' %
371 (name,sys.exc_info()[1]))
376 (name,sys.exc_info()[1]))
372
377
373 shell = self.shell
378 shell = self.shell
379 if parameter_s.strip() == "--show":
380 shell.InteractiveTB.skip_hidden = False
381 return
382 if parameter_s.strip() == "--hide":
383 shell.InteractiveTB.skip_hidden = True
384 return
385
374 new_mode = parameter_s.strip().capitalize()
386 new_mode = parameter_s.strip().capitalize()
375 try:
387 try:
376 shell.InteractiveTB.set_mode(mode=new_mode)
388 shell.InteractiveTB.set_mode(mode=new_mode)
@@ -4,20 +4,24 b''
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 bdb
8 import builtins
9 import os
7 import signal
10 import signal
11 import subprocess
8 import sys
12 import sys
9 import time
13 import time
10 import warnings
14 import warnings
15 from subprocess import PIPE, CalledProcessError, check_output
11 from tempfile import NamedTemporaryFile
16 from tempfile import NamedTemporaryFile
12 from subprocess import check_output, CalledProcessError, PIPE
17 from textwrap import dedent
13 import subprocess
14 from unittest.mock import patch
18 from unittest.mock import patch
15 import builtins
16 import bdb
17
19
18 import nose.tools as nt
20 import nose.tools as nt
19
21
20 from IPython.core import debugger
22 from IPython.core import debugger
23 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
24 from IPython.testing.decorators import skip_win32
21
25
22 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
23 # Helper classes, from CPython's Pdb test suite
27 # Helper classes, from CPython's Pdb test suite
@@ -254,3 +258,69 b' def test_interruptible_core_debugger():'
254 # implementation would involve a subprocess, but that adds issues with
258 # implementation would involve a subprocess, but that adds issues with
255 # interrupting subprocesses that are rather complex, so it's simpler
259 # interrupting subprocesses that are rather complex, so it's simpler
256 # just to do it this way.
260 # just to do it this way.
261
262 @skip_win32
263 def test_xmode_skip():
264 """that xmode skip frames
265
266 Not as a doctest as pytest does not run doctests.
267 """
268 import pexpect
269 env = os.environ.copy()
270 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
271
272 child = pexpect.spawn(
273 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
274 )
275 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
276
277 child.expect("IPython")
278 child.expect("\n")
279 child.expect_exact("In [1]")
280
281 block = dedent(
282 """
283 def f():
284 __tracebackhide__ = True
285 g()
286
287 def g():
288 raise ValueError
289
290 f()
291 """
292 )
293
294 for line in block.splitlines():
295 child.sendline(line)
296 child.expect_exact(line)
297 child.expect_exact("skipping")
298
299 block = dedent(
300 """
301 def f():
302 __tracebackhide__ = True
303 g()
304
305 def g():
306 from IPython.core.debugger import set_trace
307 set_trace()
308
309 f()
310 """
311 )
312
313 for line in block.splitlines():
314 child.sendline(line)
315 child.expect_exact(line)
316
317 child.expect("ipdb>")
318 child.sendline("w")
319 child.expect("hidden")
320 child.expect("ipdb>")
321 child.sendline("skip_hidden false")
322 child.sendline("w")
323 child.expect("__traceba")
324 child.expect("ipdb>")
325
326 child.close()
@@ -879,13 +879,36 b' class VerboseTB(TBTools):'
879 self.check_cache = check_cache
879 self.check_cache = check_cache
880
880
881 self.debugger_cls = debugger_cls or debugger.Pdb
881 self.debugger_cls = debugger_cls or debugger.Pdb
882 self.skip_hidden = True
882
883
883 def format_records(self, records, last_unique, recursion_repeat):
884 def format_records(self, records, last_unique, recursion_repeat):
884 """Format the stack frames of the traceback"""
885 """Format the stack frames of the traceback"""
885 frames = []
886 frames = []
887
888 skipped = 0
886 for r in records[:last_unique+recursion_repeat+1]:
889 for r in records[:last_unique+recursion_repeat+1]:
887 #print '*** record:',file,lnum,func,lines,index # dbg
890 if self.skip_hidden:
891 if r[0].f_locals.get("__tracebackhide__", 0):
892 skipped += 1
893 continue
894 if skipped:
895 Colors = self.Colors # just a shorthand + quicker name lookup
896 ColorsNormal = Colors.Normal # used a lot
897 frames.append(
898 " %s[... skipping hidden %s frame]%s\n"
899 % (Colors.excName, skipped, ColorsNormal)
900 )
901 skipped = 0
902
888 frames.append(self.format_record(*r))
903 frames.append(self.format_record(*r))
904
905 if skipped:
906 Colors = self.Colors # just a shorthand + quicker name lookup
907 ColorsNormal = Colors.Normal # used a lot
908 frames.append(
909 " %s[... skipping hidden %s frame]%s\n"
910 % (Colors.excName, skipped, ColorsNormal)
911 )
889
912
890 if recursion_repeat:
913 if recursion_repeat:
891 frames.append('... last %d frames repeated, from the frame below ...\n' % recursion_repeat)
914 frames.append('... last %d frames repeated, from the frame below ...\n' % recursion_repeat)
@@ -1123,8 +1146,6 b' class VerboseTB(TBTools):'
1123 head = self.prepare_header(etype, self.long_header)
1146 head = self.prepare_header(etype, self.long_header)
1124 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
1147 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
1125
1148
1126 if records is None:
1127 return ""
1128
1149
1129 last_unique, recursion_repeat = find_recursion(orig_etype, evalue, records)
1150 last_unique, recursion_repeat = find_recursion(orig_etype, evalue, records)
1130
1151
@@ -41,6 +41,16 b' class TerminalPdb(Pdb):'
41 global_namespace={},
41 global_namespace={},
42 parent=self.shell,
42 parent=self.shell,
43 )
43 )
44 # add a completer for all the do_ methods
45 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
46
47 def gen_comp(self, text):
48 return [m for m in methods_names if m.startswith(text)]
49 import types
50 newcomp = types.MethodType(gen_comp, compl)
51 compl.custom_matchers.insert(0, newcomp)
52 # end add completer.
53
44 self._ptcomp = IPythonPTCompleter(compl)
54 self._ptcomp = IPythonPTCompleter(compl)
45
55
46 options = dict(
56 options = dict(
@@ -4,13 +4,6 b' matrix:'
4
4
5 environment:
5 environment:
6 matrix:
6 matrix:
7 - PYTHON: "C:\\Python36"
8 PYTHON_VERSION: "3.6.x"
9 PYTHON_ARCH: "32"
10
11 - PYTHON: "C:\\Python36-x64"
12 PYTHON_VERSION: "3.6.x"
13 PYTHON_ARCH: "64"
14
7
15 - PYTHON: "C:\\Python37-x64"
8 - PYTHON: "C:\\Python37-x64"
16 PYTHON_VERSION: "3.7.x"
9 PYTHON_VERSION: "3.7.x"
General Comments 0
You need to be logged in to leave comments. Login now