#![cfg(not(target_arch = "wasm32"))]
use std::alloc::System;
use molt::prelude::*;
use stats_alloc::{INSTRUMENTED_SYSTEM, Region, StatsAlloc};
#[global_allocator]
static GLOBAL: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;
const ATTEMPTS: usize = 9;
fn allocations<T>(work: impl Fn() -> T) -> (usize, T) {
let (mut fewest, output) = measure(&work);
for _ in 1..ATTEMPTS {
fewest = fewest.min(measure(&work).0);
}
(fewest, output)
}
fn measure<T>(work: &impl Fn() -> T) -> (usize, T) {
let region = Region::new(GLOBAL);
let output = work();
let change = region.change();
(change.allocations + change.reallocations, output)
}
fn a_measurement_ignores_allocations_made_by_other_threads() {
let calls = std::cell::Cell::new(0_usize);
let noisy = || {
calls.set(calls.get() + 1);
if calls.get() <= 4 {
std::hint::black_box(Vec::<u8>::with_capacity(8));
std::hint::black_box(Vec::<u8>::with_capacity(8));
}
(
std::hint::black_box(Vec::<u8>::with_capacity(8)),
std::hint::black_box(Vec::<u8>::with_capacity(8)),
)
};
let (measured, _) = allocations(noisy);
assert_eq!(
measured, 2,
"the least of {ATTEMPTS} runs is the work alone"
);
assert_eq!(calls.get(), ATTEMPTS);
}
#[test]
fn the_hot_paths_stay_within_their_allocation_budgets() {
a_measurement_ignores_allocations_made_by_other_threads();
let mut report = Vec::new();
let mut check = |name: &str, measured: usize, budget: usize| {
report.push(format!("{name}: {measured} (budget {budget})"));
assert!(
measured <= budget,
"{name} made {measured} allocations, budget {budget}"
);
};
check("div()", allocations(div).0, 0);
check(
"div().add_class",
allocations(|| div().add_class("card")).0,
2,
);
check(
"li().add_class().text()",
allocations(|| li().add_class("item").text("x")).0,
3,
);
check(
"a().attr(href)",
allocations(|| a().attr("href", "/x")).0,
3,
);
check("p().text()", allocations(|| p().text("hello")).0, 1);
check("Node::text", allocations(|| Node::text("hello")).0, 1);
check(
"a().trusted_url(href)",
allocations(|| a().trusted_url("href", "/x")).0,
3,
);
let name = String::from("Ana");
check(
"html! li { (name) }",
allocations(|| molt::html! { li { (name) } }).0,
3,
);
check(
"html! li(class = \"item\") { (name) }",
allocations(|| molt::html! { li(class = "item") { (name) } }).0,
5,
);
let tree = ul().children_from((0..100).map(|i| li().add_class("item").text(format!("n{i}"))));
check(
"render compact, 100 items",
allocations(|| tree.render()).0,
6,
);
check(
"render pretty, 100 items",
allocations(|| tree.render_pretty()).0,
7,
);
check(
"escape_text clean",
allocations(|| molt::core::escape::escape_text("plain")).0,
0,
);
check(
"escape_text dirty",
allocations(|| molt::core::escape::escape_text("a<b")).0,
1,
);
check("clone, 100 items", allocations(|| tree.clone()).0, 302);
{
use std::mem::size_of;
check("size_of::<Node>()", size_of::<Node>(), 104);
check("size_of::<Element>()", size_of::<Element>(), 104);
check("size_of::<Attribute>()", size_of::<Attribute>(), 56);
}
eprintln!("{}", report.join("\n"));
}