Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

crates/dwarves-tune-graph/tests/srcgraph.rs

//! The source-level graph (dwarves' own code graph: call edges in the
//! warehouse row shape) on small Rust programs written here.

use std::path::Path;

use dwarves_tune_graph::srcgraph::{self, Edge, Libc, SrcOptions};
use dwarves_tune_graph::{cross_check, read_seen};

const MAIN: &str = r#"
use std::fs;
use std::net::TcpListener;
mod helpers;

pub struct S;

impl S {
    pub fn serve(&self) {
        let _ = TcpListener::bind("127.0.0.1:0");
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let _ = fs::read_to_string("/etc/hostname");
    if args.len() > 5 {
        helpers::rare_path();
    }
    helpers::by_method(&S);
    println!("x");
}

fn unused() {
    std::process::exit(3);
}
"#;

const HELPERS: &str = r#"
pub fn rare_path() {
    let _ = std::fs::remove_file("/tmp/x");
    unsafe { libc::sethostname(std::ptr::null(), 0) };
    let _ = std::os::unix::fs::chroot("/");
}

pub fn by_method(s: &crate::S) {
    s.serve();
}
"#;

fn workspace(dir: &Path) {
    std::fs::create_dir_all(dir.join("src")).unwrap();
    std::fs::write(
        dir.join("Cargo.toml"),
        "[package]\nname = \"prog\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
    )
    .unwrap();
    std::fs::write(dir.join("src/main.rs"), MAIN).unwrap();
    std::fs::write(dir.join("src/helpers.rs"), HELPERS).unwrap();
}

fn opts(libc: Libc) -> SrcOptions {
    SrcOptions {
        libc,
        ..SrcOptions::default()
    }
}

#[test]
#[ignore = "possible syscalls (source graph): owner engine"]
fn a_rust_program_maps_std_calls_to_glibc_syscalls() {
    let d = tempfile::tempdir().unwrap();
    workspace(d.path());
    let g = srcgraph::analyze(d.path(), &[], &opts(Libc::Glibc)).unwrap();
    assert_eq!(g.entries, vec!["prog::main".to_string()]);
    for c in ["openat", "read", "close", "statx", "write", "unlink", "sethostname"] {
        let r = g.syscalls.get(c).unwrap_or_else(|| panic!("{c} possible: {:?}", g.syscalls.keys()));
        assert!(r.direct, "{c} is on a direct path: {r:?}");
        assert_eq!(r.path.first().map(String::as_str), Some("prog::main"), "{r:?}");
    }
    let rd = &g.syscalls["read"];
    assert_eq!(rd.path, vec!["prog::main", "std::fs::read_to_string"]);
    assert!(rd.site.starts_with("prog::main at src/main.rs:"), "{}", rd.site);
    let un = &g.syscalls["unlink"];
    assert_eq!(un.path, vec!["prog::main", "prog::helpers::rare_path", "std::fs::remove_file"]);
    assert!(un.site.contains("src/helpers.rs:"), "{}", un.site);
    // TcpListener::bind sits behind a method call: matched by name, not direct.
    for c in ["socket", "bind", "listen"] {
        let r = &g.syscalls[c];
        assert!(!r.direct, "{c}: {r:?}");
        assert!(r.path.iter().any(|f| f == "prog::S::serve"), "{r:?}");
    }
    assert!(!g.syscalls.contains_key("exit_group"), "unused() is not reachable from main");
    assert!(
        g.unresolved_imports.iter().any(|i| i == "std::os::unix::fs::chroot"),
        "a std call the table does not know is named: {:?}",
        g.unresolved_imports
    );
}

#[test]
#[ignore = "possible syscalls (source graph): owner engine"]
fn musl_gets_its_own_table() {
    let d = tempfile::tempdir().unwrap();
    workspace(d.path());
    let g = srcgraph::analyze(d.path(), &[], &opts(Libc::Musl)).unwrap();
    assert!(g.syscalls.contains_key("open"), "musl opens with open: {:?}", g.syscalls.keys());
    assert!(!g.syscalls.contains_key("statx"), "std uses statx on glibc only");
    assert!(g.syscalls.contains_key("newfstatat"));
}

#[test]
#[ignore = "possible syscalls (source graph): owner engine"]
fn entries_choose_where_the_walk_starts() {
    let d = tempfile::tempdir().unwrap();
    workspace(d.path());
    let o = SrcOptions {
        only_entries: true,
        ..opts(Libc::Glibc)
    };
    let g = srcgraph::analyze(d.path(), &["unused".to_string()], &o).unwrap();
    assert_eq!(g.entries, vec!["prog::unused".to_string()]);
    assert!(g.syscalls.contains_key("exit_group"));
    assert!(!g.syscalls.contains_key("write"));
    assert!(srcgraph::analyze(d.path(), &["no_such_fn".to_string()], &o).is_err());
}

#[test]
#[ignore = "possible syscalls (source graph): owner engine"]
fn warehouse_rows_give_the_same_graph() {
    let d = tempfile::tempdir().unwrap();
    workspace(d.path());
    let rows: Vec<Edge> = srcgraph::scan(d.path()).unwrap();
    assert!(rows.iter().any(|e| e.caller_path == "prog::helpers::rare_path"
        && e.callee_ident == "std::fs::remove_file"
        && e.call_kind == "call"
        && e.file == "src/helpers.rs"));
    // The rows round-trip through the warehouse JSON shape (CallEdgeRow).
    let json = serde_json::to_string(&rows).unwrap();
    let back: Vec<Edge> = serde_json::from_str(&json).unwrap();
    let a = srcgraph::analyze_edges(&back, &[], &opts(Libc::Glibc)).unwrap();
    let b = srcgraph::analyze(d.path(), &[], &opts(Libc::Glibc)).unwrap();
    assert_eq!(a.syscalls, b.syscalls);
}

#[test]
#[ignore = "possible syscalls (source graph): owner engine"]
fn the_cross_check_runs_on_the_source_graph() {
    let d = tempfile::tempdir().unwrap();
    workspace(d.path());
    let g = srcgraph::analyze(d.path(), &[], &opts(Libc::Glibc)).unwrap();
    let pj = d.path().join("x86_64-glibc.probe.json");
    std::fs::write(
        &pj,
        r#"{"workload":"prog","arch":"x86_64","libc":"glibc",
            "runs":[{"name":"basic","error_path":false,"exit":"exit 0"}],
            "calls":[{"syscall":"write","runs":["basic"]},{"syscall":"read","runs":["basic"]},
                     {"syscall":"brk","runs":["basic"]}]}"#,
    )
    .unwrap();
    let r = cross_check(&g, &read_seen(&pj).unwrap());
    assert!(r.gaps.iter().any(|x| x.syscall == "brk"), "{:?}", r.gaps);
    assert!(r.untested.iter().any(|u| u.syscall == "unlink" && u.direct));
    assert!(r.guides_only);
}