use super::EarlySnapshotGuard;
use super::snapshot::snapshot_tagged_path;
use crate::monitor::bpf_map::BPF_MAP_TYPE_ARRAY;
use crate::monitor::btf_render::RenderedValue;
use crate::monitor::dump::{
ALL_SNAPSHOT_TAGS, FailureDumpMap, FailureDumpReport, SCHEMA_DUAL,
SNAPSHOT_TAG_EARLY_ONLY_LATE_NEVER_FIRED, SNAPSHOT_TAG_EARLY_ONLY_LATE_SUPPRESSED,
SNAPSHOT_TAG_EARLY_PRE_LATE_DEGRADED,
};
use std::panic::{AssertUnwindSafe, catch_unwind};
use tempfile::TempDir;
fn synthetic_report() -> FailureDumpReport {
FailureDumpReport {
schema: SCHEMA_DUAL.to_string(),
maps: vec![FailureDumpMap {
name: "synthetic.bss".into(),
map_type: BPF_MAP_TYPE_ARRAY,
value_size: 8,
max_entries: 1,
value: Some(RenderedValue::Uint {
bits: 32,
value: 0xCAFE,
}),
entries: Vec::new(),
percpu_entries: Vec::new(),
percpu_hash_entries: Vec::new(),
arena: None,
ringbuf: None,
stack_trace: None,
fd_array: None,
error: None,
}],
vcpu_regs: Vec::new(),
sdt_allocations: Vec::new(),
sdt_alloc_unavailable: None,
prog_runtime_stats: Vec::new(),
prog_runtime_stats_unavailable: None,
per_cpu_time: Vec::new(),
per_node_numa: Vec::new(),
per_node_numa_unavailable: None,
task_enrichments: Vec::new(),
task_enrichments_unavailable: None,
event_counter_timeline: Vec::new(),
rq_scx_states: Vec::new(),
dsq_states: Vec::new(),
scx_sched_state: None,
scx_walker_unavailable: None,
vcpu_perf_at_freeze: Vec::new(),
dump_truncated_at_us: None,
probe_counters: None,
scx_static_ranges: Default::default(),
is_placeholder: false,
}
}
fn dump_base_path(dir: &TempDir) -> std::path::PathBuf {
dir.path().join("coord.failure-dump.json")
}
#[test]
fn early_snapshot_guard_drops_on_panic_unwind() {
let tmp = TempDir::new().expect("tempdir");
let dump_path = dump_base_path(&tmp);
let synthetic = synthetic_report();
let synthetic_json = serde_json::to_string(&synthetic).expect("serialize synthetic");
let result = catch_unwind(AssertUnwindSafe(|| {
let _guard = EarlySnapshotGuard {
snapshot: Some(synthetic),
retain_tag: None,
dump_path: Some(dump_path.clone()),
dual_snapshot: true,
};
panic!("inject — guard's Drop must still flush to disk");
}));
assert!(
result.is_err(),
"the closure must propagate the injected panic"
);
let expected = snapshot_tagged_path(&dump_path, SNAPSHOT_TAG_EARLY_ONLY_LATE_NEVER_FIRED);
assert!(
expected.exists(),
"panic-unwind must land file at NEVER_FIRED tag: {}",
expected.display()
);
let body = std::fs::read_to_string(&expected).expect("read drained file");
let loaded: FailureDumpReport = serde_json::from_str(&body).expect("deserialize");
let loaded_json = serde_json::to_string(&loaded).expect("re-serialize loaded");
assert_eq!(
loaded_json, synthetic_json,
"populated FailureDumpReport fields (those NOT suppressed by \
skip_serializing_if) must roundtrip identically via the drained \
file — drift here means a field was dropped or mutated in the \
serialize → write_to_tagged_path → read_to_string → deserialize \
chain. Max-shape infallibility proof in \
failure_dump_report_serialization_is_infallible_for_max_synthetic_input."
);
}
#[test]
fn early_snapshot_guard_drain_then_drop_is_idempotent() {
let tmp = TempDir::new().expect("tempdir");
let dump_path = dump_base_path(&tmp);
let synthetic = synthetic_report();
let expected = snapshot_tagged_path(&dump_path, SNAPSHOT_TAG_EARLY_ONLY_LATE_NEVER_FIRED);
let (mtime_after_drain, len_after_drain) = {
let mut guard = EarlySnapshotGuard {
snapshot: Some(synthetic),
retain_tag: None,
dump_path: Some(dump_path.clone()),
dual_snapshot: true,
};
guard.drain_to_disk();
assert!(
guard.snapshot.is_none(),
"drain_to_disk must take() the snapshot — None proves the gate"
);
let md = std::fs::metadata(&expected).expect("file landed by explicit drain");
(md.modified().expect("mtime"), md.len())
};
let md_after_drop = std::fs::metadata(&expected).expect("file still present after Drop");
assert_eq!(
md_after_drop.len(),
len_after_drain,
"Drop after explicit drain must NOT alter the file size"
);
assert_eq!(
md_after_drop.modified().expect("mtime"),
mtime_after_drain,
"Drop after explicit drain must NOT touch the file mtime — \
a non-equal mtime proves a second write happened"
);
let tmp2 = TempDir::new().expect("tempdir");
let dump_path2 = dump_base_path(&tmp2);
let expected2 = snapshot_tagged_path(&dump_path2, SNAPSHOT_TAG_EARLY_ONLY_LATE_NEVER_FIRED);
let mut guard = EarlySnapshotGuard {
snapshot: Some(synthetic_report()),
retain_tag: None,
dump_path: Some(dump_path2.clone()),
dual_snapshot: true,
};
guard.drain_to_disk();
let md_before_unwind = std::fs::metadata(&expected2).expect("file landed by drain");
let mtime_before_unwind = md_before_unwind.modified().expect("mtime");
let result = catch_unwind(AssertUnwindSafe(move || {
let _guard = guard;
panic!("inject: drain succeeded, now unwind with guard alive");
}));
assert!(
result.is_err(),
"injected panic must propagate after successful drain"
);
let md_after_unwind = std::fs::metadata(&expected2).expect("file still present after unwind");
let mtime_after_unwind = md_after_unwind.modified().expect("mtime");
assert_eq!(
mtime_before_unwind, mtime_after_unwind,
"Drop during unwind after explicit drain must NOT rewrite — \
mtime change proves a second write fired even though the snapshot \
was already consumed by the explicit drain (the snapshot.take() \
short-circuit in drain_to_disk was removed)"
);
}
#[test]
fn early_snapshot_guard_drops_with_retain_tag_when_late_failed() {
for &(tag, label) in &[
(
SNAPSHOT_TAG_EARLY_PRE_LATE_DEGRADED,
"early-pre-late-degraded",
),
(
SNAPSHOT_TAG_EARLY_ONLY_LATE_SUPPRESSED,
"early-only-late-suppressed",
),
] {
let tmp = TempDir::new().expect("tempdir");
let dump_path = dump_base_path(&tmp);
let synthetic = synthetic_report();
let result = catch_unwind(AssertUnwindSafe(|| {
let _guard = EarlySnapshotGuard {
snapshot: Some(synthetic),
retain_tag: Some(tag),
dump_path: Some(dump_path.clone()),
dual_snapshot: true,
};
panic!("inject — guard's Drop must use retain_tag, not NEVER_FIRED");
}));
assert!(result.is_err(), "[{label}] catch_unwind must return Err");
let expected = snapshot_tagged_path(&dump_path, tag);
assert!(
expected.exists(),
"[{label}] retain_tag file must exist at {}",
expected.display()
);
for negative_tag in ALL_SNAPSHOT_TAGS {
if *negative_tag == tag {
continue;
}
let negative_path = snapshot_tagged_path(&dump_path, negative_tag);
assert!(
!negative_path.exists(),
"[{label}] retain_tag {tag:?} must override all others — \
no file may exist at {} (double-write to {negative_tag:?})",
negative_path.display()
);
}
}
}
#[test]
fn early_snapshot_guard_drop_no_op_when_dual_snapshot_disabled() {
let tmp_normal = TempDir::new().expect("tempdir");
let dump_path_normal = dump_base_path(&tmp_normal);
{
let _guard = EarlySnapshotGuard {
snapshot: Some(synthetic_report()),
retain_tag: None,
dump_path: Some(dump_path_normal.clone()),
dual_snapshot: false,
};
}
for tag in ALL_SNAPSHOT_TAGS {
let path = snapshot_tagged_path(&dump_path_normal, tag);
assert!(
!path.exists(),
"dual_snapshot=false (normal exit) must NOT write to {}",
path.display()
);
}
let tmp_panic = TempDir::new().expect("tempdir");
let dump_path_panic = dump_base_path(&tmp_panic);
let result = catch_unwind(AssertUnwindSafe(|| {
let _guard = EarlySnapshotGuard {
snapshot: Some(synthetic_report()),
retain_tag: Some(SNAPSHOT_TAG_EARLY_PRE_LATE_DEGRADED),
dump_path: Some(dump_path_panic.clone()),
dual_snapshot: false,
};
panic!("inject — dual_snapshot=false must still bypass the write");
}));
assert!(
result.is_err(),
"panic must propagate even with dual_snapshot=false"
);
for tag in ALL_SNAPSHOT_TAGS {
let path = snapshot_tagged_path(&dump_path_panic, tag);
assert!(
!path.exists(),
"dual_snapshot=false (panic unwind) must NOT write to {}",
path.display()
);
}
}
#[test]
fn early_snapshot_guard_drain_no_op_when_dump_path_unset() {
let tmp = TempDir::new().expect("tempdir");
let mut guard = EarlySnapshotGuard {
snapshot: Some(synthetic_report()),
retain_tag: None,
dump_path: None,
dual_snapshot: true,
};
guard.drain_to_disk();
assert!(
guard.snapshot.is_some(),
"dump_path=None gate must run BEFORE snapshot.take() — \
a consumed snapshot here means take() ran first and the \
snapshot is now lost"
);
let entries_after_drain: Vec<_> = std::fs::read_dir(tmp.path()).expect("readdir").collect();
assert!(
entries_after_drain.is_empty(),
"no file may be created when dump_path is unset — found {} entries",
entries_after_drain.len()
);
drop(guard);
let entries_after_drop: Vec<_> = std::fs::read_dir(tmp.path()).expect("readdir").collect();
assert!(
entries_after_drop.is_empty(),
"Drop must also no-op when dump_path is unset — found {} entries",
entries_after_drop.len()
);
}
#[test]
fn early_snapshot_guard_drop_swallows_write_failure_without_panic() {
let tmp = TempDir::new().expect("tempdir");
let blocker = tmp.path().join("blocker_file");
std::fs::write(&blocker, b"not a dir").expect("write blocker");
let dump_path = blocker.join("coord.failure-dump.json");
{
let mut guard = EarlySnapshotGuard {
snapshot: Some(synthetic_report()),
retain_tag: None,
dump_path: Some(dump_path.clone()),
dual_snapshot: true,
};
guard.drain_to_disk();
assert!(
guard.snapshot.is_none(),
"drain consumes snapshot even when the helper returns Err"
);
}
let result = catch_unwind(AssertUnwindSafe(|| {
let _guard = EarlySnapshotGuard {
snapshot: Some(synthetic_report()),
retain_tag: None,
dump_path: Some(dump_path.clone()),
dual_snapshot: true,
};
panic!("injected — write failure must not turn this into a double-panic abort");
}));
assert!(
result.is_err(),
"injected panic must propagate without Drop adding its own"
);
}
#[test]
fn early_snapshot_guard_retain_tag_without_snapshot_no_op() {
let tmp = TempDir::new().expect("tempdir");
let dump_path = dump_base_path(&tmp);
let mut guard = EarlySnapshotGuard {
snapshot: None,
retain_tag: Some(SNAPSHOT_TAG_EARLY_PRE_LATE_DEGRADED),
dump_path: Some(dump_path.clone()),
dual_snapshot: true,
};
guard.drain_to_disk();
for tag in ALL_SNAPSHOT_TAGS {
let path = snapshot_tagged_path(&dump_path, tag);
assert!(
!path.exists(),
"retain_tag=Some + snapshot=None must NOT write to {} \
after explicit drain (retain_tag is unreachable when \
the snapshot.take() gate returns None)",
path.display()
);
}
drop(guard);
for tag in ALL_SNAPSHOT_TAGS {
let path = snapshot_tagged_path(&dump_path, tag);
assert!(
!path.exists(),
"retain_tag=Some + snapshot=None must NOT write to {} \
after Drop fires (Drop must not diverge from drain_to_disk's \
gate behavior)",
path.display()
);
}
}
#[test]
fn early_snapshot_guard_drain_preserves_snapshot_when_gated_off() {
struct Row {
label: &'static str,
dual_snapshot: bool,
with_dump_path: bool,
expect_snapshot_consumed: bool,
expect_file_written: bool,
}
let rows = [
Row {
label: "(a) dual_snapshot=false, dump_path=Some",
dual_snapshot: false,
with_dump_path: true,
expect_snapshot_consumed: false,
expect_file_written: false,
},
Row {
label: "(b) dual_snapshot=true, dump_path=None",
dual_snapshot: true,
with_dump_path: false,
expect_snapshot_consumed: false,
expect_file_written: false,
},
Row {
label: "(c) control: dual_snapshot=true, dump_path=Some",
dual_snapshot: true,
with_dump_path: true,
expect_snapshot_consumed: true,
expect_file_written: true,
},
Row {
label: "(d) dual_snapshot=false, dump_path=None",
dual_snapshot: false,
with_dump_path: false,
expect_snapshot_consumed: false,
expect_file_written: false,
},
];
for row in rows {
let tmp = TempDir::new().expect("tempdir");
let dump_path = dump_base_path(&tmp);
let mut guard = EarlySnapshotGuard {
snapshot: Some(synthetic_report()),
retain_tag: None,
dump_path: row.with_dump_path.then(|| dump_path.clone()),
dual_snapshot: row.dual_snapshot,
};
guard.drain_to_disk();
assert_eq!(
guard.snapshot.is_none(),
row.expect_snapshot_consumed,
"[{}] snapshot consumption mismatch: expected_consumed={}",
row.label,
row.expect_snapshot_consumed
);
let expected = snapshot_tagged_path(&dump_path, SNAPSHOT_TAG_EARLY_ONLY_LATE_NEVER_FIRED);
assert_eq!(
expected.exists(),
row.expect_file_written,
"[{}] file-presence mismatch at {}: expected={}",
row.label,
expected.display(),
row.expect_file_written
);
}
}