miden-debug 0.17.0

An interactive debugger for Miden VM programs
Documentation
#![cfg(all(
    feature = "std",
    feature = "script",
    feature = "flamegraph",
    feature = "repl"
))]

use std::{
    io::Write,
    process::{Command, Output, Stdio},
};

use miden_assembly::{Assembler, DefaultSourceManager};
use miden_core::serde::Serializable;

fn command(directory: &std::path::Path) -> Command {
    let mut command = Command::new(env!("CARGO_BIN_EXE_miden-debug"));
    command
        .current_dir(directory)
        .env_remove("MIDEN_SYSROOT")
        .env_remove("MIDENC_TRACE_TIMING")
        .env_remove("MIDENC_TRACE");
    command
}

fn package() -> Vec<u8> {
    Assembler::new(std::sync::Arc::new(DefaultSourceManager::default()))
        .assemble_program("cli-test", "begin push.7 add end")
        .unwrap()
        .to_bytes()
}

fn assert_success(output: &Output) {
    assert!(
        output.status.success(),
        "stdout: {}\nstderr: {}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
}

#[test]
fn batch_cli_executes_compiled_packages_and_accepts_all_timestamp_precisions() {
    let directory = tempfile::tempdir().unwrap();
    let artifact = directory.path().join("program.masp");
    let commands = directory.path().join("commands.txt");
    std::fs::write(&artifact, package()).unwrap();
    std::fs::write(&commands, "continue\nstack\nvars all\nquit\n").unwrap();
    for precision in ["s", "ms", "us", "ns"] {
        let output = command(directory.path())
            .env("MIDENC_TRACE_TIMING", precision)
            .arg(&artifact)
            .arg("--commands")
            .arg(&commands)
            .args(["--", "5"])
            .output()
            .unwrap();
        assert_success(&output);
        let stdout = String::from_utf8(output.stdout).unwrap();
        assert!(stdout.contains("Program terminated successfully"));
        assert!(stdout.contains("[0] 12 (0xc)"));
        assert!(stdout.contains("Program has terminated; no live variables"));
    }
    let output = command(directory.path())
        .env("MIDENC_TRACE_TIMING", "invalid")
        .arg(&artifact)
        .output()
        .unwrap();
    assert!(!output.status.success());
    assert!(String::from_utf8_lossy(&output.stderr).contains("invalid MIDENC_TRACE_TIMING"));
}

#[test]
fn cli_reads_compiled_packages_from_stdin() {
    let directory = tempfile::tempdir().unwrap();
    let commands = directory.path().join("commands.txt");
    std::fs::write(&commands, "continue\nstack\nquit\n").unwrap();
    let mut child = command(directory.path())
        .arg("-")
        .arg("--commands")
        .arg(commands)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap();
    child.stdin.take().unwrap().write_all(&package()).unwrap();
    let output = child.wait_with_output().unwrap();
    assert_success(&output);
    assert!(String::from_utf8_lossy(&output.stdout).contains("[0] 7 (0x7)"));
}

#[test]
fn repl_cli_accepts_commands_from_a_pipe_without_terminal_io() {
    let directory = tempfile::tempdir().unwrap();
    let artifact = directory.path().join("program.masp");
    std::fs::write(&artifact, package()).unwrap();
    let mut child = command(directory.path())
        .arg(&artifact)
        .arg("--repl")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap();
    child
        .stdin
        .take()
        .unwrap()
        .write_all(b"\ninvalid-command\ncontinue\nstack\nquit\n")
        .unwrap();
    let output = child.wait_with_output().unwrap();
    assert_success(&output);
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("Miden Debugger REPL"));
    assert!(stdout.contains("[0] 7 (0x7)"));
    assert!(stdout.contains("Goodbye!"));
    assert!(String::from_utf8_lossy(&output.stderr).contains("unknown command"));
}

#[test]
fn flamegraph_cli_writes_svg_and_folded_profiles_for_compiled_packages() {
    let directory = tempfile::tempdir().unwrap();
    let artifact = directory.path().join("program.masp");
    std::fs::write(&artifact, package()).unwrap();
    for (filename, svg) in [("profile.svg", true), ("profile.folded", false)] {
        let path = directory.path().join(filename);
        let output = command(directory.path())
            .arg("flamegraph")
            .arg(&artifact)
            .arg("-o")
            .arg(&path)
            .output()
            .unwrap();
        assert_success(&output);
        let profile = std::fs::read_to_string(path).unwrap();
        assert!(!profile.is_empty());
        assert_eq!(profile.contains("<svg"), svg);
    }
}