diff --git a/Lib/bdb.py b/Lib/bdb.py index 50cf2b3f5b3e45..15ccf757542eac 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -248,6 +248,18 @@ def reset(self): self.botframe = None self._set_stopinfo(None, None) + def update_skip(self, patterns): + if self.skip is None: + self.skip = set(patterns) + else: + self.skip.update(patterns) + + def remove_skip(self, patterns): + if self.skip is None: + return + else: + self.skip.difference_update(patterns) + @contextmanager def set_enterframe(self, frame): self.enterframe = frame diff --git a/Lib/pdb.py b/Lib/pdb.py index 1ef877cce4dd96..714916b21e2eb2 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -2269,6 +2269,54 @@ def do_retval(self, arg): self.error('Not yet returned!') do_rv = do_retval + def do_skip(self, arg): + """skip module_pattern [module_pattern ...] + + Add glob-style module-name patterns to the modules that the debugger + will not step into. With no arguments, display the current patterns. + """ + if not arg: + skipped_modules = [f"\t{x}" for x in sorted(self.skip or ())] + lines = ["Skipped module patterns:"] + lines.extend(skipped_modules or ["\t(none)"]) + self.message("\n".join(lines)) + return + args = arg.split() + self.update_skip(args) + + def do_skip_stdlib(self, arg): + """skip_stdlib + + Do not step into frames originating in standard-library modules. + """ + if arg: + self._print_invalid_arg(arg) + return + self.update_skip(sys.stdlib_module_names) + self.update_skip((f"{x}.*" for x in sys.stdlib_module_names)) + + def do_unskip(self, arg): + """unskip module_pattern [module_pattern ...] + + Remove glob-style module-name patterns from the skip set. + """ + if not arg: + self._print_invalid_arg(arg) + return + args = arg.split() + self.remove_skip(args) + + def do_unskip_stdlib(self, arg): + """unskip_stdlib + + Remove standard-library module patterns from the skip set. + """ + if arg: + self._print_invalid_arg(arg) + return + self.remove_skip(sys.stdlib_module_names) + self.remove_skip((f"{x}.*" for x in sys.stdlib_module_names)) + def _getval(self, arg): try: return eval(arg, self.curframe.f_globals, self.curframe.f_locals) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 0aa4ceb71c866f..e97eefd4a53459 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -125,6 +125,13 @@ def test_pdb_basic_commands(): ... 'next', # step to test_function4() ... 'step', # stepping to test_function4() ... 'args', # display function args + ... 'skip', + ... 'skip asyncio os.*', + ... 'skip', + ... 'unskip os', + ... 'skip', + ... 'unskip os.*', + ... 'skip', ... 'continue', ... ]): ... test_function() @@ -154,7 +161,7 @@ def test_pdb_basic_commands(): [EOF] (Pdb) bt ... - (26)() + (33)() -> test_function() (3)test_function() -> ret = test_function_2('baz') @@ -224,6 +231,23 @@ def test_pdb_basic_commands(): a = 1 b = 2 c = 3 + (Pdb) skip + Skipped module patterns: + (none) + (Pdb) skip asyncio os.* + (Pdb) skip + Skipped module patterns: + asyncio + os.* + (Pdb) unskip os + (Pdb) skip + Skipped module patterns: + asyncio + os.* + (Pdb) unskip os.* + (Pdb) skip + Skipped module patterns: + asyncio (Pdb) continue BAZ """ @@ -4821,6 +4845,28 @@ def test_checkline_is_not_executable(self): for lineno in range(num_lines): self.assertFalse(db.checkline(os_helper.TESTFN, lineno)) +class SkipCommandsTestCase(unittest.TestCase): + def test_skip_and_unskip(self): + debugger = pdb.Pdb(readrc=False) + + debugger.onecmd("skip asyncio os.*") + self.assertEqual(debugger.skip, {"asyncio", "os.*"}) + + debugger.onecmd("unskip os.*") + self.assertEqual(debugger.skip, {"asyncio"}) + + def test_skip_stdlib(self): + debugger = pdb.Pdb(readrc=False) + + debugger.onecmd("skip_stdlib") + self.assertIn("os", debugger.skip) + self.assertIn("os.*", debugger.skip) + self.assertEqual(len(debugger.skip), len(sys.stdlib_module_names)*2) + + debugger.onecmd("unskip_stdlib") + self.assertNotIn("os", debugger.skip) + self.assertNotIn("os.*", debugger.skip) + self.assertEqual(len(debugger.skip), 0) @support.requires_subprocess() class PdbTestInline(unittest.TestCase):