miden-debug-engine 0.19.0

Core debugger engine for miden-debug
Documentation
use alloc::{string::ToString, sync::Arc};
use core::{
    future::Future,
    task::{Context, Poll, Waker},
};
use std::sync::atomic::{AtomicUsize, Ordering};

use miden_assembly::{Assembler, DefaultSourceManager};
use miden_processor::{
    DefaultHost, ExecutionError, FastProcessor, ProcessorState, StackInputs,
    event::{EventName, TraceError},
};

use super::{EventMutationRecorder, RecordingHost};

fn poll_immediately<F: Future>(future: F) -> F::Output {
    let mut future = core::pin::pin!(future);
    match future.as_mut().poll(&mut Context::from_waker(Waker::noop())) {
        Poll::Ready(result) => result,
        Poll::Pending => panic!("synchronous test host unexpectedly yielded"),
    }
}

#[test]
fn trace_handler_errors_preserve_the_registered_name() {
    let package = Assembler::new(Arc::new(DefaultSourceManager::default()))
        .assemble_program("program", "begin trace.event(\"test::reject\") end")
        .unwrap();
    let mut host = DefaultHost::default();
    let name = EventName::new("test::reject");
    host.register_trace_handler(
        name.clone(),
        Arc::new(|_: &ProcessorState<'_>| -> Result<(), TraceError> {
            Err(std::io::Error::other("trace rejected").into())
        }),
    )
    .unwrap();
    let mut recording = RecordingHost::new(&mut host, None, None);
    let error = poll_immediately(
        FastProcessor::new(StackInputs::default())
            .execute(&package.unwrap_program(), &mut recording),
    )
    .expect_err("the trace handler must reject execution");
    let ExecutionError::EventError {
        event_id,
        event_name,
        error,
        ..
    } = error
    else {
        panic!("unexpected execution error: {error:?}");
    };
    assert_eq!(event_id, name.to_event_id());
    assert_eq!(event_name, Some(name));
    assert_eq!(error.to_string(), "trace rejected");
}

#[test]
fn trace_callbacks_do_not_add_advice_events() {
    let package = Assembler::new(Arc::new(DefaultSourceManager::default()))
        .assemble_program(
            "program",
            "begin trace.event(\"test::observe\") emit.event(\"test::event\") \
             trace.event(\"test::unhandled\") end",
        )
        .unwrap();
    let calls = Arc::new(AtomicUsize::new(0));
    let observed = Arc::clone(&calls);
    let mut host = DefaultHost::default();
    host.register_trace_handler(
        EventName::new("test::observe"),
        Arc::new(move |_: &ProcessorState<'_>| -> Result<(), TraceError> {
            observed.fetch_add(1, Ordering::Relaxed);
            Ok(())
        }),
    )
    .unwrap();
    host.register_handler(
        EventName::new("test::event"),
        Arc::new(|_: &ProcessorState<'_>| Ok(alloc::vec::Vec::new())),
    )
    .unwrap();
    let events = EventMutationRecorder::new();
    let mut recording = RecordingHost::new(&mut host, Some(events.clone()), None);
    poll_immediately(
        FastProcessor::new(StackInputs::default())
            .execute(&package.unwrap_program(), &mut recording),
    )
    .unwrap();
    assert_eq!(calls.load(Ordering::Relaxed), 1);
    let log = events.snapshot();
    assert_eq!(log.len(), 1, "only the regular event belongs in the advice replay log");
    assert!(log[0].is_empty());
}