From e9970edd775a617226007d3d3cb0fbbc39ed3948 Mon Sep 17 00:00:00 2001 From: Gyubong Date: Tue, 23 Aug 2022 21:45:01 +0900 Subject: [PATCH] Add built-in `breakpoint` function (#4122) --- Lib/test/test_builtin.py | 5 ++- vm/src/stdlib/builtins.rs | 13 ++++++-- vm/src/stdlib/sys.rs | 67 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 92a60af70..835e7f0f8 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1900,7 +1900,6 @@ class BuiltinTest(unittest.TestCase): self.assertFalse(not NotImplemented) -@unittest.skip("TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute '__breakpointhook__'") class TestBreakpoint(unittest.TestCase): def setUp(self): # These tests require a clean slate environment. For example, if the @@ -2005,6 +2004,10 @@ class TestBreakpoint(unittest.TestCase): breakpoint() mock.assert_not_called() + def test_runtime_error_when_hook_is_lost(self): + del sys.breakpointhook + with self.assertRaises(RuntimeError): + breakpoint() @unittest.skipUnless(pty, "the pty and signal modules must be available") class PtyTests(unittest.TestCase): diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index 5ea84a0a0..d8c4aa7ae 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -80,8 +80,6 @@ mod builtins { } } - // builtin_breakpoint - #[pyfunction] fn callable(obj: PyObjectRef, vm: &VirtualMachine) -> bool { vm.is_callable(&obj) @@ -363,7 +361,16 @@ mod builtins { obj.hash(vm) } - // builtin_help + #[pyfunction] + fn breakpoint(args: FuncArgs, vm: &VirtualMachine) -> PyResult { + match vm + .sys_module + .get_attr(vm.ctx.intern_str("breakpointhook"), vm) + { + Ok(hook) => vm.invoke(hook.as_ref(), args), + Err(_) => Err(vm.new_runtime_error("lost sys.breakpointhook".to_owned())), + } + } #[pyfunction] fn hex(number: PyIntRef) -> String { diff --git a/vm/src/stdlib/sys.rs b/vm/src/stdlib/sys.rs index ec56ec926..dc6ddc2b0 100644 --- a/vm/src/stdlib/sys.rs +++ b/vm/src/stdlib/sys.rs @@ -13,13 +13,18 @@ mod sys { frame::FrameRef, function::{FuncArgs, OptionalArg, PosArgs}, stdlib::builtins, + stdlib::warnings::warn, types::PyStructSequence, version, vm::{Settings, VirtualMachine}, AsObject, PyObjectRef, PyRef, PyRefExact, PyResult, }; use num_traits::ToPrimitive; - use std::{env, mem, path, sync::atomic::Ordering}; + use std::{ + env::{self, VarError}, + mem, path, + sync::atomic::Ordering, + }; // not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu) // but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly, @@ -316,6 +321,66 @@ mod sys { vm.write_exception(&mut crate::py_io::PyWriter(stderr, vm), &exc) } + #[pyfunction(name = "__breakpointhook__")] + #[pyfunction] + pub fn breakpointhook(args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let envar = std::env::var("PYTHONBREAKPOINT") + .and_then(|envar| { + if envar.is_empty() { + Err(VarError::NotPresent) + } else { + Ok(envar) + } + }) + .unwrap_or_else(|_| "pdb.set_trace".to_owned()); + + if envar.eq("0") { + return Ok(vm.ctx.none()); + }; + + let print_unimportable_module_warn = || { + warn( + vm.ctx.exceptions.runtime_warning, + format!( + "Ignoring unimportable $PYTHONBREAKPOINT: \"{}\"", + envar.to_owned(), + ), + 0, + vm, + ) + .unwrap(); + Ok(vm.ctx.none()) + }; + + let last = match envar.split('.').last() { + Some(last) => last, + None => { + return print_unimportable_module_warn(); + } + }; + + let (modulepath, attrname) = if last.eq(&envar) { + ("builtins".to_owned(), envar.to_owned()) + } else { + ( + envar[..(envar.len() - last.len() - 1)].to_owned(), + last.to_owned(), + ) + }; + + let module = match vm.import(modulepath, None, 0) { + Ok(module) => module, + Err(_) => { + return print_unimportable_module_warn(); + } + }; + + match vm.get_attribute_opt(module, attrname) { + Ok(Some(hook)) => vm.invoke(hook.as_ref(), args), + _ => print_unimportable_module_warn(), + } + } + #[pyfunction] fn exc_info(vm: &VirtualMachine) -> (PyObjectRef, PyObjectRef, PyObjectRef) { match vm.topmost_exception() {