firedbg-rust-debugger 1.74.1

FireDBG Debugger Engine for Rust
Documentation
#![allow(dead_code)]
#![allow(unused_imports)]

use firedbg_rust_debugger::{
    new_breakpoint, DebuggerInfo, DebuggerParams, Event, FireDbgForRust, InfoMessage, SourceFile,
    EVENT_STREAM, INFO_STREAM,
};
use pretty_assertions::assert_eq;
use sea_streamer::{
    file::FileId, Error as SeaStreamerErr, Producer, SeaConnectOptions, SeaConsumer, SeaProducer,
    SeaStreamer, StreamKey, Streamer, Timestamp,
};
use std::{
    fs::{File, OpenOptions},
    io::Write,
    path::Path,
};

#[derive(Debug)]
pub enum Expected {
    FnCall { name: String, args: Vec<String> },
    FnRet { name: String, value: String },
}

pub fn debugger_params_from_file(testcase: &str) -> DebuggerParams {
    let path = format!("testcases/{testcase}.rs");
    let modified = Path::new(&format!("{}/{}", env!("CARGO_MANIFEST_DIR"), path))
        .metadata()
        .unwrap()
        .modified()
        .unwrap();
    let mut files = vec![Default::default()];
    files.push(SourceFile {
        id: 1,
        path: path.clone(),
        crate_name: testcase.into(),
        modified,
    });

    let mut breakpoints = vec![Default::default()];
    for func in firedbg_rust_parser::parse_file(&path).unwrap() {
        breakpoints.push(new_breakpoint(breakpoints.len() as u32, 1, func));
    }

    rustc(&format!("testcases/{testcase}"));

    DebuggerParams {
        binary: format!("testcases/{testcase}.o"),
        files,
        breakpoints,
        arguments: vec![],
    }
}

pub fn generate_rust_program(testcase: &str, content: &str) -> DebuggerParams {
    let path = format!("testcases/generated/{testcase}");
    let src = format!("{path}.rs");
    let mut file = std::fs::File::create(&src).unwrap();
    file.write_all(content.as_bytes()).unwrap();
    rustc(&path);
    debugger_params_from_file(&format!("generated/{testcase}"))
}

pub async fn setup(testcase: &str) -> Result<(SeaProducer, SeaConsumer), SeaStreamerErr> {
    create_env_logger();

    let file_id = temp_file(testcase).unwrap();
    println!("{file_id:?}");
    let mut options = SeaConnectOptions::default();
    options.set_file_connect_options(|options| {
        options.set_end_with_eos(true);
    });
    let streamer = SeaStreamer::connect(file_id.to_streamer_uri()?, options).await?;
    let producer = streamer.create_generic_producer(Default::default()).await?;
    let consumer = streamer
        .create_consumer(&[StreamKey::new(EVENT_STREAM)?], Default::default())
        .await?;

    producer.send_to(
        &StreamKey::new(INFO_STREAM)?,
        serde_json::to_string(&InfoMessage::Debugger(DebuggerInfo {
            debugger: FireDbgForRust,
            version: env!("CARGO_PKG_VERSION").to_owned(),
            workspace_root: "".to_owned(),
            package_name: testcase.to_owned(),
            target: testcase.to_owned(),
            arguments: vec![],
        }))
        .unwrap()
        .as_str(),
    )?;

    Ok((producer, consumer))
}

pub fn verify(testcase: &str, events: Vec<Event>, expected: Vec<Expected>) {
    for (i, expect) in expected.into_iter().enumerate() {
        match &events[i] {
            Event::Breakpoint { .. } => unreachable!(),
            Event::FunctionCall {
                function_name,
                arguments,
                ..
            } => match expect {
                Expected::FnCall { name, args } => {
                    if function_name.starts_with('<') {
                        assert_eq!(function_name, &name);
                    } else {
                        assert_eq!(function_name, &format!("{testcase}::{name}"));
                    }
                    assert_eq!(arguments.len(), args.len());
                    for (j, arg) in args.iter().enumerate() {
                        assert_wildcard(i, arg, &arguments[j].1.to_string());
                    }
                }
                e => panic!("Expected {e:?}"),
            },
            Event::FunctionReturn {
                function_name,
                return_value,
                ..
            } => match expect {
                Expected::FnRet { name, value } => {
                    if function_name.starts_with('<') {
                        assert_eq!(function_name, &name);
                    } else {
                        assert_eq!(function_name, &format!("{testcase}::{name}"));
                    }
                    assert_wildcard(i, &value, &return_value.to_string());
                }
                e => panic!("Expected {e:?}"),
            },
        }
    }
}

fn temp_file(name: &str) -> Result<FileId, std::io::Error> {
    let name = format!("{}-{}", name, millis_of(&Timestamp::now_utc()));
    let path = format!("/tmp/{name}.firedbg.ss");
    let _file = OpenOptions::new()
        .read(true)
        .write(true)
        .create_new(true)
        .open(&path)?;

    Ok(FileId::new(path))
}

fn millis_of(ts: &Timestamp) -> i64 {
    (ts.unix_timestamp_nanos() / 1_000_000) as i64
}

pub fn create_env_logger() {
    env_logger::Builder::from_default_env()
        .format_timestamp_nanos()
        .init();
}

fn assert_wildcard(i: usize, template: &str, against: &str) {
    if !firedbg_rust_debugger::typename::wildcard_match(template, against) {
        print!("[{i}] ");
        assert_eq!(against, template);
    }
}

pub fn rustc(path: &str) {
    let src = format!("{path}.rs");
    let obj = format!("{path}.o");
    let result = rustc_cmd(&src, &obj)
        .spawn()
        .unwrap()
        .wait_with_output()
        .unwrap();
    if result.status.code().unwrap() != 0 {
        panic!("Failed to compile {src}");
    }
}

pub fn rustc_optimize(path: &str) {
    let src = format!("{path}.rs");
    let obj = format!("{path}.o");
    let mut cmd = rustc_cmd(&src, &obj);
    cmd.arg("-O");
    let result = cmd.spawn().unwrap().wait_with_output().unwrap();
    if result.status.code().unwrap() != 0 {
        panic!("Failed to compile {src}");
    }
}

fn rustc_cmd(src: &str, obj: &str) -> std::process::Command {
    let mut cmd = std::process::Command::new("rustc");
    cmd.arg("--cap-lints=allow")
        .arg("--edition=2021")
        .arg("-g")
        .arg(&src)
        .arg("-o")
        .arg(&obj);
    cmd
}