#![cfg(test)]
use super::*;
use std::path::Path;
use tracing_test::traced_test;
fn make_summary(
failed: u64,
attach: &[(&'static str, u64)],
probe: &[(&'static str, u64)],
) -> ProbeSummary {
ProbeSummary {
failed,
attach_tag_counts: attach.iter().copied().collect(),
probe_tag_counts: probe.iter().copied().collect(),
..ProbeSummary::default()
}
}
#[test]
fn probe_summary_dominant_tag_picks_highest_count() {
let s = make_summary(6, &[("dwarf-parse-failure", 5)], &[("ptrace-seize", 1)]);
assert_eq!(s.dominant_tag(), Some("dwarf-parse-failure"));
}
#[test]
fn probe_summary_dominant_tag_filters_non_actionable_attach_tags() {
let s = make_summary(101, &[("jemalloc-not-found", 100)], &[("ptrace-seize", 1)]);
assert_eq!(
s.dominant_tag(),
Some("ptrace-seize"),
"jemalloc-not-found must be filtered out even at \
100x the count of an actionable tag",
);
let s = make_summary(101, &[("readlink-failure", 100)], &[("get-regset", 1)]);
assert_eq!(
s.dominant_tag(),
Some("get-regset"),
"readlink-failure must be filtered out even at \
100x the count of an actionable tag",
);
let s = make_summary(
201,
&[("jemalloc-not-found", 100), ("readlink-failure", 100)],
&[("waitpid", 1)],
);
assert_eq!(
s.dominant_tag(),
Some("waitpid"),
"both filtered attach tags together must NOT push their \
aggregate above an actionable probe tag",
);
let s = make_summary(5, &[("jemalloc-not-found", 5)], &[]);
assert_eq!(
s.dominant_tag(),
None,
"only-filtered-tags case must produce None, not the \
filtered tag itself",
);
}
#[test]
fn probe_summary_dominant_tag_breaks_ties_reverse_alphabetically() {
let s = make_summary(4, &[("ptrace-seize", 2)], &[("dwarf-parse-failure", 2)]);
assert_eq!(s.dominant_tag(), Some("dwarf-parse-failure"));
}
#[test]
fn probe_summary_ptrace_dominates_when_half_of_failures() {
let s = make_summary(6, &[], &[("ptrace-seize", 3), ("waitpid", 3)]);
assert!(s.ptrace_dominates());
}
#[test]
fn probe_summary_ptrace_does_not_dominate_when_below_half() {
let s = make_summary(6, &[], &[("ptrace-seize", 2), ("waitpid", 4)]);
assert!(!s.ptrace_dominates());
}
#[test]
fn probe_summary_no_failures_no_dominant_tag() {
let s = ProbeSummary::default();
assert!(!s.ptrace_dominates());
assert_eq!(s.dominant_tag(), None);
}
#[test]
fn ptrace_eperm_hint_uses_which_ktstr() {
assert!(
PTRACE_EPERM_HINT.contains("$(which ktstr)"),
"EPERM hint must use $(which ktstr) for portability, got: {PTRACE_EPERM_HINT}",
);
assert!(PTRACE_EPERM_HINT.contains("cap_sys_ptrace"));
assert!(PTRACE_EPERM_HINT.contains("yama.ptrace_scope"));
}
#[test]
fn to_public_carries_counters_and_dominant_tag() {
let mut s = make_summary(3, &[("dwarf-parse-failure", 2)], &[("ptrace-seize", 1)]);
s.tgids_walked = 10;
s.jemalloc_detected = 5;
s.probed_ok = 4;
let public = s.to_public();
assert_eq!(public.tgids_walked, 10);
assert_eq!(public.jemalloc_detected, 5);
assert_eq!(public.probed_ok, 4);
assert_eq!(public.failed, 3);
assert_eq!(
public.dominant_failure.as_deref(),
Some("dwarf-parse-failure"),
"dominant_tag picks the highest-count actionable tag, \
projected as an owned String",
);
assert!(
!public.privilege_dominant,
"ptrace 1/3 < 50% → privilege_dominant false",
);
}
#[test]
fn to_public_dominant_failure_is_none_when_no_failures() {
let s = make_summary(0, &[("jemalloc-not-found", 12)], &[]);
let public = s.to_public();
assert_eq!(public.failed, 0);
assert!(
public.dominant_failure.is_none(),
"no actionable failures means dominant_failure is None; \
got {:?}",
public.dominant_failure,
);
assert!(
!public.privilege_dominant,
"no failures means privilege_dominant is false",
);
}
#[test]
fn to_public_privilege_dominant_when_ptrace_crosses_threshold() {
let s = make_summary(4, &[], &[("ptrace-seize", 4)]);
let public = s.to_public();
assert_eq!(public.failed, 4);
assert!(
public.privilege_dominant,
"ptrace 4/4 ≥ 50% → privilege_dominant true",
);
let s = make_summary(4, &[("dwarf-parse-failure", 2)], &[("ptrace-seize", 2)]);
let public = s.to_public();
assert!(
public.privilege_dominant,
"ptrace 2/4 = 50% boundary → privilege_dominant true (>= threshold)",
);
let s = make_summary(4, &[("dwarf-parse-failure", 3)], &[("ptrace-seize", 1)]);
let public = s.to_public();
assert!(
!public.privilege_dominant,
"ptrace 1/4 < 50% → privilege_dominant false",
);
}
#[test]
fn to_public_privilege_dominant_ptrace_interrupt_and_edge_cases() {
let s = make_summary(2, &[], &[("ptrace-interrupt", 2)]);
let public = s.to_public();
assert!(
public.privilege_dominant,
"ptrace-interrupt 2/2 ≥ 50% → privilege_dominant true \
(matches! arm covers ptrace-interrupt as well as ptrace-seize)",
);
let s = make_summary(
4,
&[("dwarf-parse-failure", 2)],
&[("ptrace-seize", 1), ("ptrace-interrupt", 1)],
);
let public = s.to_public();
assert!(
public.privilege_dominant,
"summed ptrace 2/4 ≥ 50% → privilege_dominant true",
);
assert_eq!(
public.dominant_failure.as_deref(),
Some("dwarf-parse-failure"),
"dominant_failure names the non-ptrace tag that won the \
single-tag plurality while privilege_dominant is true — \
proves the two fields are independent",
);
let s = make_summary(1, &[], &[("ptrace-seize", 1)]);
let public = s.to_public();
assert!(
public.privilege_dominant,
"ptrace 1/1 ≥ 50% → privilege_dominant true at the \
smallest-failed boundary",
);
let s = make_summary(1, &[("dwarf-parse-failure", 1)], &[]);
let public = s.to_public();
assert!(
!public.privilege_dominant,
"no ptrace tags with failed == 1 → privilege_dominant \
false (total_ptrace == 0 keeps the gate closed)",
);
assert!(
!CtprofProbeSummary::default().privilege_dominant,
"CtprofProbeSummary::default().privilege_dominant \
must be false",
);
let s = make_summary(
10,
&[("dwarf-parse-failure", 3), ("jemalloc-in-dso", 3)],
&[("ptrace-seize", 4)],
);
let public = s.to_public();
assert!(
!public.privilege_dominant,
"ptrace 4/10 < 50% → privilege_dominant false",
);
assert_eq!(
public.dominant_failure.as_deref(),
Some("ptrace-seize"),
"dominant_failure names a ptrace tag while privilege_dominant \
is false — converse of the independence claim",
);
}
#[test]
fn remediation_hint_returns_some_iff_privilege_dominant() {
let ps = CtprofProbeSummary {
privilege_dominant: true,
..Default::default()
};
assert_eq!(
ps.remediation_hint(),
Some(PTRACE_EPERM_HINT),
"privilege_dominant=true must surface the same hint text \
the tracing summary prints",
);
let ps = CtprofProbeSummary::default();
assert!(
!ps.privilege_dominant,
"default privilege_dominant must be false (sanity)",
);
assert_eq!(
ps.remediation_hint(),
None,
"privilege_dominant=false → remediation_hint returns None",
);
}
#[traced_test]
#[test]
fn summary_emits_clean_line_when_no_failures() {
let summary = make_summary(0, &[("jemalloc-not-found", 12)], &[]);
emit_probe_summary(&summary);
assert!(logs_contain("ctprof probe:"));
assert!(logs_contain("0 tgids walked"));
assert!(logs_contain("0 failed"));
assert!(
!logs_contain("(dominant:"),
"no failures means the dominant-tag clause is omitted",
);
assert!(
!logs_contain("hint:"),
"no failures means the EPERM hint is omitted",
);
}
#[traced_test]
#[test]
fn summary_emits_privilege_hint_when_ptrace_dominates() {
let summary = ProbeSummary {
tgids_walked: 4,
jemalloc_detected: 2,
probed_ok: 0,
failed: 4,
attach_tag_counts: BTreeMap::new(),
probe_tag_counts: [("ptrace-seize", 4u64)].into_iter().collect(),
};
emit_probe_summary(&summary);
assert!(logs_contain("(dominant: ptrace-seize"));
assert!(logs_contain("hint:"));
assert!(logs_contain("$(which ktstr)"));
assert!(logs_contain("cap_sys_ptrace"));
assert!(logs_contain("yama.ptrace_scope"));
}
#[traced_test]
#[test]
fn summary_emits_privilege_hint_when_ptrace_interrupt_dominates() {
let summary = ProbeSummary {
tgids_walked: 4,
jemalloc_detected: 2,
probed_ok: 0,
failed: 4,
attach_tag_counts: BTreeMap::new(),
probe_tag_counts: [("ptrace-interrupt", 4u64)].into_iter().collect(),
};
emit_probe_summary(&summary);
assert!(logs_contain("(dominant: ptrace-interrupt"));
assert!(logs_contain("hint:"));
assert!(logs_contain("$(which ktstr)"));
assert!(logs_contain("cap_sys_ptrace"));
assert!(logs_contain("yama.ptrace_scope"));
}
#[traced_test]
#[test]
fn summary_omits_privilege_hint_when_debuginfo_failures_lead() {
let summary = ProbeSummary {
tgids_walked: 5,
jemalloc_detected: 3,
probed_ok: 0,
failed: 5,
attach_tag_counts: [("dwarf-parse-failure", 4u64)].into_iter().collect(),
probe_tag_counts: [("ptrace-seize", 1u64)].into_iter().collect(),
};
emit_probe_summary(&summary);
assert!(logs_contain("(dominant: dwarf-parse-failure"));
assert!(
!logs_contain("hint:"),
"DWARF-dominated failures must NOT trigger the privilege \
hint — only privilege failures earn the privilege remediation",
);
}
#[traced_test]
#[test]
fn parse_summary_emits_clean_line_when_no_failures() {
let tally = ParseTally::default();
emit_parse_summary(&tally);
assert!(logs_contain("ctprof parse:"));
assert!(logs_contain("0 tids walked"));
assert!(logs_contain("0 read failures"));
assert!(
!logs_contain("(dominant:"),
"no failures means the dominant clause is omitted",
);
assert!(
!logs_contain("hint:"),
"no failures means the kconfig hint is omitted",
);
assert!(
!logs_contain("negative-dotted"),
"zero negative-dotted values means the negative \
clause is omitted",
);
}
#[traced_test]
#[test]
fn parse_summary_emits_negative_dotted_clause_when_present() {
let mut tally = ParseTally {
tids_walked: 5,
..ParseTally::default()
};
tally.record_negative_dotted();
tally.record_negative_dotted();
tally.record_negative_dotted();
tally.commit_pending();
emit_parse_summary(&tally);
assert!(
logs_contain("3 negative-dotted values"),
"negative-dotted clause must surface the count when \
the tally is non-zero — the operator-visibility \
motivation depends on this rendering",
);
assert!(logs_contain("0 read failures"));
}
#[traced_test]
#[test]
fn parse_summary_emits_kconfig_hint_when_dominant() {
let mut tally = ParseTally {
tids_walked: 100,
..ParseTally::default()
};
for _ in 0..60 {
tally.record_failure("schedstat");
}
for _ in 0..40 {
tally.record_failure("io");
}
tally.commit_pending();
emit_parse_summary(&tally);
assert!(logs_contain("(dominant: schedstat)"));
assert!(logs_contain("hint:"));
assert!(logs_contain("CONFIG_SCHEDSTATS"));
assert!(logs_contain("CONFIG_TASK_IO_ACCOUNTING"));
}
#[traced_test]
#[test]
fn try_attach_probe_for_tgid_at_warns_on_pid_missing() {
let mut summary = ProbeSummary::default();
let probe = try_attach_probe_for_tgid_at(Path::new(DEFAULT_PROC_ROOT), 0, &mut summary);
assert!(probe.is_none(), "pid 0 must not produce a probe");
assert!(logs_contain("attach failed"));
assert!(logs_contain("pid-missing"));
assert_eq!(summary.failed, 1);
assert_eq!(summary.jemalloc_detected, 0);
assert_eq!(summary.tgids_walked, 1);
assert_eq!(
summary.attach_tag_counts.get("pid-missing").copied(),
Some(1),
"PidMissing tag must increment its bucket",
);
}
#[traced_test]
#[test]
fn try_attach_probe_for_tgid_at_debugs_on_non_jemalloc_target() {
let mut child = match std::process::Command::new("sleep")
.arg("3")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
{
Ok(c) => c,
Err(_) => {
eprintln!("skipping — /bin/sleep unavailable");
return;
}
};
let pid = child.id() as i32;
let exe_link = std::path::PathBuf::from(format!("/proc/{pid}/exe"));
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(1);
while std::fs::read_link(&exe_link).is_err() {
if std::time::Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
panic!(
"/proc/{pid}/exe did not become readable within 1s — \
kernel did not surface the freshly-forked child's exe \
symlink in time, the test cannot proceed"
);
}
std::thread::sleep(std::time::Duration::from_millis(1));
}
let mut summary = ProbeSummary::default();
let probe = try_attach_probe_for_tgid_at(Path::new(DEFAULT_PROC_ROOT), pid, &mut summary);
let _ = child.kill();
let _ = child.wait();
assert!(probe.is_none(), "sleep is not jemalloc-linked");
assert_eq!(summary.tgids_walked, 1);
assert_eq!(summary.jemalloc_detected, 0);
assert_eq!(
summary.failed, 0,
"jemalloc-not-found must NOT count as failure — it's the \
expected outcome for the bulk of system processes",
);
assert_eq!(
summary.attach_tag_counts.get("jemalloc-not-found").copied(),
Some(1),
);
assert!(
logs_contain("attach skipped"),
"JemallocNotFound must emit the debug 'attach skipped' \
event so log filters can route it separately from \
actionable warnings",
);
assert!(
!logs_contain("attach failed"),
"jemalloc-not-found must NOT emit the warn 'attach failed' \
event — that level is reserved for actionable failures",
);
}