use std::path::PathBuf;
use haematite::db::vacuum::{
DebrisTotals, EntryKind, IndexCheckpointReport, MalformedEntry, ManifestEntryReport,
ManifestReport, MarkTallies, MetadataOrigin, MetadataReport, MetadataSourceKind,
MetadataSourceReport, Mode as VacuumMode, NodeTotals, Report as VacuumReport, ShardPresence,
ShardReport, SweepBlocker, TornTailFileReport, UninventoriedEntry, WalTailReport,
};
fn sample_shard() -> ShardReport {
ShardReport {
shard_id: 0,
presence: ShardPresence::Materialised,
wal_committed_root: false,
nodes: NodeTotals {
enumerated_nodes: 3,
enumerated_bytes: 300,
marked_nodes: 1,
marked_bytes: 100,
unmarked_nodes: 2,
unmarked_bytes: 200,
},
temp_debris_files: 1,
temp_debris_bytes: 11,
index_checkpoint: Some(IndexCheckpointReport { size_bytes: 4_096 }),
index_temp_debris_files: 1,
index_temp_debris_bytes: 13,
torn_tails: vec![TornTailFileReport {
path: PathBuf::from(concat!(
"/db/shard-0/shard.wal.torn-64-8-",
"0000000000000000000000000000000000000000000000000000000000000000",
)),
offset: 64,
length: 8,
}],
wal_tail: Some(WalTailReport::TornLast {
offset: 128,
length: 9,
}),
malformed: vec![MalformedEntry {
path: PathBuf::from("/db/shard-0/store/zz"),
reason: "strange".to_owned(),
}],
roots_moved: true,
}
}
fn sample_report() -> VacuumReport {
VacuumReport {
mode: VacuumMode::Stats,
data_dir: PathBuf::from("/db"),
shard_count: 2,
duration_ms: 7,
lock_anchor_created: true,
beside_writer: true,
moving_shards: vec![1],
never_materialised_shards: 1,
torn_tail_count: 1,
debris: DebrisTotals {
files: 2,
bytes: 64,
},
shards: vec![sample_shard()],
totals: NodeTotals::default(),
metadata: MetadataReport {
manifest: Some(ManifestReport {
path: PathBuf::from("/db/metadata-manifest.json"),
generation: 4,
entries: vec![ManifestEntryReport {
path: PathBuf::from("/db/branches"),
kind: MetadataSourceKind::RefsDir,
state: "Publishing".to_owned(),
source_readable: false,
}],
}),
attestation: true,
sources: vec![MetadataSourceReport {
path: PathBuf::from("/elsewhere/refs"),
kind: MetadataSourceKind::RefsDir,
origin: MetadataOrigin::Supplied,
records: 2,
temp_debris_files: 1,
foreign_entries: 1,
}],
supplied_missing: vec![PathBuf::from("/typo/refs")],
},
marks: MarkTallies {
history_roots: 0,
wal_roots: 1,
branch_anchors: 2,
branch_heads: 2,
branch_kept_heads: 0,
snapshot_roots: 1,
},
verified_nodes: 42,
uninventoried: vec![UninventoriedEntry {
name: "stray.bin".to_owned(),
kind: EntryKind::File,
size_bytes: Some(9),
}],
sweep_blockers: vec![
SweepBlocker::UnreconciledWalTail {
shard_id: 0,
tail: WalTailReport::TornLast {
offset: 128,
length: 9,
},
},
SweepBlocker::MetadataUnattested,
SweepBlocker::SourceUnreadable {
path: PathBuf::from("/db/locked"),
detail: "permission denied".to_owned(),
},
],
sweep: None,
trust_boundary: "the boundary text".to_owned(),
}
}
#[test]
fn writer_refused_tail_is_distinct_from_a_cut_owed() -> Result<(), std::fmt::Error> {
let mut report = sample_report();
let tail = WalTailReport::Overrun {
offset: 128,
length: 9,
};
report.shards[0].wal_tail = Some(tail);
report.sweep_blockers[0] = SweepBlocker::UnreconciledWalTail { shard_id: 0, tail };
let text = super::render(&report)?;
assert!(text.contains("WRITER OPEN REFUSES: Overrun · offset 128 · length 9 bytes"));
assert!(text.contains("writer open refuses this tail and sweep refuses without deleting"));
assert!(!text.contains("sweep blockers: none observed"));
assert!(!text.contains("CUT OWED"));
Ok(())
}
#[test]
fn rendering_carries_every_report_section() -> Result<(), std::fmt::Error> {
let text = super::render(&sample_report())?;
for needle in [
"report-only",
"/db",
"shard count: 2",
"torn tails: 1 set-aside file(s)",
"install debris: 2 file(s), 64 bytes",
"SET-ASIDE: /db/shard-0/shard.wal.torn-64-8-",
"offset 64 · length 8 bytes",
"CUT OWED: offset 128 · length 9 bytes",
"shard-0 WAL has a TornLast tail at byte 128 (9 bytes)",
"sweep refuses without deleting; a writable open must preserve and cut the tail",
"7 ms",
"created one empty anchor",
"shard-0",
"NO committed root",
"read BESIDE a live writer",
"left out of the totals): shards [1]",
"roots MOVED during the read",
"debris 1",
"MALFORMED: /db/shard-0/store/zz — strange",
"generation 4",
"state Publishing",
"MISSING/UNREADABLE",
"attestation (sweep-only, ignored by stats): asserted",
"/elsewhere/refs",
"2 record(s)",
"1 temp debris",
"1 foreign entries",
"SUPPLIED PATH MISSING: /typo/refs",
"WAL roots 1",
"hash verifications: 42",
"stray.bin",
"not positively established",
"could not be read (permission denied)",
"the boundary text",
] {
assert!(
text.contains(needle),
"rendering must carry {needle:?}:\n{text}"
);
}
Ok(())
}