use super::*;
use crate::audio::lid::frame_count;
fn reference_spans(plan: &WindowPlan, total: usize) -> Vec<(usize, usize)> {
let (window, hop) = (plan.window_samples() as usize, plan.hop_samples() as usize);
if total == 0 {
return Vec::new();
}
if total <= window {
return vec![(0, total)];
}
let mut out = Vec::new();
let mut start = 0;
while start + window <= total {
out.push((start, window));
start += hop;
}
let last_full_start = start - hop;
if last_full_start + window < total {
match plan.tail_policy() {
TailPolicy::Drop => {}
TailPolicy::SlideBack => out.push((total - window, window)),
TailPolicy::Partial => {
let tail_start = last_full_start + hop;
let len = total - tail_start;
if len >= MIN_SAMPLES {
out.push((tail_start, len));
}
}
}
}
out
}
fn materialized(plan: &WindowPlan, total: usize) -> Vec<(usize, usize)> {
plan
.spans(total)
.expect("plan must be admissible")
.into_iter()
.map(|s| (s.start(), s.len()))
.collect()
}
#[test]
fn defaults_are_the_documented_geometry() {
assert_eq!(DEFAULT_WINDOW_SAMPLES, 160_000);
assert_eq!(DEFAULT_HOP_SAMPLES, DEFAULT_WINDOW_SAMPLES);
assert_eq!(DEFAULT_MAX_WINDOWS, 100_000);
let plan = WindowPlan::new();
assert_eq!(plan, WindowPlan::default());
assert_eq!(plan.window_samples(), DEFAULT_WINDOW_SAMPLES);
assert_eq!(plan.hop_samples(), DEFAULT_HOP_SAMPLES);
assert_eq!(plan.tail_policy(), TailPolicy::SlideBack);
assert_eq!(plan.max_windows(), DEFAULT_MAX_WINDOWS);
assert_eq!(frame_count(DEFAULT_WINDOW_SAMPLES as usize), 1_001);
assert_eq!(DEFAULT_WINDOW_SAMPLES as usize % 16_000, 0);
}
#[test]
fn tail_policy_default_matches_the_plan_default() {
assert_eq!(TailPolicy::default(), WindowPlan::new().tail_policy());
}
#[test]
fn a_clip_within_one_window_is_a_single_full_coverage_span() {
for tail in [TailPolicy::SlideBack, TailPolicy::Partial, TailPolicy::Drop] {
let plan = WindowPlan::new()
.with_geometry(160_000, 40_000)
.with_tail_policy(tail);
for total in [MIN_SAMPLES, 50_000, 159_999, 160_000] {
let spans = plan.spans(total).expect("admissible");
assert_eq!(spans.len(), 1, "{tail:?} at {total}");
assert_eq!(spans[0].start(), 0);
assert_eq!(spans[0].len(), total);
assert_eq!(spans[0].window(), 160_000);
}
}
assert!(WindowPlan::new().spans(0).expect("admissible").is_empty());
}
#[test]
fn the_three_tail_policies_differ_only_in_the_final_span() {
let base = WindowPlan::new().with_geometry(100_000, 100_000);
let total = 250_000;
assert_eq!(
materialized(&base.with_tail_policy(TailPolicy::SlideBack), total),
[(0, 100_000), (100_000, 100_000), (150_000, 100_000)],
"SlideBack ends flush with the clip, full length, overlapping by 50 000"
);
assert_eq!(
materialized(&base.with_tail_policy(TailPolicy::Partial), total),
[(0, 100_000), (100_000, 100_000), (200_000, 50_000)],
"Partial scores the leftover at its own length"
);
assert_eq!(
materialized(&base.with_tail_policy(TailPolicy::Drop), total),
[(0, 100_000), (100_000, 100_000)],
"Drop discards the leftover"
);
}
#[test]
fn an_exact_fit_produces_no_tail_under_any_policy() {
let base = WindowPlan::new().with_geometry(100_000, 50_000);
for total in [100_000, 150_000, 200_000, 500_000] {
let expected = materialized(&base.with_tail_policy(TailPolicy::Drop), total);
for tail in [TailPolicy::SlideBack, TailPolicy::Partial] {
assert_eq!(
materialized(&base.with_tail_policy(tail), total),
expected,
"{tail:?} at {total} must not add a span over already-covered audio"
);
}
}
}
#[test]
fn a_partial_tail_shorter_than_the_graph_accepts_is_dropped() {
let plan = WindowPlan::new()
.with_geometry(100_000, 100_000)
.with_tail_policy(TailPolicy::Partial);
let short = materialized(&plan, 100_000 + MIN_SAMPLES - 1);
assert_eq!(short, [(0, 100_000)]);
let kept = materialized(&plan, 100_000 + MIN_SAMPLES);
assert_eq!(kept, [(0, 100_000), (100_000, MIN_SAMPLES)]);
assert_eq!(frame_count(MIN_SAMPLES), crate::audio::lid::MIN_FRAMES);
}
#[test]
fn every_planned_span_is_a_scoreable_length() {
for window in [
MIN_SAMPLES as u32,
48_000,
DEFAULT_WINDOW_SAMPLES,
MAX_SAMPLES as u32,
] {
for hop_div in [1, 2, 3] {
for tail in [TailPolicy::SlideBack, TailPolicy::Partial, TailPolicy::Drop] {
let plan = WindowPlan::new()
.with_geometry(window, window / hop_div)
.with_tail_policy(tail);
for total in [
MIN_SAMPLES,
window as usize,
window as usize + 1,
window as usize * 3 + 7,
1_000_000,
] {
for span in plan.spans(total).expect("admissible") {
let frames = frame_count(span.len());
assert!(
(crate::audio::lid::MIN_FRAMES..=crate::audio::lid::MAX_FRAMES).contains(&frames),
"{tail:?} window {window} hop {} total {total}: span {span:?} is {frames} frames",
window / hop_div
);
assert!(span.end() <= total, "span {span:?} runs past {total}");
}
}
}
}
}
}
#[test]
fn coverage_matches_what_each_policy_promises() {
for hop_div in [1, 2, 3] {
let window = 100_000u32;
let hop = window / hop_div;
for total in [100_001, 123_456, 250_000, 400_000, 999_999] {
let covered_to = |tail| {
let plan = WindowPlan::new()
.with_geometry(window, hop)
.with_tail_policy(tail);
plan
.spans(total)
.expect("admissible")
.iter()
.map(Span::end)
.max()
.expect("non-empty")
};
assert_eq!(
covered_to(TailPolicy::SlideBack),
total,
"SlideBack {total}"
);
let partial_gap = total - covered_to(TailPolicy::Partial);
assert!(
partial_gap < MIN_SAMPLES,
"Partial left {partial_gap} at {total}"
);
let dropped = total - covered_to(TailPolicy::Drop);
assert!(dropped < hop as usize, "Drop left {dropped} at {total}");
}
}
}
#[test]
fn planned_windows_matches_materialized_len() {
for window in [2_000u32, 7_000, 100_000] {
for hop in [1_000u32, 1_999, window / 2, window] {
if hop == 0 || hop > window {
continue;
}
for tail in [TailPolicy::SlideBack, TailPolicy::Partial, TailPolicy::Drop] {
let plan = WindowPlan::new()
.with_geometry(window, hop)
.with_tail_policy(tail);
for total in (0..window as usize * 4).step_by(313) {
let spans = plan.spans(total).expect("admissible");
assert_eq!(
plan.planned_windows(total),
spans.len(),
"window {window} hop {hop} {tail:?} total {total}"
);
let reference: Vec<(usize, usize)> = spans.iter().map(|s| (s.start(), s.len())).collect();
assert_eq!(
reference,
reference_spans(&plan, total),
"window {window} hop {hop} {tail:?} total {total}"
);
}
}
}
}
}
#[test]
fn the_cap_refuses_the_full_planned_count_before_materializing() {
let plan = WindowPlan::new()
.with_geometry(160_000, 1)
.with_max_windows(10);
let error = plan.spans(200_000).expect_err("must refuse");
let Error::Windowing(WinditError::TooManyWindows { got, max }) = error else {
panic!("expected TooManyWindows, got {error:?}");
};
assert_eq!(max, 10);
assert_eq!(got, 40_001, "the FULL planned count, not max + 1");
let sized = WindowPlan::new().with_geometry(1_000_000_u32.min(MAX_SAMPLES as u32), 10_000);
let at_cap = sized.with_max_windows(3);
assert_eq!(
at_cap
.spans(MAX_SAMPLES + 20_000)
.expect("admissible")
.len(),
3
);
assert!(
sized
.with_max_windows(2)
.spans(MAX_SAMPLES + 20_000)
.is_err()
);
}
#[test]
fn the_smallest_cap_still_admits_a_single_window_clip() {
let plan = WindowPlan::new().with_max_windows(1);
assert_eq!(
plan
.spans(DEFAULT_WINDOW_SAMPLES as usize)
.expect("admissible")
.len(),
1
);
assert!(plan.spans(DEFAULT_WINDOW_SAMPLES as usize + 1).is_err());
}
#[test]
fn the_geometry_setter_round_trips_the_valid_pair() {
let plan = WindowPlan::new().with_geometry(MIN_SAMPLES as u32, 1);
assert_eq!(plan.window_samples(), MIN_SAMPLES as u32);
assert_eq!(plan.hop_samples(), 1);
let plan = WindowPlan::new().with_geometry(MAX_SAMPLES as u32, MAX_SAMPLES as u32);
assert_eq!(plan.window_samples(), MAX_SAMPLES as u32);
let mut mutated = WindowPlan::new();
mutated
.set_geometry(48_000, 16_000)
.set_tail_policy(TailPolicy::Partial)
.set_max_windows(7);
assert_eq!(mutated.window_samples(), 48_000);
assert_eq!(mutated.hop_samples(), 16_000);
assert_eq!(mutated.tail_policy(), TailPolicy::Partial);
assert_eq!(mutated.max_windows(), 7);
assert_ne!(mutated, WindowPlan::new());
}
#[test]
fn the_plan_is_const_constructible() {
const PINNED: WindowPlan = WindowPlan::new()
.with_geometry(48_000, 24_000)
.with_tail_policy(TailPolicy::Drop)
.with_max_windows(64);
assert_eq!(PINNED.window_samples(), 48_000);
assert_eq!(PINNED.hop_samples(), 24_000);
assert_eq!(PINNED.tail_policy(), TailPolicy::Drop);
assert_eq!(PINNED.max_windows(), 64);
}
#[test]
#[should_panic(expected = "window_samples must be in")]
fn a_window_the_graph_cannot_score_panics() {
let _ = WindowPlan::new().with_geometry(MIN_SAMPLES as u32 - 1, 1);
}
#[test]
#[should_panic(expected = "window_samples must be in")]
fn a_window_past_the_graph_ceiling_panics() {
let _ = WindowPlan::new().with_geometry(MAX_SAMPLES as u32 + 1, 1);
}
#[test]
#[should_panic(expected = "hop_samples must be")]
fn a_zero_hop_panics() {
let _ = WindowPlan::new().with_geometry(160_000, 0);
}
#[test]
#[should_panic(expected = "hop_samples must be")]
fn a_hop_past_the_window_panics() {
let _ = WindowPlan::new().with_geometry(160_000, 160_001);
}
#[test]
#[should_panic(expected = "max_windows must be > 0")]
fn a_zero_cap_panics() {
let _ = WindowPlan::new().with_max_windows(0);
}
#[cfg(feature = "serde")]
#[test]
fn the_serde_path_defaults_and_validates_like_the_setters() {
let plan = WindowPlan::new()
.with_geometry(48_000, 16_000)
.with_tail_policy(TailPolicy::Partial)
.with_max_windows(9);
let json = serde_json::to_string(&plan).expect("serialize");
assert!(json.contains("\"partial\""), "snake_case spelling: {json}");
assert_eq!(
serde_json::from_str::<WindowPlan>(&json).expect("deserialize"),
plan
);
assert_eq!(
serde_json::from_str::<WindowPlan>("{}").expect("all fields default"),
WindowPlan::new()
);
assert_eq!(
serde_json::from_str::<WindowPlan>(r#"{"tail":"drop"}"#)
.expect("partial")
.tail_policy(),
TailPolicy::Drop
);
for rejected in [
r#"{"hop_samples":0}"#,
r#"{"hop_samples":160001}"#,
r#"{"window_samples":1439}"#,
r#"{"window_samples":480160}"#,
r#"{"window_samples":48000,"hop_samples":48001}"#,
r#"{"max_windows":0}"#,
r#"{"tail":"pad"}"#,
] {
assert!(
serde_json::from_str::<WindowPlan>(rejected).is_err(),
"{rejected} must not deserialize"
);
}
}
#[cfg(feature = "serde")]
#[test]
fn tail_policy_wire_spellings_are_pinned() {
for policy in [TailPolicy::SlideBack, TailPolicy::Partial, TailPolicy::Drop] {
let doc = match policy {
TailPolicy::SlideBack => r#""slide_back""#,
TailPolicy::Partial => r#""partial""#,
TailPolicy::Drop => r#""drop""#,
};
assert_eq!(serde_json::to_string(&policy).unwrap(), doc);
assert_eq!(serde_json::from_str::<TailPolicy>(doc).unwrap(), policy);
}
assert_eq!(
serde_json::to_string(&WindowPlan::new()).unwrap(),
r#"{"window_samples":160000,"hop_samples":160000,"tail":"slide_back","max_windows":100000}"#
);
let doc = r#"{"window_samples":48000,"hop_samples":16000,"tail":"partial","max_windows":9}"#;
let plan = WindowPlan::new()
.with_geometry(48_000, 16_000)
.with_tail_policy(TailPolicy::Partial)
.with_max_windows(9);
assert_eq!(serde_json::to_string(&plan).unwrap(), doc);
assert_eq!(serde_json::from_str::<WindowPlan>(doc).unwrap(), plan);
}
#[cfg(feature = "serde")]
#[test]
fn a_misspelled_key_is_refused_rather_than_silently_defaulted() {
for (doc, key) in [
(r#"{"max_window":1}"#, "max_window"),
(r#"{"window":48000}"#, "window"),
(r#"{"hop":16000}"#, "hop"),
(r#"{"tail_policy":"drop"}"#, "tail_policy"),
(r#"{"hop_samples":16000,"max_window":1}"#, "max_window"),
] {
let err = match serde_json::from_str::<WindowPlan>(doc) {
Ok(plan) => panic!(
"{doc} must be refused; it deserialized to {plan:?} (max_windows {})",
plan.max_windows()
),
Err(e) => e.to_string(),
};
assert!(
err.contains(key),
"the refusal must name {key}, got {err:?}"
);
}
let ok: WindowPlan =
serde_json::from_str(r#"{"window_samples":48000,"hop_samples":16000,"max_windows":1}"#)
.unwrap();
assert_eq!(ok.window_samples(), 48_000);
assert_eq!(ok.hop_samples(), 16_000);
assert_eq!(ok.max_windows(), 1);
}
#[cfg(feature = "serde")]
#[test]
fn every_variant_round_trips_through_a_non_self_describing_format() {
for policy in [TailPolicy::SlideBack, TailPolicy::Partial, TailPolicy::Drop] {
let bytes = postcard::to_allocvec(&policy).unwrap();
assert_eq!(
postcard::from_bytes::<TailPolicy>(&bytes).unwrap(),
policy,
"postcard round-trip lost {policy:?} (bytes {bytes:?})"
);
}
for plan in [
WindowPlan::new(),
WindowPlan::new()
.with_geometry(48_000, 16_000)
.with_tail_policy(TailPolicy::Partial)
.with_max_windows(9),
] {
let bytes = postcard::to_allocvec(&plan).unwrap();
assert_eq!(
postcard::from_bytes::<WindowPlan>(&bytes).unwrap(),
plan,
"postcard round-trip lost {plan:?} (bytes {bytes:?})"
);
}
}
#[test]
fn tail_policy_display_pins_the_wire_word() {
assert_eq!(TailPolicy::SlideBack.to_string(), "slide_back");
assert_eq!(TailPolicy::Partial.to_string(), "partial");
assert_eq!(TailPolicy::Drop.to_string(), "drop");
}
#[test]
fn tail_policy_composes_into_window_plans_display() {
assert_eq!(
WindowPlan::new().to_string(),
"window_samples=160000,hop_samples=160000,tail=slide_back,max_windows=100000"
);
let built = WindowPlan::new()
.with_geometry(48_000, 24_000)
.with_tail_policy(TailPolicy::Partial)
.with_max_windows(500);
assert_eq!(
built.to_string(),
"window_samples=48000,hop_samples=24000,tail=partial,max_windows=500"
);
let mut dropped = WindowPlan::new();
dropped.set_tail_policy(TailPolicy::Drop);
assert_eq!(
dropped.to_string(),
"window_samples=160000,hop_samples=160000,tail=drop,max_windows=100000"
);
}