##// END OF EJS Templates
Expand and Fix PDB skip....
Matthias Bussonnier -
Show More
@@ -1,1093 +1,1106 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Pdb debugger class.
4 4
5 5
6 6 This is an extension to PDB which adds a number of new features.
7 7 Note that there is also the `IPython.terminal.debugger` class which provides UI
8 8 improvements.
9 9
10 10 We also strongly recommend to use this via the `ipdb` package, which provides
11 11 extra configuration options.
12 12
13 13 Among other things, this subclass of PDB:
14 14 - supports many IPython magics like pdef/psource
15 15 - hide frames in tracebacks based on `__tracebackhide__`
16 16 - allows to skip frames based on `__debuggerskip__`
17 17
18 18 The skipping and hiding frames are configurable via the `skip_predicates`
19 19 command.
20 20
21 21 By default, frames from readonly files will be hidden, frames containing
22 22 ``__tracebackhide__=True`` will be hidden.
23 23
24 24 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
25 25 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
26 26
27 >>> def helper_1():
27 >>> def helpers_helper():
28 ... pass
29 ...
30 ... def helper_1():
28 31 ... print("don't step in me")
32 ... helpers_helpers() # will be stepped over unless breakpoint set.
29 33 ...
30 34 ...
31 35 ... def helper_2():
32 36 ... print("in me neither")
33 37 ...
34 38
35 39 One can define a decorator that wraps a function between the two helpers:
36 40
37 41 >>> def pdb_skipped_decorator(function):
38 42 ...
39 43 ...
40 44 ... def wrapped_fn(*args, **kwargs):
41 45 ... __debuggerskip__ = True
42 46 ... helper_1()
43 47 ... __debuggerskip__ = False
44 48 ... result = function(*args, **kwargs)
45 49 ... __debuggerskip__ = True
46 50 ... helper_2()
51 ... # setting __debuggerskip__ to False again is not necessary
47 52 ... return result
48 53 ...
49 54 ... return wrapped_fn
50 55
51 56 When decorating a function, ipdb will directly step into ``bar()`` by
52 57 default:
53 58
54 59 >>> @foo_decorator
55 60 ... def bar(x, y):
56 61 ... return x * y
57 62
58 63
59 64 You can toggle the behavior with
60 65
61 66 ipdb> skip_predicates debuggerskip false
62 67
63 68 or configure it in your ``.pdbrc``
64 69
65 70
66 71
67 72 Licencse
68 73 --------
69 74
70 75 Modified from the standard pdb.Pdb class to avoid including readline, so that
71 76 the command line completion of other programs which include this isn't
72 77 damaged.
73 78
74 79 In the future, this class will be expanded with improvements over the standard
75 80 pdb.
76 81
77 82 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
78 83 with minor changes. Licensing should therefore be under the standard Python
79 84 terms. For details on the PSF (Python Software Foundation) standard license,
80 85 see:
81 86
82 87 https://docs.python.org/2/license.html
83 88
84 89
85 90 All the changes since then are under the same license as IPython.
86 91
87 92 """
88 93
89 94 #*****************************************************************************
90 95 #
91 96 # This file is licensed under the PSF license.
92 97 #
93 98 # Copyright (C) 2001 Python Software Foundation, www.python.org
94 99 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
95 100 #
96 101 #
97 102 #*****************************************************************************
98 103
99 104 import bdb
100 105 import functools
101 106 import inspect
102 107 import linecache
103 108 import sys
104 109 import warnings
105 110 import re
106 111 import os
107 112
108 113 from IPython import get_ipython
109 114 from IPython.utils import PyColorize
110 115 from IPython.utils import coloransi, py3compat
111 116 from IPython.core.excolors import exception_colors
112 117 from IPython.testing.skipdoctest import skip_doctest
113 118
114 119
115 120 prompt = 'ipdb> '
116 121
117 122 # We have to check this directly from sys.argv, config struct not yet available
118 123 from pdb import Pdb as OldPdb
119 124
120 125 # Allow the set_trace code to operate outside of an ipython instance, even if
121 126 # it does so with some limitations. The rest of this support is implemented in
122 127 # the Tracer constructor.
123 128
124 129 DEBUGGERSKIP = "__debuggerskip__"
125 130
126 131
127 132 def make_arrow(pad):
128 133 """generate the leading arrow in front of traceback or debugger"""
129 134 if pad >= 2:
130 135 return '-'*(pad-2) + '> '
131 136 elif pad == 1:
132 137 return '>'
133 138 return ''
134 139
135 140
136 141 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
137 142 """Exception hook which handles `BdbQuit` exceptions.
138 143
139 144 All other exceptions are processed using the `excepthook`
140 145 parameter.
141 146 """
142 147 warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1",
143 148 DeprecationWarning, stacklevel=2)
144 149 if et == bdb.BdbQuit:
145 150 print('Exiting Debugger.')
146 151 elif excepthook is not None:
147 152 excepthook(et, ev, tb)
148 153 else:
149 154 # Backwards compatibility. Raise deprecation warning?
150 155 BdbQuit_excepthook.excepthook_ori(et, ev, tb)
151 156
152 157
153 158 def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None):
154 159 warnings.warn(
155 160 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1",
156 161 DeprecationWarning, stacklevel=2)
157 162 print('Exiting Debugger.')
158 163
159 164
160 165 class Tracer(object):
161 166 """
162 167 DEPRECATED
163 168
164 169 Class for local debugging, similar to pdb.set_trace.
165 170
166 171 Instances of this class, when called, behave like pdb.set_trace, but
167 172 providing IPython's enhanced capabilities.
168 173
169 174 This is implemented as a class which must be initialized in your own code
170 175 and not as a standalone function because we need to detect at runtime
171 176 whether IPython is already active or not. That detection is done in the
172 177 constructor, ensuring that this code plays nicely with a running IPython,
173 178 while functioning acceptably (though with limitations) if outside of it.
174 179 """
175 180
176 181 @skip_doctest
177 182 def __init__(self, colors=None):
178 183 """
179 184 DEPRECATED
180 185
181 186 Create a local debugger instance.
182 187
183 188 Parameters
184 189 ----------
185 190 colors : str, optional
186 191 The name of the color scheme to use, it must be one of IPython's
187 192 valid color schemes. If not given, the function will default to
188 193 the current IPython scheme when running inside IPython, and to
189 194 'NoColor' otherwise.
190 195
191 196 Examples
192 197 --------
193 198 ::
194 199
195 200 from IPython.core.debugger import Tracer; debug_here = Tracer()
196 201
197 202 Later in your code::
198 203
199 204 debug_here() # -> will open up the debugger at that point.
200 205
201 206 Once the debugger activates, you can use all of its regular commands to
202 207 step through code, set breakpoints, etc. See the pdb documentation
203 208 from the Python standard library for usage details.
204 209 """
205 210 warnings.warn("`Tracer` is deprecated since version 5.1, directly use "
206 211 "`IPython.core.debugger.Pdb.set_trace()`",
207 212 DeprecationWarning, stacklevel=2)
208 213
209 214 ip = get_ipython()
210 215 if ip is None:
211 216 # Outside of ipython, we set our own exception hook manually
212 217 sys.excepthook = functools.partial(BdbQuit_excepthook,
213 218 excepthook=sys.excepthook)
214 219 def_colors = 'NoColor'
215 220 else:
216 221 # In ipython, we use its custom exception handler mechanism
217 222 def_colors = ip.colors
218 223 ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook)
219 224
220 225 if colors is None:
221 226 colors = def_colors
222 227
223 228 # The stdlib debugger internally uses a modified repr from the `repr`
224 229 # module, that limits the length of printed strings to a hardcoded
225 230 # limit of 30 characters. That much trimming is too aggressive, let's
226 231 # at least raise that limit to 80 chars, which should be enough for
227 232 # most interactive uses.
228 233 try:
229 234 from reprlib import aRepr
230 235 aRepr.maxstring = 80
231 236 except:
232 237 # This is only a user-facing convenience, so any error we encounter
233 238 # here can be warned about but can be otherwise ignored. These
234 239 # printouts will tell us about problems if this API changes
235 240 import traceback
236 241 traceback.print_exc()
237 242
238 243 self.debugger = Pdb(colors)
239 244
240 245 def __call__(self):
241 246 """Starts an interactive debugger at the point where called.
242 247
243 248 This is similar to the pdb.set_trace() function from the std lib, but
244 249 using IPython's enhanced debugger."""
245 250
246 251 self.debugger.set_trace(sys._getframe().f_back)
247 252
248 253
249 254 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
250 255
251 256
252 257 def strip_indentation(multiline_string):
253 258 return RGX_EXTRA_INDENT.sub('', multiline_string)
254 259
255 260
256 261 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
257 262 """Make new_fn have old_fn's doc string. This is particularly useful
258 263 for the ``do_...`` commands that hook into the help system.
259 264 Adapted from from a comp.lang.python posting
260 265 by Duncan Booth."""
261 266 def wrapper(*args, **kw):
262 267 return new_fn(*args, **kw)
263 268 if old_fn.__doc__:
264 269 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
265 270 return wrapper
266 271
267 272
268 273 class Pdb(OldPdb):
269 274 """Modified Pdb class, does not load readline.
270 275
271 276 for a standalone version that uses prompt_toolkit, see
272 277 `IPython.terminal.debugger.TerminalPdb` and
273 278 `IPython.terminal.debugger.set_trace()`
274 279
275 280
276 281 This debugger can hide and skip frames that are tagged according to some predicates.
277 282 See the `skip_predicates` commands.
278 283
279 284 """
280 285
281 286 default_predicates = {
282 287 "tbhide": True,
283 288 "readonly": False,
284 289 "ipython_internal": True,
285 290 "debuggerskip": True,
286 291 }
287 292
288 293 def __init__(self, color_scheme=None, completekey=None,
289 294 stdin=None, stdout=None, context=5, **kwargs):
290 295 """Create a new IPython debugger.
291 296
292 297 Parameters
293 298 ----------
294 299 color_scheme : default None
295 300 Deprecated, do not use.
296 301 completekey : default None
297 302 Passed to pdb.Pdb.
298 303 stdin : default None
299 304 Passed to pdb.Pdb.
300 305 stdout : default None
301 306 Passed to pdb.Pdb.
302 307 context : int
303 308 Number of lines of source code context to show when
304 309 displaying stacktrace information.
305 310 **kwargs
306 311 Passed to pdb.Pdb.
307 312
308 313 Notes
309 314 -----
310 315 The possibilities are python version dependent, see the python
311 316 docs for more info.
312 317 """
313 318
314 319 # Parent constructor:
315 320 try:
316 321 self.context = int(context)
317 322 if self.context <= 0:
318 323 raise ValueError("Context must be a positive integer")
319 324 except (TypeError, ValueError) as e:
320 325 raise ValueError("Context must be a positive integer") from e
321 326
322 327 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
323 328 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
324 329
325 330 # IPython changes...
326 331 self.shell = get_ipython()
327 332
328 333 if self.shell is None:
329 334 save_main = sys.modules['__main__']
330 335 # No IPython instance running, we must create one
331 336 from IPython.terminal.interactiveshell import \
332 337 TerminalInteractiveShell
333 338 self.shell = TerminalInteractiveShell.instance()
334 339 # needed by any code which calls __import__("__main__") after
335 340 # the debugger was entered. See also #9941.
336 341 sys.modules["__main__"] = save_main
337 342
338 343 if color_scheme is not None:
339 344 warnings.warn(
340 345 "The `color_scheme` argument is deprecated since version 5.1",
341 346 DeprecationWarning, stacklevel=2)
342 347 else:
343 348 color_scheme = self.shell.colors
344 349
345 350 self.aliases = {}
346 351
347 352 # Create color table: we copy the default one from the traceback
348 353 # module and add a few attributes needed for debugging
349 354 self.color_scheme_table = exception_colors()
350 355
351 356 # shorthands
352 357 C = coloransi.TermColors
353 358 cst = self.color_scheme_table
354 359
355 360 cst['NoColor'].colors.prompt = C.NoColor
356 361 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
357 362 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
358 363
359 364 cst['Linux'].colors.prompt = C.Green
360 365 cst['Linux'].colors.breakpoint_enabled = C.LightRed
361 366 cst['Linux'].colors.breakpoint_disabled = C.Red
362 367
363 368 cst['LightBG'].colors.prompt = C.Blue
364 369 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
365 370 cst['LightBG'].colors.breakpoint_disabled = C.Red
366 371
367 372 cst['Neutral'].colors.prompt = C.Blue
368 373 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
369 374 cst['Neutral'].colors.breakpoint_disabled = C.Red
370 375
371 376 # Add a python parser so we can syntax highlight source while
372 377 # debugging.
373 378 self.parser = PyColorize.Parser(style=color_scheme)
374 379 self.set_colors(color_scheme)
375 380
376 381 # Set the prompt - the default prompt is '(Pdb)'
377 382 self.prompt = prompt
378 383 self.skip_hidden = True
379 384 self.report_skipped = True
380 385
381 386 # list of predicates we use to skip frames
382 387 self._predicates = self.default_predicates
383 388
384 389 #
385 390 def set_colors(self, scheme):
386 391 """Shorthand access to the color table scheme selector method."""
387 392 self.color_scheme_table.set_active_scheme(scheme)
388 393 self.parser.style = scheme
389 394
390 395 def set_trace(self, frame=None):
391 396 if frame is None:
392 397 frame = sys._getframe().f_back
393 398 self.initial_frame = frame
394 399 return super().set_trace(frame)
395 400
396 401 def _hidden_predicate(self, frame):
397 402 """
398 403 Given a frame return whether it it should be hidden or not by IPython.
399 404 """
400 405
401 406 if self._predicates["readonly"]:
402 407 fname = frame.f_code.co_filename
403 408 # we need to check for file existence and interactively define
404 409 # function would otherwise appear as RO.
405 410 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
406 411 return True
407 412
408 413 if self._predicates["tbhide"]:
409 414 if frame in (self.curframe, getattr(self, "initial_frame", None)):
410 415 return False
411 416 else:
412 417 return self._get_frame_locals(frame).get("__tracebackhide__", False)
413 418
414 419 return False
415 420
416 421 def hidden_frames(self, stack):
417 422 """
418 423 Given an index in the stack return whether it should be skipped.
419 424
420 425 This is used in up/down and where to skip frames.
421 426 """
422 427 # The f_locals dictionary is updated from the actual frame
423 428 # locals whenever the .f_locals accessor is called, so we
424 429 # avoid calling it here to preserve self.curframe_locals.
425 430 # Futhermore, there is no good reason to hide the current frame.
426 431 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
427 432 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
428 433 if ip_start and self._predicates["ipython_internal"]:
429 434 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
430 435 return ip_hide
431 436
432 437 def interaction(self, frame, traceback):
433 438 try:
434 439 OldPdb.interaction(self, frame, traceback)
435 440 except KeyboardInterrupt:
436 441 self.stdout.write("\n" + self.shell.get_exception_only())
437 442
438 443 def precmd(self, line):
439 444 """Perform useful escapes on the command before it is executed."""
440 445
441 446 if line.endswith("??"):
442 447 line = "pinfo2 " + line[:-2]
443 448 elif line.endswith("?"):
444 449 line = "pinfo " + line[:-1]
445 450
446 451 line = super().precmd(line)
447 452
448 453 return line
449 454
450 455 def new_do_frame(self, arg):
451 456 OldPdb.do_frame(self, arg)
452 457
453 458 def new_do_quit(self, arg):
454 459
455 460 if hasattr(self, 'old_all_completions'):
456 461 self.shell.Completer.all_completions = self.old_all_completions
457 462
458 463 return OldPdb.do_quit(self, arg)
459 464
460 465 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
461 466
462 467 def new_do_restart(self, arg):
463 468 """Restart command. In the context of ipython this is exactly the same
464 469 thing as 'quit'."""
465 470 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
466 471 return self.do_quit(arg)
467 472
468 473 def print_stack_trace(self, context=None):
469 474 Colors = self.color_scheme_table.active_colors
470 475 ColorsNormal = Colors.Normal
471 476 if context is None:
472 477 context = self.context
473 478 try:
474 479 context = int(context)
475 480 if context <= 0:
476 481 raise ValueError("Context must be a positive integer")
477 482 except (TypeError, ValueError) as e:
478 483 raise ValueError("Context must be a positive integer") from e
479 484 try:
480 485 skipped = 0
481 486 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
482 487 if hidden and self.skip_hidden:
483 488 skipped += 1
484 489 continue
485 490 if skipped:
486 491 print(
487 492 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
488 493 )
489 494 skipped = 0
490 495 self.print_stack_entry(frame_lineno, context=context)
491 496 if skipped:
492 497 print(
493 498 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
494 499 )
495 500 except KeyboardInterrupt:
496 501 pass
497 502
498 503 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
499 504 context=None):
500 505 if context is None:
501 506 context = self.context
502 507 try:
503 508 context = int(context)
504 509 if context <= 0:
505 510 raise ValueError("Context must be a positive integer")
506 511 except (TypeError, ValueError) as e:
507 512 raise ValueError("Context must be a positive integer") from e
508 513 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
509 514
510 515 # vds: >>
511 516 frame, lineno = frame_lineno
512 517 filename = frame.f_code.co_filename
513 518 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
514 519 # vds: <<
515 520
516 521 def _get_frame_locals(self, frame):
517 522 """ "
518 523 Acessing f_local of current frame reset the namespace, so we want to avoid
519 524 that or the following can happend
520 525
521 526 ipdb> foo
522 527 "old"
523 528 ipdb> foo = "new"
524 529 ipdb> foo
525 530 "new"
526 531 ipdb> where
527 532 ipdb> foo
528 533 "old"
529 534
530 535 So if frame is self.current_frame we instead return self.curframe_locals
531 536
532 537 """
533 538 if frame is self.curframe:
534 539 return self.curframe_locals
535 540 else:
536 541 return frame.f_locals
537 542
538 543 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
539 544 if context is None:
540 545 context = self.context
541 546 try:
542 547 context = int(context)
543 548 if context <= 0:
544 549 print("Context must be a positive integer", file=self.stdout)
545 550 except (TypeError, ValueError):
546 551 print("Context must be a positive integer", file=self.stdout)
547 552
548 553 import reprlib
549 554
550 555 ret = []
551 556
552 557 Colors = self.color_scheme_table.active_colors
553 558 ColorsNormal = Colors.Normal
554 559 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
555 560 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
556 561 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
557 562 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
558 563
559 564 frame, lineno = frame_lineno
560 565
561 566 return_value = ''
562 567 loc_frame = self._get_frame_locals(frame)
563 568 if "__return__" in loc_frame:
564 569 rv = loc_frame["__return__"]
565 570 # return_value += '->'
566 571 return_value += reprlib.repr(rv) + "\n"
567 572 ret.append(return_value)
568 573
569 574 #s = filename + '(' + `lineno` + ')'
570 575 filename = self.canonic(frame.f_code.co_filename)
571 576 link = tpl_link % py3compat.cast_unicode(filename)
572 577
573 578 if frame.f_code.co_name:
574 579 func = frame.f_code.co_name
575 580 else:
576 581 func = "<lambda>"
577 582
578 583 call = ""
579 584 if func != "?":
580 585 if "__args__" in loc_frame:
581 586 args = reprlib.repr(loc_frame["__args__"])
582 587 else:
583 588 args = '()'
584 589 call = tpl_call % (func, args)
585 590
586 591 # The level info should be generated in the same format pdb uses, to
587 592 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
588 593 if frame is self.curframe:
589 594 ret.append('> ')
590 595 else:
591 596 ret.append(" ")
592 597 ret.append("%s(%s)%s\n" % (link, lineno, call))
593 598
594 599 start = lineno - 1 - context//2
595 600 lines = linecache.getlines(filename)
596 601 start = min(start, len(lines) - context)
597 602 start = max(start, 0)
598 603 lines = lines[start : start + context]
599 604
600 605 for i, line in enumerate(lines):
601 606 show_arrow = start + 1 + i == lineno
602 607 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
603 608 ret.append(
604 609 self.__format_line(
605 610 linetpl, filename, start + 1 + i, line, arrow=show_arrow
606 611 )
607 612 )
608 613 return "".join(ret)
609 614
610 615 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
611 616 bp_mark = ""
612 617 bp_mark_color = ""
613 618
614 619 new_line, err = self.parser.format2(line, 'str')
615 620 if not err:
616 621 line = new_line
617 622
618 623 bp = None
619 624 if lineno in self.get_file_breaks(filename):
620 625 bps = self.get_breaks(filename, lineno)
621 626 bp = bps[-1]
622 627
623 628 if bp:
624 629 Colors = self.color_scheme_table.active_colors
625 630 bp_mark = str(bp.number)
626 631 bp_mark_color = Colors.breakpoint_enabled
627 632 if not bp.enabled:
628 633 bp_mark_color = Colors.breakpoint_disabled
629 634
630 635 numbers_width = 7
631 636 if arrow:
632 637 # This is the line with the error
633 638 pad = numbers_width - len(str(lineno)) - len(bp_mark)
634 639 num = '%s%s' % (make_arrow(pad), str(lineno))
635 640 else:
636 641 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
637 642
638 643 return tpl_line % (bp_mark_color + bp_mark, num, line)
639 644
640 645 def print_list_lines(self, filename, first, last):
641 646 """The printing (as opposed to the parsing part of a 'list'
642 647 command."""
643 648 try:
644 649 Colors = self.color_scheme_table.active_colors
645 650 ColorsNormal = Colors.Normal
646 651 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
647 652 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
648 653 src = []
649 654 if filename == "<string>" and hasattr(self, "_exec_filename"):
650 655 filename = self._exec_filename
651 656
652 657 for lineno in range(first, last+1):
653 658 line = linecache.getline(filename, lineno)
654 659 if not line:
655 660 break
656 661
657 662 if lineno == self.curframe.f_lineno:
658 663 line = self.__format_line(
659 664 tpl_line_em, filename, lineno, line, arrow=True
660 665 )
661 666 else:
662 667 line = self.__format_line(
663 668 tpl_line, filename, lineno, line, arrow=False
664 669 )
665 670
666 671 src.append(line)
667 672 self.lineno = lineno
668 673
669 674 print(''.join(src), file=self.stdout)
670 675
671 676 except KeyboardInterrupt:
672 677 pass
673 678
674 679 def do_skip_predicates(self, args):
675 680 """
676 681 Turn on/off individual predicates as to whether a frame should be hidden/skip.
677 682
678 683 The global option to skip (or not) hidden frames is set with skip_hidden
679 684
680 685 To change the value of a predicate
681 686
682 687 skip_predicates key [true|false]
683 688
684 689 Call without arguments to see the current values.
685 690
686 691 To permanently change the value of an option add the corresponding
687 692 command to your ``~/.pdbrc`` file. If you are programmatically using the
688 693 Pdb instance you can also change the ``default_predicates`` class
689 694 attribute.
690 695 """
691 696 if not args.strip():
692 697 print("current predicates:")
693 698 for (p, v) in self._predicates.items():
694 699 print(" ", p, ":", v)
695 700 return
696 701 type_value = args.strip().split(" ")
697 702 if len(type_value) != 2:
698 703 print(
699 704 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
700 705 )
701 706 return
702 707
703 708 type_, value = type_value
704 709 if type_ not in self._predicates:
705 710 print(f"{type_!r} not in {set(self._predicates.keys())}")
706 711 return
707 712 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
708 713 print(
709 714 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
710 715 )
711 716 return
712 717
713 718 self._predicates[type_] = value.lower() in ("true", "yes", "1")
714 719 if not any(self._predicates.values()):
715 720 print(
716 721 "Warning, all predicates set to False, skip_hidden may not have any effects."
717 722 )
718 723
719 724 def do_skip_hidden(self, arg):
720 725 """
721 726 Change whether or not we should skip frames with the
722 727 __tracebackhide__ attribute.
723 728 """
724 729 if not arg.strip():
725 730 print(
726 731 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
727 732 )
728 733 elif arg.strip().lower() in ("true", "yes"):
729 734 self.skip_hidden = True
730 735 elif arg.strip().lower() in ("false", "no"):
731 736 self.skip_hidden = False
732 737 if not any(self._predicates.values()):
733 738 print(
734 739 "Warning, all predicates set to False, skip_hidden may not have any effects."
735 740 )
736 741
737 742 def do_list(self, arg):
738 743 """Print lines of code from the current stack frame
739 744 """
740 745 self.lastcmd = 'list'
741 746 last = None
742 747 if arg:
743 748 try:
744 749 x = eval(arg, {}, {})
745 750 if type(x) == type(()):
746 751 first, last = x
747 752 first = int(first)
748 753 last = int(last)
749 754 if last < first:
750 755 # Assume it's a count
751 756 last = first + last
752 757 else:
753 758 first = max(1, int(x) - 5)
754 759 except:
755 760 print('*** Error in argument:', repr(arg), file=self.stdout)
756 761 return
757 762 elif self.lineno is None:
758 763 first = max(1, self.curframe.f_lineno - 5)
759 764 else:
760 765 first = self.lineno + 1
761 766 if last is None:
762 767 last = first + 10
763 768 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
764 769
765 770 # vds: >>
766 771 lineno = first
767 772 filename = self.curframe.f_code.co_filename
768 773 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
769 774 # vds: <<
770 775
771 776 do_l = do_list
772 777
773 778 def getsourcelines(self, obj):
774 779 lines, lineno = inspect.findsource(obj)
775 780 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
776 781 # must be a module frame: do not try to cut a block out of it
777 782 return lines, 1
778 783 elif inspect.ismodule(obj):
779 784 return lines, 1
780 785 return inspect.getblock(lines[lineno:]), lineno+1
781 786
782 787 def do_longlist(self, arg):
783 788 """Print lines of code from the current stack frame.
784 789
785 790 Shows more lines than 'list' does.
786 791 """
787 792 self.lastcmd = 'longlist'
788 793 try:
789 794 lines, lineno = self.getsourcelines(self.curframe)
790 795 except OSError as err:
791 796 self.error(err)
792 797 return
793 798 last = lineno + len(lines)
794 799 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
795 800 do_ll = do_longlist
796 801
797 802 def do_debug(self, arg):
798 803 """debug code
799 804 Enter a recursive debugger that steps through the code
800 805 argument (which is an arbitrary expression or statement to be
801 806 executed in the current environment).
802 807 """
803 808 trace_function = sys.gettrace()
804 809 sys.settrace(None)
805 810 globals = self.curframe.f_globals
806 811 locals = self.curframe_locals
807 812 p = self.__class__(completekey=self.completekey,
808 813 stdin=self.stdin, stdout=self.stdout)
809 814 p.use_rawinput = self.use_rawinput
810 815 p.prompt = "(%s) " % self.prompt.strip()
811 816 self.message("ENTERING RECURSIVE DEBUGGER")
812 817 sys.call_tracing(p.run, (arg, globals, locals))
813 818 self.message("LEAVING RECURSIVE DEBUGGER")
814 819 sys.settrace(trace_function)
815 820 self.lastcmd = p.lastcmd
816 821
817 822 def do_pdef(self, arg):
818 823 """Print the call signature for any callable object.
819 824
820 825 The debugger interface to %pdef"""
821 826 namespaces = [
822 827 ("Locals", self.curframe_locals),
823 828 ("Globals", self.curframe.f_globals),
824 829 ]
825 830 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
826 831
827 832 def do_pdoc(self, arg):
828 833 """Print the docstring for an object.
829 834
830 835 The debugger interface to %pdoc."""
831 836 namespaces = [
832 837 ("Locals", self.curframe_locals),
833 838 ("Globals", self.curframe.f_globals),
834 839 ]
835 840 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
836 841
837 842 def do_pfile(self, arg):
838 843 """Print (or run through pager) the file where an object is defined.
839 844
840 845 The debugger interface to %pfile.
841 846 """
842 847 namespaces = [
843 848 ("Locals", self.curframe_locals),
844 849 ("Globals", self.curframe.f_globals),
845 850 ]
846 851 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
847 852
848 853 def do_pinfo(self, arg):
849 854 """Provide detailed information about an object.
850 855
851 856 The debugger interface to %pinfo, i.e., obj?."""
852 857 namespaces = [
853 858 ("Locals", self.curframe_locals),
854 859 ("Globals", self.curframe.f_globals),
855 860 ]
856 861 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
857 862
858 863 def do_pinfo2(self, arg):
859 864 """Provide extra detailed information about an object.
860 865
861 866 The debugger interface to %pinfo2, i.e., obj??."""
862 867 namespaces = [
863 868 ("Locals", self.curframe_locals),
864 869 ("Globals", self.curframe.f_globals),
865 870 ]
866 871 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
867 872
868 873 def do_psource(self, arg):
869 874 """Print (or run through pager) the source code for an object."""
870 875 namespaces = [
871 876 ("Locals", self.curframe_locals),
872 877 ("Globals", self.curframe.f_globals),
873 878 ]
874 879 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
875 880
876 881 def do_where(self, arg):
877 882 """w(here)
878 883 Print a stack trace, with the most recent frame at the bottom.
879 884 An arrow indicates the "current frame", which determines the
880 885 context of most commands. 'bt' is an alias for this command.
881 886
882 887 Take a number as argument as an (optional) number of context line to
883 888 print"""
884 889 if arg:
885 890 try:
886 891 context = int(arg)
887 892 except ValueError as err:
888 893 self.error(err)
889 894 return
890 895 self.print_stack_trace(context)
891 896 else:
892 897 self.print_stack_trace()
893 898
894 899 do_w = do_where
895 900
896 901 def break_anywhere(self, frame):
897 902 """
898 903
899 904 _stop_in_decorator_internals is overly restrictive, as we may still want
900 905 to trace function calls, so we need to also update break_anywhere so
901 906 that is we don't `stop_here`, because of debugger skip, we may still
902 907 stop at any point inside the function
903 908
904 909 """
910
911 sup = super().break_anywhere(frame)
912 if sup:
913 return sup
905 914 if self._predicates["debuggerskip"]:
906 915 if DEBUGGERSKIP in frame.f_code.co_varnames:
907 916 return True
908 917 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
909 918 return True
910 return super().break_anywhere(frame)
919 return False
911 920
912 921 @skip_doctest
913 922 def _is_in_decorator_internal_and_should_skip(self, frame):
914 923 """
915 924 Utility to tell us whether we are in a decorator internal and should stop.
916 925
917 926
918 927
919 928 """
920 929
921 930 # if we are disabled don't skip
922 931 if not self._predicates["debuggerskip"]:
923 932 return False
924 933
925 934 # if frame is tagged, skip by default.
926 935 if DEBUGGERSKIP in frame.f_code.co_varnames:
927 936 return True
928 937
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
938 # if one of the parent frame value set to True skip as well.
939
940 cframe = frame
941 while getattr(cframe, "f_back", None):
942 cframe = cframe.f_back
943 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
944 return True
932 945
933 946 return False
934 947
935 948 def stop_here(self, frame):
936 949
937 950 if self._is_in_decorator_internal_and_should_skip(frame) is True:
938 951 return False
939 952
940 953 hidden = False
941 954 if self.skip_hidden:
942 955 hidden = self._hidden_predicate(frame)
943 956 if hidden:
944 957 if self.report_skipped:
945 958 Colors = self.color_scheme_table.active_colors
946 959 ColorsNormal = Colors.Normal
947 960 print(
948 961 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
949 962 )
950 963 return super().stop_here(frame)
951 964
952 965 def do_up(self, arg):
953 966 """u(p) [count]
954 967 Move the current frame count (default one) levels up in the
955 968 stack trace (to an older frame).
956 969
957 970 Will skip hidden frames.
958 971 """
959 972 # modified version of upstream that skips
960 973 # frames with __tracebackhide__
961 974 if self.curindex == 0:
962 975 self.error("Oldest frame")
963 976 return
964 977 try:
965 978 count = int(arg or 1)
966 979 except ValueError:
967 980 self.error("Invalid frame count (%s)" % arg)
968 981 return
969 982 skipped = 0
970 983 if count < 0:
971 984 _newframe = 0
972 985 else:
973 986 counter = 0
974 987 hidden_frames = self.hidden_frames(self.stack)
975 988 for i in range(self.curindex - 1, -1, -1):
976 989 if hidden_frames[i] and self.skip_hidden:
977 990 skipped += 1
978 991 continue
979 992 counter += 1
980 993 if counter >= count:
981 994 break
982 995 else:
983 996 # if no break occured.
984 997 self.error(
985 998 "all frames above hidden, use `skip_hidden False` to get get into those."
986 999 )
987 1000 return
988 1001
989 1002 Colors = self.color_scheme_table.active_colors
990 1003 ColorsNormal = Colors.Normal
991 1004 _newframe = i
992 1005 self._select_frame(_newframe)
993 1006 if skipped:
994 1007 print(
995 1008 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
996 1009 )
997 1010
998 1011 def do_down(self, arg):
999 1012 """d(own) [count]
1000 1013 Move the current frame count (default one) levels down in the
1001 1014 stack trace (to a newer frame).
1002 1015
1003 1016 Will skip hidden frames.
1004 1017 """
1005 1018 if self.curindex + 1 == len(self.stack):
1006 1019 self.error("Newest frame")
1007 1020 return
1008 1021 try:
1009 1022 count = int(arg or 1)
1010 1023 except ValueError:
1011 1024 self.error("Invalid frame count (%s)" % arg)
1012 1025 return
1013 1026 if count < 0:
1014 1027 _newframe = len(self.stack) - 1
1015 1028 else:
1016 1029 counter = 0
1017 1030 skipped = 0
1018 1031 hidden_frames = self.hidden_frames(self.stack)
1019 1032 for i in range(self.curindex + 1, len(self.stack)):
1020 1033 if hidden_frames[i] and self.skip_hidden:
1021 1034 skipped += 1
1022 1035 continue
1023 1036 counter += 1
1024 1037 if counter >= count:
1025 1038 break
1026 1039 else:
1027 1040 self.error(
1028 1041 "all frames bellow hidden, use `skip_hidden False` to get get into those."
1029 1042 )
1030 1043 return
1031 1044
1032 1045 Colors = self.color_scheme_table.active_colors
1033 1046 ColorsNormal = Colors.Normal
1034 1047 if skipped:
1035 1048 print(
1036 1049 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1037 1050 )
1038 1051 _newframe = i
1039 1052
1040 1053 self._select_frame(_newframe)
1041 1054
1042 1055 do_d = do_down
1043 1056 do_u = do_up
1044 1057
1045 1058 def do_context(self, context):
1046 1059 """context number_of_lines
1047 1060 Set the number of lines of source code to show when displaying
1048 1061 stacktrace information.
1049 1062 """
1050 1063 try:
1051 1064 new_context = int(context)
1052 1065 if new_context <= 0:
1053 1066 raise ValueError()
1054 1067 self.context = new_context
1055 1068 except ValueError:
1056 1069 self.error("The 'context' command requires a positive integer argument.")
1057 1070
1058 1071
1059 1072 class InterruptiblePdb(Pdb):
1060 1073 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1061 1074
1062 1075 def cmdloop(self, intro=None):
1063 1076 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1064 1077 try:
1065 1078 return OldPdb.cmdloop(self, intro=intro)
1066 1079 except KeyboardInterrupt:
1067 1080 self.stop_here = lambda frame: False
1068 1081 self.do_quit("")
1069 1082 sys.settrace(None)
1070 1083 self.quitting = False
1071 1084 raise
1072 1085
1073 1086 def _cmdloop(self):
1074 1087 while True:
1075 1088 try:
1076 1089 # keyboard interrupts allow for an easy way to cancel
1077 1090 # the current command, so allow them during interactive input
1078 1091 self.allow_kbdint = True
1079 1092 self.cmdloop()
1080 1093 self.allow_kbdint = False
1081 1094 break
1082 1095 except KeyboardInterrupt:
1083 1096 self.message('--KeyboardInterrupt--')
1084 1097 raise
1085 1098
1086 1099
1087 1100 def set_trace(frame=None):
1088 1101 """
1089 1102 Start debugging from `frame`.
1090 1103
1091 1104 If frame is not specified, debugging starts from caller's frame.
1092 1105 """
1093 1106 Pdb().set_trace(frame or sys._getframe().f_back)
@@ -1,502 +1,576 b''
1 1 """Tests for debugging machinery.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import bdb
8 8 import builtins
9 9 import os
10 10 import signal
11 11 import subprocess
12 12 import sys
13 13 import time
14 14 import warnings
15
15 16 from subprocess import PIPE, CalledProcessError, check_output
16 17 from tempfile import NamedTemporaryFile
17 18 from textwrap import dedent
18 19 from unittest.mock import patch
19 20
20 21 import nose.tools as nt
21 22
22 23 from IPython.core import debugger
23 24 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
24 25 from IPython.testing.decorators import skip_win32
25 26
26 27 #-----------------------------------------------------------------------------
27 28 # Helper classes, from CPython's Pdb test suite
28 29 #-----------------------------------------------------------------------------
29 30
30 31 class _FakeInput(object):
31 32 """
32 33 A fake input stream for pdb's interactive debugger. Whenever a
33 34 line is read, print it (to simulate the user typing it), and then
34 35 return it. The set of lines to return is specified in the
35 36 constructor; they should not have trailing newlines.
36 37 """
37 38 def __init__(self, lines):
38 39 self.lines = iter(lines)
39 40
40 41 def readline(self):
41 42 line = next(self.lines)
42 43 print(line)
43 44 return line+'\n'
44 45
45 46 class PdbTestInput(object):
46 47 """Context manager that makes testing Pdb in doctests easier."""
47 48
48 49 def __init__(self, input):
49 50 self.input = input
50 51
51 52 def __enter__(self):
52 53 self.real_stdin = sys.stdin
53 54 sys.stdin = _FakeInput(self.input)
54 55
55 56 def __exit__(self, *exc):
56 57 sys.stdin = self.real_stdin
57 58
58 59 #-----------------------------------------------------------------------------
59 60 # Tests
60 61 #-----------------------------------------------------------------------------
61 62
62 63 def test_longer_repr():
63 64 from reprlib import repr as trepr
64 65
65 66 a = '1234567890'* 7
66 67 ar = "'1234567890123456789012345678901234567890123456789012345678901234567890'"
67 68 a_trunc = "'123456789012...8901234567890'"
68 69 nt.assert_equal(trepr(a), a_trunc)
69 70 # The creation of our tracer modifies the repr module's repr function
70 71 # in-place, since that global is used directly by the stdlib's pdb module.
71 72 with warnings.catch_warnings():
72 73 warnings.simplefilter('ignore', DeprecationWarning)
73 74 debugger.Tracer()
74 75 nt.assert_equal(trepr(a), ar)
75 76
76 77 def test_ipdb_magics():
77 78 '''Test calling some IPython magics from ipdb.
78 79
79 80 First, set up some test functions and classes which we can inspect.
80 81
81 82 >>> class ExampleClass(object):
82 83 ... """Docstring for ExampleClass."""
83 84 ... def __init__(self):
84 85 ... """Docstring for ExampleClass.__init__"""
85 86 ... pass
86 87 ... def __str__(self):
87 88 ... return "ExampleClass()"
88 89
89 90 >>> def example_function(x, y, z="hello"):
90 91 ... """Docstring for example_function."""
91 92 ... pass
92 93
93 94 >>> old_trace = sys.gettrace()
94 95
95 96 Create a function which triggers ipdb.
96 97
97 98 >>> def trigger_ipdb():
98 99 ... a = ExampleClass()
99 100 ... debugger.Pdb().set_trace()
100 101
101 102 >>> with PdbTestInput([
102 103 ... 'pdef example_function',
103 104 ... 'pdoc ExampleClass',
104 105 ... 'up',
105 106 ... 'down',
106 107 ... 'list',
107 108 ... 'pinfo a',
108 109 ... 'll',
109 110 ... 'continue',
110 111 ... ]):
111 112 ... trigger_ipdb()
112 113 --Return--
113 114 None
114 115 > <doctest ...>(3)trigger_ipdb()
115 116 1 def trigger_ipdb():
116 117 2 a = ExampleClass()
117 118 ----> 3 debugger.Pdb().set_trace()
118 119 <BLANKLINE>
119 120 ipdb> pdef example_function
120 121 example_function(x, y, z='hello')
121 122 ipdb> pdoc ExampleClass
122 123 Class docstring:
123 124 Docstring for ExampleClass.
124 125 Init docstring:
125 126 Docstring for ExampleClass.__init__
126 127 ipdb> up
127 128 > <doctest ...>(11)<module>()
128 129 7 'pinfo a',
129 130 8 'll',
130 131 9 'continue',
131 132 10 ]):
132 133 ---> 11 trigger_ipdb()
133 134 <BLANKLINE>
134 135 ipdb> down
135 136 None
136 137 > <doctest ...>(3)trigger_ipdb()
137 138 1 def trigger_ipdb():
138 139 2 a = ExampleClass()
139 140 ----> 3 debugger.Pdb().set_trace()
140 141 <BLANKLINE>
141 142 ipdb> list
142 143 1 def trigger_ipdb():
143 144 2 a = ExampleClass()
144 145 ----> 3 debugger.Pdb().set_trace()
145 146 <BLANKLINE>
146 147 ipdb> pinfo a
147 148 Type: ExampleClass
148 149 String form: ExampleClass()
149 150 Namespace: Local...
150 151 Docstring: Docstring for ExampleClass.
151 152 Init docstring: Docstring for ExampleClass.__init__
152 153 ipdb> ll
153 154 1 def trigger_ipdb():
154 155 2 a = ExampleClass()
155 156 ----> 3 debugger.Pdb().set_trace()
156 157 <BLANKLINE>
157 158 ipdb> continue
158 159
159 160 Restore previous trace function, e.g. for coverage.py
160 161
161 162 >>> sys.settrace(old_trace)
162 163 '''
163 164
164 165 def test_ipdb_magics2():
165 166 '''Test ipdb with a very short function.
166 167
167 168 >>> old_trace = sys.gettrace()
168 169
169 170 >>> def bar():
170 171 ... pass
171 172
172 173 Run ipdb.
173 174
174 175 >>> with PdbTestInput([
175 176 ... 'continue',
176 177 ... ]):
177 178 ... debugger.Pdb().runcall(bar)
178 179 > <doctest ...>(2)bar()
179 180 1 def bar():
180 181 ----> 2 pass
181 182 <BLANKLINE>
182 183 ipdb> continue
183 184
184 185 Restore previous trace function, e.g. for coverage.py
185 186
186 187 >>> sys.settrace(old_trace)
187 188 '''
188 189
189 190 def can_quit():
190 191 '''Test that quit work in ipydb
191 192
192 193 >>> old_trace = sys.gettrace()
193 194
194 195 >>> def bar():
195 196 ... pass
196 197
197 198 >>> with PdbTestInput([
198 199 ... 'quit',
199 200 ... ]):
200 201 ... debugger.Pdb().runcall(bar)
201 202 > <doctest ...>(2)bar()
202 203 1 def bar():
203 204 ----> 2 pass
204 205 <BLANKLINE>
205 206 ipdb> quit
206 207
207 208 Restore previous trace function, e.g. for coverage.py
208 209
209 210 >>> sys.settrace(old_trace)
210 211 '''
211 212
212 213
213 214 def can_exit():
214 215 '''Test that quit work in ipydb
215 216
216 217 >>> old_trace = sys.gettrace()
217 218
218 219 >>> def bar():
219 220 ... pass
220 221
221 222 >>> with PdbTestInput([
222 223 ... 'exit',
223 224 ... ]):
224 225 ... debugger.Pdb().runcall(bar)
225 226 > <doctest ...>(2)bar()
226 227 1 def bar():
227 228 ----> 2 pass
228 229 <BLANKLINE>
229 230 ipdb> exit
230 231
231 232 Restore previous trace function, e.g. for coverage.py
232 233
233 234 >>> sys.settrace(old_trace)
234 235 '''
235 236
236 237
237 238 def test_interruptible_core_debugger():
238 239 """The debugger can be interrupted.
239 240
240 241 The presumption is there is some mechanism that causes a KeyboardInterrupt
241 242 (this is implemented in ipykernel). We want to ensure the
242 243 KeyboardInterrupt cause debugging to cease.
243 244 """
244 245 def raising_input(msg="", called=[0]):
245 246 called[0] += 1
246 247 if called[0] == 1:
247 248 raise KeyboardInterrupt()
248 249 else:
249 250 raise AssertionError("input() should only be called once!")
250 251
251 252 with patch.object(builtins, "input", raising_input):
252 253 debugger.InterruptiblePdb().set_trace()
253 254 # The way this test will fail is by set_trace() never exiting,
254 255 # resulting in a timeout by the test runner. The alternative
255 256 # implementation would involve a subprocess, but that adds issues with
256 257 # interrupting subprocesses that are rather complex, so it's simpler
257 258 # just to do it this way.
258 259
259 260 @skip_win32
260 261 def test_xmode_skip():
261 262 """that xmode skip frames
262 263
263 264 Not as a doctest as pytest does not run doctests.
264 265 """
265 266 import pexpect
266 267 env = os.environ.copy()
267 268 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
268 269
269 270 child = pexpect.spawn(
270 271 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
271 272 )
272 273 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
273 274
274 275 child.expect("IPython")
275 276 child.expect("\n")
276 277 child.expect_exact("In [1]")
277 278
278 279 block = dedent(
279 280 """
280 281 def f():
281 282 __tracebackhide__ = True
282 283 g()
283 284
284 285 def g():
285 286 raise ValueError
286 287
287 288 f()
288 289 """
289 290 )
290 291
291 292 for line in block.splitlines():
292 293 child.sendline(line)
293 294 child.expect_exact(line)
294 295 child.expect_exact("skipping")
295 296
296 297 block = dedent(
297 298 """
298 299 def f():
299 300 __tracebackhide__ = True
300 301 g()
301 302
302 303 def g():
303 304 from IPython.core.debugger import set_trace
304 305 set_trace()
305 306
306 307 f()
307 308 """
308 309 )
309 310
310 311 for line in block.splitlines():
311 312 child.sendline(line)
312 313 child.expect_exact(line)
313 314
314 315 child.expect("ipdb>")
315 316 child.sendline("w")
316 317 child.expect("hidden")
317 318 child.expect("ipdb>")
318 319 child.sendline("skip_hidden false")
319 320 child.sendline("w")
320 321 child.expect("__traceba")
321 322 child.expect("ipdb>")
322 323
323 324 child.close()
324 325
325 326
326 327 skip_decorators_blocks = (
327 328 """
329 def helpers_helper():
330 pass # should not stop here except breakpoint
331 """,
332 """
328 333 def helper_1():
329 pass # should not stop here
334 helpers_helper() # should not stop here
330 335 """,
331 336 """
332 337 def helper_2():
333 338 pass # should not stop here
334 339 """,
335 340 """
341 def pdb_skipped_decorator2(function):
342 def wrapped_fn(*args, **kwargs):
343 __debuggerskip__ = True
344 helper_2()
345 __debuggerskip__ = False
346 result = function(*args, **kwargs)
347 __debuggerskip__ = True
348 helper_2()
349 return result
350 return wrapped_fn
351 """,
352 """
336 353 def pdb_skipped_decorator(function):
337 354 def wrapped_fn(*args, **kwargs):
338 355 __debuggerskip__ = True
339 356 helper_1()
340 357 __debuggerskip__ = False
341 358 result = function(*args, **kwargs)
342 359 __debuggerskip__ = True
343 360 helper_2()
344 361 return result
345 362 return wrapped_fn
346 363 """,
347 364 """
348 365 @pdb_skipped_decorator
366 @pdb_skipped_decorator2
349 367 def bar(x, y):
350 368 return x * y
351 369 """,
352 370 """import IPython.terminal.debugger as ipdb""",
353 371 """
354 372 def f():
355 373 ipdb.set_trace()
356 374 bar(3, 4)
357 375 """,
358 376 """
359 377 f()
360 378 """,
361 379 )
362 380
363 381
364 382 def _decorator_skip_setup():
365 383 import pexpect
366 384
367 385 env = os.environ.copy()
368 386 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
369 387
370 388 child = pexpect.spawn(
371 389 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
372 390 )
373 391 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
374 392
375 393 child.expect("IPython")
376 394 child.expect("\n")
377 395
378 396 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
379 397 in_prompt_number = 1
380 398 for cblock in dedented_blocks:
381 399 child.expect_exact(f"In [{in_prompt_number}]:")
382 400 in_prompt_number += 1
383 401 for line in cblock.splitlines():
384 402 child.sendline(line)
385 403 child.expect_exact(line)
386 404 child.sendline("")
387 405 return child
388 406
389 407
390 408 @skip_win32
391 409 def test_decorator_skip():
392 410 """test that decorator frames can be skipped."""
393 411
394 412 child = _decorator_skip_setup()
395 413
396 414 child.expect_exact("3 bar(3, 4)")
397 415 child.expect("ipdb>")
398 416
399 417 child.expect("ipdb>")
400 418 child.sendline("step")
401 419 child.expect_exact("step")
402 420
403 421 child.expect_exact("1 @pdb_skipped_decorator")
404 422
405 423 child.sendline("s")
406 424 child.expect_exact("return x * y")
407 425
408 426 child.close()
409 427
410 428
411 429 @skip_win32
412 430 def test_decorator_skip_disabled():
413 431 """test that decorator frame skipping can be disabled"""
414 432
415 433 child = _decorator_skip_setup()
416 434
417 435 child.expect_exact("3 bar(3, 4)")
418 436
419 437 for input_, expected in [
420 438 ("skip_predicates debuggerskip False", ""),
421 439 ("skip_predicates", "debuggerskip : False"),
422 440 ("step", "---> 2 def wrapped_fn"),
423 441 ("step", "----> 3 __debuggerskip__"),
424 442 ("step", "----> 4 helper_1()"),
425 443 ("step", "---> 1 def helper_1():"),
426 ("next", "----> 2 pass"),
444 ("next", "----> 2 helpers_helper()"),
427 445 ("next", "--Return--"),
428 446 ("next", "----> 5 __debuggerskip__ = False"),
429 447 ]:
430 448 child.expect("ipdb>")
431 449 child.sendline(input_)
432 450 child.expect_exact(input_)
433 451 child.expect_exact(expected)
434 452
435 453 child.close()
436 454
437 455
438 456 @skip_win32
457 def test_decorator_skip_with_breakpoint():
458 """test that decorator frame skipping can be disabled"""
459
460 import pexpect
461
462 env = os.environ.copy()
463 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
464
465 child = pexpect.spawn(
466 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
467 )
468 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
469
470 child.expect("IPython")
471 child.expect("\n")
472
473 ### we need a filename, so we need to exec the full block with a filename
474 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
475
476 name = tf.name[:-3].split("/")[-1]
477 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
478 tf.flush()
479 codeblock = f"from {name} import f"
480
481 dedented_blocks = [
482 codeblock,
483 "f()",
484 ]
485
486 in_prompt_number = 1
487 for cblock in dedented_blocks:
488 child.expect_exact(f"In [{in_prompt_number}]:")
489 in_prompt_number += 1
490 for line in cblock.splitlines():
491 child.sendline(line)
492 child.expect_exact(line)
493 child.sendline("")
494
495 # as the filename does not exists, we'll rely on the filename prompt
496 child.expect_exact("47 bar(3, 4)")
497
498 for input_, expected in [
499 (f"b {name}.py:3", ""),
500 ("step", "1---> 3 pass # should not stop here except"),
501 ("step", "---> 38 @pdb_skipped_decorator"),
502 ("continue", ""),
503 ]:
504 child.expect("ipdb>")
505 child.sendline(input_)
506 child.expect_exact(input_)
507 child.expect_exact(expected)
508
509 child.close()
510
511
512 @skip_win32
439 513 def test_where_erase_value():
440 514 """Test that `where` does not access f_locals and erase values."""
441 515 import pexpect
442 516
443 517 env = os.environ.copy()
444 518 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
445 519
446 520 child = pexpect.spawn(
447 521 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
448 522 )
449 523 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
450 524
451 525 child.expect("IPython")
452 526 child.expect("\n")
453 527 child.expect_exact("In [1]")
454 528
455 529 block = dedent(
456 530 """
457 531 def simple_f():
458 532 myvar = 1
459 533 print(myvar)
460 534 1/0
461 535 print(myvar)
462 536 simple_f() """
463 537 )
464 538
465 539 for line in block.splitlines():
466 540 child.sendline(line)
467 541 child.expect_exact(line)
468 542 child.expect_exact("ZeroDivisionError")
469 543 child.expect_exact("In [2]:")
470 544
471 545 child.sendline("%debug")
472 546
473 547 ##
474 548 child.expect("ipdb>")
475 549
476 550 child.sendline("myvar")
477 551 child.expect("1")
478 552
479 553 ##
480 554 child.expect("ipdb>")
481 555
482 556 child.sendline("myvar = 2")
483 557
484 558 ##
485 559 child.expect_exact("ipdb>")
486 560
487 561 child.sendline("myvar")
488 562
489 563 child.expect_exact("2")
490 564
491 565 ##
492 566 child.expect("ipdb>")
493 567 child.sendline("where")
494 568
495 569 ##
496 570 child.expect("ipdb>")
497 571 child.sendline("myvar")
498 572
499 573 child.expect_exact("2")
500 574 child.expect("ipdb>")
501 575
502 576 child.close()
General Comments 0
You need to be logged in to leave comments. Login now