#![allow(clippy::unwrap_used)]
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Mutex;
struct CountingAllocator;
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
static MEASURING: AtomicBool = AtomicBool::new(false);
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if MEASURING.load(Ordering::Relaxed) {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
}
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout)
}
}
#[global_allocator]
static GLOBAL: CountingAllocator = CountingAllocator;
fn measure_lock() -> &'static Mutex<()> {
static LOCK: std::sync::OnceLock<Mutex<()>> = std::sync::OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
#[must_use = "binding the guard to `_guard` keeps the file-wide test serialisation alive"]
fn enter_perf_test() -> std::sync::MutexGuard<'static, ()> {
measure_lock().lock().unwrap()
}
fn measure_allocs<R>(label: &'static str, f: impl FnOnce() -> R) -> (R, usize) {
let _maybe_guard = measure_lock().try_lock();
ALLOC_COUNT.store(0, Ordering::Relaxed);
MEASURING.store(true, Ordering::Relaxed);
let r = f();
MEASURING.store(false, Ordering::Relaxed);
let count = ALLOC_COUNT.load(Ordering::Relaxed);
eprintln!("[{}] allocations = {}", label, count);
(r, count)
}
#[test]
fn framestate_reuse_steady_state_alloc_count_low() {
let _guard = enter_perf_test();
use slt::TestBackend;
let mut tb = TestBackend::new(80, 24);
tb.render(|ui| {
let _ = ui.bordered(slt::Border::Rounded).title("warm").col(|ui| {
ui.text("hello").bold();
ui.text("world").dim();
});
});
let (_, count) = measure_allocs("framestate_100_frames", || {
for _ in 0..100 {
tb.render(|ui| {
let _ = ui.bordered(slt::Border::Rounded).title("frame").col(|ui| {
ui.text("hello").bold();
ui.text("world").dim();
});
});
}
});
assert!(
count < 1500,
"framestate-reuse regression: 100 frames allocated {} times (budget 1500)",
count
);
}
#[test]
fn wrap_segments_alloc_count_low_via_bench_helper() {
let _guard = enter_perf_test();
let make_segments = |seed: u32| -> Vec<(String, slt::Style)> {
vec![
(format!("hello {} world", seed), slt::Style::new().bold()),
(" ".to_string(), slt::Style::default()),
(
"alpha beta gamma delta epsilon zeta eta theta".to_string(),
slt::Style::new().italic(),
),
]
};
let _ = slt::__bench_wrap_segments(&make_segments(0), 40);
let (_, count) = measure_allocs("wrap_segments_1000_iters", || {
for i in 0..1000u32 {
let segs = make_segments(i);
let _wrapped = slt::__bench_wrap_segments(&segs, 40);
}
});
eprintln!(
"wrap_segments avg allocs/call = {:.2}",
count as f64 / 1000.0
);
assert!(
count < 25000,
"wrap_segments alloc regression: 1000 iters allocated {} times (budget 25000)",
count
);
}
#[test]
fn kitty_placement_flush_first_flush_one_arc_clone() {
let _guard = enter_perf_test();
let mut fx = slt::__bench_new_kitty_fixture(3);
let before = fx.rgba_strong_counts();
let mut sink: Vec<u8> = Vec::new();
fx.flush_inline(&mut sink, 5).unwrap();
let after = fx.rgba_strong_counts();
for (b, a) in before.iter().zip(after.iter()) {
assert_eq!(
*a - *b,
1,
"first flush should add exactly 1 strong ref per image (was {} -> {})",
b,
a
);
}
}
#[test]
fn kitty_placement_flush_steady_state_no_arc_growth() {
let _guard = enter_perf_test();
let mut fx = slt::__bench_new_kitty_fixture(3);
let mut sink: Vec<u8> = Vec::new();
fx.flush_inline(&mut sink, 5).unwrap();
let after_first = fx.rgba_strong_counts();
sink.clear();
for _ in 0..50 {
fx.flush_inline(&mut sink, 5).unwrap();
}
let after_50 = fx.rgba_strong_counts();
for (b, a) in after_first.iter().zip(after_50.iter()) {
assert_eq!(
*a, *b,
"stable flush should not change Arc strong count ({}=>{})",
b, a
);
}
}
#[test]
fn kitty_placement_flush_alloc_count_low() {
let _guard = enter_perf_test();
let mut fx = slt::__bench_new_kitty_fixture(3);
let mut sink: Vec<u8> = Vec::new();
fx.flush_inline(&mut sink, 5).unwrap();
sink.clear();
sink.shrink_to_fit();
let (_, count) = measure_allocs("kitty_100_flushes_stable", || {
for _ in 0..100 {
fx.flush_inline(&mut sink, 5).unwrap();
}
});
assert!(
count < 50,
"kitty stable flush regression: 100 flushes allocated {} times (budget 50)",
count
);
}
#[test]
fn dim_buffer_modal_perimeter_not_area() {
let _guard = enter_perf_test();
use slt::buffer::Buffer;
use slt::rect::Rect;
let area = Rect::new(0, 0, 200, 60);
let modal = Rect::new(60, 20, 80, 20);
let mut buf = Buffer::empty(area);
slt::__bench_dim_buffer_around(&mut buf, modal);
let mut dim_count = 0;
let mut nondim_count = 0;
for y in 0..60u32 {
for x in 0..200u32 {
let cell = buf.get(x, y);
let has_dim = cell.style.modifiers.contains(slt::Modifiers::DIM);
let inside_modal =
x >= modal.x && x < modal.right() && y >= modal.y && y < modal.bottom();
if has_dim {
dim_count += 1;
assert!(
!inside_modal,
"DIM should not be applied inside modal at ({},{})",
x, y
);
} else {
nondim_count += 1;
assert!(
inside_modal,
"DIM should be applied outside modal at ({},{})",
x, y
);
}
}
}
let modal_area = (modal.width * modal.height) as usize;
let total = (area.width * area.height) as usize;
assert_eq!(dim_count, total - modal_area);
assert_eq!(nondim_count, modal_area);
}
#[test]
fn dim_buffer_modal_full_screen_falls_back_correctly() {
let _guard = enter_perf_test();
use slt::buffer::Buffer;
use slt::rect::Rect;
let area = Rect::new(0, 0, 80, 24);
let modal = Rect::new(0, 0, 80, 24);
let mut buf = Buffer::empty(area);
slt::__bench_dim_buffer_around(&mut buf, modal);
for y in 0..24u32 {
for x in 0..80u32 {
let cell = buf.get(x, y);
assert!(
!cell.style.modifiers.contains(slt::Modifiers::DIM),
"full-screen modal should not dim any cell"
);
}
}
}
#[test]
fn dim_buffer_modal_zero_size_falls_back_to_full() {
let _guard = enter_perf_test();
use slt::buffer::Buffer;
use slt::rect::Rect;
let area = Rect::new(0, 0, 40, 12);
let modal = Rect::new(10, 5, 0, 0);
let mut buf = Buffer::empty(area);
slt::__bench_dim_buffer_around(&mut buf, modal);
for y in 0..12u32 {
for x in 0..40u32 {
let cell = buf.get(x, y);
assert!(
cell.style.modifiers.contains(slt::Modifiers::DIM),
"zero-size modal should dim every cell at ({},{})",
x,
y
);
}
}
}
#[test]
fn use_state_keyed_allocates_one_string_per_call() {
let _guard = enter_perf_test();
use slt::TestBackend;
const N: usize = 100;
let mut tb = TestBackend::new(20, 3);
let pre_keys: Vec<String> = (0..N).map(|i| format!("k-{i}")).collect();
for _ in 0..3 {
tb.render(|ui| {
for k in &pre_keys {
let _ = ui.use_state_keyed(k.clone(), || 0i32);
}
});
}
let (_, baseline) = measure_allocs("use_state_keyed_baseline_empty_frame", || {
tb.render(|ui| {
let _ = ui.use_state_keyed(String::from("baseline-anchor"), || 0i32);
});
});
let (_, with_calls) = measure_allocs("use_state_keyed_n_cache_hit_calls", || {
tb.render(|ui| {
let _ = ui.use_state_keyed(String::from("baseline-anchor"), || 0i32);
for k in &pre_keys {
let _ = ui.use_state_keyed(k.clone(), || 0i32);
}
});
});
let delta = with_calls.saturating_sub(baseline);
eprintln!(
"use_state_keyed: baseline = {}, with {} calls = {}, delta = {}",
baseline, N, with_calls, delta
);
let budget = (N * 3) / 2;
assert!(
delta <= budget,
"use_state_keyed alloc regression: {} cache-hit calls added {} allocations over baseline (budget {}); pre-fix double-clone would add >= {}",
N,
delta,
budget,
N * 2
);
}
#[test]
fn use_state_keyed_cache_hit_scales_one_per_call() {
let _guard = enter_perf_test();
use slt::TestBackend;
const SMALL_N: usize = 10;
const LARGE_N: usize = 100;
let mut tb = TestBackend::new(20, 3);
let pre_keys: Vec<String> = (0..LARGE_N).map(|i| format!("scale-k-{i}")).collect();
for _ in 0..3 {
tb.render(|ui| {
for k in &pre_keys {
let _ = ui.use_state_keyed(k.clone(), || 0i32);
}
});
}
let (_, with_small) = measure_allocs("use_state_keyed_scale_small", || {
tb.render(|ui| {
for k in &pre_keys[..SMALL_N] {
let _ = ui.use_state_keyed(k.clone(), || 0i32);
}
});
});
let (_, with_large) = measure_allocs("use_state_keyed_scale_large", || {
tb.render(|ui| {
for k in &pre_keys[..LARGE_N] {
let _ = ui.use_state_keyed(k.clone(), || 0i32);
}
});
});
let extra_calls = LARGE_N - SMALL_N;
let extra_allocs = with_large.saturating_sub(with_small);
eprintln!(
"use_state_keyed marginal allocs for +{} calls = {} ({} = {}, {} = {})",
extra_calls, extra_allocs, SMALL_N, with_small, LARGE_N, with_large
);
let budget = (extra_calls * 3) / 2;
assert!(
extra_allocs <= budget,
"use_state_keyed marginal-cost regression: {} extra cache-hit calls added {} allocations (budget {}); pre-fix double-clone would add >= {}",
extra_calls,
extra_allocs,
budget,
extra_calls * 2
);
}
#[test]
fn kitty_flush_resize_reemits() {
let _guard = enter_perf_test();
let mut fx = slt::__bench_new_kitty_fixture(3);
let mut sink: Vec<u8> = Vec::new();
fx.flush_inline(&mut sink, 10).unwrap();
let after_first = sink.len();
assert!(
after_first > 0,
"first flush at row_offset=10 must emit placements (sink_len={after_first})"
);
sink.clear();
fx.flush_inline(&mut sink, 10).unwrap();
assert_eq!(
sink.len(),
0,
"second flush at same row_offset=10 must hit fast-path and emit no bytes"
);
sink.clear();
fx.flush_inline(&mut sink, 15).unwrap();
assert!(
!sink.is_empty(),
"third flush at row_offset=15 (resize) must re-emit placements (sink_len={})",
sink.len()
);
sink.clear();
fx.flush_inline(&mut sink, 15).unwrap();
assert_eq!(
sink.len(),
0,
"fourth flush at same row_offset=15 must return to fast-path with no bytes"
);
}
#[test]
fn framestate_reuse_buffers_restored_on_error_boundary_panic() {
let _guard = enter_perf_test();
use slt::TestBackend;
let mut tb = TestBackend::new(80, 12);
tb.render(|ui| {
let _ = ui.col(|ui| {
ui.text("normal frame");
});
});
tb.render(|ui| {
ui.error_boundary_with(
|ui| {
let _ = ui.group("transient-group").col(|ui| {
ui.text("inside-transient-group-text");
panic!("simulated child panic");
});
},
|ui, msg| {
ui.text(format!("recovered: {msg}"));
},
);
ui.text("sibling-after-boundary");
});
let dump = tb.to_string_trimmed();
assert!(
dump.contains("recovered: simulated child panic"),
"fallback must render after panic, got:\n{dump}"
);
assert!(
!dump.contains("inside-transient-group-text"),
"rolled-back child commands must not render: \n{dump}"
);
assert!(
dump.contains("sibling-after-boundary"),
"sibling text after boundary must render normally, got:\n{dump}"
);
tb.render(|ui| {
let _ = ui.col(|ui| {
ui.text("post-recovery-frame");
});
});
tb.assert_contains("post-recovery-frame");
}