use size_classes::{
build_size2class, build_table, size2class_len, InvalidAlign, Params, SizeClasses,
};
mod common;
use common::{
HUGE_THRESHOLD, JUMP_A, JUMP_B, JUMP_DENSE, JUMP_MULTI, JUMP_NONE, SEFER_EXTRAS, SEFER_GEO,
SEFER_L, SEFER_MAX, SEFER_MIN_BLOCK, SEFER_N, SEFER_SC, SEFER_TABLE,
};
fn reference_table(
min_block: usize,
growth: (usize, usize),
geo_count: usize,
extras: &[usize],
) -> Vec<usize> {
let (num, den) = growth;
let mask = min_block - 1;
let mut geo = Vec::with_capacity(geo_count);
let mut cur = min_block;
for i in 0..geo_count {
geo.push(cur);
if i + 1 < geo_count {
let scaled = (cur as u128 * num as u128).div_ceil(den as u128);
let rounded = (scaled + mask as u128) & !(mask as u128);
let rounded: usize = rounded
.try_into()
.expect("reference_table: geometric progression overflows usize");
cur = if rounded > cur {
rounded
} else {
cur.checked_add(min_block)
.expect("reference_table: geometric progression overflows usize")
};
}
}
let mut out = Vec::with_capacity(geo_count + extras.len());
let (mut gi, mut ei) = (0, 0);
while gi < geo.len() || ei < extras.len() {
let take_geo = if gi >= geo.len() {
false
} else if ei >= extras.len() {
true
} else {
geo[gi] < extras[ei]
};
if take_geo {
out.push(geo[gi]);
gi += 1;
} else {
out.push(extras[ei]);
ei += 1;
}
}
out
}
fn reference_class_for(table: &[usize], size: usize, align: usize) -> Option<usize> {
let need = size.max(align);
table
.iter()
.position(|&b| b >= need && b.is_multiple_of(align))
}
#[cfg(target_pointer_width = "64")]
#[test]
fn reference_table_does_not_overcompute_at_the_geo_count_182_boundary() {
const GEO_COUNT: usize = 182;
const N: usize = GEO_COUNT;
let params = Params::new(16, (5, 4), GEO_COUNT, &[], 1 << 20);
let want = reference_table(16, (5, 4), GEO_COUNT, &[]);
let got = build_table::<N>(params);
assert_eq!(&got[..], &want[..]);
}
#[test]
fn sefer_table_matches_reference_and_is_strictly_increasing() {
let want = reference_table(SEFER_MIN_BLOCK, (5, 4), SEFER_GEO, SEFER_EXTRAS);
assert_eq!(&SEFER_TABLE[..], &want[..]);
assert_eq!(SEFER_SC.count(), SEFER_N);
assert_eq!(SEFER_SC.min_block(), SEFER_MIN_BLOCK);
assert_eq!(SEFER_SC.small_align_max(), SEFER_MIN_BLOCK); assert_eq!(SEFER_SC.huge_threshold(), HUGE_THRESHOLD);
for w in SEFER_TABLE.windows(2) {
assert!(w[0] < w[1], "table must be strictly increasing: {w:?}");
}
for &b in &SEFER_TABLE {
assert!(
b.is_multiple_of(SEFER_MIN_BLOCK),
"class {b} not a multiple of min_block"
);
}
for &e in SEFER_EXTRAS {
assert!(SEFER_TABLE.contains(&e), "extra {e} missing from table");
}
}
#[test]
fn sefer_class_for_matches_reference_over_full_small_sweep() {
let mut aligns = vec![1usize, 2, 4, 8, 16];
let mut a = 32;
while a <= SEFER_MAX {
aligns.push(a);
a <<= 1;
}
aligns.push(a);
const SMALL_STEP_CEIL: usize = 8192;
let mut boundary_points: Vec<usize> = Vec::new();
for &b in SEFER_TABLE.iter() {
boundary_points.push(b.saturating_sub(1));
boundary_points.push(b);
boundary_points.push(b.saturating_add(1));
}
for &al in &aligns {
boundary_points.push(al.saturating_sub(1));
boundary_points.push(al);
boundary_points.push(al.saturating_add(1));
}
boundary_points.retain(|&s| (1..=SEFER_MAX + 1).contains(&s) && s > SMALL_STEP_CEIL);
boundary_points.sort_unstable();
boundary_points.dedup();
let mut sizes: Vec<usize> = (1..=SMALL_STEP_CEIL)
.chain(boundary_points)
.chain((SMALL_STEP_CEIL + 1..=SEFER_MAX + 1).step_by(SEFER_MIN_BLOCK))
.collect();
sizes.sort_unstable();
sizes.dedup();
for &align in &aligns {
for &size in &sizes {
let got = SEFER_SC.class_for(size, align);
let want = reference_class_for(&SEFER_TABLE, size, align);
assert_eq!(got, want, "drift at size={size} align={align}");
if let Some(idx) = got {
let block = SEFER_TABLE[idx];
assert!(block >= size.max(align));
assert!(block.is_multiple_of(align));
}
assert_eq!(SEFER_SC.try_class_for(size, align), Ok(got));
}
}
}
#[test]
fn sefer_size2class_matches_scan_for_every_bucket() {
let s2c = SEFER_SC.size2class();
for (k, &class_idx) in s2c.iter().enumerate() {
let need = ((k + 1) * SEFER_MIN_BLOCK).min(SEFER_MAX);
let want = SEFER_TABLE.iter().position(|&b| b >= need).unwrap();
assert_eq!(
class_idx as usize, want,
"SIZE2CLASS[{k}] drift (need={need})"
);
}
}
const DOMAIN_MB: usize = 16;
const DOMAIN_N: usize = 3;
const DOMAIN_P: Params = Params::new(DOMAIN_MB, (2, 1), DOMAIN_N, &[], 1 << 20);
const DOMAIN_T: [usize; DOMAIN_N] = build_table::<DOMAIN_N>(DOMAIN_P);
const DOMAIN_L: usize = size2class_len(DOMAIN_T[DOMAIN_N - 1], DOMAIN_MB);
static DOMAIN_SC: SizeClasses<DOMAIN_N, DOMAIN_L> = SizeClasses::build(DOMAIN_P);
#[test]
fn size2class_raw_domain_valid_and_false_sentinel_zones() {
assert_eq!(DOMAIN_T, [16, 32, 64]);
assert_eq!(DOMAIN_L, 5);
let s2c = DOMAIN_SC.size2class();
for size in 1..=64usize {
let idx = (size - 1) >> DOMAIN_SC.min_block_shift();
let raw = s2c[idx] as usize;
assert_eq!(
Some(raw),
DOMAIN_SC.class_for(size, 1),
"size={size} raw lookup must agree with class_for in the valid domain"
);
}
for size in 65..=80usize {
let idx = (size - 1) >> DOMAIN_SC.min_block_shift();
assert_eq!(idx, DOMAIN_L - 1, "size={size} must land on the top bucket");
let raw = s2c[idx] as usize;
assert_eq!(
raw,
DOMAIN_N - 1,
"size={size} sentinel must be the last class"
);
assert_eq!(
DOMAIN_SC.class_for(size, 1),
None,
"class_for must correctly reject size={size}, unlike the raw sentinel"
);
}
}
#[test]
fn debug_impl_prints_a_summary_not_the_raw_tables() {
let s = format!("{DOMAIN_SC:?}");
assert!(s.contains("SizeClasses"), "got: {s}");
assert!(s.contains(&format!("min_block: {DOMAIN_MB}")), "got: {s}");
assert!(
s.contains(&format!("small_max: {}", DOMAIN_T[DOMAIN_N - 1])),
"got: {s}"
);
assert!(
!s.contains("table:"),
"must not print the raw table field: {s}"
);
assert!(
!s.contains("size2class:"),
"must not print the raw LUT field: {s}"
);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn size2class_raw_domain_first_out_of_bounds_size_panics() {
let size = 81usize;
let idx = (size - 1) >> DOMAIN_SC.min_block_shift();
assert_eq!(
idx, DOMAIN_L,
"precondition: this size must compute idx == L"
);
let _ = DOMAIN_SC.size2class()[idx];
}
#[test]
fn sefer_jump_skips_non_divisible_run_for_align_128() {
let got = SEFER_SC.class_for(128, 128).expect("(128,128) resolves");
let block = SEFER_TABLE[got];
assert!(block.is_multiple_of(128));
assert!(block >= 128);
let seed = SEFER_SC.size2class()[(128 - 1) >> SEFER_MIN_BLOCK.trailing_zeros()] as usize;
assert!(!SEFER_TABLE[seed].is_multiple_of(128));
}
#[test]
fn sefer_need_zero_underflow_index_lands_past_the_seed_range() {
let shift = SEFER_SC.min_block_shift();
let idx = 0usize.wrapping_sub(1) >> shift;
assert!(idx >= SEFER_L - 1);
}
#[test]
fn class_for_slow_path_rejects_next_mult_landing_exactly_on_the_l_minus_1_boundary() {
const MIN_BLOCK: usize = 16;
const EXTRAS: &[usize] = &[32, 48];
const N: usize = 3;
const PARAMS: Params = Params::new(MIN_BLOCK, (5, 4), 1, EXTRAS, 1 << 20);
const TABLE: [usize; N] = build_table::<N>(PARAMS);
const L: usize = size2class_len(TABLE[N - 1], MIN_BLOCK);
static SC: SizeClasses<N, L> = SizeClasses::build(PARAMS);
assert_eq!(
TABLE,
[16, 32, 48],
"precondition: this test needs this exact table"
);
assert_eq!(L, 4, "precondition: this test needs this exact L");
assert_eq!(SC.class_for(48, 32), None);
}
fn simulate_jump_loop(seed: usize, align: usize) -> (usize, Option<usize>) {
let shift = SEFER_MIN_BLOCK.trailing_zeros();
let small_max = *SEFER_TABLE.last().unwrap();
let mut i = seed;
let mut iters = 0usize;
while i < SEFER_TABLE.len() {
iters += 1;
let block = SEFER_TABLE[i];
if block.is_multiple_of(align) {
return (iters, Some(i));
}
let next_mult = (block | (align - 1)) + 1;
if next_mult > small_max {
return (iters, None);
}
i = SEFER_SC.size2class()[(next_mult - 1) >> shift] as usize;
}
(iters, None)
}
#[test]
fn sefer_bench_jump_rows_genuinely_exercise_the_slow_path() {
for &(size, align, want_iters, want) in &[
(JUMP_A.0, JUMP_A.1, 4usize, Some(21usize)),
(JUMP_B.0, JUMP_B.1, 3, Some(25)),
(JUMP_MULTI.0, JUMP_MULTI.1, 2, Some(17)),
(JUMP_DENSE.0, JUMP_DENSE.1, 2, Some(9)),
(JUMP_NONE.0, JUMP_NONE.1, 10, None),
] {
let need = size.max(align);
let small_max = *SEFER_TABLE.last().unwrap();
assert!(
need <= small_max,
"size={size} align={align}: must not be early-rejected"
);
let seed = SEFER_SC.size2class()[(need - 1) >> SEFER_MIN_BLOCK.trailing_zeros()] as usize;
assert!(
!SEFER_TABLE[seed].is_multiple_of(align),
"size={size} align={align}: seed class {} (block {}) is already \
align-divisible -- the jump loop's round-up body would never run",
seed,
SEFER_TABLE[seed]
);
let (iters, result) = simulate_jump_loop(seed, align);
assert_eq!(
iters, want_iters,
"size={size} align={align}: jump loop took {iters} iteration(s), \
expected {want_iters}"
);
assert_eq!(
result, want,
"size={size} align={align}: simulated result drift"
);
assert_eq!(
SEFER_SC.class_for(size, align),
want,
"size={size} align={align}: class_for disagrees with the simulation"
);
if let Some(got) = result {
assert!(SEFER_TABLE[got].is_multiple_of(align));
assert!(SEFER_TABLE[got] >= need);
}
}
}
#[test]
#[should_panic(expected = "multiple of min_block")]
fn extras_not_multiple_of_min_block_panics() {
const MIN_BLOCK: usize = 16;
const EXTRAS: &[usize] = &[100, 200];
const GEO_COUNT: usize = 8;
const N: usize = GEO_COUNT + EXTRAS.len();
let params = Params::new(MIN_BLOCK, (5, 4), GEO_COUNT, EXTRAS, 1 << 20);
let _ = build_table::<N>(params);
}
#[test]
#[should_panic(expected = "build_table: merged table must be strictly increasing")]
fn extras_overlapping_geometric_run_panics_in_build_table() {
const MIN_BLOCK: usize = 16;
const EXTRAS: &[usize] = &[16, 32];
const GEO_COUNT: usize = 8;
const N: usize = GEO_COUNT + EXTRAS.len();
let params = Params::new(MIN_BLOCK, (5, 4), GEO_COUNT, EXTRAS, 1 << 20);
let _ = build_table::<N>(params);
}
#[test]
#[should_panic(expected = "table must be strictly increasing (hand-built tables must satisfy")]
fn hand_built_overlapping_table_panics_in_build_size2class() {
const MIN_BLOCK: usize = 16;
const N: usize = 3;
const TABLE: [usize; N] = [16, 16, 32];
const L: usize = size2class_len(32, MIN_BLOCK);
let _ = build_size2class::<N, L>(&TABLE, MIN_BLOCK);
}
#[test]
fn hand_built_table_with_a_non_min_block_multiple_entry_leaves_it_unreachable() {
const MIN_BLOCK: usize = 16;
const N: usize = 3;
const TABLE: [usize; N] = [16, 24, 32];
const L: usize = size2class_len(32, MIN_BLOCK);
let s2c = build_size2class::<N, L>(&TABLE, MIN_BLOCK);
assert_eq!(s2c, [0, 2, 2]);
}
#[test]
#[should_panic(expected = "every entry must be >= min_block")]
fn extras_zero_class_panics() {
const MIN_BLOCK: usize = 16;
const EXTRAS: &[usize] = &[0];
const GEO_COUNT: usize = 8;
const N: usize = GEO_COUNT + EXTRAS.len();
let params = Params::new(MIN_BLOCK, (5, 4), GEO_COUNT, EXTRAS, 1 << 20);
let _ = build_table::<N>(params);
}
#[test]
#[should_panic(expected = "size2class_len: min_block must be a power of two")]
fn size2class_len_rejects_non_pow2_min_block() {
let _ = size2class_len(64, 12);
}
#[test]
#[should_panic(expected = "min_block must be a power of two")]
fn build_table_rejects_non_pow2_min_block() {
let params = Params::new(12, (5, 4), 1, &[], 1 << 20);
let _ = build_table::<1>(params);
}
#[test]
#[should_panic(expected = "min_block must be a power of two")]
fn build_size2class_rejects_non_pow2_min_block() {
const TABLE: [usize; 3] = [16, 32, 48];
let _ = build_size2class::<3, 4>(&TABLE, 12);
}
#[test]
#[should_panic(expected = "geo_count must be > 0")]
fn build_table_rejects_zero_geo_count() {
let params = Params::new(16, (5, 4), 0, &[16, 32], 1 << 20);
let _ = build_table::<2>(params);
}
#[test]
#[should_panic(expected = "growth denominator must be > 0")]
fn build_table_rejects_zero_growth_denominator() {
let params = Params::new(16, (1, 0), 1, &[], 1 << 20);
let _ = build_table::<1>(params);
}
#[test]
#[should_panic(expected = "N must equal geo_count + extras.len()")]
fn build_table_rejects_n_mismatch() {
let params = Params::new(16, (5, 4), 3, &[64], 1 << 20);
let _ = build_table::<5>(params);
}
#[test]
#[should_panic(expected = "Params::extras: must be strictly increasing")]
fn build_table_rejects_non_increasing_extras_among_themselves() {
let params = Params::new(16, (5, 4), 4, &[64, 32], 1 << 20);
let _ = build_table::<6>(params);
}
#[test]
#[should_panic(expected = "table must be non-empty")]
fn build_size2class_rejects_empty_table() {
const TABLE: [usize; 0] = [];
let _ = build_size2class::<0, 1>(&TABLE, 16);
}
#[test]
#[should_panic(expected = "L must equal size2class_len(max_class, min_block)")]
fn build_size2class_rejects_wrong_l() {
const TABLE: [usize; 3] = [16, 32, 48];
let _ = build_size2class::<3, 5>(&TABLE, 16);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn block_size_rejects_out_of_range_index() {
let _ = DOMAIN_SC.block_size(DOMAIN_N);
}
#[test]
fn geometric_run_matches_hand_derived_golden_values() {
const GOLDEN: [usize; 8] = [16, 32, 48, 64, 80, 112, 144, 192];
const MIN_BLOCK: usize = 16;
const GEO_COUNT: usize = 8;
const P: Params = Params::new(MIN_BLOCK, (5, 4), GEO_COUNT, &[], 1 << 20);
const T: [usize; GEO_COUNT] = build_table::<GEO_COUNT>(P);
assert_eq!(
T, GOLDEN,
"geometric run drifted from the hand-derived golden values"
);
}
#[test]
#[should_panic(expected = "geometric progression overflows usize")]
fn geometric_advance_overflow_panics_instead_of_silently_wrapping() {
const MIN_BLOCK: usize = 1usize << (usize::BITS - 1);
const GEO_COUNT: usize = 2;
const N: usize = GEO_COUNT;
let params = Params::new(MIN_BLOCK, (2, 1), GEO_COUNT, &[], 1 << 20);
let _ = build_table::<N>(params);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn sefer_growth_geo_count_182_is_the_last_that_fits_on_64_bit() {
const GEO_COUNT: usize = 182;
const N: usize = GEO_COUNT;
let params = Params::new(16, (5, 4), GEO_COUNT, &[], 1 << 20);
let _ = build_table::<N>(params);
}
#[cfg(target_pointer_width = "64")]
#[test]
#[should_panic(expected = "geometric progression overflows usize")]
fn sefer_growth_geo_count_183_overflows_on_64_bit() {
const GEO_COUNT: usize = 183;
const N: usize = GEO_COUNT;
let params = Params::new(16, (5, 4), GEO_COUNT, &[], 1 << 20);
let _ = build_table::<N>(params);
}
#[cfg(target_pointer_width = "32")]
#[test]
fn sefer_growth_geo_count_83_is_the_last_that_fits_on_32_bit() {
const GEO_COUNT: usize = 83;
const N: usize = GEO_COUNT;
let params = Params::new(16, (5, 4), GEO_COUNT, &[], 1 << 20);
let _ = build_table::<N>(params);
}
#[cfg(target_pointer_width = "32")]
#[test]
#[should_panic(expected = "geometric progression overflows usize")]
fn sefer_growth_geo_count_84_overflows_on_32_bit() {
const GEO_COUNT: usize = 84;
const N: usize = GEO_COUNT;
let params = Params::new(16, (5, 4), GEO_COUNT, &[], 1 << 20);
let _ = build_table::<N>(params);
}
#[test]
#[should_panic(expected = "geometric progression overflows usize")]
fn min_step_fallback_overflow_panics_instead_of_silently_wrapping() {
const MIN_BLOCK: usize = 1usize << (usize::BITS - 1);
const GEO_COUNT: usize = 2;
const N: usize = GEO_COUNT;
let params = Params::new(MIN_BLOCK, (0, 1), GEO_COUNT, &[], 1 << 20);
let _ = build_table::<N>(params);
}
#[test]
#[should_panic(expected = "size2class_len: max_class / min_block + 1 overflows usize")]
fn size2class_len_overflow_panics_instead_of_silently_wrapping() {
let _ = size2class_len(usize::MAX, 1);
}
#[test]
#[should_panic(expected = "size2class_len: max_class / min_block + 1 overflows usize")]
fn build_size2class_l_check_overflow_panics_instead_of_accepting_a_wrong_l() {
let table: [usize; 1] = [usize::MAX];
let _ = build_size2class::<1, 0>(&table, 1);
}
#[cfg(target_pointer_width = "64")]
mod extreme64_overflow {
use super::*;
const EXTREME64_MIN_BLOCK: usize = 1usize << 62;
const EXTREME64_GEO_COUNT: usize = 1;
const EXTREME64_EXTRAS: &[usize] = &[2 << 62, 3 << 62];
const EXTREME64_N: usize = EXTREME64_GEO_COUNT + EXTREME64_EXTRAS.len();
const EXTREME64_SMALL_MAX: usize = 3 << 62;
const EXTREME64_L: usize = size2class_len(EXTREME64_SMALL_MAX, EXTREME64_MIN_BLOCK);
const EXTREME64_PARAMS: Params = Params::new(
EXTREME64_MIN_BLOCK,
(5, 4),
EXTREME64_GEO_COUNT,
EXTREME64_EXTRAS,
1 << 20,
);
const EXTREME64_SC: SizeClasses<EXTREME64_N, EXTREME64_L> =
SizeClasses::build(EXTREME64_PARAMS);
fn extreme64_scheme_runtime() -> SizeClasses<EXTREME64_N, EXTREME64_L> {
SizeClasses::build(EXTREME64_PARAMS)
}
#[test]
fn build_size2class_bucket_need_overflow_clamps_to_last_class() {
let rt = extreme64_scheme_runtime();
assert_eq!(EXTREME64_SC.table(), &[1usize << 62, 2 << 62, 3 << 62]);
assert_eq!(rt.table(), EXTREME64_SC.table());
assert_eq!(rt.size2class(), EXTREME64_SC.size2class());
assert_eq!(
rt.size2class()[EXTREME64_L - 1],
(EXTREME64_N - 1) as u8,
"top bucket must clamp to the last class"
);
assert_eq!(
EXTREME64_SC.size2class()[EXTREME64_L - 1],
(EXTREME64_N - 1) as u8
);
let max_multiplier = EXTREME64_SMALL_MAX / EXTREME64_MIN_BLOCK; for (k, &class_idx) in rt.size2class().iter().enumerate() {
let need = (k + 1).min(max_multiplier) * EXTREME64_MIN_BLOCK;
let want = rt.table().iter().position(|&b| b >= need).unwrap();
assert_eq!(
class_idx as usize, want,
"SIZE2CLASS[{k}] drift (need={need})"
);
}
}
#[test]
fn raw_index_via_shift_avoids_the_overflowing_l_times_min_block_bound() {
let shift = EXTREME64_SC.min_block_shift();
for size in [EXTREME64_SMALL_MAX + 1, usize::MAX] {
let idx = size.checked_sub(1).expect("size > 0 here") >> shift;
assert_eq!(
idx,
EXTREME64_L - 1,
"size={size} must land on the sentinel bucket without computing L * min_block"
);
assert_eq!(
EXTREME64_SC.size2class()[idx] as usize,
EXTREME64_N - 1,
"size={size} sentinel must resolve to the last class"
);
}
}
#[test]
fn class_for_next_multiple_overflow_returns_none() {
let sc = extreme64_scheme_runtime();
assert_eq!(sc.class_for(2 << 62, 1 << 63), Some(1));
assert_eq!(sc.class_for(3 << 62, 1 << 63), None);
}
#[test]
fn build_size2class_bucket_need_overflow_flips_the_release_answer_for_a_hand_built_table() {
const MIN_BLOCK: usize = 1usize << 62;
const N: usize = 4;
const TABLE: [usize; N] = [1 << 62, 2 << 62, (3 << 62) + 2, (3 << 62) + 5];
const SMALL_MAX: usize = TABLE[N - 1];
const L: usize = size2class_len(SMALL_MAX, MIN_BLOCK);
let s2c = build_size2class::<N, L>(&TABLE, MIN_BLOCK);
assert_eq!(
s2c[L - 1],
(N - 1) as u8,
"top bucket must resolve to the last class, not the pointer's pre-overflow position"
);
for (k, &class_idx) in s2c.iter().enumerate() {
let need_u128 = (k as u128 + 1) * MIN_BLOCK as u128;
let want_need = if need_u128 < SMALL_MAX as u128 {
need_u128 as usize
} else {
SMALL_MAX
};
let want = TABLE.iter().position(|&b| b >= want_need).unwrap();
assert_eq!(class_idx as usize, want, "size2class[{k}] drift");
}
}
#[test]
fn need_zero_underflow_index_never_lands_inside_the_seed_range() {
let shift = EXTREME64_SC.min_block_shift();
let idx = 0usize.wrapping_sub(1) >> shift;
assert_eq!(
shift, 62,
"precondition: this test needs the tightest shift"
);
assert_eq!(
idx,
EXTREME64_L - 1,
"need=0 underflow index must land exactly on the L-1 boundary for this scheme"
);
assert!(idx >= EXTREME64_L - 1);
}
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "align must be a power of two")]
fn class_for_non_pow2_align_violates_debug_assert() {
const MIN_BLOCK: usize = 16;
const N: usize = 4;
const P: Params = Params::new(MIN_BLOCK, (5, 4), N, &[], 1 << 20);
const T: [usize; N] = build_table::<N>(P);
const L: usize = size2class_len(T[N - 1], MIN_BLOCK);
const SC: SizeClasses<N, L> = SizeClasses::build(P);
let _ = SC.class_for(32, 6);
}
#[test]
fn try_class_for_matches_class_for_on_every_valid_input() {
for &(size, align) in &[(1usize, 1usize), (200, 16), (1025, 256), (2049, 1024)] {
assert_eq!(
SEFER_SC.try_class_for(size, align),
Ok(SEFER_SC.class_for(size, align)),
"size={size}, align={align}"
);
}
}
#[test]
fn try_class_for_rejects_non_pow2_align() {
assert_eq!(SEFER_SC.try_class_for(32, 6), Err(InvalidAlign(6)));
}
#[test]
fn try_class_for_rejects_zero_align_without_panicking() {
assert_eq!(SEFER_SC.try_class_for(0, 0), Err(InvalidAlign(0)));
}
#[test]
fn invalid_align_display_names_the_offending_value() {
let msg = InvalidAlign(6).to_string();
assert!(msg.contains('6'), "got: {msg}");
}
const _: fn() = || {
fn assert_error<T: core::error::Error>() {}
fn assert_clone<T: Clone>() {}
assert_error::<InvalidAlign>();
assert_clone::<SizeClasses<1, 1>>();
};
#[test]
fn is_huge_uses_the_policy_threshold_not_an_os_constant() {
const P_SMALL: Params = Params::new(16, (5, 4), 4, &[], 1024);
const N: usize = 4;
const T: [usize; N] = build_table::<N>(P_SMALL);
const L: usize = size2class_len(T[N - 1], 16);
const SC: SizeClasses<N, L> = SizeClasses::build(P_SMALL);
assert!(SC.is_huge(1024));
assert!(SC.is_huge(4096));
assert!(!SC.is_huge(1023));
const P_LARGE_THRESHOLD: Params = Params::new(16, (5, 4), 4, &[], 4096);
const T2: [usize; N] = build_table::<N>(P_LARGE_THRESHOLD);
const L2: usize = size2class_len(T2[N - 1], 16);
const SC2: SizeClasses<N, L2> = SizeClasses::build(P_LARGE_THRESHOLD);
const PROBE: usize = 2048;
assert!(
SC.is_huge(PROBE),
"SC (threshold 1024) must call {PROBE} huge"
);
assert!(
!SC2.is_huge(PROBE),
"SC2 (threshold 4096) must NOT call {PROBE} huge -- same size, opposite \
verdict across the two schemes proves is_huge is genuinely parameterized \
by Params::huge_threshold, not hardcoded"
);
}
const MAX_N: usize = 256;
const MAX_PARAMS: Params = Params::new(1, (0, 1), MAX_N, &[], 1 << 20);
const MAX_TABLE: [usize; MAX_N] = build_table::<MAX_N>(MAX_PARAMS);
const MAX_L: usize = size2class_len(MAX_TABLE[MAX_N - 1], 1);
#[test]
fn exactly_256_classes_build_and_index_up_to_255() {
assert_eq!(MAX_TABLE[0], 1);
assert_eq!(MAX_TABLE[MAX_N - 1], 256);
assert_eq!(MAX_L, 257);
let s2c = build_size2class::<MAX_N, MAX_L>(&MAX_TABLE, 1);
assert_eq!(
s2c[MAX_L - 2],
u8::MAX,
"bucket for the largest in-range size must resolve to class 255"
);
assert_eq!(
s2c[MAX_L - 1],
u8::MAX,
"top bucket clamps to the last class"
);
for (k, &class_idx) in s2c.iter().enumerate() {
let need = (k + 1).min(MAX_TABLE[MAX_N - 1]);
let want = MAX_TABLE.iter().position(|&b| b >= need).unwrap();
assert_eq!(class_idx as usize, want, "size2class[{k}] drift");
}
}
#[test]
fn exactly_256_classes_class_for_fast_and_slow_paths() {
static MAX_SC: SizeClasses<MAX_N, MAX_L> = SizeClasses::build(MAX_PARAMS);
assert_eq!(MAX_SC.min_block_shift(), 0);
assert_eq!(MAX_SC.count(), MAX_N);
assert_eq!(&MAX_TABLE[..], &reference_table(1, (0, 1), MAX_N, &[])[..]);
assert_eq!(MAX_SC.class_for(1, 1), Some(0));
assert_eq!(MAX_SC.class_for(137, 1), Some(136));
assert_eq!(MAX_SC.class_for(256, 1), Some(255));
assert_eq!(MAX_SC.class_for(0, 1), Some(0));
assert_eq!(MAX_SC.class_for(257, 1), None);
assert_eq!(MAX_SC.class_for(257, 2), None);
assert_eq!(MAX_SC.class_for(1, 2), Some(1));
assert_eq!(MAX_SC.class_for(3, 2), Some(3));
assert_eq!(MAX_SC.class_for(5, 4), Some(7));
assert_eq!(MAX_SC.class_for(100, 64), Some(127));
assert_eq!(MAX_SC.class_for(255, 2), Some(255));
for align in [1usize, 2, 4, 8, 16, 32, 64, 128, 256] {
for size in 0..=257usize {
let got = MAX_SC.class_for(size, align);
assert_eq!(
got,
reference_class_for(&MAX_TABLE, size, align),
"drift at size={size} align={align}"
);
if let Some(idx) = got {
let block = MAX_TABLE[idx];
assert!(block >= size.max(align), "size={size} align={align}");
assert!(block.is_multiple_of(align));
}
}
}
}
#[test]
#[should_panic(expected = "the class count must not exceed 256")]
fn exactly_257_classes_are_rejected() {
const N: usize = 257;
const PARAMS: Params = Params::new(1, (0, 1), N, &[], 1 << 20);
let table = build_table::<N>(PARAMS);
let _ = build_size2class::<N, 258>(&table, 1);
}
#[test]
#[cfg(target_pointer_width = "64")]
fn representable_next_class_survives_an_unrepresentable_intermediate_product() {
const MIN_BLOCK: usize = 1usize << 62;
const N: usize = 3;
const PARAMS: Params = Params::new(MIN_BLOCK, (3, 3), N, &[], 1 << 20);
const TABLE: [usize; N] = build_table::<N>(PARAMS);
assert_eq!(TABLE, [1usize << 62, 1usize << 63, 3usize << 62]);
for w in TABLE.windows(2) {
assert!(w[0] < w[1], "table must be strictly increasing: {w:?}");
}
}
#[test]
#[cfg(target_pointer_width = "64")]
#[should_panic(expected = "geometric progression overflows usize")]
fn a_genuinely_unrepresentable_next_class_still_panics() {
const MIN_BLOCK: usize = 1usize << 63;
const N: usize = 2;
const PARAMS: Params = Params::new(MIN_BLOCK, (2, 1), N, &[], 1 << 20);
let _ = build_table::<N>(PARAMS);
}
#[test]
fn extras_interleaving_the_geometric_run_is_accepted_and_preserved() {
const MIN_BLOCK: usize = 16;
const GEO_COUNT: usize = 6;
const EXTRAS: &[usize] = &[96];
const N: usize = GEO_COUNT + EXTRAS.len();
const PARAMS: Params = Params::new(MIN_BLOCK, (5, 4), GEO_COUNT, EXTRAS, 1 << 20);
const TABLE: [usize; N] = build_table::<N>(PARAMS);
assert_eq!(TABLE, [16, 32, 48, 64, 80, 96, 112]);
assert!(
TABLE.contains(&96),
"an interleaving extra must survive the merge, not be dropped"
);
for w in TABLE.windows(2) {
assert!(w[0] < w[1], "merged table must stay strictly increasing");
}
const L: usize = size2class_len(TABLE[N - 1], MIN_BLOCK);
const SC: SizeClasses<N, L> = SizeClasses::build(PARAMS);
let idx = SC.class_for(81, 1).expect("81 B resolves");
assert_eq!(
SC.block_size(idx),
96,
"a size in the gap the extra fills must resolve TO that extra"
);
}
#[test]
fn readme_example_compiles_and_derives_its_generics() {
const MIN_BLOCK: usize = 16;
const GEO_COUNT: usize = 40;
const EXTRAS: &[usize] = &[256, 512, 1024, 2048, 4096];
const PARAMS: Params = Params::new(MIN_BLOCK, (5, 4), GEO_COUNT, EXTRAS, 4 << 20);
const N: usize = GEO_COUNT + EXTRAS.len();
const TABLE: [usize; N] = build_table::<N>(PARAMS);
const L: usize = size2class_len(TABLE[N - 1], MIN_BLOCK);
static SC: SizeClasses<N, L> = SizeClasses::build(PARAMS);
assert_eq!(SC.count(), N);
assert_eq!(SC.small_max(), TABLE[N - 1]);
let class = SC.try_class_for(100, 8).unwrap().unwrap();
assert!(SC.block_size(class) >= 100);
assert!(matches!(SC.try_class_for(100, 3), Err(InvalidAlign(3))));
}
#[test]
fn readme_example_lines_appear_verbatim_in_readme_md() {
let readme = include_str!("../README.md");
let declaration_lines = [
"use size_classes::{build_table, size2class_len, InvalidAlign, Params, SizeClasses};",
"const MIN_BLOCK: usize = 16;",
"const GEO_COUNT: usize = 40;",
"const EXTRAS: &[usize] = &[256, 512, 1024, 2048, 4096];",
"const PARAMS: Params = Params::new(MIN_BLOCK, (5, 4), GEO_COUNT, EXTRAS, 4 << 20);",
"const N: usize = GEO_COUNT + EXTRAS.len();",
"const TABLE: [usize; N] = build_table::<N>(PARAMS);",
"const L: usize = size2class_len(TABLE[N - 1], MIN_BLOCK);",
"static SC: SizeClasses<N, L> = SizeClasses::build(PARAMS);",
"fn demo() {",
" let class = SC.try_class_for(100, 8).unwrap().unwrap();",
" assert!(SC.block_size(class) >= 100);",
" assert!(matches!(SC.try_class_for(100, 3), Err(InvalidAlign(3))));",
"}",
];
for line in declaration_lines {
assert!(
readme.contains(line),
"README.md's example no longer contains the line `{line}` -- \
it has drifted from the mirrored copy in \
readme_example_compiles_and_derives_its_generics above"
);
}
}