##// END OF EJS Templates
Merge pull request #13142 from Carreau/pdbskip...
Matthias Bussonnier -
r26816:d50de437 merge
parent child Browse files
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,8 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
54
126
55 def make_arrow(pad):
127 def make_arrow(pad):
56 """generate the leading arrow in front of traceback or debugger"""
128 """generate the leading arrow in front of traceback or debugger"""
@@ -206,7 +278,12 b' class Pdb(OldPdb):'
206
278
207 """
279 """
208
280
209 default_predicates = {"tbhide": True, "readonly": False, "ipython_internal": True}
281 default_predicates = {
282 "tbhide": True,
283 "readonly": False,
284 "ipython_internal": True,
285 "debuggerskip": True,
286 }
210
287
211 def __init__(self, color_scheme=None, completekey=None,
288 def __init__(self, color_scheme=None, completekey=None,
212 stdin=None, stdout=None, context=5, **kwargs):
289 stdin=None, stdout=None, context=5, **kwargs):
@@ -304,6 +381,7 b' class Pdb(OldPdb):'
304 # list of predicates we use to skip frames
381 # list of predicates we use to skip frames
305 self._predicates = self.default_predicates
382 self._predicates = self.default_predicates
306
383
384 #
307 def set_colors(self, scheme):
385 def set_colors(self, scheme):
308 """Shorthand access to the color table scheme selector method."""
386 """Shorthand access to the color table scheme selector method."""
309 self.color_scheme_table.set_active_scheme(scheme)
387 self.color_scheme_table.set_active_scheme(scheme)
@@ -815,7 +893,50 b' class Pdb(OldPdb):'
815
893
816 do_w = do_where
894 do_w = do_where
817
895
896 def break_anywhere(self, frame):
897 """
898
899 _stop_in_decorator_internals is overly restrictive, as we may still want
900 to trace function calls, so we need to also update break_anywhere so
901 that is we don't `stop_here`, because of debugger skip, we may still
902 stop at any point inside the function
903
904 """
905 if self._predicates["debuggerskip"]:
906 if DEBUGGERSKIP in frame.f_code.co_varnames:
907 return True
908 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
909 return True
910 return super().break_anywhere(frame)
911
912 @skip_doctest
913 def _is_in_decorator_internal_and_should_skip(self, frame):
914 """
915 Utility to tell us whether we are in a decorator internal and should stop.
916
917
918
919 """
920
921 # if we are disabled don't skip
922 if not self._predicates["debuggerskip"]:
923 return False
924
925 # if frame is tagged, skip by default.
926 if DEBUGGERSKIP in frame.f_code.co_varnames:
927 return True
928
929 # if parent frame value set to True skip as well.
930 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
931 return True
932
933 return False
934
818 def stop_here(self, frame):
935 def stop_here(self, frame):
936
937 if self._is_in_decorator_internal_and_should_skip(frame) is True:
938 return False
939
819 hidden = False
940 hidden = False
820 if self.skip_hidden:
941 if self.skip_hidden:
821 hidden = self._hidden_predicate(frame)
942 hidden = self._hidden_predicate(frame)
@@ -938,10 +1059,10 b' class Pdb(OldPdb):'
938 class InterruptiblePdb(Pdb):
1059 class InterruptiblePdb(Pdb):
939 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1060 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
940
1061
941 def cmdloop(self):
1062 def cmdloop(self, intro=None):
942 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1063 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
943 try:
1064 try:
944 return OldPdb.cmdloop(self)
1065 return OldPdb.cmdloop(self, intro=intro)
945 except KeyboardInterrupt:
1066 except KeyboardInterrupt:
946 self.stop_here = lambda frame: False
1067 self.stop_here = lambda frame: False
947 self.do_quit("")
1068 self.do_quit("")
@@ -323,6 +323,118 b' def test_xmode_skip():'
323 child.close()
323 child.close()
324
324
325
325
326 skip_decorators_blocks = (
327 """
328 def helper_1():
329 pass # should not stop here
330 """,
331 """
332 def helper_2():
333 pass # should not stop here
334 """,
335 """
336 def pdb_skipped_decorator(function):
337 def wrapped_fn(*args, **kwargs):
338 __debuggerskip__ = True
339 helper_1()
340 __debuggerskip__ = False
341 result = function(*args, **kwargs)
342 __debuggerskip__ = True
343 helper_2()
344 return result
345 return wrapped_fn
346 """,
347 """
348 @pdb_skipped_decorator
349 def bar(x, y):
350 return x * y
351 """,
352 """import IPython.terminal.debugger as ipdb""",
353 """
354 def f():
355 ipdb.set_trace()
356 bar(3, 4)
357 """,
358 """
359 f()
360 """,
361 )
362
363
364 def _decorator_skip_setup():
365 import pexpect
366
367 env = os.environ.copy()
368 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
369
370 child = pexpect.spawn(
371 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
372 )
373 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
374
375 child.expect("IPython")
376 child.expect("\n")
377
378 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
379 in_prompt_number = 1
380 for cblock in dedented_blocks:
381 child.expect_exact(f"In [{in_prompt_number}]:")
382 in_prompt_number += 1
383 for line in cblock.splitlines():
384 child.sendline(line)
385 child.expect_exact(line)
386 child.sendline("")
387 return child
388
389
390 @skip_win32
391 def test_decorator_skip():
392 """test that decorator frames can be skipped."""
393
394 child = _decorator_skip_setup()
395
396 child.expect_exact("3 bar(3, 4)")
397 child.expect("ipdb>")
398
399 child.expect("ipdb>")
400 child.sendline("step")
401 child.expect_exact("step")
402
403 child.expect_exact("1 @pdb_skipped_decorator")
404
405 child.sendline("s")
406 child.expect_exact("return x * y")
407
408 child.close()
409
410
411 @skip_win32
412 def test_decorator_skip_disabled():
413 """test that decorator frame skipping can be disabled"""
414
415 child = _decorator_skip_setup()
416
417 child.expect_exact("3 bar(3, 4)")
418
419 for input_, expected in [
420 ("skip_predicates debuggerskip False", ""),
421 ("skip_predicates", "debuggerskip : False"),
422 ("step", "---> 2 def wrapped_fn"),
423 ("step", "----> 3 __debuggerskip__"),
424 ("step", "----> 4 helper_1()"),
425 ("step", "---> 1 def helper_1():"),
426 ("next", "----> 2 pass"),
427 ("next", "--Return--"),
428 ("next", "----> 5 __debuggerskip__ = False"),
429 ]:
430 child.expect("ipdb>")
431 child.sendline(input_)
432 child.expect_exact(input_)
433 child.expect_exact(expected)
434
435 child.close()
436
437
326 @skip_win32
438 @skip_win32
327 def test_where_erase_value():
439 def test_where_erase_value():
328 """Test that `where` does not access f_locals and erase values."""
440 """Test that `where` does not access f_locals and erase values."""
@@ -79,7 +79,7 b' class TerminalPdb(Pdb):'
79 enable_history_search=True,
79 enable_history_search=True,
80 mouse_support=self.shell.mouse_support,
80 mouse_support=self.shell.mouse_support,
81 complete_style=self.shell.pt_complete_style,
81 complete_style=self.shell.pt_complete_style,
82 style=self.shell.style,
82 style=getattr(self.shell, "style", None),
83 color_depth=self.shell.color_depth,
83 color_depth=self.shell.color_depth,
84 )
84 )
85
85
@@ -104,7 +104,6 b' class TerminalPdb(Pdb):'
104 # prompt itself in a different thread (we can't start an event loop
104 # prompt itself in a different thread (we can't start an event loop
105 # within an event loop). This new thread won't have any event loop
105 # within an event loop). This new thread won't have any event loop
106 # running, and here we run our prompt-loop.
106 # running, and here we run our prompt-loop.
107
108 self.preloop()
107 self.preloop()
109
108
110 try:
109 try:
@@ -139,7 +138,6 b' class TerminalPdb(Pdb):'
139
138
140 if keyboard_interrupt:
139 if keyboard_interrupt:
141 raise KeyboardInterrupt
140 raise KeyboardInterrupt
142
143 line = self.precmd(line)
141 line = self.precmd(line)
144 stop = self.onecmd(line)
142 stop = self.onecmd(line)
145 stop = self.postcmd(stop, line)
143 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