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