use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use abstracttui::base::{Rgba, Size};
use abstracttui::render::{Cell as RenderCell, FrameDiff, PresentCaps, Presenter, Style, Surface};
use abstracttui::testing::VtScreen;
thread_local! {
static TL_ALLOCS: Cell<u64> = const { Cell::new(0) };
static TL_REALLOCS: Cell<u64> = const { Cell::new(0) };
static TL_BYTES: Cell<u64> = const { Cell::new(0) };
}
struct CountingAlloc;
impl CountingAlloc {
fn snapshot(&self) -> (u64, u64, u64) {
(
TL_ALLOCS.try_with(Cell::get).unwrap_or(0),
TL_REALLOCS.try_with(Cell::get).unwrap_or(0),
TL_BYTES.try_with(Cell::get).unwrap_or(0),
)
}
}
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let _ = TL_ALLOCS.try_with(|c| c.set(c.get() + 1));
let _ = TL_BYTES.try_with(|c| c.set(c.get() + layout.size() as u64));
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout)
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let _ = TL_REALLOCS.try_with(|c| c.set(c.get() + 1));
let _ =
TL_BYTES.try_with(|c| c.set(c.get() + new_size.saturating_sub(layout.size()) as u64));
System.realloc(ptr, layout, new_size)
}
}
#[global_allocator]
static ALLOC: CountingAlloc = CountingAlloc;
static SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn serial() -> std::sync::MutexGuard<'static, ()> {
SERIAL.lock().unwrap_or_else(|poison| poison.into_inner())
}
fn alloc_delta(f: impl FnOnce()) -> (u64, u64, u64) {
let before = ALLOC.snapshot();
f();
let after = ALLOC.snapshot();
(after.0 - before.0, after.1 - before.1, after.2 - before.2)
}
#[test]
fn allocator_counts_and_measures_deltas() {
let _serial = serial();
let (a, _, bytes) = alloc_delta(|| {
let v: Vec<u64> = Vec::with_capacity(100);
std::hint::black_box(&v);
});
assert!(a >= 1, "one Vec allocation must be visible");
assert!(bytes >= 800, "100 u64s = at least 800 bytes, saw {bytes}");
let (a2, r2, _) = alloc_delta(|| {
std::hint::black_box(42u64);
});
assert_eq!((a2, r2), (0, 0), "an empty region must measure zero");
}
fn styled_frame(size: Size, tick: u8) -> Surface {
let mut s = Surface::new(size, RenderCell::default());
for y in 0..size.h {
let style = Style::new().fg(Rgba::rgb(tick, (y * 4) as u8, 255 - tick));
for chunk in 0..(size.w / 20).max(1) {
s.draw_text(chunk * 20, y, "abcdefghij0123456789", style);
}
}
s
}
#[test]
fn diff_present_steady_state_allocates_nothing() {
let _serial = serial();
let (d, p) = measure_stages();
eprintln!(
"alloc attribution: diff = {} allocs/{} reallocs/{} B; present = {} allocs/{} reallocs/{} B",
d.0, d.1, d.2, p.0, p.1, p.2
);
assert_eq!(
(d.0, d.1),
(0, 0),
"steady-state DIFF allocated: {} allocs / {} reallocs / {} bytes",
d.0,
d.1,
d.2
);
assert_eq!(
(p.0, p.1),
(0, 0),
"steady-state PRESENT allocated: {} allocs / {} reallocs / {} bytes",
p.0,
p.1,
p.2
);
}
fn measure_stages() -> ((u64, u64, u64), (u64, u64, u64)) {
let size = Size::new(200, 60);
let caps = PresentCaps::FULL;
let mut diff = FrameDiff::new();
let mut presenter = Presenter::new();
let mut out: Vec<u8> = Vec::new();
let frames: Vec<Surface> = (0..6).map(|i| styled_frame(size, i * 40)).collect();
for w in [0usize, 1, 2] {
let runs = diff.compute_full(&frames[w], &frames[w + 1]);
out.clear();
presenter.emit(runs, &frames[w + 1], &caps, &mut out);
}
let prev = &frames[3];
let next = &frames[4];
let mut d = (0, 0, 0);
let mut runs_len = 0;
let d1 = alloc_delta(|| {
let runs = diff.compute_full(prev, next);
runs_len = runs.len();
});
d.0 += d1.0;
d.1 += d1.1;
d.2 += d1.2;
let runs = diff.compute_full(prev, next);
out.clear();
let p = alloc_delta(|| {
presenter.emit(runs, next, &caps, &mut out);
});
assert!(runs_len > 0, "the measured frames really did change");
(d, p)
}
#[test]
fn presenter_no_change_frame_emits_and_allocates_nothing() {
let _serial = serial();
let size = Size::new(80, 24);
let frame = styled_frame(size, 7);
let caps = PresentCaps::FULL;
let mut diff = FrameDiff::new();
let mut presenter = Presenter::new();
let mut out: Vec<u8> = Vec::new();
let runs = diff.compute_full(&frame, &frame);
presenter.emit(runs, &frame, &caps, &mut out);
out.clear();
let (allocs, reallocs, _) = alloc_delta(|| {
let runs = diff.compute_full(&frame, &frame);
presenter.emit(runs, &frame, &caps, &mut out);
});
assert_eq!((allocs, reallocs), (0, 0), "identical frames must be free");
assert!(out.is_empty(), "identical frames must emit zero bytes");
}
#[test]
fn vt_model_feed_allocation_is_bounded() {
let _serial = serial();
let mut screen = VtScreen::new(Size::new(200, 60));
let mut frame_bytes = Vec::new();
for y in 1..=60 {
frame_bytes.extend_from_slice(format!("\x1b[{y};1H\x1b[38;2;1;2;3m").as_bytes());
frame_bytes.extend_from_slice("x".repeat(200).as_bytes());
}
screen.feed(&frame_bytes); let (allocs, _, bytes) = alloc_delta(|| {
screen.feed(&frame_bytes);
});
let cells = 200 * 60;
let rows = 60;
assert!(
allocs <= 8 * rows,
"VT model allocation blew up: {allocs} allocs / {bytes} bytes for \
{cells} cells in {rows} rows (budget {} = 8/row)",
8 * rows
);
}
#[test]
fn jpeg_dimension_bomb_allocates_within_budget() {
let _serial = serial();
use abstracttui::gfx::jpeg;
use abstracttui::testing::jpeg_build::FlatJpeg;
let mut bytes = FlatJpeg::grayscale(16, 16).build();
let sof = bytes
.windows(2)
.position(|w| w[0] == 0xFF && (w[1] == 0xC0 || w[1] == 0xC1))
.expect("SOF present");
for i in 0..4 {
bytes[sof + 5 + i] = 0xFF;
}
let (allocs, _, alloc_bytes) = alloc_delta(|| {
let r = jpeg::decode(&bytes);
assert!(r.is_err(), "dimension bomb must be rejected");
std::hint::black_box(&r);
});
assert!(
alloc_bytes < 64 * 1024,
"dimension-bomb rejection allocated {alloc_bytes} bytes in {allocs} allocs — guard fired too late"
);
}
#[test]
fn gltf_animation_sampling_is_allocation_free_per_frame() {
let _serial = serial();
use abstracttui::three::animation::{Animation, Interpolation, NodePose, Track, TrackValues};
use abstracttui::three::Vec3;
let mut tracks = Vec::new();
for node in 0..4 {
let times: Vec<f32> = (0..8).map(|k| k as f32 * 0.5).collect();
tracks.push(Track {
node,
times: times.clone(),
values: TrackValues::Translation(
(0..8).map(|k| [k as f32, node as f32, 0.0]).collect(),
),
interpolation: Interpolation::Linear,
});
tracks.push(Track {
node,
times: times.clone(),
values: TrackValues::Rotation((0..8).map(|_| [0.0, 0.0, 0.0, 1.0]).collect()),
interpolation: Interpolation::Linear,
});
}
let anim = Animation::new(None, tracks);
let rest = NodePose {
translation: Vec3::ZERO,
rotation: [0.0, 0.0, 0.0, 1.0],
scale: Vec3::new(1.0, 1.0, 1.0),
};
let mut poses = vec![rest; 4];
anim.sample(1.0, &mut poses);
let (allocs, reallocs, _) = alloc_delta(|| {
for i in 0..240 {
let t = (i as f32) * 0.01;
anim.sample(t, &mut poses);
}
});
assert_eq!(
(allocs, reallocs),
(0, 0),
"animation sampling allocated on the hot path: {allocs} allocs, {reallocs} reallocs over 240 frames"
);
}
#[test]
fn idle_turns_with_feed_interval_parked_popup_and_parked_image_allocate_nothing() {
use abstracttui::app::{App, Driver, RunConfig};
use abstracttui::prelude::*;
use abstracttui::reactive::interval;
use abstracttui::testing::CaptureTerm;
use abstracttui::ui::text;
use abstracttui::widgets::{Feed, FeedItem, FeedState};
use std::time::Duration;
let _serial = serial();
let size = Size::new(60, 16);
let mut term = CaptureTerm::new(size);
let mut app = App::new(size);
let start = std::time::Instant::now();
app.mount(|cx| {
let feed = FeedState::new(cx);
for i in 0..8 {
feed.push(
format!("h{i}"),
FeedItem::markdown(format!("**msg {i}** body")),
);
}
feed.push_stream("live");
feed.stream_append("live", "streaming answer paused mid-");
let ticks = cx.signal(0u32);
interval(cx, Duration::from_secs(3600), move || {
ticks.update(|t| *t += 1);
});
let follow = cx.signal(true);
Element::new()
.style(LayoutStyle::column())
.child(
Select::new(vec![
SelectOption::new("stable"),
SelectOption::new("beta"),
SelectOption::new("nightly"),
])
.layout(LayoutStyle::default().w(20).h(1).shrink(0.0))
.view(cx),
)
.child(
Element::new()
.style(LayoutStyle::column().grow(1.0))
.child(
Scroll::new(Feed::new(&feed).view(cx))
.follow_tail(follow)
.view(cx),
)
.build(),
)
.child(text(" status"))
.build()
})
.expect("mount");
let cfg = RunConfig {
caps: Some(abstracttui::term::Capabilities::with(|c| {
c.truecolor = true;
c.colors_256 = true;
c.kitty_graphics = true;
})),
enter: None,
probe: false,
..RunConfig::default()
};
let mut driver = Driver::new(&mut app, &mut term, cfg).expect("driver");
let now = std::rc::Rc::new(std::cell::Cell::new(start));
let clock = now.clone();
driver.set_clock(move || clock.get());
let overlays = app.overlays();
let _img = overlays.image(
Rect::new(44, 2, 12, 6),
abstracttui::gfx::Bitmap::new(16, 12, Rgba::rgb(200, 40, 40)),
);
for _ in 0..64 {
if driver.turn(&mut app, &mut term).expect("turn").idle {
break;
}
}
term.push_input(b"\t\r");
for _ in 0..64 {
if driver.turn(&mut app, &mut term).expect("turn").idle {
break;
}
}
assert!(
term.screen().to_text().contains("nightly"),
"precondition: the popup is open and parked:\n{}",
term.screen().to_text()
);
let setup_bytes = term.take_bytes();
assert!(
setup_bytes.windows(3).any(|w| w == b"\x1b_G"),
"precondition: the image went through the kitty byte channel"
);
let (allocs, reallocs, bytes) = alloc_delta(|| {
for _ in 0..16 {
let turn = driver.turn(&mut app, &mut term).expect("idle turn");
assert!(turn.idle, "turn must report idle");
assert!(!turn.rendered, "idle turn rendered");
}
});
assert_eq!(
(allocs, reallocs),
(0, 0),
"idle turns allocated with the new mounts parked: \
{allocs} allocs / {reallocs} reallocs / {bytes} B over 16 turns"
);
assert!(term.bytes().is_empty(), "idle turns wrote bytes");
}
#[test]
fn idle_turns_with_parked_meter_scope_and_key_state_allocate_nothing() {
use abstracttui::app::{use_key_state, App, Driver, PushToTalk, RunConfig};
use abstracttui::prelude::*;
use abstracttui::testing::CaptureTerm;
use abstracttui::ui::text;
use std::time::Duration;
let _serial = serial();
let size = Size::new(60, 12);
let mut term = CaptureTerm::new(size);
let mut app = App::new(size);
let level_slot: std::rc::Rc<Cell<Option<abstracttui::reactive::Signal<f32>>>> =
std::rc::Rc::new(Cell::new(None));
let level_out = level_slot.clone();
app.mount(move |cx| {
let _keys = use_key_state(cx); let _ptt = PushToTalk::bind(cx, KeyChord::plain(Key::Char(' ')));
let level = cx.signal(0.0f32);
level_out.set(Some(level));
let window = cx.signal(vec![0.2f32, 0.6, 0.4, 0.1]);
Element::new()
.style(LayoutStyle::column())
.child(
Meter::new(level)
.decay(240.0)
.peak_hold(Duration::from_millis(50))
.view(cx),
)
.child(AudioScope::new(window).range(0.0, 1.0).view(cx))
.child(text(" status"))
.build()
})
.expect("mount");
let cfg = RunConfig {
caps: Some(abstracttui::term::Capabilities::with(|c| {
c.truecolor = true;
c.colors_256 = true;
c.kitty_keyboard = true; })),
enter: None,
probe: false,
..RunConfig::default()
};
let mut driver = Driver::new(&mut app, &mut term, cfg).expect("driver");
let start = std::time::Instant::now();
let now = std::rc::Rc::new(Cell::new(start));
let clock = now.clone();
driver.set_clock(move || clock.get());
for _ in 0..64 {
if driver.turn(&mut app, &mut term).expect("turn").idle {
break;
}
}
term.push_input(b"\x1b[32u"); driver.turn(&mut app, &mut term).expect("turn");
let level = level_slot.get().expect("level signal");
level.set(0.85);
driver.turn(&mut app, &mut term).expect("turn");
term.push_input(b"\x1b[32;1:3u"); driver.turn(&mut app, &mut term).expect("turn");
level.set(0.0); let mut parked = false;
for _ in 0..240 {
now.set(now.get() + Duration::from_millis(16));
if driver.turn(&mut app, &mut term).expect("turn").idle {
parked = true;
break;
}
}
assert!(parked, "precondition: the meter decayed to its fixpoint");
let _ = term.take_bytes();
let (allocs, reallocs, bytes) = alloc_delta(|| {
for _ in 0..16 {
now.set(now.get() + Duration::from_millis(16));
let turn = driver.turn(&mut app, &mut term).expect("idle turn");
assert!(turn.idle, "turn must report idle");
assert!(!turn.rendered, "idle turn rendered");
}
});
assert_eq!(
(allocs, reallocs),
(0, 0),
"idle turns allocated with the voice surfaces parked: \
{allocs} allocs / {reallocs} reallocs / {bytes} B over 16 turns"
);
assert!(term.bytes().is_empty(), "idle turns wrote bytes");
}
#[test]
fn dot_canvas_stroke_and_blit_paths_allocate_nothing() {
use abstracttui::base::{Point, Rect};
use abstracttui::canvas::{fill_h, fill_v, DotCanvas};
use abstracttui::ui::BufferCanvas;
let _serial = serial();
let mut dots = DotCanvas::braille(40, 12); let mut out = BufferCanvas::new(Size::new(40, 12));
let ink = Rgba::rgb(240, 170, 40);
let (allocs, reallocs, bytes) = alloc_delta(|| {
for frame in 0..8 {
dots.clear_all(); dots.line((0, frame), (79, 47 - frame));
dots.polyline(&[(0, 40), (20, 8), (40, 30), (79, 2)]);
dots.bezier_quad((0.0, 47.0), (40.0, -20.0), (79.0, 47.0), 0.25);
dots.bezier_cubic((0.0, 2.0), (30.0, 60.0), (50.0, -12.0), (79.0, 40.0), 0.25);
dots.ellipse_arc((40.0, 24.0), 30.0, 18.0, 0.0, std::f32::consts::TAU);
dots.line((-1_000_000, 24), (1_000_000, 24));
dots.blit(&mut out, Point::new(0, 0), ink);
fill_v(
&mut out,
Rect::new(0, 0, 4, 12),
0.6,
ink,
Rgba::TRANSPARENT,
);
fill_h(
&mut out,
Rect::new(0, 11, 40, 1),
0.4,
ink,
Rgba::TRANSPARENT,
);
}
std::hint::black_box(&out);
});
assert_eq!(
(allocs, reallocs),
(0, 0),
"the stroke/blit steady state allocated: \
{allocs} allocs / {reallocs} reallocs / {bytes} B over 8 frames"
);
}
#[test]
fn jpeg_hostile_corpus_allocation_is_bounded() {
let _serial = serial();
use abstracttui::gfx::jpeg;
use abstracttui::testing::jpeg_build::FlatJpeg;
let base = FlatJpeg::grayscale(16, 16).with_flat_code_len(16).build();
let (allocs, _, alloc_bytes) = alloc_delta(|| {
for cut in (2..base.len()).step_by(3) {
let _ = std::hint::black_box(jpeg::decode(&base[..cut]));
}
});
let attempts = (base.len() - 2).div_ceil(3);
assert!(
alloc_bytes < attempts as u64 * 4 * 1024,
"hostile-corpus decode allocated {alloc_bytes} bytes over {attempts} attempts ({allocs} allocs)"
);
}