use super::*;
fn offsets(plan: &WindowPlan, total: usize) -> Vec<(usize, usize)> {
plan
.spans(total)
.unwrap()
.iter()
.map(|s| (s.start(), s.end()))
.collect()
}
fn chunk_slices_reference(total: usize, window: usize, hop: usize) -> Vec<(usize, usize)> {
let mut out = Vec::new();
let mut start = 0usize;
while start < total {
out.push((start, total.min(start + window)));
start += hop;
}
out
}
#[test]
fn window_samples_is_the_model_geometry() {
assert_eq!(WINDOW_SAMPLES, 160_000); assert_eq!(DEFAULT_HOP_SAMPLES as usize, WINDOW_SAMPLES);
}
#[test]
fn default_plan_is_no_overlap_pad() {
let plan = WindowPlan::default();
assert_eq!(plan, WindowPlan::new());
assert_eq!(plan.hop_samples(), DEFAULT_HOP_SAMPLES);
assert_eq!(plan.tail_policy(), TailPolicy::Pad);
}
#[test]
fn empty_clip_plans_no_windows() {
assert!(WindowPlan::new().spans(0).unwrap().is_empty());
}
#[test]
fn short_clip_is_one_window_regardless_of_hop() {
let plan = WindowPlan::new().with_hop_samples(1_000);
assert_eq!(offsets(&plan, 50_000), vec![(0, 50_000)]);
assert_eq!(offsets(&plan, WINDOW_SAMPLES), vec![(0, WINDOW_SAMPLES)]);
}
#[test]
fn short_clip_survives_drop_below_min() {
let plan =
WindowPlan::new().with_tail_policy(TailPolicy::DropBelowMin(DropBelowMin::new(100_000)));
assert_eq!(offsets(&plan, 50_000), vec![(0, 50_000)]);
}
#[test]
fn short_clip_coverage_is_padding_aware() {
let spans = WindowPlan::new().spans(40_000).unwrap();
assert_eq!(spans.len(), 1);
let coverage = spans[0].coverage();
assert!(
(coverage - 0.25).abs() < 1e-6,
"40_000 / 160_000 = 0.25, got {coverage}"
);
}
#[test]
fn no_overlap_tiling_with_padded_tail() {
let plan = WindowPlan::new();
assert_eq!(
offsets(&plan, 400_000),
vec![(0, 160_000), (160_000, 320_000), (320_000, 400_000)]
);
}
#[test]
fn drop_below_min_drops_the_short_tail() {
let plan =
WindowPlan::new().with_tail_policy(TailPolicy::DropBelowMin(DropBelowMin::new(100_000)));
assert_eq!(
offsets(&plan, 400_000),
vec![(0, 160_000), (160_000, 320_000)]
);
assert_eq!(
offsets(&plan, 420_000),
vec![(0, 160_000), (160_000, 320_000), (320_000, 420_000)]
);
}
#[test]
fn overlapping_hop_produces_full_windows_then_tails() {
let plan = WindowPlan::new().with_hop_samples(80_000);
assert_eq!(
offsets(&plan, 400_000),
vec![
(0, 160_000),
(80_000, 240_000),
(160_000, 320_000),
(240_000, 400_000),
(320_000, 400_000),
]
);
}
#[test]
fn spans_match_soundevents_chunk_slices_for_long_clips() {
for (total, hop) in [
(400_000usize, 80_000u32),
(500_000, 60_000),
(160_001, 160_000),
(1_000_000, 160_000),
(330_000, 100_000),
] {
let plan = WindowPlan::new().with_hop_samples(hop);
assert_eq!(
offsets(&plan, total),
chunk_slices_reference(total, WINDOW_SAMPLES, hop as usize),
"total={total} hop={hop}"
);
}
}
#[test]
fn window_just_over_boundary_keeps_a_one_sample_tail_under_pad() {
assert_eq!(
offsets(&WindowPlan::new(), WINDOW_SAMPLES + 1),
vec![(0, WINDOW_SAMPLES), (WINDOW_SAMPLES, WINDOW_SAMPLES + 1)]
);
}
#[test]
fn span_geometry_accessors() {
let spans = WindowPlan::new().spans(400_000).unwrap();
let tail = spans[2];
assert_eq!(tail.start(), 320_000);
assert_eq!(tail.len(), 80_000);
assert_eq!(tail.end(), 400_000);
assert!((tail.coverage() - 0.5).abs() < 1e-6);
}
#[test]
#[should_panic(expected = "hop_samples")]
fn zero_hop_setter_panics() {
let _ = WindowPlan::new().with_hop_samples(0);
}
#[test]
#[should_panic(expected = "hop_samples")]
fn hop_past_window_setter_panics() {
let _ = WindowPlan::new().with_hop_samples(WINDOW_SAMPLES as u32 + 1);
}
#[test]
#[should_panic(expected = "min_samples")]
fn zero_drop_min_setter_panics() {
let _ = WindowPlan::new().with_tail_policy(TailPolicy::DropBelowMin(DropBelowMin::new(0)));
}
#[test]
fn hop_one_twenty_second_clip_is_rejected_typed() {
let plan = WindowPlan::new().with_hop_samples(1);
let total = 2 * WINDOW_SAMPLES; let err = plan.spans(total).unwrap_err();
assert!(
matches!(
err,
Error::Windowing(WinditError::TooManyWindows { got: 320_000, max })
if max == DEFAULT_MAX_WINDOWS as usize
),
"expected TooManyWindows {{ got: 320_000, max: {} }}, got {err:?}",
DEFAULT_MAX_WINDOWS
);
}
#[test]
fn cap_boundary_exact_count_passes_and_plus_one_fails() {
let expected = vec![
(0, 160_000),
(80_000, 240_000),
(160_000, 320_000),
(240_000, 400_000),
(320_000, 400_000),
];
let at_cap = WindowPlan::new()
.with_hop_samples(80_000)
.with_max_windows(5);
assert_eq!(offsets(&at_cap, 400_000), expected);
let under_cap = WindowPlan::new()
.with_hop_samples(80_000)
.with_max_windows(4);
let err = under_cap.spans(400_000).unwrap_err();
assert!(
matches!(
err,
Error::Windowing(WinditError::TooManyWindows { got: 5, max: 4 })
),
"got {err:?}"
);
}
#[test]
fn planned_windows_matches_materialized_len() {
let pad = |hop: u32| {
WindowPlan::new()
.with_hop_samples(hop)
.with_max_windows(u32::MAX)
};
let drop_min = |hop: u32, min: u32| {
WindowPlan::new()
.with_hop_samples(hop)
.with_tail_policy(TailPolicy::DropBelowMin(DropBelowMin::new(min)))
.with_max_windows(u32::MAX)
};
let cases: [(WindowPlan, usize); 20] = [
(pad(80_000), 400_000),
(pad(60_000), 500_000),
(pad(160_000), 160_001),
(pad(160_000), 1_000_000),
(pad(100_000), 330_000),
(pad(160_000), 0),
(pad(160_000), 40_000),
(pad(160_000), WINDOW_SAMPLES - 1),
(pad(160_000), WINDOW_SAMPLES),
(pad(160_000), WINDOW_SAMPLES + 1),
(pad(1), WINDOW_SAMPLES), (pad(1), WINDOW_SAMPLES + 100), (drop_min(160_000, 100_000), 400_000),
(drop_min(160_000, 100_000), 420_000),
(drop_min(80_000, 100_000), 400_000),
(drop_min(60_000, 40_000), 500_000),
(drop_min(160_000, 160_000), 320_001),
(drop_min(2, 100_000), 170_000), (drop_min(160_000, 100_000), 40_000),
(drop_min(160_000, 100_000), 0),
];
for (plan, total) in cases {
assert_eq!(
plan.planned_windows(total),
plan.spans(total).unwrap().len(),
"planned_windows != materialized len for hop={} tail={:?} total={total}",
plan.hop_samples(),
plan.tail_policy(),
);
}
}
#[test]
fn short_clip_never_trips_cap() {
let plan = WindowPlan::new().with_max_windows(1).with_hop_samples(1);
let spans = plan.spans(WINDOW_SAMPLES).unwrap();
assert_eq!(spans.len(), 1);
assert_eq!((spans[0].start(), spans[0].end()), (0, WINDOW_SAMPLES));
}
#[test]
#[should_panic(expected = "max_windows")]
fn zero_max_windows_setter_panics() {
let _ = WindowPlan::new().with_max_windows(0);
}
#[cfg(feature = "serde")]
mod serde_tests {
use super::*;
#[test]
fn round_trips_through_json() {
let plan = WindowPlan::new()
.with_hop_samples(80_000)
.with_tail_policy(TailPolicy::DropBelowMin(DropBelowMin::new(40_000)))
.with_max_windows(50_000);
let json = serde_json::to_string(&plan).unwrap();
let back: WindowPlan = serde_json::from_str(&json).unwrap();
assert_eq!(back, plan);
}
#[test]
fn defaults_fill_for_a_partial_config() {
let plan: WindowPlan = serde_json::from_str("{}").unwrap();
assert_eq!(plan, WindowPlan::new());
assert_eq!(plan.max_windows(), DEFAULT_MAX_WINDOWS);
}
#[test]
fn tail_policy_wire_spellings_are_pinned() {
for kind in [
TailPolicy::Pad,
TailPolicy::DropBelowMin(DropBelowMin::new(40_000)),
] {
let expected = match kind {
TailPolicy::Pad => "{\"kind\":\"pad\"}".to_string(),
TailPolicy::DropBelowMin(d) => {
let min_samples = d.min_samples();
format!("{{\"kind\":\"drop_below_min\",\"value\":{{\"min_samples\":{min_samples}}}}}")
}
};
assert_eq!(serde_json::to_string(&kind).unwrap(), expected);
let back: TailPolicy = serde_json::from_str(&expected).unwrap();
assert_eq!(back, kind);
}
assert_eq!(
serde_json::to_string(&WindowPlan::new()).unwrap(),
"{\"hop_samples\":160000,\"tail\":{\"kind\":\"pad\"},\"max_windows\":100000}"
);
let doc = "{\"hop_samples\":80000,\"tail\":{\"kind\":\"drop_below_min\",\
\"value\":{\"min_samples\":40000}},\"max_windows\":50000}";
let plan = WindowPlan::new()
.with_hop_samples(80_000)
.with_tail_policy(TailPolicy::DropBelowMin(DropBelowMin::new(40_000)))
.with_max_windows(50_000);
assert_eq!(serde_json::to_string(&plan).unwrap(), doc);
assert_eq!(serde_json::from_str::<WindowPlan>(doc).unwrap(), plan);
assert!(serde_json::from_str::<TailPolicy>("\"pad\"").is_err());
assert!(
serde_json::from_str::<TailPolicy>("{\"drop_below_min\":{\"min_samples\":40000}}").is_err()
);
}
#[test]
fn every_variant_round_trips_through_a_non_self_describing_format() {
for policy in [
TailPolicy::Pad,
TailPolicy::DropBelowMin(DropBelowMin::new(40_000)),
] {
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_hop_samples(80_000)
.with_tail_policy(TailPolicy::DropBelowMin(DropBelowMin::new(40_000)))
.with_max_windows(50_000),
] {
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 invalid_hop_fails_to_deserialize() {
assert!(serde_json::from_str::<WindowPlan>("{\"hop_samples\":0}").is_err());
assert!(serde_json::from_str::<WindowPlan>("{\"hop_samples\":160001}").is_err());
}
#[test]
fn invalid_tail_min_fails_to_deserialize() {
let json = "{\"tail\":{\"kind\":\"drop_below_min\",\"value\":{\"min_samples\":0}}}";
assert!(serde_json::from_str::<WindowPlan>(json).is_err());
let json = "{\"tail\":{\"kind\":\"drop_below_min\",\"value\":{\"min_samples\":160001}}}";
assert!(serde_json::from_str::<WindowPlan>(json).is_err());
}
#[test]
fn invalid_tail_min_rejection_message_names_the_payload_once() {
let err = WindowPlan::try_from(WindowPlanRepr {
hop_samples: DEFAULT_HOP_SAMPLES,
tail: TailPolicy::DropBelowMin(DropBelowMin::new(0)),
max_windows: DEFAULT_MAX_WINDOWS,
})
.unwrap_err();
assert_eq!(
err,
"tail DropBelowMin.min_samples must be > 0 and <= WINDOW_SAMPLES \
(160000), got DropBelowMin { min_samples: 0 }"
);
}
#[test]
fn zero_max_windows_fails_to_deserialize() {
assert!(serde_json::from_str::<WindowPlan>("{\"max_windows\":0}").is_err());
}
#[test]
fn a_misspelled_key_is_refused_rather_than_silently_defaulted() {
for (doc, key) in [
(r#"{"max_window":1}"#, "max_window"),
(r#"{"hop":80000}"#, "hop"),
(r#"{"tail_policy":{"kind":"pad"}}"#, "tail_policy"),
(r#"{"hop_samples":80000,"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#"{"hop_samples":80000,"max_windows":1}"#).unwrap();
assert_eq!(ok.hop_samples(), 80_000);
assert_eq!(ok.max_windows(), 1);
}
}