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 | 281 | # Set the prompt - the default prompt is '(Pdb)' |
|
282 | 282 | self.prompt = prompt |
|
283 | self.skip_hidden = True | |
|
283 | 284 | |
|
284 | 285 | def set_colors(self, scheme): |
|
285 | 286 | """Shorthand access to the color table scheme selector method.""" |
|
286 | 287 | self.color_scheme_table.set_active_scheme(scheme) |
|
287 | 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 | 303 | def interaction(self, frame, traceback): |
|
290 | 304 | try: |
|
291 | 305 | OldPdb.interaction(self, frame, traceback) |
|
292 | 306 | except KeyboardInterrupt: |
|
293 |
self.stdout.write( |
|
|
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) | |
|
307 | self.stdout.write("\n" + self.shell.get_exception_only()) | |
|
303 | 308 | |
|
304 | 309 | def new_do_frame(self, arg): |
|
305 | 310 | OldPdb.do_frame(self, arg) |
@@ -320,6 +325,8 b' class Pdb(OldPdb):' | |||
|
320 | 325 | return self.do_quit(arg) |
|
321 | 326 | |
|
322 | 327 | def print_stack_trace(self, context=None): |
|
328 | Colors = self.color_scheme_table.active_colors | |
|
329 | ColorsNormal = Colors.Normal | |
|
323 | 330 | if context is None: |
|
324 | 331 | context = self.context |
|
325 | 332 | try: |
@@ -329,8 +336,21 b' class Pdb(OldPdb):' | |||
|
329 | 336 | except (TypeError, ValueError): |
|
330 | 337 | raise ValueError("Context must be a positive integer") |
|
331 | 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 | 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 | 354 | except KeyboardInterrupt: |
|
335 | 355 | pass |
|
336 | 356 | |
@@ -487,6 +507,16 b' class Pdb(OldPdb):' | |||
|
487 | 507 | except KeyboardInterrupt: |
|
488 | 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 | 520 | def do_list(self, arg): |
|
491 | 521 | """Print lines of code from the current stack frame |
|
492 | 522 | """ |
@@ -633,6 +663,109 b' class Pdb(OldPdb):' | |||
|
633 | 663 | |
|
634 | 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 | 770 | class InterruptiblePdb(Pdb): |
|
638 | 771 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" |
@@ -2211,11 +2211,20 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2211 | 2211 | with self.builtin_trap: |
|
2212 | 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 | 2215 | """Adds a new custom completer function. |
|
2216 | 2216 | |
|
2217 | 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 | 2229 | newcomp = types.MethodType(completer, self.Completer) |
|
2221 | 2230 | self.Completer.custom_matchers.insert(pos,newcomp) |
@@ -3310,6 +3319,9 b' class InteractiveShell(SingletonConfigurable):' | |||
|
3310 | 3319 | False : successful execution. |
|
3311 | 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 | 3325 | # Set our own excepthook in case the user code tries to call it |
|
3314 | 3326 | # directly, so that the IPython crash handler doesn't get triggered |
|
3315 | 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 | 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 | 374 | def xmode_switch_err(name): |
|
370 | 375 | warn('Error changing %s exception modes.\n%s' % |
|
371 | 376 | (name,sys.exc_info()[1])) |
|
372 | 377 | |
|
373 | 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 | 386 | new_mode = parameter_s.strip().capitalize() |
|
375 | 387 | try: |
|
376 | 388 | shell.InteractiveTB.set_mode(mode=new_mode) |
@@ -4,20 +4,24 b'' | |||
|
4 | 4 | # Copyright (c) IPython Development Team. |
|
5 | 5 | # Distributed under the terms of the Modified BSD License. |
|
6 | 6 | |
|
7 | import bdb | |
|
8 | import builtins | |
|
9 | import os | |
|
7 | 10 | import signal |
|
11 | import subprocess | |
|
8 | 12 | import sys |
|
9 | 13 | import time |
|
10 | 14 | import warnings |
|
15 | from subprocess import PIPE, CalledProcessError, check_output | |
|
11 | 16 | from tempfile import NamedTemporaryFile |
|
12 | from subprocess import check_output, CalledProcessError, PIPE | |
|
13 | import subprocess | |
|
17 | from textwrap import dedent | |
|
14 | 18 | from unittest.mock import patch |
|
15 | import builtins | |
|
16 | import bdb | |
|
17 | 19 | |
|
18 | 20 | import nose.tools as nt |
|
19 | 21 | |
|
20 | 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 | 27 | # Helper classes, from CPython's Pdb test suite |
@@ -254,3 +258,69 b' def test_interruptible_core_debugger():' | |||
|
254 | 258 | # implementation would involve a subprocess, but that adds issues with |
|
255 | 259 | # interrupting subprocesses that are rather complex, so it's simpler |
|
256 | 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 | 879 | self.check_cache = check_cache |
|
880 | 880 | |
|
881 | 881 | self.debugger_cls = debugger_cls or debugger.Pdb |
|
882 | self.skip_hidden = True | |
|
882 | 883 | |
|
883 | 884 | def format_records(self, records, last_unique, recursion_repeat): |
|
884 | 885 | """Format the stack frames of the traceback""" |
|
885 | 886 | frames = [] |
|
887 | ||
|
888 | skipped = 0 | |
|
886 | 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 | 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 | 913 | if recursion_repeat: |
|
891 | 914 | frames.append('... last %d frames repeated, from the frame below ...\n' % recursion_repeat) |
@@ -1123,8 +1146,6 b' class VerboseTB(TBTools):' | |||
|
1123 | 1146 | head = self.prepare_header(etype, self.long_header) |
|
1124 | 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 | 1150 | last_unique, recursion_repeat = find_recursion(orig_etype, evalue, records) |
|
1130 | 1151 |
@@ -41,6 +41,16 b' class TerminalPdb(Pdb):' | |||
|
41 | 41 | global_namespace={}, |
|
42 | 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 | 54 | self._ptcomp = IPythonPTCompleter(compl) |
|
45 | 55 | |
|
46 | 56 | options = dict( |
General Comments 0
You need to be logged in to leave comments.
Login now