miden-debug 0.17.0

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

#[test]
fn script_debugger_executes_commands_and_exposes_state() {
    let debugger = test_debugger();

    assert_eq!(debugger.cycle(), 0);

    let output = debugger.handle_command("step").unwrap();
    assert!(output.contains("in") || output.is_empty(), "unexpected output: {output}");
    assert_eq!(debugger.cycle(), 1);

    let stack_output = debugger.handle_command("stack").unwrap();
    assert!(stack_output.contains("Operand Stack"));
}

#[test]
fn script_debugger_can_manage_breakpoints() {
    let debugger = test_debugger();

    let bp = debugger.set_breakpoint("after 1").unwrap();
    assert_eq!(bp.id, 0);
    assert_eq!(debugger.breakpoints().len(), 1);

    debugger.delete_breakpoint(Some(bp.id)).unwrap();
    assert!(debugger.breakpoints().is_empty());
}

#[test]
fn script_execution_context_and_clones_follow_the_same_session() {
    let debugger = test_debugger();
    let cloned = debugger.clone();
    let initial = debugger.execution_context();
    assert_eq!(initial.cycle, 0);
    assert!(initial.stopped);
    assert!(!initial.terminated);
    assert!(initial.frame.variables.is_empty());
    assert!(debugger.stack().is_empty());
    assert!(
        debugger
            .source_path_prefixes()
            .contains(&miden_debug_engine::normalize_source_path(
                std::env::current_dir().unwrap().to_str().unwrap()
            ))
    );
    assert!(debugger.result().unwrap().is_none());
    debugger.step(2).unwrap();
    assert_eq!(cloned.cycle(), 2);
    assert_eq!(debugger.frame(), cloned.frame());
    assert!(debugger.frame_with_variables(true).variables.is_empty());
    assert!(debugger.continue_().unwrap().contains("terminated successfully"));
    assert!(cloned.terminated());
    assert_eq!(debugger.stack()[0], 7);
    assert!(debugger.execution_context().terminated);
    assert!(debugger.frame().variables.is_empty());
    assert!(debugger.handle_command("quit").unwrap_err().contains("quit requested"));
    assert!(debugger.step(1).unwrap_err().contains("terminated"));
    debugger.reload().unwrap();
    assert_eq!(cloned.cycle(), 0);
    assert!(!cloned.terminated());
}

#[test]
fn script_breakpoint_hits_and_deletion_preserve_internal_breakpoints() {
    let debugger = test_debugger();
    let breakpoint = debugger.set_breakpoint("at 3").unwrap();
    assert_eq!(breakpoint.spec, "at cycle 3");
    assert!(!breakpoint.internal);
    assert!(breakpoint.one_shot);
    debugger.continue_().unwrap();
    assert_eq!(debugger.cycle(), 3);
    assert_eq!(debugger.hit_breakpoints()[0].id, breakpoint.id);
    debugger.clear_hit_breakpoints();
    assert!(debugger.hit_breakpoints().is_empty());
    assert!(debugger.delete_breakpoint(Some(99)).unwrap_err().contains("99"));
    assert!(debugger.set_breakpoint("at invalid").is_err());
    debugger.delete_breakpoint(None).unwrap();
    assert!(debugger.breakpoints().is_empty());
    debugger.engine.borrow_mut().state_mut().create_breakpoint(BreakpointType::Step);
    debugger.delete_breakpoint(None).unwrap();
    assert!(!debugger.engine.borrow().state().breakpoints.is_empty());
    assert!(debugger.breakpoints().is_empty());
    assert_eq!(debugger.read_memory("0 -t u32").unwrap(), "0");
    assert!(debugger.read_memory("invalid").is_err());
}

#[test]
fn script_step_wrappers_advance_execution() {
    for operation in [ScriptDebugger::next, ScriptDebugger::next_line, ScriptDebugger::finish] {
        let debugger = test_debugger();
        operation(&debugger).unwrap();
        assert!(debugger.cycle() > 0);
    }
}

pub(crate) fn test_debugger() -> ScriptDebugger {
    let config = DebuggerConfig {
        input: Some(crate::program_loader::test_package_input()),
        ..Default::default()
    };
    ScriptDebugger::from_config(Box::new(config)).expect("test package should load")
}