Skip to content

Commit e9970ed

Browse files
authored
Add built-in breakpoint function (RustPython#4122)
1 parent e34d8b1 commit e9970ed

File tree

3 files changed

+80
-5
lines changed

3 files changed

+80
-5
lines changed

Lib/test/test_builtin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1900,7 +1900,6 @@ def test_warning_notimplemented(self):
19001900
self.assertFalse(not NotImplemented)
19011901

19021902

1903-
@unittest.skip("TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute '__breakpointhook__'")
19041903
class TestBreakpoint(unittest.TestCase):
19051904
def setUp(self):
19061905
# These tests require a clean slate environment. For example, if the
@@ -2005,6 +2004,10 @@ def test_envar_ignored_when_hook_is_set(self):
20052004
breakpoint()
20062005
mock.assert_not_called()
20072006

2007+
def test_runtime_error_when_hook_is_lost(self):
2008+
del sys.breakpointhook
2009+
with self.assertRaises(RuntimeError):
2010+
breakpoint()
20082011

20092012
@unittest.skipUnless(pty, "the pty and signal modules must be available")
20102013
class PtyTests(unittest.TestCase):

vm/src/stdlib/builtins.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ mod builtins {
8080
}
8181
}
8282

83-
// builtin_breakpoint
84-
8583
#[pyfunction]
8684
fn callable(obj: PyObjectRef, vm: &VirtualMachine) -> bool {
8785
vm.is_callable(&obj)
@@ -363,7 +361,16 @@ mod builtins {
363361
obj.hash(vm)
364362
}
365363

366-
// builtin_help
364+
#[pyfunction]
365+
fn breakpoint(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
366+
match vm
367+
.sys_module
368+
.get_attr(vm.ctx.intern_str("breakpointhook"), vm)
369+
{
370+
Ok(hook) => vm.invoke(hook.as_ref(), args),
371+
Err(_) => Err(vm.new_runtime_error("lost sys.breakpointhook".to_owned())),
372+
}
373+
}
367374

368375
#[pyfunction]
369376
fn hex(number: PyIntRef) -> String {

vm/src/stdlib/sys.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ mod sys {
1313
frame::FrameRef,
1414
function::{FuncArgs, OptionalArg, PosArgs},
1515
stdlib::builtins,
16+
stdlib::warnings::warn,
1617
types::PyStructSequence,
1718
version,
1819
vm::{Settings, VirtualMachine},
1920
AsObject, PyObjectRef, PyRef, PyRefExact, PyResult,
2021
};
2122
use num_traits::ToPrimitive;
22-
use std::{env, mem, path, sync::atomic::Ordering};
23+
use std::{
24+
env::{self, VarError},
25+
mem, path,
26+
sync::atomic::Ordering,
27+
};
2328

2429
// not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu)
2530
// but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly,
@@ -316,6 +321,66 @@ mod sys {
316321
vm.write_exception(&mut crate::py_io::PyWriter(stderr, vm), &exc)
317322
}
318323

324+
#[pyfunction(name = "__breakpointhook__")]
325+
#[pyfunction]
326+
pub fn breakpointhook(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
327+
let envar = std::env::var("PYTHONBREAKPOINT")
328+
.and_then(|envar| {
329+
if envar.is_empty() {
330+
Err(VarError::NotPresent)
331+
} else {
332+
Ok(envar)
333+
}
334+
})
335+
.unwrap_or_else(|_| "pdb.set_trace".to_owned());
336+
337+
if envar.eq("0") {
338+
return Ok(vm.ctx.none());
339+
};
340+
341+
let print_unimportable_module_warn = || {
342+
warn(
343+
vm.ctx.exceptions.runtime_warning,
344+
format!(
345+
"Ignoring unimportable $PYTHONBREAKPOINT: \"{}\"",
346+
envar.to_owned(),
347+
),
348+
0,
349+
vm,
350+
)
351+
.unwrap();
352+
Ok(vm.ctx.none())
353+
};
354+
355+
let last = match envar.split('.').last() {
356+
Some(last) => last,
357+
None => {
358+
return print_unimportable_module_warn();
359+
}
360+
};
361+
362+
let (modulepath, attrname) = if last.eq(&envar) {
363+
("builtins".to_owned(), envar.to_owned())
364+
} else {
365+
(
366+
envar[..(envar.len() - last.len() - 1)].to_owned(),
367+
last.to_owned(),
368+
)
369+
};
370+
371+
let module = match vm.import(modulepath, None, 0) {
372+
Ok(module) => module,
373+
Err(_) => {
374+
return print_unimportable_module_warn();
375+
}
376+
};
377+
378+
match vm.get_attribute_opt(module, attrname) {
379+
Ok(Some(hook)) => vm.invoke(hook.as_ref(), args),
380+
_ => print_unimportable_module_warn(),
381+
}
382+
}
383+
319384
#[pyfunction]
320385
fn exc_info(vm: &VirtualMachine) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
321386
match vm.topmost_exception() {

0 commit comments

Comments
 (0)