##// END OF EJS Templates
Merge pull request #12359 from Carreau/tbhide
Matthias Bussonnier -
r25842:61c77f73 merge
parent child Browse files
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) as e:
336 except (TypeError, ValueError) as e:
330 raise ValueError("Context must be a positive integer") from e
337 raise ValueError("Context must be a positive integer") from e
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
@@ -485,6 +505,16 b' class Pdb(OldPdb):'
485 except KeyboardInterrupt:
505 except KeyboardInterrupt:
486 pass
506 pass
487
507
508 def do_skip_hidden(self, arg):
509 """
510 Change whether or not we should skip frames with the
511 __tracebackhide__ attribute.
512 """
513 if arg.strip().lower() in ("true", "yes"):
514 self.skip_hidden = True
515 elif arg.strip().lower() in ("false", "no"):
516 self.skip_hidden = False
517
488 def do_list(self, arg):
518 def do_list(self, arg):
489 """Print lines of code from the current stack frame
519 """Print lines of code from the current stack frame
490 """
520 """
@@ -631,6 +661,109 b' class Pdb(OldPdb):'
631
661
632 do_w = do_where
662 do_w = do_where
633
663
664 def stop_here(self, frame):
665 hidden = False
666 if self.skip_hidden:
667 hidden = frame.f_locals.get("__tracebackhide__", False)
668 if hidden:
669 Colors = self.color_scheme_table.active_colors
670 ColorsNormal = Colors.Normal
671 print(f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n")
672
673 return super().stop_here(frame)
674
675 def do_up(self, arg):
676 """u(p) [count]
677 Move the current frame count (default one) levels up in the
678 stack trace (to an older frame).
679
680 Will skip hidden frames.
681 """
682 ## modified version of upstream that skips
683 # frames with __tracebackide__
684 if self.curindex == 0:
685 self.error("Oldest frame")
686 return
687 try:
688 count = int(arg or 1)
689 except ValueError:
690 self.error("Invalid frame count (%s)" % arg)
691 return
692 skipped = 0
693 if count < 0:
694 _newframe = 0
695 else:
696 _newindex = self.curindex
697 counter = 0
698 hidden_frames = self.hidden_frames(self.stack)
699 for i in range(self.curindex - 1, -1, -1):
700 frame = self.stack[i][0]
701 if hidden_frames[i] and self.skip_hidden:
702 skipped += 1
703 continue
704 counter += 1
705 if counter >= count:
706 break
707 else:
708 # if no break occured.
709 self.error("all frames above hidden")
710 return
711
712 Colors = self.color_scheme_table.active_colors
713 ColorsNormal = Colors.Normal
714 _newframe = i
715 self._select_frame(_newframe)
716 if skipped:
717 print(
718 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
719 )
720
721 def do_down(self, arg):
722 """d(own) [count]
723 Move the current frame count (default one) levels down in the
724 stack trace (to a newer frame).
725
726 Will skip hidden frames.
727 """
728 if self.curindex + 1 == len(self.stack):
729 self.error("Newest frame")
730 return
731 try:
732 count = int(arg or 1)
733 except ValueError:
734 self.error("Invalid frame count (%s)" % arg)
735 return
736 if count < 0:
737 _newframe = len(self.stack) - 1
738 else:
739 _newindex = self.curindex
740 counter = 0
741 skipped = 0
742 hidden_frames = self.hidden_frames(self.stack)
743 for i in range(self.curindex + 1, len(self.stack)):
744 frame = self.stack[i][0]
745 if hidden_frames[i] and self.skip_hidden:
746 skipped += 1
747 continue
748 counter += 1
749 if counter >= count:
750 break
751 else:
752 self.error("all frames bellow hidden")
753 return
754
755 Colors = self.color_scheme_table.active_colors
756 ColorsNormal = Colors.Normal
757 if skipped:
758 print(
759 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
760 )
761 _newframe = i
762
763 self._select_frame(_newframe)
764
765 do_d = do_down
766 do_u = do_up
634
767
635 class InterruptiblePdb(Pdb):
768 class InterruptiblePdb(Pdb):
636 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
769 """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
@@ -251,3 +255,69 b' def test_interruptible_core_debugger():'
251 # implementation would involve a subprocess, but that adds issues with
255 # implementation would involve a subprocess, but that adds issues with
252 # interrupting subprocesses that are rather complex, so it's simpler
256 # interrupting subprocesses that are rather complex, so it's simpler
253 # just to do it this way.
257 # just to do it this way.
258
259 @skip_win32
260 def test_xmode_skip():
261 """that xmode skip frames
262
263 Not as a doctest as pytest does not run doctests.
264 """
265 import pexpect
266 env = os.environ.copy()
267 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
268
269 child = pexpect.spawn(
270 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
271 )
272 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
273
274 child.expect("IPython")
275 child.expect("\n")
276 child.expect_exact("In [1]")
277
278 block = dedent(
279 """
280 def f():
281 __tracebackhide__ = True
282 g()
283
284 def g():
285 raise ValueError
286
287 f()
288 """
289 )
290
291 for line in block.splitlines():
292 child.sendline(line)
293 child.expect_exact(line)
294 child.expect_exact("skipping")
295
296 block = dedent(
297 """
298 def f():
299 __tracebackhide__ = True
300 g()
301
302 def g():
303 from IPython.core.debugger import set_trace
304 set_trace()
305
306 f()
307 """
308 )
309
310 for line in block.splitlines():
311 child.sendline(line)
312 child.expect_exact(line)
313
314 child.expect("ipdb>")
315 child.sendline("w")
316 child.expect("hidden")
317 child.expect("ipdb>")
318 child.sendline("skip_hidden false")
319 child.sendline("w")
320 child.expect("__traceba")
321 child.expect("ipdb>")
322
323 child.close()
@@ -579,12 +579,15 b' class VerboseTB(TBTools):'
579 self.check_cache = check_cache
579 self.check_cache = check_cache
580
580
581 self.debugger_cls = debugger_cls or debugger.Pdb
581 self.debugger_cls = debugger_cls or debugger.Pdb
582 self.skip_hidden = True
582
583
583 def format_record(self, frame_info):
584 def format_record(self, frame_info):
584 """Format a single stack frame"""
585 """Format a single stack frame"""
585 Colors = self.Colors # just a shorthand + quicker name lookup
586 Colors = self.Colors # just a shorthand + quicker name lookup
586 ColorsNormal = Colors.Normal # used a lot
587 ColorsNormal = Colors.Normal # used a lot
587
588
589
590
588 if isinstance(frame_info, stack_data.RepeatedFrames):
591 if isinstance(frame_info, stack_data.RepeatedFrames):
589 return ' %s[... skipping similar frames: %s]%s\n' % (
592 return ' %s[... skipping similar frames: %s]%s\n' % (
590 Colors.excName, frame_info.description, ColorsNormal)
593 Colors.excName, frame_info.description, ColorsNormal)
@@ -699,7 +702,23 b' class VerboseTB(TBTools):'
699 head = self.prepare_header(etype, self.long_header)
702 head = self.prepare_header(etype, self.long_header)
700 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
703 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
701
704
702 frames = list(map(self.format_record, records))
705 frames = []
706 skipped = 0
707 for r in records:
708 if not isinstance(r, stack_data.RepeatedFrames) and self.skip_hidden:
709 if r.frame.f_locals.get("__tracebackhide__", 0):
710 skipped += 1
711 continue
712 if skipped:
713 Colors = self.Colors # just a shorthand + quicker name lookup
714 ColorsNormal = Colors.Normal # used a lot
715 frames.append(
716 " %s[... skipping hidden %s frame]%s\n"
717 % (Colors.excName, skipped, ColorsNormal)
718 )
719 skipped = 0
720 frames.append(self.format_record(r))
721
703
722
704 formatted_exception = self.format_exception(etype, evalue)
723 formatted_exception = self.format_exception(etype, evalue)
705 if records:
724 if records:
@@ -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