use std::process::Command;
fn fixture_path() -> String {
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/dump_4_philosophers_report.json"
)
.to_string()
}
fn render_html() -> String {
let out = Command::new(env!("CARGO_BIN_EXE_hprof-analyzer"))
.arg(fixture_path())
.arg("--format")
.arg("html")
.output()
.expect("failed to run hprof-analyzer --format html");
assert!(
out.status.success(),
"render failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout).expect("HTML output is UTF-8")
}
#[test]
fn html_is_self_contained_no_network() {
let html = render_html();
assert!(
!html.contains("http://"),
"HTML must not contain http:// references"
);
assert!(
!html.contains("https://"),
"HTML must not contain https:// references"
);
assert!(
!html.contains("src=\"//") && !html.contains("href=\"//"),
"HTML must not contain protocol-relative URLs"
);
assert!(
!html.contains("src=\""),
"HTML must not contain any src=\"...\" attribute (external resource)"
);
assert!(
html.contains("id=\"report-data\""),
"HTML must embed the report-data script blob"
);
assert!(
html.contains("id=\"app-bundle\""),
"HTML must embed the app-bundle script blob"
);
}
#[test]
fn html_render_is_deterministic() {
let a = render_html();
let b = render_html();
assert_eq!(
a, b,
"two renders of the same report must be byte-identical"
);
}
#[test]
fn bundle_is_within_size_budget() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/web/dist/bundle.js");
let size = std::fs::metadata(path)
.expect("web/dist/bundle.js exists (generated by build.rs)")
.len();
assert!(size > 0, "bundle.js must not be empty");
const BUDGET: u64 = 1600 * 1024;
assert!(
size <= BUDGET,
"web/dist/bundle.js is {size} bytes, over the {BUDGET}-byte budget"
);
}
fn fixture_is_present(path: &str) -> bool {
std::fs::metadata(path)
.map(|m| m.len() >= 1024)
.unwrap_or(false)
}
fn analyze_to_html(hprof: &str, extra_args: &[&str]) -> String {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_hprof-analyzer"));
cmd.arg(hprof).args(extra_args).arg("--format").arg("html");
let out = cmd.output().expect("failed to run hprof-analyzer");
assert!(
out.status.success(),
"analyze failed for {hprof} with args {extra_args:?}: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout).expect("HTML is UTF-8")
}
fn bundle_js() -> String {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/web/dist/bundle.js");
std::fs::read_to_string(path).expect("web/dist/bundle.js must exist")
}
fn bundle_contains_section_id(bundle: &str, id: &str) -> bool {
bundle.contains(&format!("id:\"{id}\""))
}
fn assert_core_sections(html: &str, label: &str) {
assert!(
html.contains("id=\"report-data\""),
"{label}: missing id=\"report-data\" blob"
);
assert!(
html.contains("id=\"app-bundle\""),
"{label}: missing id=\"app-bundle\" blob"
);
assert!(
!html.contains(">NaN<") && !html.contains(">undefined<"),
"{label}: HTML contains NaN or undefined in visible text"
);
let bundle = bundle_js();
for id in &[
"memory-triage",
"system-overview",
"leak-suspects",
"collections",
"glossary",
] {
assert!(
bundle_contains_section_id(&bundle, id),
"{label}: bundle missing section id:\"{id}\""
);
}
}
#[test]
fn html_all_fixtures_default() {
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures");
let entries: Vec<_> = std::fs::read_dir(dir)
.expect("fixtures dir")
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map(|x| x == "hprof").unwrap_or(false))
.collect();
assert!(!entries.is_empty(), "no .hprof fixtures found");
let mut ran = 0;
for entry in entries {
let path = entry.path();
let path_str = path.to_str().unwrap();
if !fixture_is_present(path_str) {
continue; }
let label = path.file_name().unwrap().to_string_lossy().to_string();
let html = analyze_to_html(path_str, &[]);
assert_core_sections(&html, &label);
ran += 1;
}
if ran == 0 {
eprintln!("html_all_fixtures_default: all fixtures are LFS stubs, skipped");
}
}
#[test]
fn html_all_flags_philosophers() {
let hprof = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/dump_4_philosophers.hprof"
);
if !fixture_is_present(hprof) {
eprintln!("html_all_flags_philosophers: fixture is LFS stub, skipped");
return;
}
let bundle = bundle_js();
for id in &["duplicate-strings", "container-attribution"] {
assert!(
bundle_contains_section_id(&bundle, id),
"bundle missing flag-gated section id:\"{id}\""
);
}
let flag_combos: &[&[&str]] = &[
&[],
&["--find-duplicates"],
&["--collections"],
&["--find-duplicates", "--collections"],
&["--detail", "minimal"],
&["--detail", "max"],
];
for args in flag_combos {
let label = format!("philosophers {:?}", args);
let html = analyze_to_html(hprof, args);
assert_core_sections(&html, &label);
}
}
#[test]
fn html_anchor_ids_present() {
let html = render_html();
assert_core_sections(&html, "json-fixture");
let bundle = bundle_js();
let required_ids = [
"memory-triage",
"system-overview",
"hprof-record-census",
"leak-suspects",
"top-consumers",
"dominator-analysis",
"threads",
"arrays-by-size",
"collections",
"references",
"unreachable-objects",
"glossary",
];
for id in &required_ids {
assert!(
bundle_contains_section_id(&bundle, id),
"bundle missing required section id:\"{id}\""
);
}
}