miden-debug 0.17.0

An interactive debugger for Miden VM programs
Documentation
use super::*;

fn test_debugger() -> ScriptDebugger {
    crate::script::test_debugger()
}

#[test]
fn parses_script_commands_only() {
    assert!(matches!(
        parse_script_command("script"),
        Some(ScriptCommand::InteractiveConsole)
    ));
    assert!(matches!(
        parse_script_command("script 1 + 1"),
        Some(ScriptCommand::Snippet("1 + 1"))
    ));
    assert!(matches!(
        parse_script_command("command script import /tmp/demo.py"),
        Some(ScriptCommand::Import("/tmp/demo.py"))
    ));
    assert!(parse_script_command("scripts").is_none());
    assert!(parse_script_command("step").is_none());
}

#[test]
fn executes_script_snippets_through_repl_route() {
    let _guard = crate::script::python::python_test_lock();
    let debugger = test_debugger();
    let mut python = PythonScriptSession::new(debugger.clone()).unwrap();
    let mut output = Vec::new();

    execute_line(&debugger, &mut python, "script x = 1", &mut output).unwrap();
    execute_line(&debugger, &mut python, "script x + 1", &mut output).unwrap();

    assert_eq!(String::from_utf8(output).unwrap(), "2\n");
}

#[test]
fn imports_script_and_runs_initializer() {
    let _guard = crate::script::python::python_test_lock();
    let debugger = test_debugger();
    let mut python = PythonScriptSession::new(debugger.clone()).unwrap();
    let temp_path = std::env::temp_dir().join(format!(
        "miden-debug-python-import-{}-{}.py",
        std::process::id(),
        0
    ));
    std::fs::write(
        &temp_path,
        r#"
def __miden_init_module(debugger, internal_dict):
    internal_dict["loaded_cycle"] = debugger.get_cycle()
"#,
    )
    .unwrap();

    let mut output = Vec::new();
    execute_line(
        &debugger,
        &mut python,
        &format!("command script import {}", temp_path.display()),
        &mut output,
    )
    .unwrap();
    execute_line(&debugger, &mut python, "script internal_dict['loaded_cycle']", &mut output)
        .unwrap();

    let _ = std::fs::remove_file(&temp_path);
    let output = String::from_utf8(output).unwrap();
    assert!(output.contains("Imported Python script:"));
    assert!(output.ends_with("0\n"));
}

#[test]
fn registers_and_executes_custom_python_command() {
    let _guard = crate::script::python::python_test_lock();
    let debugger = test_debugger();
    let mut python = PythonScriptSession::new(debugger.clone()).unwrap();
    let temp_path =
        std::env::temp_dir().join(format!("miden_debug_python_command_{}.py", std::process::id()));
    std::fs::write(
        &temp_path,
        r#"
def cycle(debugger, command, exe_ctx, result, internal_dict):
    print(f"{debugger.get_cycle()}:{command}", file=result)
"#,
    )
    .unwrap();

    let mut output = Vec::new();
    execute_line(
        &debugger,
        &mut python,
        &format!("command script import {}", temp_path.display()),
        &mut output,
    )
    .unwrap();
    execute_line(
        &debugger,
        &mut python,
        &format!(
            "command script add py-cycle -f {}.cycle",
            temp_path.file_stem().unwrap().to_str().unwrap()
        ),
        &mut output,
    )
    .unwrap();
    execute_line(&debugger, &mut python, "py-cycle hello", &mut output).unwrap();
    execute_line(&debugger, &mut python, "command script list", &mut output).unwrap();
    execute_line(&debugger, &mut python, "command script delete py-cycle", &mut output).unwrap();

    let _ = std::fs::remove_file(&temp_path);
    let output = String::from_utf8(output).unwrap();
    assert!(output.contains("Added Python command: py-cycle"));
    assert!(output.contains("0:hello\n"));
    assert!(output.contains("Python commands:\n  py-cycle\n"));
    assert!(output.contains("Deleted Python command: py-cycle"));
}

#[test]
fn breakpoint_callback_false_continues_execution() {
    let _guard = crate::script::python::python_test_lock();
    let debugger = test_debugger();
    let mut python = PythonScriptSession::new(debugger.clone()).unwrap();
    let temp_path =
        std::env::temp_dir().join(format!("miden_debug_bp_callback_{}.py", std::process::id()));
    std::fs::write(
        &temp_path,
        r#"
def never_stop(frame, breakpoint, internal_dict):
    internal_dict["called"] = internal_dict.get("called", 0) + 1
    return False
"#,
    )
    .unwrap();

    let mut output = Vec::new();
    execute_line(&debugger, &mut python, "b after 1", &mut output).unwrap();
    execute_line(
        &debugger,
        &mut python,
        &format!("command script import {}", temp_path.display()),
        &mut output,
    )
    .unwrap();
    execute_line(
        &debugger,
        &mut python,
        &format!(
            "breakpoint command add 0 -f {}.never_stop",
            temp_path.file_stem().unwrap().to_str().unwrap()
        ),
        &mut output,
    )
    .unwrap();
    execute_line(&debugger, &mut python, "continue", &mut output).unwrap();
    execute_line(&debugger, &mut python, "script internal_dict['called']", &mut output).unwrap();

    let _ = std::fs::remove_file(&temp_path);
    let output = String::from_utf8(output).unwrap();
    assert!(output.contains("Added Python callback for breakpoint 0"), "output:\n{output}");
    assert!(output.contains("Program terminated successfully"), "output:\n{output}");
    assert!(output.ends_with("1\n"), "output:\n{output}");
}