##// END OF EJS Templates
Backport PR #13142: Pdbskip #13136...
Matthias Bussonnier -
Show More
@@ -2,6 +2,71 b''
2 2 """
3 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 70 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 71 the command line completion of other programs which include this isn't
7 72 damaged.
@@ -9,11 +74,16 b' damaged.'
9 74 In the future, this class will be expanded with improvements over the standard
10 75 pdb.
11 76
12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
13 changes. Licensing should therefore be under the standard Python terms. For
14 details on the PSF (Python Software Foundation) standard license, see:
77 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
78 with minor changes. Licensing should therefore be under the standard Python
79 terms. For details on the PSF (Python Software Foundation) standard license,
80 see:
15 81
16 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 121 # it does so with some limitations. The rest of this support is implemented in
52 122 # the Tracer constructor.
53 123
124 DEBUGGERSKIP = "__debuggerskip__"
125
126
54 127 def make_arrow(pad):
55 128 """generate the leading arrow in front of traceback or debugger"""
56 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 289 def __init__(self, color_scheme=None, completekey=None,
212 290 stdin=None, stdout=None, context=5, **kwargs):
@@ -305,6 +383,7 b' class Pdb(OldPdb):'
305 383 # list of predicates we use to skip frames
306 384 self._predicates = self.default_predicates
307 385
386 #
308 387 def set_colors(self, scheme):
309 388 """Shorthand access to the color table scheme selector method."""
310 389 self.color_scheme_table.set_active_scheme(scheme)
@@ -804,10 +883,53 b' class Pdb(OldPdb):'
804 883
805 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 925 def stop_here(self, frame):
808 926 """Check if pdb should stop here"""
809 927 if not super().stop_here(frame):
810 928 return False
929
930 if self._is_in_decorator_internal_and_should_skip(frame) is True:
931 return False
932
811 933 hidden = False
812 934 if self.skip_hidden:
813 935 hidden = self._hidden_predicate(frame)
@@ -929,10 +1051,10 b' class Pdb(OldPdb):'
929 1051 class InterruptiblePdb(Pdb):
930 1052 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
931 1053
932 def cmdloop(self):
1054 def cmdloop(self, intro=None):
933 1055 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
934 1056 try:
935 return OldPdb.cmdloop(self)
1057 return OldPdb.cmdloop(self, intro=intro)
936 1058 except KeyboardInterrupt:
937 1059 self.stop_here = lambda frame: False
938 1060 self.do_quit("")
@@ -326,6 +326,118 b' def test_xmode_skip():'
326 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 441 @skip_win32
330 442 def test_where_erase_value():
331 443 """Test that `where` does not access f_locals and erase values."""
@@ -71,7 +71,7 b' class TerminalPdb(Pdb):'
71 71 enable_history_search=True,
72 72 mouse_support=self.shell.mouse_support,
73 73 complete_style=self.shell.pt_complete_style,
74 style=self.shell.style,
74 style=getattr(self.shell, "style", None),
75 75 color_depth=self.shell.color_depth,
76 76 )
77 77
@@ -96,7 +96,6 b' class TerminalPdb(Pdb):'
96 96 # prompt itself in a different thread (we can't start an event loop
97 97 # within an event loop). This new thread won't have any event loop
98 98 # running, and here we run our prompt-loop.
99
100 99 self.preloop()
101 100
102 101 try:
@@ -131,7 +130,6 b' class TerminalPdb(Pdb):'
131 130
132 131 if keyboard_interrupt:
133 132 raise KeyboardInterrupt
134
135 133 line = self.precmd(line)
136 134 stop = self.onecmd(line)
137 135 stop = self.postcmd(stop, line)
@@ -2,6 +2,7 b''
2 2 7.x Series
3 3 ============
4 4
5
5 6 .. _version 7.28:
6 7
7 8 IPython 7.28
@@ -543,7 +544,7 b' Change of API and exposed objects automatically detected using `frappuccino'
543 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 549 + IPython.core.debugger.Pdb.do_down(self, arg)
549 550 + IPython.core.debugger.Pdb.do_skip_hidden(self, arg)
General Comments 0
You need to be logged in to leave comments. Login now