##// END OF EJS Templates
Backport PR #13142: Pdbskip #13136...
Matthias Bussonnier -
Show More
@@ -2,6 +2,71 b''
2 """
2 """
3 Pdb debugger class.
3 Pdb debugger class.
4
4
5
6 This is an extension to PDB which adds a number of new features.
7 Note that there is also the `IPython.terminal.debugger` class which provides UI
8 improvements.
9
10 We also strongly recommend to use this via the `ipdb` package, which provides
11 extra configuration options.
12
13 Among other things, this subclass of PDB:
14 - supports many IPython magics like pdef/psource
15 - hide frames in tracebacks based on `__tracebackhide__`
16 - allows to skip frames based on `__debuggerskip__`
17
18 The skipping and hiding frames are configurable via the `skip_predicates`
19 command.
20
21 By default, frames from readonly files will be hidden, frames containing
22 ``__tracebackhide__=True`` will be hidden.
23
24 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
25 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
26
27 >>> def helper_1():
28 ... print("don't step in me")
29 ...
30 ...
31 ... def helper_2():
32 ... print("in me neither")
33 ...
34
35 One can define a decorator that wraps a function between the two helpers:
36
37 >>> def pdb_skipped_decorator(function):
38 ...
39 ...
40 ... def wrapped_fn(*args, **kwargs):
41 ... __debuggerskip__ = True
42 ... helper_1()
43 ... __debuggerskip__ = False
44 ... result = function(*args, **kwargs)
45 ... __debuggerskip__ = True
46 ... helper_2()
47 ... return result
48 ...
49 ... return wrapped_fn
50
51 When decorating a function, ipdb will directly step into ``bar()`` by
52 default:
53
54 >>> @foo_decorator
55 ... def bar(x, y):
56 ... return x * y
57
58
59 You can toggle the behavior with
60
61 ipdb> skip_predicates debuggerskip false
62
63 or configure it in your ``.pdbrc``
64
65
66
67 Licencse
68 --------
69
5 Modified from the standard pdb.Pdb class to avoid including readline, so that
70 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 the command line completion of other programs which include this isn't
71 the command line completion of other programs which include this isn't
7 damaged.
72 damaged.
@@ -9,11 +74,16 b' damaged.'
9 In the future, this class will be expanded with improvements over the standard
74 In the future, this class will be expanded with improvements over the standard
10 pdb.
75 pdb.
11
76
12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
77 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
13 changes. Licensing should therefore be under the standard Python terms. For
78 with minor changes. Licensing should therefore be under the standard Python
14 details on the PSF (Python Software Foundation) standard license, see:
79 terms. For details on the PSF (Python Software Foundation) standard license,
80 see:
15
81
16 https://docs.python.org/2/license.html
82 https://docs.python.org/2/license.html
83
84
85 All the changes since then are under the same license as IPython.
86
17 """
87 """
18
88
19 #*****************************************************************************
89 #*****************************************************************************
@@ -51,6 +121,9 b' from pdb import Pdb as OldPdb'
51 # it does so with some limitations. The rest of this support is implemented in
121 # it does so with some limitations. The rest of this support is implemented in
52 # the Tracer constructor.
122 # the Tracer constructor.
53
123
124 DEBUGGERSKIP = "__debuggerskip__"
125
126
54 def make_arrow(pad):
127 def make_arrow(pad):
55 """generate the leading arrow in front of traceback or debugger"""
128 """generate the leading arrow in front of traceback or debugger"""
56 if pad >= 2:
129 if pad >= 2:
@@ -206,7 +279,12 b' class Pdb(OldPdb):'
206
279
207 """
280 """
208
281
209 default_predicates = {"tbhide": True, "readonly": False, "ipython_internal": True}
282 default_predicates = {
283 "tbhide": True,
284 "readonly": False,
285 "ipython_internal": True,
286 "debuggerskip": True,
287 }
210
288
211 def __init__(self, color_scheme=None, completekey=None,
289 def __init__(self, color_scheme=None, completekey=None,
212 stdin=None, stdout=None, context=5, **kwargs):
290 stdin=None, stdout=None, context=5, **kwargs):
@@ -305,6 +383,7 b' class Pdb(OldPdb):'
305 # list of predicates we use to skip frames
383 # list of predicates we use to skip frames
306 self._predicates = self.default_predicates
384 self._predicates = self.default_predicates
307
385
386 #
308 def set_colors(self, scheme):
387 def set_colors(self, scheme):
309 """Shorthand access to the color table scheme selector method."""
388 """Shorthand access to the color table scheme selector method."""
310 self.color_scheme_table.set_active_scheme(scheme)
389 self.color_scheme_table.set_active_scheme(scheme)
@@ -804,10 +883,53 b' class Pdb(OldPdb):'
804
883
805 do_w = do_where
884 do_w = do_where
806
885
886 def break_anywhere(self, frame):
887 """
888
889 _stop_in_decorator_internals is overly restrictive, as we may still want
890 to trace function calls, so we need to also update break_anywhere so
891 that is we don't `stop_here`, because of debugger skip, we may still
892 stop at any point inside the function
893
894 """
895 if self._predicates["debuggerskip"]:
896 if DEBUGGERSKIP in frame.f_code.co_varnames:
897 return True
898 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
899 return True
900 return super().break_anywhere(frame)
901
902 @skip_doctest
903 def _is_in_decorator_internal_and_should_skip(self, frame):
904 """
905 Utility to tell us whether we are in a decorator internal and should stop.
906
907
908
909 """
910
911 # if we are disabled don't skip
912 if not self._predicates["debuggerskip"]:
913 return False
914
915 # if frame is tagged, skip by default.
916 if DEBUGGERSKIP in frame.f_code.co_varnames:
917 return True
918
919 # if parent frame value set to True skip as well.
920 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
921 return True
922
923 return False
924
807 def stop_here(self, frame):
925 def stop_here(self, frame):
808 """Check if pdb should stop here"""
926 """Check if pdb should stop here"""
809 if not super().stop_here(frame):
927 if not super().stop_here(frame):
810 return False
928 return False
929
930 if self._is_in_decorator_internal_and_should_skip(frame) is True:
931 return False
932
811 hidden = False
933 hidden = False
812 if self.skip_hidden:
934 if self.skip_hidden:
813 hidden = self._hidden_predicate(frame)
935 hidden = self._hidden_predicate(frame)
@@ -929,10 +1051,10 b' class Pdb(OldPdb):'
929 class InterruptiblePdb(Pdb):
1051 class InterruptiblePdb(Pdb):
930 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1052 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
931
1053
932 def cmdloop(self):
1054 def cmdloop(self, intro=None):
933 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1055 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
934 try:
1056 try:
935 return OldPdb.cmdloop(self)
1057 return OldPdb.cmdloop(self, intro=intro)
936 except KeyboardInterrupt:
1058 except KeyboardInterrupt:
937 self.stop_here = lambda frame: False
1059 self.stop_here = lambda frame: False
938 self.do_quit("")
1060 self.do_quit("")
@@ -326,6 +326,118 b' def test_xmode_skip():'
326 child.close()
326 child.close()
327
327
328
328
329 skip_decorators_blocks = (
330 """
331 def helper_1():
332 pass # should not stop here
333 """,
334 """
335 def helper_2():
336 pass # should not stop here
337 """,
338 """
339 def pdb_skipped_decorator(function):
340 def wrapped_fn(*args, **kwargs):
341 __debuggerskip__ = True
342 helper_1()
343 __debuggerskip__ = False
344 result = function(*args, **kwargs)
345 __debuggerskip__ = True
346 helper_2()
347 return result
348 return wrapped_fn
349 """,
350 """
351 @pdb_skipped_decorator
352 def bar(x, y):
353 return x * y
354 """,
355 """import IPython.terminal.debugger as ipdb""",
356 """
357 def f():
358 ipdb.set_trace()
359 bar(3, 4)
360 """,
361 """
362 f()
363 """,
364 )
365
366
367 def _decorator_skip_setup():
368 import pexpect
369
370 env = os.environ.copy()
371 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
372
373 child = pexpect.spawn(
374 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
375 )
376 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
377
378 child.expect("IPython")
379 child.expect("\n")
380
381 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
382 in_prompt_number = 1
383 for cblock in dedented_blocks:
384 child.expect_exact(f"In [{in_prompt_number}]:")
385 in_prompt_number += 1
386 for line in cblock.splitlines():
387 child.sendline(line)
388 child.expect_exact(line)
389 child.sendline("")
390 return child
391
392
393 @skip_win32
394 def test_decorator_skip():
395 """test that decorator frames can be skipped."""
396
397 child = _decorator_skip_setup()
398
399 child.expect_exact("3 bar(3, 4)")
400 child.expect("ipdb>")
401
402 child.expect("ipdb>")
403 child.sendline("step")
404 child.expect_exact("step")
405
406 child.expect_exact("1 @pdb_skipped_decorator")
407
408 child.sendline("s")
409 child.expect_exact("return x * y")
410
411 child.close()
412
413
414 @skip_win32
415 def test_decorator_skip_disabled():
416 """test that decorator frame skipping can be disabled"""
417
418 child = _decorator_skip_setup()
419
420 child.expect_exact("3 bar(3, 4)")
421
422 for input_, expected in [
423 ("skip_predicates debuggerskip False", ""),
424 ("skip_predicates", "debuggerskip : False"),
425 ("step", "---> 2 def wrapped_fn"),
426 ("step", "----> 3 __debuggerskip__"),
427 ("step", "----> 4 helper_1()"),
428 ("step", "---> 1 def helper_1():"),
429 ("next", "----> 2 pass"),
430 ("next", "--Return--"),
431 ("next", "----> 5 __debuggerskip__ = False"),
432 ]:
433 child.expect("ipdb>")
434 child.sendline(input_)
435 child.expect_exact(input_)
436 child.expect_exact(expected)
437
438 child.close()
439
440
329 @skip_win32
441 @skip_win32
330 def test_where_erase_value():
442 def test_where_erase_value():
331 """Test that `where` does not access f_locals and erase values."""
443 """Test that `where` does not access f_locals and erase values."""
@@ -71,7 +71,7 b' class TerminalPdb(Pdb):'
71 enable_history_search=True,
71 enable_history_search=True,
72 mouse_support=self.shell.mouse_support,
72 mouse_support=self.shell.mouse_support,
73 complete_style=self.shell.pt_complete_style,
73 complete_style=self.shell.pt_complete_style,
74 style=self.shell.style,
74 style=getattr(self.shell, "style", None),
75 color_depth=self.shell.color_depth,
75 color_depth=self.shell.color_depth,
76 )
76 )
77
77
@@ -96,7 +96,6 b' class TerminalPdb(Pdb):'
96 # prompt itself in a different thread (we can't start an event loop
96 # prompt itself in a different thread (we can't start an event loop
97 # within an event loop). This new thread won't have any event loop
97 # within an event loop). This new thread won't have any event loop
98 # running, and here we run our prompt-loop.
98 # running, and here we run our prompt-loop.
99
100 self.preloop()
99 self.preloop()
101
100
102 try:
101 try:
@@ -131,7 +130,6 b' class TerminalPdb(Pdb):'
131
130
132 if keyboard_interrupt:
131 if keyboard_interrupt:
133 raise KeyboardInterrupt
132 raise KeyboardInterrupt
134
135 line = self.precmd(line)
133 line = self.precmd(line)
136 stop = self.onecmd(line)
134 stop = self.onecmd(line)
137 stop = self.postcmd(stop, line)
135 stop = self.postcmd(stop, line)
@@ -2,6 +2,7 b''
2 7.x Series
2 7.x Series
3 ============
3 ============
4
4
5
5 .. _version 7.28:
6 .. _version 7.28:
6
7
7 IPython 7.28
8 IPython 7.28
@@ -543,7 +544,7 b' Change of API and exposed objects automatically detected using `frappuccino'
543 <https://pypi.org/project/frappuccino/>`_ (still in beta):
544 <https://pypi.org/project/frappuccino/>`_ (still in beta):
544
545
545
546
546 The following items are new and mostly related to understanding ``__tracebackbide__``::
547 The following items are new and mostly related to understanding ``__tracebackhide__``::
547
548
548 + IPython.core.debugger.Pdb.do_down(self, arg)
549 + IPython.core.debugger.Pdb.do_down(self, arg)
549 + IPython.core.debugger.Pdb.do_skip_hidden(self, arg)
550 + IPython.core.debugger.Pdb.do_skip_hidden(self, arg)
General Comments 0
You need to be logged in to leave comments. Login now