use std::collections::VecDeque;
use std::error::Error;
use std::fmt;
use std::io;
use taria::wire::MAX_LINE_BYTES;
use taria::{Action, Node, NodeId, Role};
const SNAPSHOT_ENVELOPE_BYTES: usize = concat!(
r#"{"type":"snapshot","protocol_version":1,"seq":18446744073709551615,"#,
r#""after_input":18446744073709551615,"root":}"#
)
.len();
pub(crate) const ROOT_BUDGET: usize = MAX_LINE_BYTES - SNAPSHOT_ENVELOPE_BYTES;
const STRING_FLOOR: usize = 256;
const MARKER: &str = "\u{2026}";
const MARKER_BYTES: usize = MARKER.len();
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct SnapshotTooLarge {
bytes: usize,
limit: usize,
strings_trimmed: usize,
nodes_dropped: usize,
largest: NodeId,
}
impl SnapshotTooLarge {
pub(crate) fn new(bounded: Bounded) -> Self {
Self {
bytes: bounded.root_bytes.saturating_add(SNAPSHOT_ENVELOPE_BYTES),
limit: MAX_LINE_BYTES,
strings_trimmed: bounded.strings_trimmed,
nodes_dropped: bounded.nodes_dropped,
largest: bounded.largest,
}
}
pub fn bytes(&self) -> usize {
self.bytes
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn strings_trimmed(&self) -> usize {
self.strings_trimmed
}
pub fn nodes_dropped(&self) -> usize {
self.nodes_dropped
}
pub fn largest(&self) -> &NodeId {
&self.largest
}
}
impl fmt::Display for SnapshotTooLarge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"snapshot is {} bytes with its longest string at node `{}`, over the {} byte line \
limit for a taria snapshot; {} string(s) trimmed and {} node(s) dropped to fit",
self.bytes, self.largest.0, self.limit, self.strings_trimmed, self.nodes_dropped
)
}
}
impl Error for SnapshotTooLarge {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Bounded {
pub(crate) root_bytes: usize,
pub(crate) strings_trimmed: usize,
pub(crate) nodes_dropped: usize,
pub(crate) largest: NodeId,
}
#[cfg(test)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct Runs {
pub(crate) measured: usize,
pub(crate) cut: usize,
pub(crate) bare: usize,
}
#[cfg(test)]
thread_local! {
static RUNS: std::cell::Cell<Runs> = const {
std::cell::Cell::new(Runs { measured: 0, cut: 0, bare: 0 })
};
}
#[cfg(test)]
pub(crate) fn runs() -> Runs {
RUNS.with(std::cell::Cell::get)
}
#[cfg(test)]
fn count(update: impl FnOnce(&mut Runs)) {
RUNS.with(|runs| {
let mut now = runs.get();
update(&mut now);
runs.set(now);
});
}
pub(crate) fn measure_root(root: &Node) -> Option<usize> {
#[cfg(test)]
count(|runs| runs.measured += 1);
measure(root)
}
pub(crate) fn cut_to_fit(root: &mut Node, budget: usize, before: Option<usize>) -> Bounded {
#[cfg(test)]
count(|runs| runs.cut += 1);
let mut survey = Survey::of(root);
let Some(before) = before else {
return bare_root(root, &survey, usize::MAX);
};
let Some(framing) = Framing::measured() else {
return bare_root(root, &survey, before);
};
let Some(mut slots) = flatten(root) else {
return bare_root(root, &survey, before);
};
let Some(unfit) = mark_reach(&mut slots, framing, budget) else {
return bare_root(root, &survey, before);
};
let mut now = before;
let mut flattened = Some(slots);
if unfit > 0 {
if let Some(slots) = flattened.take() {
let fits: Vec<bool> = slots.iter().map(|slot| slot.fits).collect();
keep_only(root, &slots, &fits);
}
let Some(after) = measure(root) else {
return bare_root(root, &survey, before);
};
now = after;
survey.recount_strings(root);
}
let (strings_trimmed, nodes_dropped) = if now <= budget {
(0, unfit)
} else {
match cut_strings(root, &mut survey, now - budget) {
Some(strings) => (strings, unfit),
None => match drop_to_fit(root, flattened, framing, budget, &mut survey) {
Some((strings, dropped)) => (strings, unfit.saturating_add(dropped)),
None => return bare_root(root, &survey, before),
},
}
};
match measure(root) {
Some(after) if after <= budget => Bounded {
root_bytes: before,
strings_trimmed,
nodes_dropped,
largest: survey.largest,
},
_ => bare_root(root, &survey, before),
}
}
fn cut_strings(root: &mut Node, survey: &mut Survey, excess: usize) -> Option<usize> {
if let Some(level) = water_level(&mut survey.values, excess) {
return Some(trim_strings(root, level, Strings::Values));
}
let mut both = survey.values.clone();
both.extend_from_slice(&survey.labels);
let level = water_level(&mut both, excess)?;
Some(trim_strings(root, level, Strings::ValuesAndLabels))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Strings {
Values,
ValuesAndLabels,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Keep {
Head,
Tail,
}
fn keep_for(role: Role) -> Keep {
if matches!(role, Role::Log | Role::Terminal) {
Keep::Tail
} else {
Keep::Head
}
}
fn byte_cost(byte: u8) -> usize {
match byte {
b'"' | b'\\' | b'\x08' | b'\t' | b'\n' | b'\x0c' | b'\r' => 2,
0x00..=0x1f => 6,
_ => 1,
}
}
fn char_cost(c: char) -> usize {
if c.is_ascii() {
byte_cost(c as u8)
} else {
c.len_utf8()
}
}
fn escaped_len(s: &str) -> usize {
s.bytes()
.fold(0usize, |len, byte| len.saturating_add(byte_cost(byte)))
}
struct ByteCount(usize);
impl io::Write for ByteCount {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0 = self.0.saturating_add(buf.len());
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn measure(node: &Node) -> Option<usize> {
let mut count = ByteCount(0);
serde_json::to_writer(&mut count, node).ok()?;
Some(count.0)
}
fn measure_own_at_floor(node: &mut Node) -> Option<usize> {
let children = std::mem::take(&mut node.children);
let own = measure(node);
node.children = children;
let keep = keep_for(node.role);
let saving = [
(&node.value, keep),
(&node.description, Keep::Head),
(&node.label, Keep::Head),
]
.into_iter()
.filter_map(|(text, keep)| Some(floor_saving(text.as_deref()?, keep)))
.fold(0usize, usize::saturating_add);
own?.checked_sub(saving)
}
struct Survey {
values: Vec<usize>,
labels: Vec<usize>,
largest: NodeId,
nodes: usize,
root_over_floor: usize,
}
impl Survey {
fn of(root: &Node) -> Self {
let over_floor =
|text: Option<&str>| text.is_some_and(|text| escaped_len(text) > STRING_FLOOR);
let mut values = Vec::new();
let mut labels = Vec::new();
let mut largest = (0, &root.id);
let mut nodes = 0usize;
let mut pending = vec![root];
while let Some(node) = pending.pop() {
nodes = nodes.saturating_add(1);
let fixed = node.actions.iter().filter_map(|action| match action {
Action::Custom(name) => Some(name.as_str()),
_ => None,
});
for text in std::iter::once(node.id.0.as_str()).chain(fixed) {
let len = escaped_len(text);
if len > largest.0 {
largest = (len, &node.id);
}
}
let strings = [
(&node.value, false),
(&node.description, false),
(&node.label, true),
];
for (text, is_label) in strings {
let Some(text) = text else { continue };
let lengths = if is_label { &mut labels } else { &mut values };
let len = escaped_len(text);
if len > largest.0 {
largest = (len, &node.id);
}
if len > STRING_FLOOR {
lengths.push(len);
}
}
pending.extend(node.children.iter().rev());
}
Self {
values,
labels,
largest: largest.1.clone(),
nodes,
root_over_floor: usize::from(over_floor(root.label.as_deref()))
+ usize::from(over_floor(root.value.as_deref()))
+ usize::from(over_floor(root.description.as_deref())),
}
}
fn recount_strings(&mut self, root: &Node) {
let rest = Self::of(root);
self.values = rest.values;
self.labels = rest.labels;
}
}
fn water_level(lengths: &mut [usize], excess: usize) -> Option<usize> {
lengths.sort_unstable_by(|a, b| b.cmp(a));
let mut sum = 0usize;
for (index, &len) in lengths.iter().enumerate() {
if len <= STRING_FLOOR {
break;
}
sum = sum.saturating_add(len);
let count = index + 1;
let below = lengths.get(count).copied().unwrap_or(0).max(STRING_FLOOR);
if sum.saturating_sub(below.saturating_mul(count)) >= excess {
return Some(sum.saturating_sub(excess) / count);
}
}
None
}
fn trim_strings(root: &mut Node, level: usize, which: Strings) -> usize {
let mut trimmed = 0usize;
let mut pending = vec![root];
while let Some(node) = pending.pop() {
let keep = keep_for(node.role);
if let Some(value) = node.value.as_mut()
&& escaped_len(value) > level
{
trim_to(value, level, keep);
trimmed = trimmed.saturating_add(1);
}
if let Some(description) = node.description.as_mut()
&& escaped_len(description) > level
{
trim_to(description, level, Keep::Head);
trimmed = trimmed.saturating_add(1);
}
if which == Strings::ValuesAndLabels
&& let Some(label) = node.label.as_mut()
&& escaped_len(label) > level
{
trim_to(label, level, Keep::Head);
trimmed = trimmed.saturating_add(1);
}
pending.extend(node.children.iter_mut());
}
trimmed
}
fn trim_to_floor(node: &mut Node) -> usize {
let keep = keep_for(node.role);
let mut trimmed = 0;
if let Some(value) = node.value.as_mut()
&& escaped_len(value) > STRING_FLOOR
{
trim_to(value, STRING_FLOOR, keep);
trimmed += 1;
}
if let Some(description) = node.description.as_mut()
&& escaped_len(description) > STRING_FLOOR
{
trim_to(description, STRING_FLOOR, Keep::Head);
trimmed += 1;
}
if let Some(label) = node.label.as_mut()
&& escaped_len(label) > STRING_FLOOR
{
trim_to(label, STRING_FLOOR, Keep::Head);
trimmed += 1;
}
trimmed
}
fn trim_to(s: &mut String, target: usize, keep: Keep) {
let (at, _) = cut_point(s, target, keep);
match keep {
Keep::Head => {
s.truncate(at);
s.push_str(MARKER);
}
Keep::Tail => s.replace_range(..at, MARKER),
}
}
fn cut_point(s: &str, target: usize, keep: Keep) -> (usize, usize) {
let room = target.saturating_sub(MARKER_BYTES);
let mut used = 0usize;
let mut take = |c: char| {
let next = used.saturating_add(char_cost(c));
if next > room {
return false;
}
used = next;
true
};
let at = match keep {
Keep::Head => s
.char_indices()
.find(|&(_, c)| !take(c))
.map_or(s.len(), |(at, _)| at),
Keep::Tail => s
.char_indices()
.rev()
.find(|&(_, c)| !take(c))
.map_or(0, |(at, c)| at + c.len_utf8()),
};
(at, used)
}
fn floor_saving(s: &str, keep: Keep) -> usize {
let len = escaped_len(s);
if len <= STRING_FLOOR {
return 0;
}
let (_, kept) = cut_point(s, STRING_FLOOR, keep);
len.saturating_sub(kept.saturating_add(MARKER_BYTES))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Framing {
list: usize,
comma: usize,
}
impl Framing {
fn measured() -> Option<Self> {
let leaf = Node::new("", Role::Other);
let alone = measure(&leaf)?;
let one = measure(&leaf.clone().child(leaf.clone()))?;
let two = measure(&leaf.clone().children([leaf.clone(), leaf.clone()]))?;
Some(Self {
list: one.checked_sub(alone.checked_mul(2)?)?,
comma: two.checked_sub(one.checked_add(alone)?)?,
})
}
fn around(self, kept: usize) -> usize {
match kept {
0 => 0,
_ => self
.list
.saturating_add(self.comma.saturating_mul(kept - 1)),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
struct Slot {
parent: usize,
own: usize,
first_child: usize,
children: usize,
rank: usize,
reach: usize,
fits: bool,
}
fn drop_to_fit(
root: &mut Node,
slots: Option<Vec<Slot>>,
framing: Framing,
budget: usize,
survey: &mut Survey,
) -> Option<(usize, usize)> {
let slots = match slots {
Some(slots) => slots,
None => flatten(root)?,
};
let widest = slots.iter().map(|slot| slot.children).max().unwrap_or(0);
let mut scratch = vec![0usize; slots.len()];
let mut size = |cap| size_at_cap(&slots, framing, cap, &mut scratch);
let mut fits = 0;
let mut used = size(0);
if used > budget {
return None;
}
let all = size(widest);
if all <= budget {
fits = widest;
used = all;
} else {
let mut over = widest;
while over - fits > 1 {
let mid = fits + (over - fits) / 2;
let bytes = size(mid);
if bytes <= budget {
fits = mid;
used = bytes;
} else {
over = mid;
}
}
}
let kept = refill(&slots, framing, fits, budget, used);
keep_only(root, &slots, &kept);
let published = kept.iter().filter(|kept| **kept).count();
let dropped = slots.len().saturating_sub(published);
let now = measure(root)?;
if now <= budget {
return Some((0, dropped));
}
survey.recount_strings(root);
let strings = cut_strings(root, survey, now - budget)
.unwrap_or_else(|| trim_strings(root, STRING_FLOOR, Strings::ValuesAndLabels));
Some((strings, dropped))
}
fn flatten(root: &mut Node) -> Option<Vec<Slot>> {
let mut slots = Vec::new();
let mut queue: VecDeque<(&mut Node, usize, usize)> = VecDeque::from([(root, 0, 0)]);
while let Some((node, parent, rank)) = queue.pop_front() {
let index = slots.len();
let own = measure_own_at_floor(node)?;
slots.push(Slot {
parent,
own,
first_child: index.saturating_add(queue.len()).saturating_add(1),
children: node.children.len(),
rank,
..Slot::default()
});
queue.extend(
node.children
.iter_mut()
.enumerate()
.map(|(rank, child)| (child, index, rank)),
);
}
Some(slots)
}
fn mark_reach(slots: &mut [Slot], framing: Framing, budget: usize) -> Option<usize> {
let root = slots.first_mut()?;
root.reach = root.own;
root.fits = root.reach <= budget;
if !root.fits {
return None;
}
let mut unfit = 0usize;
for index in 0..slots.len() {
let Some(&parent) = slots.get(index) else {
break;
};
let range = parent.first_child..parent.first_child.saturating_add(parent.children);
let Some(children) = slots.get_mut(range) else {
continue;
};
for child in children {
child.reach = parent
.reach
.saturating_add(framing.list)
.saturating_add(child.own);
child.fits = parent.fits && child.reach <= budget;
if !child.fits {
unfit = unfit.saturating_add(1);
}
}
}
Some(unfit)
}
fn size_at_cap(slots: &[Slot], framing: Framing, cap: usize, kept_bytes: &mut [usize]) -> usize {
kept_bytes.fill(0);
for (index, slot) in slots.iter().enumerate().rev() {
let bytes = slot
.own
.saturating_add(framing.around(slot.children.min(cap)))
.saturating_add(kept_bytes.get(index).copied().unwrap_or(0));
if index == 0 {
return bytes;
}
if slot.rank < cap
&& let Some(parent) = kept_bytes.get_mut(slot.parent)
{
*parent = parent.saturating_add(bytes);
}
}
0
}
fn refill(
slots: &[Slot],
framing: Framing,
cap: usize,
budget: usize,
mut used: usize,
) -> Vec<bool> {
let mut kept = vec![false; slots.len()];
let mut kept_children = vec![0usize; slots.len()];
let mut closed = vec![false; slots.len()];
if let Some(root) = kept.first_mut() {
*root = true;
}
let is_kept = |kept: &[bool], index: usize| kept.get(index).copied().unwrap_or(false);
for (index, slot) in slots.iter().enumerate().skip(1) {
if slot.rank < cap && is_kept(&kept, slot.parent) {
if let Some(entry) = kept.get_mut(index) {
*entry = true;
}
if let Some(count) = kept_children.get_mut(slot.parent) {
*count += 1;
}
}
}
for (index, slot) in slots.iter().enumerate().skip(1) {
if is_kept(&kept, index)
|| !is_kept(&kept, slot.parent)
|| closed.get(slot.parent).copied().unwrap_or(true)
{
continue;
}
let Some(siblings) = kept_children.get_mut(slot.parent) else {
continue;
};
let frame = if *siblings == 0 {
framing.list
} else {
framing.comma
};
let cost = slot.own.saturating_add(frame);
if used.saturating_add(cost) <= budget {
used = used.saturating_add(cost);
*siblings += 1;
if let Some(entry) = kept.get_mut(index) {
*entry = true;
}
} else if let Some(parent) = closed.get_mut(slot.parent) {
*parent = true;
}
}
kept
}
fn keep_only(root: &mut Node, slots: &[Slot], kept: &[bool]) {
let mut pending = vec![(root, 0usize)];
while let Some((node, index)) = pending.pop() {
let Some(slot) = slots.get(index) else {
continue;
};
let first = slot.first_child;
let mut at = first;
node.children.retain(|_| {
let keep = kept.get(at).copied().unwrap_or(false);
at += 1;
keep
});
let kept_slots = (first..first.saturating_add(slot.children))
.filter(|&child| kept.get(child).copied().unwrap_or(false));
pending.extend(node.children.iter_mut().zip(kept_slots));
}
}
fn bare_root(root: &mut Node, survey: &Survey, before: usize) -> Bounded {
#[cfg(test)]
count(|runs| runs.bare += 1);
root.children.clear();
trim_to_floor(root);
Bounded {
root_bytes: before,
strings_trimmed: survey.root_over_floor,
nodes_dropped: survey.nodes.saturating_sub(1),
largest: survey.largest.clone(),
}
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, HashSet};
use std::time::{Duration, Instant};
use taria::Snapshot;
use taria::wire::{AppToBridge, InputId};
use super::*;
const ROLES: [Role; 29] = [
Role::App,
Role::Pane,
Role::List,
Role::ListItem,
Role::Tree,
Role::TreeItem,
Role::Table,
Role::Row,
Role::Cell,
Role::TextInput,
Role::Button,
Role::Checkbox,
Role::Select,
Role::Option,
Role::Tabs,
Role::Tab,
Role::Link,
Role::Text,
Role::Log,
Role::Terminal,
Role::Image,
Role::Chart,
Role::ProgressBar,
Role::Status,
Role::Scrollbar,
Role::Dialog,
Role::Menu,
Role::MenuItem,
Role::Other,
];
fn built_in_actions() -> [Action; 7] {
[
Action::Activate,
Action::Focus,
Action::Select,
Action::Toggle,
Action::Scroll,
Action::SetValue,
Action::Dismiss,
]
}
fn json_len(node: &Node) -> usize {
serde_json::to_vec(node).unwrap().len()
}
fn bound_root(root: &mut Node, budget: usize) -> Option<Bounded> {
let before = measure_root(root);
if before.is_some_and(|bytes| bytes <= budget) {
return None;
}
Some(cut_to_fit(root, budget, before))
}
fn cap_children(root: &mut Node, cap: usize) {
let mut pending = vec![root];
while let Some(node) = pending.pop() {
node.children.truncate(cap);
pending.extend(node.children.iter_mut());
}
}
fn count_nodes(root: &Node) -> usize {
let mut count = 0;
let mut pending = vec![root];
while let Some(node) = pending.pop() {
count += 1;
pending.extend(&node.children);
}
count
}
const ESCAPE_HEAVY: &str = "\x1b[31m\"quoted\"\\path\n\t\r\x08\x0c\x00\x7f é€😀";
fn varied_tree() -> Node {
let mut root = Node::new("app", Role::App)
.label("varied")
.actions(built_in_actions())
.action(Action::Custom(format!("custom {ESCAPE_HEAVY}")));
for (index, role) in ROLES.into_iter().enumerate() {
let mut node = Node::new(format!("node-{index}-{ESCAPE_HEAVY}"), role)
.focused(index == 3)
.relayed(index % 4 == 1)
.label(ESCAPE_HEAVY.repeat(index % 4))
.value(format!("{index}:{}", ESCAPE_HEAVY.repeat(index % 3)));
if index % 2 == 0 {
node = node
.actions(built_in_actions())
.actions((0..index).map(|n| Action::Custom(format!("act-{n}\u{1}"))));
}
if index % 5 == 0 {
node = node.children(
(0..index).map(|n| Node::new(format!("leaf-{index}-{n}"), Role::Cell)),
);
}
node = match index % 4 {
0 => node
.description(ESCAPE_HEAVY.repeat(index % 5))
.selected(index % 8 == 0)
.checked(taria::Checked::Mixed),
1 => node
.expanded(false)
.disabled(true)
.range(-0.5, -1e300, 1e300),
2 => node
.range(index as f64, 0.0, 29.0)
.pos_in_set(1 + index as u32),
_ => node
.set_size(u32::MAX)
.cell(index as u32, 2)
.table_size(0, 7),
};
root = root.child(node);
}
root
}
fn one_value(role: Role, value: &str) -> Node {
Node::new("app", Role::App)
.label("bound")
.child(Node::new("field", role).value(value))
}
fn value_of(root: &Node, index: usize) -> &str {
root.children[index].value.as_deref().unwrap()
}
fn row(n: usize) -> Node {
Node::new(format!("row-{n:06}"), Role::ListItem)
.label(format!("Task {n:06}"))
.action(Action::Select)
}
#[test]
fn the_envelope_is_measured_not_assumed() {
let root = Node::new("app", Role::App).label("envelope");
let root_len = json_len(&root);
let line_len = |seq, stamp: Option<u64>| {
let mut snapshot = Snapshot::new(seq, root.clone());
if let Some(stamp) = stamp {
snapshot = snapshot.after(InputId::new(stamp));
}
serde_json::to_vec(&AppToBridge::Snapshot(snapshot))
.unwrap()
.len()
};
assert_eq!(
line_len(u64::MAX, Some(u64::MAX)) - root_len,
SNAPSHOT_ENVELOPE_BYTES
);
for seq in [0, 1, 9, 10, 99, 100, 12_345, u64::MAX / 10, u64::MAX] {
for stamp in [None, Some(0), Some(1), Some(12_345), Some(u64::MAX)] {
assert!(
line_len(seq, stamp) - root_len <= SNAPSHOT_ENVELOPE_BYTES,
"seq {seq}, stamp {stamp:?}"
);
}
}
assert_eq!(
line_len(1, Some(u64::MAX)) - root_len,
SNAPSHOT_ENVELOPE_BYTES - 19
);
assert_eq!(
line_len(1, Some(1)) - root_len,
SNAPSHOT_ENVELOPE_BYTES - 19 - 19
);
let field = r#""after_input":18446744073709551615,"#.len();
assert_eq!(field, 35);
assert_eq!(
line_len(u64::MAX, None) - root_len,
SNAPSHOT_ENVELOPE_BYTES - field
);
assert_eq!(ROOT_BUDGET + SNAPSHOT_ENVELOPE_BYTES, MAX_LINE_BYTES);
}
#[test]
fn the_size_model_matches_serde_at_full_width() {
let framing = Framing::measured().unwrap();
assert_eq!(
framing,
Framing {
list: r#","children":[]"#.len(),
comma: 1
},
"what serde_json writes today"
);
let mut tree = varied_tree();
let before = tree.clone();
let mut slots = flatten(&mut tree).unwrap();
assert_eq!(tree, before, "flattening cuts nothing");
assert_eq!(slots.len(), count_nodes(&tree));
assert_eq!(mark_reach(&mut slots, framing, usize::MAX), Some(0));
let mut scratch = vec![0; slots.len()];
assert_eq!(
size_at_cap(&slots, framing, usize::MAX, &mut scratch),
json_len(&tree)
);
for cap in [0, 1, 2, 5] {
let mut capped = tree.clone();
cap_children(&mut capped, cap);
assert_eq!(
size_at_cap(&slots, framing, cap, &mut scratch),
json_len(&capped),
"cap {cap}"
);
}
let mut long = varied_tree();
let mut pending = vec![&mut long];
while let Some(node) = pending.pop() {
node.label = Some(ESCAPE_HEAVY.repeat(30));
if node.value.is_some() {
node.value = Some(format!("{}tail", ESCAPE_HEAVY.repeat(40)));
}
pending.extend(node.children.iter_mut());
}
let untouched = long.clone();
let slots = flatten(&mut long).unwrap();
assert_eq!(long, untouched, "flattening cuts nothing");
let mut at_floor = long.clone();
let cut = trim_strings(&mut at_floor, STRING_FLOOR, Strings::ValuesAndLabels);
assert!(cut > count_nodes(&long), "labels and values were over");
let mut scratch = vec![0; slots.len()];
assert_eq!(
size_at_cap(&slots, framing, usize::MAX, &mut scratch),
json_len(&at_floor)
);
for text in [ESCAPE_HEAVY, "", "plain", MARKER, "\x1f\x20"] {
assert_eq!(
escaped_len(text) + 2,
serde_json::to_vec(text).unwrap().len(),
"{text:?}"
);
}
}
#[test]
fn a_tree_at_the_budget_is_untouched_and_one_byte_over_is_trimmed() {
let base = json_len(&one_value(Role::TextInput, ""));
let at = "x".repeat(ROOT_BUDGET - base);
let mut root = one_value(Role::TextInput, &at);
assert_eq!(json_len(&root), ROOT_BUDGET);
let untouched = root.clone();
assert_eq!(bound_root(&mut root, ROOT_BUDGET), None);
assert_eq!(root, untouched);
let mut root = one_value(Role::TextInput, &format!("{at}x"));
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("one byte over is cut");
assert_eq!(bounded.root_bytes, ROOT_BUDGET + 1);
let report = SnapshotTooLarge::new(bounded.clone());
assert_eq!(report.bytes(), MAX_LINE_BYTES + 1, "one byte over the line");
assert_eq!(report.limit(), MAX_LINE_BYTES);
assert_eq!(bounded.strings_trimmed, 1);
assert_eq!(bounded.nodes_dropped, 0);
assert_eq!(bounded.largest, NodeId("field".into()));
assert!(value_of(&root, 0).ends_with(MARKER), "the cut is marked");
assert_eq!(json_len(&root), ROOT_BUDGET);
assert_eq!(value_of(&root, 0), format!("{}{MARKER}", &at[3..]));
}
#[test]
fn the_longest_values_are_cut_first_and_short_ones_are_untouched() {
let lengths = [5000, 3000, 400, 100];
let root =
Node::new("app", Role::App)
.label("fill")
.children(lengths.iter().enumerate().map(|(index, &len)| {
Node::new(format!("v{index}"), Role::Text).value("y".repeat(len))
}));
let budget = json_len(&root) - 3000;
let mut bounded_root = root.clone();
let bounded = bound_root(&mut bounded_root, budget).expect("over budget");
assert_eq!(bounded.strings_trimmed, 2);
assert_eq!(bounded.largest, NodeId("v0".into()));
assert!(json_len(&bounded_root) <= budget);
let (first, second) = (value_of(&bounded_root, 0), value_of(&bounded_root, 1));
assert!(first.ends_with(MARKER) && second.ends_with(MARKER));
assert!(escaped_len(first) <= 2500 && escaped_len(first) >= 2496);
assert_eq!(escaped_len(first), escaped_len(second));
assert_eq!(value_of(&bounded_root, 2), value_of(&root, 2));
assert_eq!(value_of(&bounded_root, 3), value_of(&root, 3));
}
#[test]
fn log_and_terminal_keep_the_tail_and_other_roles_keep_the_head() {
let text: String = (0..2000)
.map(|n| char::from(b'a' + (n % 26) as u8))
.collect();
for role in ROLES {
let mut root = one_value(role, &text);
let budget = json_len(&root) - 1000;
bound_root(&mut root, budget).expect("over budget");
let value = value_of(&root, 0);
let kept = value.trim_start_matches(MARKER).trim_end_matches(MARKER);
if matches!(role, Role::Log | Role::Terminal) {
assert!(value.starts_with(MARKER), "{role:?}: {value}");
assert!(text.ends_with(kept), "{role:?} keeps the tail");
} else {
assert!(value.ends_with(MARKER), "{role:?}: {value}");
assert!(text.starts_with(kept), "{role:?} keeps the head");
}
assert!(json_len(&root) <= budget, "{role:?}");
}
}
#[test]
fn escapes_cost_what_they_cost_on_the_line_and_cuts_land_between_chars() {
for (piece, keep) in [
("\x1b", Keep::Head),
("\n", Keep::Head),
("\"", Keep::Tail),
("a\x1b[0m\"\n", Keep::Head),
("é€😀", Keep::Head),
("é€😀", Keep::Tail),
] {
let original = piece.repeat(500);
let mut text = original.clone();
trim_to(&mut text, STRING_FLOOR, keep);
let quoted = serde_json::to_vec(&text).unwrap().len();
assert!(quoted - 2 <= STRING_FLOOR, "{piece:?}: {quoted}");
assert_eq!(quoted - 2, escaped_len(&text), "{piece:?}");
assert!(escaped_len(&text) > STRING_FLOOR - 6, "{piece:?}");
match keep {
Keep::Head => assert!(original.starts_with(text.strip_suffix(MARKER).unwrap())),
Keep::Tail => assert!(original.ends_with(text.strip_prefix(MARKER).unwrap())),
}
}
let mut text = format!("aa{}", "😀".repeat(100));
trim_to(&mut text, STRING_FLOOR, Keep::Head);
assert_eq!(text, format!("aa{}{MARKER}", "😀".repeat(62)));
let mut root = one_value(Role::Terminal, &ESCAPE_HEAVY.repeat(200));
let budget = json_len(&root) - 2000;
bound_root(&mut root, budget).expect("over budget");
assert!(json_len(&root) <= budget);
assert!(value_of(&root, 0).starts_with(MARKER));
}
#[test]
fn labels_are_trimmed_only_when_values_are_not_enough() {
let root = Node::new("app", Role::App).label("labels").child(
Node::new("both", Role::Text)
.label("l".repeat(3000))
.value("v".repeat(3000)),
);
let label_of = |root: &Node| root.children[0].label.clone().unwrap();
let mut values_only = root.clone();
let budget = json_len(&root) - 1000;
let bounded = bound_root(&mut values_only, budget).expect("over budget");
assert_eq!(bounded.strings_trimmed, 1);
assert_eq!(
label_of(&values_only),
label_of(&root),
"the label is untouched"
);
assert!(value_of(&values_only, 0).ends_with(MARKER));
let mut both = root.clone();
let budget = json_len(&root) - 4000;
let bounded = bound_root(&mut both, budget).expect("over budget");
assert_eq!(bounded.strings_trimmed, 2);
assert!(json_len(&both) <= budget);
let (label, value) = (label_of(&both), value_of(&both, 0).to_string());
assert!(label.ends_with(MARKER) && value.ends_with(MARKER));
assert_eq!(escaped_len(&label), escaped_len(&value));
assert!(
escaped_len(&value) > STRING_FLOOR,
"the shared level sits above the floor"
);
}
#[test]
fn descriptions_are_cut_with_values_before_labels() {
let root = Node::new("app", Role::App).label("described").child(
Node::new("field", Role::TextInput)
.label("l".repeat(3000))
.value("v".repeat(3000))
.description("d".repeat(3000))
.checked(true)
.range(1.0, 0.0, 2.0),
);
let field = |root: &Node| root.children[0].clone();
let mut first = root.clone();
let budget = json_len(&root) - 4000;
let bounded = bound_root(&mut first, budget).expect("over budget");
assert_eq!(bounded.strings_trimmed, 2);
assert!(json_len(&first) <= budget);
let cut = field(&first);
assert_eq!(cut.label, field(&root).label, "the label is untouched");
let (value, description) = (cut.value.unwrap(), cut.description.unwrap());
assert!(value.ends_with(MARKER) && description.ends_with(MARKER));
assert_eq!(escaped_len(&value), escaped_len(&description));
assert_eq!(cut.checked, Some(taria::Checked::True));
assert_eq!(cut.value_now.map(taria::Number::get), Some(1.0));
let mut all = root.clone();
let budget = json_len(&root) - 7000;
let bounded = bound_root(&mut all, budget).expect("over budget");
assert_eq!(bounded.strings_trimmed, 3);
assert!(json_len(&all) <= budget);
let cut = field(&all);
let label = cut.label.unwrap();
assert!(label.ends_with(MARKER));
assert_eq!(escaped_len(&label), escaped_len(&cut.description.unwrap()));
}
#[test]
fn a_long_description_is_cut_not_dropped_and_named_largest() {
let root = Node::new("app", Role::App).label("described").children([
Node::new("help", Role::Text).description("d".repeat(100_000)),
Node::new("status", Role::Status).value("ok"),
]);
let mut cut = root.clone();
let budget = 2000;
let bounded = bound_root(&mut cut, budget).expect("over budget");
assert!(json_len(&cut) <= budget);
assert_eq!(bounded.nodes_dropped, 0);
assert_eq!(bounded.strings_trimmed, 1);
assert_eq!(bounded.largest, NodeId("help".into()));
let description = cut.children[0].description.as_deref().unwrap();
assert!(description.ends_with(MARKER));
assert!(
escaped_len(description) > STRING_FLOOR,
"cut only as far as needed"
);
let mut bare = Node::new("app", Role::App).description("d".repeat(5000));
let bounded = bound_root(&mut bare, 1000).expect("over budget");
assert_eq!(bounded.strings_trimmed, 1);
assert!(json_len(&bare) <= 1000);
}
#[test]
fn a_relayed_node_cut_to_fit_keeps_its_mark() {
let root = Node::new("app", Role::App).label("relayed").children([
Node::new("reply", Role::Log)
.label("l".repeat(3000))
.value("v".repeat(3000))
.relayed(true),
Node::new("status", Role::Status).value("streaming"),
]);
let mut cut = root.clone();
let budget = json_len(&root) - 4000;
let bounded = bound_root(&mut cut, budget).expect("over budget");
assert_eq!(bounded.strings_trimmed, 2);
assert!(json_len(&cut) <= budget);
let reply = &cut.children[0];
assert!(
value_of(&cut, 0).starts_with(MARKER),
"a log keeps its tail"
);
assert!(reply.label.as_deref().is_some_and(|l| l.ends_with(MARKER)));
assert!(reply.relayed, "cut or not, it is still relayed text");
let json = serde_json::to_string(reply).unwrap();
assert!(json.contains(r#""relayed":true"#), "{json}");
assert!(!cut.relayed && !cut.children[1].relayed);
}
#[test]
fn a_huge_list_drops_trailing_rows_and_keeps_its_sibling_status_bar() {
let mut root = Node::new("app", Role::App).label("huge").children([
Node::new("list", Role::List)
.label("Tasks")
.children((0..50_000).map(row)),
Node::new("status", Role::Status).value("50000 tasks"),
]);
assert!(json_len(&root) > ROOT_BUDGET);
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("over budget");
assert!(json_len(&root) <= ROOT_BUDGET);
assert_eq!(bounded.strings_trimmed, 0, "no string was over the floor");
let ids: Vec<&str> = root.children.iter().map(|c| c.id.0.as_str()).collect();
assert_eq!(ids, ["list", "status"], "the status bar survives the list");
let list = &root.children[0];
let kept = list.children.len();
assert!(kept > 1000 && kept < 50_000, "kept {kept} rows");
assert_eq!(bounded.nodes_dropped, 50_000 - kept);
for (n, kept_row) in list.children.iter().enumerate() {
assert_eq!(kept_row.id, row(n).id);
}
let mut one_more = root.clone();
one_more.children[0].children.push(row(kept));
assert!(json_len(&one_more) > ROOT_BUDGET);
}
#[test]
fn a_huge_list_beside_tabs_is_capped_alone_and_fills_the_budget() {
let tabs = ["Inbox", "Today", "Done"].map(|name| {
Node::new(format!("tab-{name}"), Role::Tab)
.label(name)
.action(Action::Select)
});
let mut root = Node::new("app", Role::App).label("tabs").children([
Node::new("list", Role::List)
.label("Tasks")
.children((0..50_000).map(row)),
Node::new("tabs", Role::Tabs).children(tabs.clone()),
]);
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("over budget");
let len = json_len(&root);
assert!(len <= ROOT_BUDGET);
assert!(
ROOT_BUDGET - len < 4096,
"{} bytes of the budget left unused",
ROOT_BUDGET - len
);
assert_eq!(root.children[1].children, tabs, "every tab is kept");
let kept = root.children[0].children.len();
assert!(kept < 50_000);
assert_eq!(bounded.nodes_dropped, 50_000 - kept, "only rows went");
}
#[test]
fn the_refill_keeps_each_list_a_run_of_its_first_rows() {
let long = "r".repeat(200);
let all: Vec<Node> = (0..100)
.map(|n| {
let id = if n < 50 {
format!("row-{n:03}-{long}")
} else {
format!("row-{n:03}")
};
Node::new(id, Role::ListItem)
})
.collect();
let tree = |rows: &[Node]| {
Node::new("app", Role::App).label("mixed").children([
Node::new("list", Role::List).children(rows.to_vec()),
Node::new("status", Role::Status).value("100 rows"),
])
};
let mut root = tree(&all);
let budget = json_len(&tree(&all[..10])) + 100;
assert!(json_len(&all[10]) > 101 && json_len(&all[50]) < 99);
let bounded = bound_root(&mut root, budget).expect("over budget");
assert!(json_len(&root) <= budget);
assert_eq!(
root.children[0].children,
all[..10],
"the first ten, no gap"
);
assert_eq!(root.children[1].id.0, "status");
assert_eq!(bounded.nodes_dropped, 90);
}
#[test]
fn a_child_too_large_to_fit_alone_is_dropped_without_its_siblings() {
let chrome = [
Node::new("status", Role::Status).value("3 tasks"),
Node::new("help", Role::Text).label("q quit, ? help"),
Node::new("quit", Role::Button)
.label("Quit")
.action(Action::Activate),
];
let huge_actions = Node::new("actions", Role::Button)
.actions((0..500).map(|n| Action::Custom(format!("action-{n}"))));
for huge in [Node::new("i".repeat(5000), Role::Text), huge_actions] {
let name = huge.id.0.chars().take(8).collect::<String>();
let huge = huge.children((0..3).map(|n| Node::new(format!("under-{n}"), Role::Text)));
let mut root = Node::new("app", Role::App)
.label("chrome")
.child(huge)
.children(chrome.clone());
let bounded = bound_root(&mut root, 2000).expect("over budget");
assert_eq!(root.children, chrome, "{name}: the siblings survive whole");
assert_eq!(
bounded.nodes_dropped, 4,
"{name}: the node and everything under it"
);
assert!(json_len(&root) <= 2000, "{name}");
}
}
#[test]
fn a_child_too_large_for_the_room_left_closes_its_parents_list() {
let mut root = Node::new("app", Role::App)
.label("closed")
.child(Node::new("a".repeat(800), Role::Text))
.child(Node::new("b".repeat(900), Role::Text))
.children(status_and_quit());
let bounded = bound_root(&mut root, 1500).expect("over budget");
let ids: Vec<&str> = root.children.iter().map(|c| c.id.0.as_str()).collect();
assert_eq!(ids, ["a".repeat(800)], "the run ends at the second");
assert_eq!(bounded.nodes_dropped, 3);
assert!(json_len(&root) <= 1500);
}
fn status_and_quit() -> [Node; 2] {
[
Node::new("status", Role::Status).value("3 tasks"),
Node::new("quit", Role::Button)
.label("Quit")
.action(Action::Activate),
]
}
#[test]
fn a_node_that_could_never_fit_costs_its_siblings_nothing() {
let draft = "d".repeat(300_000);
let mut root = Node::new("app", Role::App)
.label("unfit")
.child(Node::new("I".repeat(2_000_000), Role::Text))
.child(Node::new("draft", Role::TextInput).value(draft.clone()))
.children(status_and_quit());
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("over budget");
let ids: Vec<&str> = root.children.iter().map(|c| c.id.0.as_str()).collect();
assert_eq!(ids, ["draft", "status", "quit"]);
assert_eq!(value_of(&root, 0), draft, "the draft goes out whole");
assert_eq!(bounded.strings_trimmed, 0, "no string was cut");
assert_eq!(bounded.nodes_dropped, 1);
assert_eq!(bounded.largest.0, "I".repeat(2_000_000));
assert!(json_len(&root) > 300_000);
}
#[test]
fn a_long_draft_beside_a_node_that_could_never_fit_fills_the_line() {
let mut root = Node::new("app", Role::App)
.label("unfit")
.child(Node::new("I".repeat(1_100_000), Role::Text))
.child(Node::new("draft", Role::TextInput).value("d".repeat(1_500_000)))
.children(status_and_quit());
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("over budget");
let ids: Vec<&str> = root.children.iter().map(|c| c.id.0.as_str()).collect();
assert_eq!(ids, ["draft", "status", "quit"]);
assert_eq!(root.children[1..], status_and_quit());
let kept = escaped_len(value_of(&root, 0));
assert!(
kept > ROOT_BUDGET - 1024,
"{kept} bytes of draft in a line of {ROOT_BUDGET}"
);
assert!(json_len(&root) <= ROOT_BUDGET);
assert_eq!(bounded.strings_trimmed, 1);
assert_eq!(bounded.nodes_dropped, 1);
}
#[test]
fn a_log_beside_custom_actions_that_could_never_fit_keeps_its_tail_whole() {
let log: String = (0..526_000)
.map(|n| if n % 80 == 79 { '\n' } else { 'l' })
.collect();
let actions = Node::new("actions", Role::Menu)
.actions((0..80_000).map(|n| Action::Custom(format!("action-{n:06}"))));
assert!(json_len(&actions) > 2_000_000, "{}", json_len(&actions));
let mut root = Node::new("app", Role::App)
.label("unfit")
.child(actions)
.child(Node::new("log", Role::Log).value(log.clone()))
.children(status_and_quit());
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("over budget");
let ids: Vec<&str> = root.children.iter().map(|c| c.id.0.as_str()).collect();
assert_eq!(ids, ["log", "status", "quit"]);
assert_eq!(value_of(&root, 0), log, "the log goes out whole");
assert_eq!(bounded.strings_trimmed, 0);
assert_eq!(bounded.nodes_dropped, 1);
}
#[test]
fn a_draft_beside_nodes_that_must_go_is_kept_whole() {
let draft = "d".repeat(300_000);
let mut root = Node::new("app", Role::App).label("drop").children([
Node::new("draft", Role::TextInput).value(draft.clone()),
Node::new("A".repeat(600_000), Role::Text),
Node::new("B".repeat(600_000), Role::Text),
]);
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("over budget");
let ids: Vec<&str> = root.children.iter().map(|c| c.id.0.as_str()).collect();
assert_eq!(ids, ["draft".to_string(), "A".repeat(600_000)]);
assert_eq!(value_of(&root, 0), draft, "the draft goes out whole");
assert_eq!(bounded.strings_trimmed, 0, "no string was cut");
assert_eq!(bounded.nodes_dropped, 1);
assert!(json_len(&root) > 900_000);
}
#[test]
fn a_log_beside_nodes_that_must_go_keeps_most_of_its_tail() {
let log: String = (0..941_000)
.map(|n| if n % 80 == 79 { '\n' } else { 'l' })
.collect();
let mut root = Node::new("app", Role::App).label("drop").children([
Node::new("log", Role::Log).value(log.clone()),
Node::new("A".repeat(322_000), Role::Text),
Node::new("B".repeat(800_000), Role::Text),
]);
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("over budget");
assert_eq!(root.children.len(), 2, "the last child went");
assert_eq!(bounded.nodes_dropped, 1);
assert_eq!(bounded.strings_trimmed, 1);
let kept = value_of(&root, 0);
let tail = kept.strip_prefix(MARKER).expect("the cut is marked");
assert!(log.ends_with(tail), "a log keeps its tail");
assert!(
escaped_len(kept) > ROOT_BUDGET - 322_000 - 1024,
"{} bytes of log kept",
escaped_len(kept)
);
let len = json_len(&root);
assert!(
len <= ROOT_BUDGET && ROOT_BUDGET - len < 6,
"{len} of {ROOT_BUDGET}"
);
}
#[test]
fn a_deep_branch_behind_wide_siblings_is_refilled_to_the_budget() {
let leaves = |level: usize| {
(0..40).map(move |n| {
Node::new(format!("leaf-{level}-{n}"), Role::Text).label("x".repeat(200))
})
};
let mut chain = Node::new("n20", Role::TreeItem).children(leaves(20));
for level in (1..20).rev() {
chain = Node::new(format!("n{level}"), Role::TreeItem)
.children(leaves(level))
.child(chain);
}
let mut root = Node::new("app", Role::App).label("deep").child(chain);
let budget = json_len(&root) / 3;
let bounded = bound_root(&mut root, budget).expect("over budget");
let len = json_len(&root);
assert!(len <= budget);
assert!(
len * 10 >= budget * 9,
"{len} of {budget} bytes used, {} nodes dropped",
bounded.nodes_dropped
);
assert_eq!(root.children[0].children.len(), 41);
}
#[test]
fn largest_names_a_long_id_or_custom_action_name() {
let label = || Node::new("label", Role::Text).label("l".repeat(300));
let long_id = "x".repeat(3000);
let mut root = Node::new("app", Role::App)
.label("largest")
.children([label(), Node::new(long_id.clone(), Role::Text)]);
let bounded = bound_root(&mut root, 2000).expect("over budget");
assert_eq!(bounded.largest.0, long_id, "the id is what ran over");
let mut root = Node::new("app", Role::App).label("largest").children([
label(),
Node::new("act", Role::Button).action(Action::Custom("a".repeat(3000))),
]);
let bounded = bound_root(&mut root, 2000).expect("over budget");
assert_eq!(bounded.largest.0, "act", "the action name is what ran over");
let message = SnapshotTooLarge::new(bounded).to_string();
assert!(
message.contains("longest string at node `act`"),
"{message}"
);
}
#[test]
fn the_bare_root_is_the_last_resort() {
let mut root = Node::new("app", Role::App)
.label("r".repeat(10_000))
.children([
Node::new("first", Role::Text),
Node::new("second", Role::Text),
]);
let bounded = bound_root(&mut root, 200).expect("over budget");
assert!(root.children.is_empty());
assert_eq!(bounded.nodes_dropped, 2);
assert_eq!(bounded.strings_trimmed, 1, "the root's label");
assert_eq!(root.id.0, "app");
let mut root = Node::new("app", Role::App).child(Node::new("child", Role::Text));
let bounded = cut_to_fit(&mut root, ROOT_BUDGET, None);
assert!(root.children.is_empty());
assert_eq!(bounded.root_bytes, usize::MAX);
let mut root = Node::new("app", Role::App)
.label(format!("{}\n", "\x1b".repeat(100_000)))
.focused(true)
.children(
(0..1000).map(|n| Node::new(format!("{n}"), Role::Text).value("v".repeat(5000))),
);
let survey = Survey::of(&root);
let before = json_len(&root);
let bounded = bare_root(&mut root, &survey, before);
assert_eq!(bounded.root_bytes, before);
assert!(root.children.is_empty());
assert_eq!(bounded.nodes_dropped, 1000);
assert_eq!(bounded.strings_trimmed, 1);
let len = json_len(&root);
assert!(len <= 512, "a bare root is a few hundred bytes, not {len}");
assert!(len <= ROOT_BUDGET);
}
#[test]
fn ids_and_custom_action_names_are_never_changed() {
let long = "k".repeat(2000);
let mut root = Node::new("app", Role::App)
.label("ids")
.children((0..300).map(|n| {
Node::new(format!("{long}-{n}"), Role::Button)
.label(long.clone())
.value(long.clone())
.action(Action::Custom(format!("{long}-act-{n}")))
}));
let original = root.clone();
let bounded = bound_root(&mut root, 200_000).expect("over budget");
assert!(bounded.nodes_dropped > 0, "the drop stage ran");
assert!(json_len(&root) <= 200_000);
assert_eq!(root.id, original.id);
assert!(!root.children.is_empty());
for (kept, was) in root.children.iter().zip(&original.children) {
assert_eq!(kept.id, was.id);
assert_eq!(kept.actions, was.actions);
assert_eq!(kept.role, was.role);
assert_ne!(kept.value, was.value, "the strings were what gave way");
}
}
#[test]
fn the_water_level_is_the_highest_that_frees_enough() {
let mut lengths = vec![STRING_FLOOR + 10, STRING_FLOOR + 5];
assert_eq!(water_level(&mut lengths, 15), Some(STRING_FLOOR));
let mut lengths = vec![STRING_FLOOR + 10, STRING_FLOOR + 5];
assert_eq!(water_level(&mut lengths, 16), None);
let mut lengths = vec![1000, 600, 300];
assert_eq!(water_level(&mut lengths, 400), Some(600));
assert_eq!(water_level(&mut lengths, 401), Some(599));
assert_eq!(water_level(&mut Vec::new(), 1), None);
}
#[test]
fn a_list_of_two_hundred_thousand_rows_is_bounded_in_linear_time() {
let mut times = Vec::new();
for rows in [50_000, 200_000] {
let mut root = Node::new("app", Role::App).label("rows").children([
Node::new("list", Role::List).children((0..rows).map(row)),
Node::new("status", Role::Status).value(format!("{rows} rows")),
]);
let start = Instant::now();
let bounded = bound_root(&mut root, ROOT_BUDGET).expect("over budget");
let elapsed = start.elapsed();
eprintln!("{rows} rows bounded in {elapsed:?}");
assert!(json_len(&root) <= ROOT_BUDGET);
assert_eq!(root.children.len(), 2, "the status bar is kept");
assert_eq!(
bounded.nodes_dropped,
rows - root.children[0].children.len()
);
times.push(elapsed);
}
assert!(
times[1] < Duration::from_secs(30),
"200000 rows took {:?}",
times[1]
);
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn below(&mut self, n: usize) -> usize {
(self.next() % n.max(1) as u64) as usize
}
fn one_in(&mut self, n: usize) -> bool {
self.below(n) == 0
}
fn text(&mut self) -> String {
const PIECES: [&str; 15] = [
"a", "Z", "0", " ", "\"", "\\", "\n", "\t", "\x1b", "\x00", "\x7f", "é", "€", "😀",
MARKER,
];
let max = [2, 12, 60, 400][self.below(4)];
let mut text = String::new();
for _ in 0..self.below(8) {
let piece = PIECES[self.below(PIECES.len())];
text.push_str(&piece.repeat(self.below(max + 1)));
}
text
}
}
fn random_node(rng: &mut Rng, depth: usize, ids: &mut usize, room: &mut usize) -> Node {
*ids += 1;
let long = if rng.one_in(10) {
rng.text()
} else {
String::new()
};
let mut node = Node::new(format!("n{ids}:{long}"), ROLES[rng.below(ROLES.len())])
.focused(rng.one_in(20));
if rng.one_in(2) {
node = node.label(rng.text());
}
if rng.one_in(2) {
node = node.value(rng.text());
}
if rng.one_in(4) {
node = node
.description(rng.text())
.checked(rng.one_in(2))
.range(rng.below(1000) as f64 / 7.0, 0.0, 1e9)
.pos_in_set(rng.below(100) as u32);
}
if rng.one_in(3) {
let actions: Vec<Action> = built_in_actions()
.into_iter()
.filter(|_| rng.one_in(2))
.collect();
node = node.actions(actions);
}
if rng.one_in(4) {
for _ in 0..1 + rng.below(3) {
node = node.action(Action::Custom(format!("c:{}", rng.text())));
}
}
if depth > 0 {
let fanout = match rng.below(4) {
0 => 0,
1 => rng.below(4),
2 => rng.below(20),
_ => rng.below(120),
};
for _ in 0..fanout {
if *room == 0 {
break;
}
*room -= 1;
let child = random_node(rng, depth - 1, ids, room);
node = node.child(child);
}
}
node
}
fn fitting_ids(original: &Node, budget: usize) -> HashSet<String> {
fn walk(node: &mut Node, path: &mut Vec<Node>, budget: usize, ids: &mut HashSet<String>) {
let children = std::mem::take(&mut node.children);
let mut alone = node.clone();
node.children = children;
trim_to_floor(&mut alone);
path.push(alone);
let chain = path
.drain(..)
.rev()
.reduce(|child, parent| parent.child(child))
.unwrap();
let fits = json_len(&chain) <= budget;
let mut next = Some(chain);
while let Some(mut link) = next {
next = link.children.pop();
path.push(link);
}
if fits {
ids.insert(node.id.0.clone());
for child in &mut node.children {
walk(child, path, budget, ids);
}
}
path.pop();
}
let mut ids = HashSet::new();
walk(&mut original.clone(), &mut Vec::new(), budget, &mut ids);
ids
}
fn make_one_huge(rng: &mut Rng, root: &mut Node, nodes: usize, extra: usize) {
let prefix = format!("n{}:", 1 + rng.below(nodes));
let by_id = rng.one_in(2);
let mut pending = vec![root];
while let Some(node) = pending.pop() {
if node.id.0.starts_with(&prefix) {
if by_id {
node.id.0.push_str(&"U".repeat(extra));
} else {
let name = "U".repeat(1000);
node.actions.extend(
(0..=extra / 1000).map(|n| Action::Custom(format!("huge-{n}-{name}"))),
);
}
return;
}
pending.extend(node.children.iter_mut());
}
}
fn is_subsequence(kept: &[&str], original: &[&str]) -> bool {
let mut rest = original.iter();
kept.iter().all(|id| rest.any(|was| was == id))
}
#[test]
fn random_trees_always_fit_and_keep_what_they_keep_intact() {
let (mut fitted, mut strings_only, mut dropped, mut unfit_only) = (0, 0, 0, 0);
let mut dropped_then_cut = 0;
for seed in 1..=300u64 {
let mut rng = Rng(seed.wrapping_mul(0x9e37_79b9_7f4a_7c15) | 1);
let huge = rng.one_in(3);
let mut ids = 0;
let mut room = if huge { 40 } else { 150 };
let mut root = Node::new("app", Role::App)
.label(rng.text())
.focused(rng.one_in(2));
for _ in 0..1 + rng.below(6) {
let child = random_node(&mut rng, 4, &mut ids, &mut room);
root = root.child(child);
}
let budget = if huge {
let extra = json_len(&root) * (2 + rng.below(4));
make_one_huge(&mut rng, &mut root, ids, extra);
400 + rng.below(extra)
} else {
400 + rng.below(json_len(&root) * 2)
};
let original = root.clone();
let original_len = json_len(&original);
let bare_before = runs().bare;
let Some(bounded) = bound_root(&mut root, budget) else {
assert!(original_len <= budget, "seed {seed}");
assert_eq!(root, original, "seed {seed}: a tree that fits is untouched");
fitted += 1;
continue;
};
if bounded.nodes_dropped == 0 {
strings_only += 1;
} else {
dropped += 1;
}
let len = json_len(&root);
assert!(original_len > budget, "seed {seed}");
assert!(len <= budget, "seed {seed}: {len} over {budget}");
assert_eq!(runs().bare, bare_before, "seed {seed}: no bare root");
assert_eq!(bounded.root_bytes, original_len, "seed {seed}");
assert_eq!(
bounded.nodes_dropped,
count_nodes(&original) - count_nodes(&root),
"seed {seed}"
);
let mut by_id = HashMap::new();
let mut pending = vec![&original];
while let Some(node) = pending.pop() {
by_id.insert(node.id.0.as_str(), node);
pending.extend(&node.children);
}
let fitting = fitting_ids(&original, budget);
let mut flat = original.clone();
let mut slots = flatten(&mut flat).unwrap();
mark_reach(&mut slots, Framing::measured().unwrap(), budget).unwrap();
let mut planned = HashSet::new();
let mut queue = VecDeque::from([&flat]);
let mut slots = slots.iter();
while let Some(node) = queue.pop_front() {
if slots.next().is_some_and(|slot| slot.fits) {
planned.insert(node.id.0.clone());
}
queue.extend(&node.children);
}
assert_eq!(
planned, fitting,
"seed {seed}: the first stage and the oracle disagree"
);
let unfit = count_nodes(&original) - fitting.len();
if bounded.nodes_dropped > unfit && bounded.strings_trimmed > 0 {
dropped_then_cut += 1;
}
assert!(
bounded.strings_trimmed == 0 || len + 6 * bounded.strings_trimmed > budget,
"seed {seed}: {len} of {budget} bytes with {} string(s) cut",
bounded.strings_trimmed
);
let mut pruned = original.clone();
let mut pending = vec![&mut pruned];
while let Some(node) = pending.pop() {
node.children
.retain(|child| fitting.contains(child.id.0.as_str()));
pending.extend(node.children.iter_mut());
}
if json_len(&pruned) <= budget {
assert_eq!(root, pruned, "seed {seed}: cut more than the unfit nodes");
assert_eq!(bounded.strings_trimmed, 0, "seed {seed}");
unfit_only += 1;
}
let mut trimmed = 0;
let mut pending = vec![&root];
while let Some(node) = pending.pop() {
let was = by_id[node.id.0.as_str()];
assert_eq!(node.role, was.role, "seed {seed}");
assert_eq!(node.actions, was.actions, "seed {seed}");
assert_eq!(node.focused, was.focused, "seed {seed}");
let strings = [
(&node.label, &was.label),
(&node.value, &was.value),
(&node.description, &was.description),
];
for (now, then) in strings {
if now == then {
continue;
}
trimmed += 1;
let (now, then) = (now.as_deref().unwrap(), then.as_deref().unwrap());
let kept_head = now
.strip_suffix(MARKER)
.is_some_and(|head| then.starts_with(head));
let kept_tail = now
.strip_prefix(MARKER)
.is_some_and(|tail| then.ends_with(tail));
assert!(kept_head || kept_tail, "seed {seed}: {now:?}");
assert!(escaped_len(now) < escaped_len(then), "seed {seed}");
}
let kept: Vec<&str> = node.children.iter().map(|c| c.id.0.as_str()).collect();
let all: Vec<&str> = was.children.iter().map(|c| c.id.0.as_str()).collect();
assert!(
is_subsequence(&kept, &all),
"seed {seed}: children reordered"
);
let fit: Vec<&str> = all
.iter()
.copied()
.filter(|id| fitting.contains(*id))
.collect();
assert!(
fit.starts_with(&kept),
"seed {seed}: a gap in the children of {:?}",
node.id.0
);
pending.extend(&node.children);
}
assert_eq!(bounded.strings_trimmed, trimmed, "seed {seed}");
}
eprintln!(
"{fitted} fitted, {strings_only} cut in their strings, {dropped} lost nodes, \
{unfit_only} only those that could never fit, {dropped_then_cut} strings cut \
after the drop stage"
);
for (stage, count) in [
("fit", fitted),
("strings", strings_only),
("nodes", dropped),
("unfit nodes only", unfit_only),
("strings after the drop stage", dropped_then_cut),
] {
assert!(count >= 30, "only {count} of 300 trees reached `{stage}`");
}
}
}