use super::ValueGenerator;
use crate::{ConfigError, SondaError};
pub struct SequenceGenerator {
values: Vec<f64>,
repeat: bool,
}
impl SequenceGenerator {
pub fn new(values: Vec<f64>, repeat: bool) -> Result<Self, SondaError> {
if values.is_empty() {
return Err(SondaError::Config(ConfigError::invalid(
"sequence generator requires at least one value",
)));
}
Ok(Self { values, repeat })
}
}
impl ValueGenerator for SequenceGenerator {
fn value(&self, tick: u64) -> f64 {
let len = self.values.len();
let index = if self.repeat {
(tick % len as u64) as usize
} else {
(tick.min((len - 1) as u64)) as usize
};
self.values[index]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_with_empty_values_returns_config_error() {
let result = SequenceGenerator::new(vec![], true);
assert!(result.is_err(), "empty values must be rejected");
let err = result.err().expect("already confirmed is_err");
let msg = format!("{err}");
assert!(
msg.contains("at least one value"),
"error message should mention 'at least one value', got: {msg}"
);
}
#[test]
fn new_with_empty_values_repeat_false_returns_config_error() {
let result = SequenceGenerator::new(vec![], false);
assert!(
result.is_err(),
"empty values must be rejected even with repeat=false"
);
}
#[test]
fn new_with_single_value_succeeds() {
let gen = SequenceGenerator::new(vec![42.0], true).expect("single value should be valid");
assert_eq!(gen.value(0), 42.0);
}
#[test]
fn new_with_multiple_values_succeeds() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], true)
.expect("multiple values should be valid");
assert_eq!(gen.value(0), 1.0);
}
#[test]
fn repeat_tick_zero_returns_first_value() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], true).unwrap();
assert_eq!(gen.value(0), 1.0);
}
#[test]
fn repeat_tick_one_returns_second_value() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], true).unwrap();
assert_eq!(gen.value(1), 2.0);
}
#[test]
fn repeat_tick_two_returns_third_value() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], true).unwrap();
assert_eq!(gen.value(2), 3.0);
}
#[test]
fn repeat_wraps_at_sequence_boundary() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], true).unwrap();
assert_eq!(gen.value(3), 1.0, "tick=3 should wrap to index 0");
}
#[test]
fn repeat_wraps_correctly_at_tick_5() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], true).unwrap();
assert_eq!(gen.value(5), 3.0, "tick=5 should wrap to index 2");
}
#[test]
fn repeat_multiple_full_cycles() {
let gen = SequenceGenerator::new(vec![10.0, 20.0], true).unwrap();
assert_eq!(gen.value(0), 10.0);
assert_eq!(gen.value(1), 20.0);
assert_eq!(gen.value(2), 10.0);
assert_eq!(gen.value(3), 20.0);
}
#[test]
fn repeat_single_value_always_returns_that_value() {
let gen = SequenceGenerator::new(vec![7.5], true).unwrap();
assert_eq!(gen.value(0), 7.5);
assert_eq!(gen.value(1), 7.5);
assert_eq!(gen.value(100), 7.5);
assert_eq!(gen.value(999_999), 7.5);
}
#[test]
fn no_repeat_tick_zero_returns_first_value() {
let gen = SequenceGenerator::new(vec![1.0, 2.0], false).unwrap();
assert_eq!(gen.value(0), 1.0);
}
#[test]
fn no_repeat_tick_one_returns_second_value() {
let gen = SequenceGenerator::new(vec![1.0, 2.0], false).unwrap();
assert_eq!(gen.value(1), 2.0);
}
#[test]
fn no_repeat_beyond_length_clamps_to_last() {
let gen = SequenceGenerator::new(vec![1.0, 2.0], false).unwrap();
assert_eq!(
gen.value(5),
2.0,
"tick beyond sequence length should clamp to last value"
);
}
#[test]
fn no_repeat_at_exact_boundary_clamps_to_last() {
let gen = SequenceGenerator::new(vec![1.0, 2.0], false).unwrap();
assert_eq!(gen.value(2), 2.0);
}
#[test]
fn no_repeat_single_value_always_returns_that_value() {
let gen = SequenceGenerator::new(vec![99.0], false).unwrap();
assert_eq!(gen.value(0), 99.0);
assert_eq!(gen.value(1), 99.0);
assert_eq!(gen.value(1000), 99.0);
}
#[test]
fn repeat_large_tick_does_not_panic() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], true).unwrap();
let large_tick: u64 = 1_000_000_000;
let val = gen.value(large_tick);
let expected_index = (large_tick % 3) as usize;
let expected = [1.0, 2.0, 3.0][expected_index];
assert_eq!(val, expected);
}
#[test]
fn no_repeat_large_tick_does_not_panic() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], false).unwrap();
let large_tick: u64 = 1_000_000_000;
assert_eq!(gen.value(large_tick), 3.0, "should clamp to last value");
}
#[test]
fn determinism_same_tick_returns_same_value() {
let gen = SequenceGenerator::new(vec![10.0, 20.0, 30.0], true).unwrap();
for tick in 0..100 {
let first_call = gen.value(tick);
let second_call = gen.value(tick);
assert_eq!(
first_call, second_call,
"value must be deterministic: tick={tick} returned {first_call} then {second_call}"
);
}
}
#[test]
fn determinism_separate_instances_same_config() {
let gen1 = SequenceGenerator::new(vec![5.0, 10.0, 15.0], true).unwrap();
let gen2 = SequenceGenerator::new(vec![5.0, 10.0, 15.0], true).unwrap();
for tick in 0..100 {
assert_eq!(
gen1.value(tick),
gen2.value(tick),
"two generators with same config must produce same values at tick={tick}"
);
}
}
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn sequence_generator_is_send_and_sync() {
assert_send_sync::<SequenceGenerator>();
}
#[test]
fn repeat_tick_above_u32_max_uses_u64_modulo() {
let gen = SequenceGenerator::new(vec![10.0, 20.0, 30.0], true).unwrap();
let tick: u64 = u64::from(u32::MAX) + 1;
assert_eq!(
gen.value(tick),
20.0,
"tick {} mod 3 = 1, should return values[1] = 20.0",
tick
);
}
#[test]
fn repeat_tick_at_u64_max_does_not_panic() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], true).unwrap();
let val = gen.value(u64::MAX);
assert_eq!(val, 1.0, "u64::MAX % 3 = 0, should return values[0]");
}
#[test]
fn no_repeat_tick_above_u32_max_clamps_correctly() {
let gen = SequenceGenerator::new(vec![1.0, 2.0, 3.0], false).unwrap();
let tick: u64 = u64::from(u32::MAX) + 1;
assert_eq!(
gen.value(tick),
3.0,
"tick {} beyond length should clamp to last value",
tick
);
}
#[test]
fn no_repeat_tick_at_u64_max_clamps_correctly() {
let gen = SequenceGenerator::new(vec![1.0, 2.0], false).unwrap();
assert_eq!(
gen.value(u64::MAX),
2.0,
"u64::MAX should clamp to last value"
);
}
#[test]
fn cpu_spike_pattern_produces_expected_values() {
let pattern = vec![
10.0, 10.0, 10.0, 10.0, 10.0, 95.0, 95.0, 95.0, 95.0, 95.0, 10.0, 10.0, 10.0, 10.0,
10.0, 10.0,
];
let gen = SequenceGenerator::new(pattern.clone(), true).unwrap();
for (i, expected) in pattern.iter().enumerate() {
assert_eq!(
gen.value(i as u64),
*expected,
"first cycle mismatch at tick={i}"
);
}
for (i, expected) in pattern.iter().enumerate() {
assert_eq!(
gen.value((i + 16) as u64),
*expected,
"second cycle mismatch at tick={}",
i + 16
);
}
}
#[test]
fn handles_negative_values() {
let gen = SequenceGenerator::new(vec![-1.0, -2.5, 0.0, 3.14], true).unwrap();
assert_eq!(gen.value(0), -1.0);
assert_eq!(gen.value(1), -2.5);
assert_eq!(gen.value(2), 0.0);
assert_eq!(gen.value(3), 3.14);
}
#[test]
fn handles_special_float_values() {
let gen =
SequenceGenerator::new(vec![f64::INFINITY, f64::NEG_INFINITY, 0.0], true).unwrap();
assert_eq!(gen.value(0), f64::INFINITY);
assert_eq!(gen.value(1), f64::NEG_INFINITY);
assert_eq!(gen.value(2), 0.0);
}
}