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);
}