fn run_ours_args(hprof: &str, extra: &[&str], oql: &str) -> String {
let bin = env!("CARGO_BIN_EXE_hprof-analyzer");
let mut args: Vec<&str> = vec!["query", hprof];
args.extend_from_slice(extra);
args.extend_from_slice(&["--query", oql]);
let out = std::process::Command::new(bin)
.args(&args)
.output()
.unwrap_or_else(|e| panic!("failed to spawn binary {bin:?} for OQL {oql:?}: {e}"));
if !out.status.success() {
panic!(
"binary {bin:?} exited with {status} for OQL {oql:?} (args {args:?})\nstderr:\n{stderr}",
status = out.status,
stderr = String::from_utf8_lossy(&out.stderr),
);
}
String::from_utf8_lossy(&out.stdout).into_owned()
}
fn run_query(hprof: &str, oql: &str) -> String {
run_ours_args(hprof, &[], oql)
}
fn philosophers_hprof() -> Option<String> {
let p = format!(
"{}/tests/fixtures/dump_4_philosophers.hprof",
env!("CARGO_MANIFEST_DIR")
);
match std::fs::metadata(&p) {
Ok(m) if m.len() >= 1024 => Some(p),
_ => None,
}
}
#[test]
fn group_by_count_matches_manual_count() {
let Some(hprof) = philosophers_hprof() else {
return;
};
let grouped = run_query(
&hprof,
"SELECT @displayName, COUNT(*) AS n FROM java.lang.String GROUP BY @displayName",
);
let direct = run_query(&hprof, "SELECT COUNT(*) FROM java.lang.String");
let grouped_n: u64 = grouped
.lines()
.find(|l| l.contains("String") && l.contains('|'))
.and_then(|l| l.split('|').next_back())
.and_then(|s| s.trim().parse().ok())
.unwrap_or(0);
let direct_n: u64 = direct
.lines()
.find_map(|l| l.trim().parse::<u64>().ok())
.unwrap_or(1);
assert_eq!(
grouped_n, direct_n,
"GROUP BY count must match direct COUNT(*)\ngrouped:\n{grouped}\ndirect:\n{direct}"
);
}
#[cfg(feature = "mat-oracle")]
mod mat_oracle {
use super::run_ours_args;
use std::collections::BTreeSet;
fn mat_home() -> Option<String> {
std::env::var("MAT_HOME").ok()
}
fn addr_set(lines: &str) -> BTreeSet<u64> {
lines
.lines()
.filter_map(|l| {
let t = l.trim().trim_start_matches("0x");
u64::from_str_radix(t, 16).ok().or_else(|| t.parse().ok())
})
.collect()
}
fn normalize_for_mat(oql: &str) -> String {
oql.to_string()
}
#[allow(dead_code)]
fn compare_scalar(ours: &str, mat: &str) -> Result<(), String> {
if ours.trim() == mat.trim() {
Ok(())
} else {
Err(format!("scalar mismatch: ours={ours:?} mat={mat:?}"))
}
}
fn compare_superset(ours: &BTreeSet<u64>, mat: &BTreeSet<u64>) -> Result<(), String> {
let mat_only: Vec<_> = mat.difference(ours).take(10).collect();
if mat_only.is_empty() {
let extra = ours.len().saturating_sub(mat.len());
eprintln!(
" MAT ⊆ ours ✓ (|mat|={} |ours|={}, {extra} extra ~= unreachable)",
mat.len(),
ours.len(),
);
Ok(())
} else {
Err(format!(
"MAT returned objects we MISSED (≤10 shown): {mat_only:?} |mat|={} |ours|={}",
mat.len(),
ours.len(),
))
}
}
fn compare_exact(ours: &BTreeSet<u64>, mat: &BTreeSet<u64>) -> Result<(), String> {
let mat_only: Vec<_> = mat.difference(ours).take(10).collect();
let ours_only: Vec<_> = ours.difference(mat).take(10).collect();
if mat_only.is_empty() && ours_only.is_empty() {
eprintln!(" ours == MAT ✓ (|both|={})", mat.len());
Ok(())
} else {
Err(format!(
"exact-parity mismatch: mat_only(≤10)={mat_only:?} ours_only(≤10)={ours_only:?} |mat|={} |ours|={}",
mat.len(),
ours.len(),
))
}
}
fn run_mat(hprof: &str, oql: &str) -> String {
let script = concat!(env!("CARGO_MANIFEST_DIR"), "/scripts/mat-oracle.sh");
let out = std::process::Command::new(script)
.args([hprof, oql])
.output()
.unwrap_or_else(|e| {
panic!("failed to spawn MAT oracle script {script:?} for OQL {oql:?}: {e}")
});
if !out.status.success() {
panic!(
"MAT oracle script {script:?} exited with {status} for OQL {oql:?}\nstderr:\n{stderr}",
status = out.status,
stderr = String::from_utf8_lossy(&out.stderr),
);
}
String::from_utf8_lossy(&out.stdout).into_owned()
}
fn run_ours_reachable(hprof: &str, oql: &str) -> String {
run_ours_args(hprof, &[], oql)
}
fn run_ours_all(hprof: &str, oql: &str) -> String {
run_ours_args(hprof, &["--all"], oql)
}
#[derive(Clone, Copy)]
enum Cmp {
Superset,
Exact,
#[allow(dead_code)]
Scalar,
}
const ORACLE_QUERIES: &[(&str, &str, Cmp)] = &[
(
"strings",
"SELECT @objectAddress FROM java.lang.String",
Cmp::Superset,
),
(
"threads",
"SELECT @objectAddress FROM java.lang.Thread",
Cmp::Superset,
),
(
"instanceof_thread",
"SELECT @objectAddress FROM INSTANCEOF java.lang.Thread",
Cmp::Superset,
),
(
"threads_exact",
"SELECT @objectAddress FROM java.lang.Thread",
Cmp::Exact,
),
(
"instanceof_thread_exact",
"SELECT @objectAddress FROM INSTANCEOF java.lang.Thread",
Cmp::Exact,
),
(
"hashmap_exact",
"SELECT @objectAddress FROM java.util.HashMap",
Cmp::Exact,
),
];
#[test]
#[ignore = "requires MAT_HOME and --features mat-oracle"]
fn oracle_differential() {
let Some(_home) = mat_home() else {
eprintln!("MAT_HOME unset — skipping");
return;
};
let hprof = std::env::var("ORACLE_HPROF").expect("set ORACLE_HPROF to a dump path");
let mut failures = Vec::new();
for (label, oql, kind) in ORACLE_QUERIES {
eprintln!("[{label}] {oql}");
let mat_out = run_mat(&hprof, &normalize_for_mat(oql)); let res = match kind {
Cmp::Scalar => compare_scalar(&run_ours_reachable(&hprof, oql), &mat_out),
Cmp::Superset => {
compare_superset(&addr_set(&run_ours_all(&hprof, oql)), &addr_set(&mat_out))
}
Cmp::Exact => compare_exact(
&addr_set(&run_ours_reachable(&hprof, oql)),
&addr_set(&mat_out),
),
};
if let Err(e) = res {
failures.push(format!("[{label}] {e}"));
}
}
assert!(
failures.is_empty(),
"oracle divergences:\n{}",
failures.join("\n")
);
}
}