use ftui_layout::{
PaneAssumption, PaneExecutionPolicy, PaneId, PaneInteractionTimeline, PaneLeaf,
PaneMemoryStrategy, PaneMonitorReport, PaneMonitorStatus, PaneMonitorThresholds, PaneNodeKind,
PaneOperation, PanePlacement, PaneRetentionPolicy, PaneSplitRatio, PaneTree, PaneVersionStore,
PaneWorkloadProfile, SplitAxis, VersionedPaneTree, apply_retention_to_version_store,
monitor_fallback_frequency, monitor_latency_envelope, monitor_replay_depth,
monitor_retention_pressure, monitor_selector_churn,
};
fn seed_tree(leaf_count: usize) -> PaneTree {
let mut tree = PaneTree::singleton("leaf-0");
let ratio = PaneSplitRatio::new(1, 1).expect("ratio");
let mut next = 1u64;
loop {
let leaves: Vec<PaneId> = tree
.nodes()
.filter_map(|n| match n.kind {
PaneNodeKind::Leaf(_) => Some(n.id),
PaneNodeKind::Split(_) => None,
})
.collect();
if leaves.len() >= leaf_count {
break;
}
tree.apply_operation(
next,
PaneOperation::SplitLeaf {
target: leaves[0],
axis: if next.is_multiple_of(2) {
SplitAxis::Horizontal
} else {
SplitAxis::Vertical
},
ratio,
placement: PanePlacement::ExistingFirst,
new_leaf: PaneLeaf::new(format!("leaf-{next}")),
},
)
.expect("split applies");
next += 1;
}
tree
}
fn first_split(tree: &PaneTree) -> PaneId {
tree.nodes()
.find_map(|n| match n.kind {
PaneNodeKind::Split(_) => Some(n.id),
PaneNodeKind::Leaf(_) => None,
})
.expect("at least one split")
}
#[test]
fn healthy_resize_session_has_no_violations() {
let mut tree = seed_tree(16);
let split = first_split(&tree);
let mut timeline = PaneInteractionTimeline::with_baseline(&tree);
let ratios = [
PaneSplitRatio::new(3, 2).expect("ratio"),
PaneSplitRatio::new(2, 3).expect("ratio"),
PaneSplitRatio::new(5, 4).expect("ratio"),
];
let mut ops = Vec::new();
for i in 0..64u64 {
let op = PaneOperation::SetSplitRatio {
split,
ratio: ratios[(i % 3) as usize],
};
timeline
.apply_and_record(&mut tree, i, i + 1, op.clone())
.expect("apply");
ops.push(op);
}
let thresholds = PaneMonitorThresholds::default();
let replay = monitor_replay_depth(&timeline.replay_diagnostics(), &thresholds);
assert_eq!(
replay.status,
PaneMonitorStatus::Healthy,
"healthy resize replay should be within the interval: {}",
replay.explanation
);
let policy = PaneExecutionPolicy::adaptive(PaneRetentionPolicy::unbounded());
let profile = PaneWorkloadProfile::observe(&ops, 200, true);
let decisions: Vec<_> = (0..8).map(|_| policy.select(profile)).collect();
let churn = monitor_selector_churn(&decisions, &thresholds);
let fallback = monitor_fallback_frequency(&decisions, &thresholds);
assert_eq!(
churn.status,
PaneMonitorStatus::Healthy,
"{}",
churn.explanation
);
assert!(!fallback.status.is_violation(), "{}", fallback.explanation);
let latency = monitor_latency_envelope(
PaneMemoryStrategy::Checkpointed,
5_000.0,
50_000.0,
&thresholds,
);
let report = PaneMonitorReport::new("healthy_resize_session")
.with(replay)
.with(churn)
.with(fallback)
.with(latency);
assert!(
!report.has_violations(),
"healthy session unexpectedly violated an assumption:\n{}",
report.summary_log()
);
assert_eq!(report.worst_status(), PaneMonitorStatus::Healthy);
}
#[test]
fn impossible_retention_budget_is_flagged_as_violation() {
let tree = seed_tree(12);
let mut store = PaneVersionStore::new(VersionedPaneTree::from_pane_tree(&tree));
let split = first_split(&tree);
for i in 0..24u64 {
store
.apply(&PaneOperation::SetSplitRatio {
split,
ratio: PaneSplitRatio::new(1 + (i % 4) as u32, 2).expect("ratio"),
})
.expect("store apply");
}
let decision =
apply_retention_to_version_store(&mut store, &PaneRetentionPolicy::bounded(1, 0));
let thresholds = PaneMonitorThresholds::default();
let verdict = monitor_retention_pressure(&decision, &thresholds);
assert_eq!(
verdict.status,
PaneMonitorStatus::Violated,
"impossible budget should violate retention pressure: {}",
verdict.explanation
);
assert_eq!(verdict.assumption, PaneAssumption::RetentionPressure);
assert_eq!(decision.current_state_hash, tree_head_hash(&store));
}
fn tree_head_hash(store: &PaneVersionStore) -> u64 {
store.current().state_hash().expect("flatten head")
}
#[test]
fn thrashing_selector_is_flagged_as_violation() {
let policy = PaneExecutionPolicy::adaptive(PaneRetentionPolicy::unbounded());
let split = PaneTree::singleton("x").root();
let storm_ops: Vec<_> = (0..128)
.map(|i| PaneOperation::SetSplitRatio {
split,
ratio: PaneSplitRatio::new(1 + (i % 3) as u32, 2).expect("ratio"),
})
.collect();
let storm = PaneWorkloadProfile::observe(&storm_ops, 240, true);
let no_history = PaneWorkloadProfile::observe(&[], 0, false);
let decisions: Vec<_> = (0..8u32)
.map(|i| {
if i.is_multiple_of(2) {
policy.select(storm)
} else {
policy.select(no_history)
}
})
.collect();
let thresholds = PaneMonitorThresholds::default();
let churn = monitor_selector_churn(&decisions, &thresholds);
assert_eq!(
churn.status,
PaneMonitorStatus::Violated,
"alternating profiles should thrash: {}",
churn.explanation
);
assert_eq!(churn.assumption, PaneAssumption::SelectorChurn);
assert!(churn.explanation.contains("thrash"));
}