feat: add test for mappers

This commit is contained in:
2026-07-20 21:00:36 +02:00
parent b48a2d7562
commit 05cf7fc26c
2 changed files with 653 additions and 0 deletions
+531
View File
@@ -856,3 +856,534 @@ pub fn copy_channel_to_raw(source: types::Channel, destination: types::Channel)
Ok((channel_to_raw_value(source), channel_to_raw_value(destination)))
}
#[cfg(test)]
mod tests {
use super::*;
// ------------------------------------------------------------------
// CHANNEL
// ------------------------------------------------------------------
#[test]
fn channel_round_trip_all_inputs() {
let channels = [
types::InputChannel::InA,
types::InputChannel::InB,
types::InputChannel::InC,
types::InputChannel::InD,
];
for ch in channels {
let raw = input_channel_to_raw(ch).unwrap();
let decoded = raw_to_channel(raw).unwrap();
assert_eq!(decoded, types::Channel::Input(ch));
}
}
#[test]
fn channel_round_trip_all_outputs() {
let channels = [
types::OutputChannel::Out1,
types::OutputChannel::Out2,
types::OutputChannel::Out3,
types::OutputChannel::Out4,
types::OutputChannel::Out5,
types::OutputChannel::Out6,
types::OutputChannel::Out7,
types::OutputChannel::Out8,
];
for ch in channels {
let raw = output_channel_to_raw(ch).unwrap();
let decoded = raw_to_channel(raw).unwrap();
assert_eq!(decoded, types::Channel::Output(ch));
}
}
#[test]
fn channel_specific_raw_values() {
assert_eq!(channel_to_raw(types::Channel::Input(types::InputChannel::InA)).unwrap(), 0x00);
assert_eq!(channel_to_raw(types::Channel::Input(types::InputChannel::InD)).unwrap(), 0x03);
assert_eq!(channel_to_raw(types::Channel::Output(types::OutputChannel::Out1)).unwrap(), 0x04);
assert_eq!(channel_to_raw(types::Channel::Output(types::OutputChannel::Out8)).unwrap(), 0x0B);
}
// ------------------------------------------------------------------
// NAME
// ------------------------------------------------------------------
#[test]
fn channel_name_round_trip() {
let name = "In ABCDE";
let raw = channel_name_to_raw(name).unwrap();
assert_eq!(raw.len(), client_constants::CHANNEL_NAME_LENGTH);
let decoded = raw_to_channel_name(&raw).unwrap();
assert_eq!(decoded, name);
}
#[test]
fn channel_name_padding_is_applied() {
let raw = channel_name_to_raw(" Kick ").unwrap();
assert_eq!(raw.len(), client_constants::CHANNEL_NAME_LENGTH);
}
// ------------------------------------------------------------------
// ATTACK / RELEASE / HOLD (simple offset-by-one mappings)
// ------------------------------------------------------------------
#[test]
fn attack_ms_mapping() {
assert_eq!(attack_ms_to_raw(1).unwrap(), 0);
assert_eq!(attack_ms_to_raw(999).unwrap(), 998);
assert_eq!(raw_to_attack_ms(0).unwrap(), 1);
assert_eq!(raw_to_attack_ms(998).unwrap(), 999);
}
#[test]
fn attack_ms_round_trip() {
for ms in [1u16, 50, 500, 999] {
let raw = attack_ms_to_raw(ms).unwrap();
assert_eq!(raw_to_attack_ms(raw).unwrap(), ms);
}
}
#[test]
fn release_ms_mapping() {
assert_eq!(release_ms_to_raw(10).unwrap(), 9);
assert_eq!(release_ms_to_raw(3000).unwrap(), 2999);
assert_eq!(raw_to_release_ms(9).unwrap(), 10);
assert_eq!(raw_to_release_ms(2999).unwrap(), 3000);
}
#[test]
fn release_ms_round_trip() {
for ms in [10u16, 100, 1500, 3000] {
let raw = release_ms_to_raw(ms).unwrap();
assert_eq!(raw_to_release_ms(raw).unwrap(), ms);
}
}
#[test]
fn hold_ms_mapping() {
assert_eq!(hold_ms_to_raw(10).unwrap(), 9);
assert_eq!(hold_ms_to_raw(999).unwrap(), 998);
assert_eq!(raw_to_hold_ms(9).unwrap(), 10);
assert_eq!(raw_to_hold_ms(998).unwrap(), 999);
}
#[test]
fn hold_ms_round_trip() {
for ms in [10u16, 250, 999] {
let raw = hold_ms_to_raw(ms).unwrap();
assert_eq!(raw_to_hold_ms(raw).unwrap(), ms);
}
}
// ------------------------------------------------------------------
// GATE THRESHOLD
// ------------------------------------------------------------------
#[test]
fn gate_threshold_bounds() {
assert_eq!(gate_threshold_db_to_raw(-90.0).unwrap(), 0);
assert_eq!(gate_threshold_db_to_raw(0.0).unwrap(), 180); // (0 - -90) * 2
}
#[test]
fn gate_threshold_round_trip() {
for db in [-90.0f32, -45.5, -20.25, 0.0] {
let raw = gate_threshold_db_to_raw(db).unwrap();
let decoded = raw_to_gate_threshold_db(raw).unwrap();
assert!((decoded - (db * 2.0).round() / 2.0).abs() < 1e-4);
}
}
// ------------------------------------------------------------------
// EQ GAIN
// ------------------------------------------------------------------
#[test]
fn eq_gain_round_trip_endpoints() {
for db in [-12.0f32, 0.0, 12.0] {
let raw = eq_gain_db_to_raw(db).unwrap();
let decoded = raw_to_eq_gain(raw).unwrap();
assert!((decoded - db).abs() < 1e-2);
}
}
#[test]
fn eq_gain_round_trip_arbitrary() {
for db in [-7.3f32, 3.9, 11.99] {
let raw = eq_gain_db_to_raw(db).unwrap();
let decoded = raw_to_eq_gain(raw).unwrap();
// allow one quantization step of tolerance
assert!((decoded - db).abs() <= eq_gain_step_db() + 1e-4);
}
}
// ------------------------------------------------------------------
// PEQ BAND
// ------------------------------------------------------------------
#[test]
fn peq_band_output_all_bands() {
let bands = [
types::PEQBand::B1, types::PEQBand::B2, types::PEQBand::B3,
types::PEQBand::B4, types::PEQBand::B5, types::PEQBand::B6,
types::PEQBand::B7, types::PEQBand::B8, types::PEQBand::B9,
];
let out_channel = types::Channel::Output(types::OutputChannel::Out1);
for (i, band) in bands.into_iter().enumerate() {
assert_eq!(peq_band_to_raw(band, out_channel).unwrap(), i as u8);
}
}
#[test]
fn peq_band_input_allowed_bands() {
let bands = [
types::PEQBand::B1, types::PEQBand::B2, types::PEQBand::B3,
types::PEQBand::B4, types::PEQBand::B5, types::PEQBand::B6,
types::PEQBand::B7, types::PEQBand::B8,
];
let in_channel = types::Channel::Input(types::InputChannel::InA);
for (i, band) in bands.into_iter().enumerate() {
assert_eq!(peq_band_to_raw(band, in_channel).unwrap(), i as u8);
}
}
// ------------------------------------------------------------------
// GAIN (channel gain)
// ------------------------------------------------------------------
#[test]
fn gain_round_trip_low_range() {
for db in [-60.0f32, -40.0, -20.5] {
let raw = gain_db_to_raw(db).unwrap();
let decoded = raw_to_gain(raw).unwrap();
assert!((decoded - db).abs() < 1e-3);
}
}
#[test]
fn gain_round_trip_high_range() {
for db in [-19.9f32, 0.0, 12.0] {
let raw = gain_db_to_raw(db).unwrap();
let decoded = raw_to_gain(raw).unwrap();
assert!((decoded - db).abs() < 1e-3);
}
}
#[test]
fn gain_split_boundary_is_low_step() {
let below = gain_db_to_raw(-20.5).unwrap();
let above = gain_db_to_raw(-19.9).unwrap();
assert_eq!(raw_to_gain(below).unwrap(), -20.5);
assert!((raw_to_gain(above).unwrap() - (-19.9)).abs() < 1e-3);
}
// ------------------------------------------------------------------
// CONTIGUOUS FREQUENCY
// ------------------------------------------------------------------
#[test]
fn frequency_round_trip_endpoints() {
for hz in [19.7f32, 20160.0] {
let raw = frequency_hz_to_raw(hz).unwrap();
let decoded = raw_to_frequency_hz(raw).unwrap();
// log scale quantization tolerance: within 1% of range
assert!((decoded - hz).abs() / hz < 0.01);
}
}
#[test]
fn frequency_round_trip_mid() {
let hz = 1000.0f32;
let raw = frequency_hz_to_raw(hz).unwrap();
let decoded = raw_to_frequency_hz(raw).unwrap();
assert!((decoded - hz).abs() / hz < 0.02);
}
// ------------------------------------------------------------------
// DISCRETE FREQUENCY
// ------------------------------------------------------------------
#[test]
fn discrete_frequency_round_trip_all() {
let freqs = [
types::DiscreteFrequency::F20, types::DiscreteFrequency::F25,
types::DiscreteFrequency::F31_5, types::DiscreteFrequency::F40,
types::DiscreteFrequency::F50, types::DiscreteFrequency::F63,
types::DiscreteFrequency::F80, types::DiscreteFrequency::F100,
types::DiscreteFrequency::F125, types::DiscreteFrequency::F160,
types::DiscreteFrequency::F200, types::DiscreteFrequency::F250,
types::DiscreteFrequency::F315, types::DiscreteFrequency::F400,
types::DiscreteFrequency::F500, types::DiscreteFrequency::F630,
types::DiscreteFrequency::F800, types::DiscreteFrequency::F1000,
types::DiscreteFrequency::F1250, types::DiscreteFrequency::F1600,
types::DiscreteFrequency::F2000, types::DiscreteFrequency::F2500,
types::DiscreteFrequency::F3150, types::DiscreteFrequency::F4000,
types::DiscreteFrequency::F5000, types::DiscreteFrequency::F6300,
types::DiscreteFrequency::F8000, types::DiscreteFrequency::F10000,
types::DiscreteFrequency::F12500, types::DiscreteFrequency::F16000,
types::DiscreteFrequency::F20000,
];
for (i, f) in freqs.into_iter().enumerate() {
let raw = frequency_value_to_raw(f).unwrap();
assert_eq!(raw, i as u8);
assert_eq!(raw_to_frequency_value(raw).unwrap(), f);
}
}
// ------------------------------------------------------------------
// CROSSOVER FILTER
// ------------------------------------------------------------------
#[test]
fn crossover_filter_round_trip_all() {
let filters = [
types::CrossoverFilter::Bypass, types::CrossoverFilter::Bw6,
types::CrossoverFilter::Bl6, types::CrossoverFilter::Bw12,
types::CrossoverFilter::Bl12, types::CrossoverFilter::Lk12,
types::CrossoverFilter::Bw18, types::CrossoverFilter::Bl18,
types::CrossoverFilter::Bw24, types::CrossoverFilter::Bl24,
types::CrossoverFilter::Lk24, types::CrossoverFilter::Bw30,
types::CrossoverFilter::Bl30, types::CrossoverFilter::Bw36,
types::CrossoverFilter::Bl36, types::CrossoverFilter::Lk36,
types::CrossoverFilter::Bw42, types::CrossoverFilter::Bl42,
types::CrossoverFilter::Bw48, types::CrossoverFilter::Bl48,
types::CrossoverFilter::Lk48,
];
for (i, f) in filters.into_iter().enumerate() {
let raw = crossover_filter_to_raw(f).unwrap();
assert_eq!(raw, i as u8);
assert_eq!(raw_to_crossover_filter(raw).unwrap(), f);
}
}
// ------------------------------------------------------------------
// PEQ Q
// ------------------------------------------------------------------
#[test]
fn peq_q_round_trip_peak_filter() {
for q in [0.40f32, 1.0, 10.0, 128.0] {
let raw = peq_q_to_raw(q, types::PEQFilter::Peak).unwrap();
let decoded = raw_to_peq_q(raw).unwrap();
// log scale, allow 5% relative tolerance
assert!((decoded - q).abs() / q < 0.05);
}
}
#[test]
fn peq_q_round_trip_shelf_filter() {
for q in [0.40f32, 1.5, 3.0] {
let raw = peq_q_to_raw(q, types::PEQFilter::LowShelf).unwrap();
let decoded = raw_to_peq_q(raw).unwrap();
assert!((decoded - q).abs() / q < 0.05);
}
}
// ------------------------------------------------------------------
// PEQ FILTER
// ------------------------------------------------------------------
#[test]
fn peq_filter_round_trip_all() {
let filters = [
types::PEQFilter::Peak, types::PEQFilter::LowShelf,
types::PEQFilter::HighShelf, types::PEQFilter::Lp6Db,
types::PEQFilter::Lp12Db, types::PEQFilter::Hp6Db,
types::PEQFilter::Hp12Db, types::PEQFilter::AllPass1,
types::PEQFilter::AllPass2,
];
for (i, f) in filters.into_iter().enumerate() {
let raw = peq_filter_to_raw(f).unwrap();
assert_eq!(raw, i as u8);
assert_eq!(raw_to_peq_filter(raw).unwrap(), f);
}
}
// ------------------------------------------------------------------
// RATIO
// ------------------------------------------------------------------
#[test]
fn ratio_round_trip_all() {
let ratios = [
types::Ratio::Ratio1_1, types::Ratio::Ratio1_1_1, types::Ratio::Ratio1_1_3,
types::Ratio::Ratio1_1_5, types::Ratio::Ratio1_1_7, types::Ratio::Ratio1_2,
types::Ratio::Ratio1_2_5, types::Ratio::Ratio1_3, types::Ratio::Ratio1_3_5,
types::Ratio::Ratio1_4, types::Ratio::Ratio1_5, types::Ratio::Ratio1_6,
types::Ratio::Ratio1_8, types::Ratio::Ratio1_10, types::Ratio::Ratio1_20,
types::Ratio::Limit,
];
for (i, r) in ratios.into_iter().enumerate() {
let raw = ratio_to_raw(r).unwrap();
assert_eq!(raw, i as u16);
assert_eq!(raw_to_ratio(raw).unwrap(), r);
}
}
// ------------------------------------------------------------------
// KNEE
// ------------------------------------------------------------------
#[test]
fn knee_is_identity_mapping() {
for db in [0u16, 6, 12] {
let raw = knee_db_to_raw(db).unwrap();
assert_eq!(raw, db);
assert_eq!(raw_to_knee_db(raw).unwrap(), db);
}
}
// ------------------------------------------------------------------
// THRESHOLD
// ------------------------------------------------------------------
#[test]
fn threshold_round_trip() {
for db in [-90.0f32, -35.5, 0.0, 20.0] {
let raw = threshold_db_to_raw(db).unwrap();
let decoded = raw_to_threshold_db(raw).unwrap();
assert!((decoded - (db * 2.0).round() / 2.0).abs() < 1e-4);
}
}
// ------------------------------------------------------------------
// LINK
// ------------------------------------------------------------------
#[test]
fn link_input_channels() {
let source = types::Channel::Input(types::InputChannel::InA);
let destinations = vec![
types::Channel::Input(types::InputChannel::InA),
types::Channel::Input(types::InputChannel::InC),
];
let mask = link_channels_to_raw(source, &destinations).unwrap();
assert_eq!(mask, 0b0000_0101);
let source_raw = channel_to_raw(source).unwrap();
let decoded = raw_to_link_channels(source_raw, mask).unwrap();
assert_eq!(decoded, destinations);
}
#[test]
fn link_output_channels() {
let source = types::Channel::Output(types::OutputChannel::Out2);
let destinations = vec![
types::Channel::Output(types::OutputChannel::Out2),
types::Channel::Output(types::OutputChannel::Out5),
types::Channel::Output(types::OutputChannel::Out8),
];
let mask = link_channels_to_raw(source, &destinations).unwrap();
assert_eq!(mask, (1 << 1) | (1 << 4) | (1 << 7));
let source_raw = channel_to_raw(source).unwrap();
let decoded = raw_to_link_channels(source_raw, mask).unwrap();
assert_eq!(decoded, destinations);
}
// ------------------------------------------------------------------
// MATRIX GAIN
// ------------------------------------------------------------------
#[test]
fn matrix_gain_round_trip() {
for db in [-60.0f32, -30.25, -19.9, 0.0] {
let raw = matrix_gain_db_to_raw(db).unwrap();
let decoded = raw_to_matrix_gain(raw).unwrap();
assert!((decoded - db).abs() < 0.6);
}
}
// ------------------------------------------------------------------
// MATRIX
// ------------------------------------------------------------------
#[test]
fn matrix_round_trip_all_inputs() {
let all = vec![
types::InputChannel::InA,
types::InputChannel::InB,
types::InputChannel::InC,
types::InputChannel::InD,
];
let raw = matrix_to_raw(&all).unwrap();
let mut decoded = raw_to_matrix(raw).unwrap();
decoded.sort_by_key(|c| format!("{:?}", c));
let mut expected = all.clone();
expected.sort_by_key(|c| format!("{:?}", c));
assert_eq!(decoded, expected);
}
#[test]
fn matrix_round_trip_subset() {
let subset = vec![types::InputChannel::InB, types::InputChannel::InD];
let raw = matrix_to_raw(&subset).unwrap();
let decoded = raw_to_matrix(raw).unwrap();
assert_eq!(decoded.len(), 2);
assert!(decoded.contains(&types::InputChannel::InB));
assert!(decoded.contains(&types::InputChannel::InD));
}
#[test]
fn matrix_empty() {
let raw = matrix_to_raw(&[]).unwrap();
assert_eq!(raw, 0);
assert!(raw_to_matrix(raw).unwrap().is_empty());
}
// ------------------------------------------------------------------
// DELAY
// ------------------------------------------------------------------
#[test]
fn delay_round_trip_milliseconds() {
for ms in [0.0f32, 340.0, 680.0] {
let raw = delay_to_raw(ms, types::DelayUnit::Millisecond).unwrap();
let decoded = raw_to_delay(raw, types::DelayUnit::Millisecond).unwrap();
assert!((decoded - ms).abs() < 5.0); // quantized to 8-bit resolution
}
}
#[test]
fn delay_round_trip_meters() {
for m in [0.0f32, 100.0, 233.58] {
let raw = delay_to_raw(m, types::DelayUnit::Meter).unwrap();
let decoded = raw_to_delay(raw, types::DelayUnit::Meter).unwrap();
assert!((decoded - m).abs() < 2.0);
}
}
#[test]
fn delay_round_trip_feet() {
for ft in [0.0f32, 400.0, 766.329] {
let raw = delay_to_raw(ft, types::DelayUnit::Feet).unwrap();
let decoded = raw_to_delay(raw, types::DelayUnit::Feet).unwrap();
assert!((decoded - ft).abs() < 5.0);
}
}
// ------------------------------------------------------------------
// COPY CHANNEL
// ------------------------------------------------------------------
#[test]
fn copy_channel_inputs() {
let source = types::Channel::Input(types::InputChannel::InA);
let dest = types::Channel::Input(types::InputChannel::InC);
let (raw_source, raw_dest) = copy_channel_to_raw(source, dest).unwrap();
assert_eq!(raw_source, 0x00);
assert_eq!(raw_dest, 0x02);
}
#[test]
fn copy_channel_outputs() {
let source = types::Channel::Output(types::OutputChannel::Out1);
let dest = types::Channel::Output(types::OutputChannel::Out8);
let (raw_source, raw_dest) = copy_channel_to_raw(source, dest).unwrap();
assert_eq!(raw_source, 0x04);
assert_eq!(raw_dest, 0x0B);
}
}
+122
View File
@@ -146,3 +146,125 @@ pub fn password_to_raw(password: &str) -> Result<Vec<u8>, MapperError> {
Ok(encode_ascii(password, client_constants::PASSWORD_LENGTH, client_constants::PASSWORD_PADDING)?)
}
#[cfg(test)]
mod tests {
use super::*;
// -------- Delay Unit --------
#[test]
fn delay_unit_round_trip() {
for unit in [
types::DelayUnit::Millisecond,
types::DelayUnit::Meter,
types::DelayUnit::Feet,
] {
let raw = delay_unit_to_raw(unit).unwrap();
assert_eq!(raw_to_delay_unit(raw).unwrap(), unit);
}
}
#[test]
fn delay_unit_rejects_invalid_raw() {
assert!(raw_to_delay_unit(0xff).is_err());
}
// -------- Preset Index --------
#[test]
fn preset_index_to_raw_bounds() {
assert_eq!(preset_index_to_raw(0).unwrap(), 0);
assert_eq!(
preset_index_to_raw(client_constants::PRESET_INDEX_MAX).unwrap(),
client_constants::PRESET_INDEX_MAX as u8,
);
}
#[test]
fn preset_index_without_factory_to_raw_bounds() {
assert_eq!(preset_index_without_factory_to_raw(1).unwrap(), 0);
assert_eq!(
preset_index_without_factory_to_raw(client_constants::PRESET_INDEX_MAX).unwrap(),
client_constants::PRESET_INDEX_MAX as u8 - 1,
);
}
#[test]
fn preset_index_rejects_out_of_range() {
assert!(preset_index_to_raw(client_constants::PRESET_INDEX_MAX + 1).is_err());
assert!(preset_index_without_factory_to_raw(0).is_err());
}
// -------- Config Chunk --------
#[test]
fn config_chunk_bounds() {
assert_eq!(config_chunk_to_raw(0).unwrap(), 0);
assert_eq!(
config_chunk_to_raw(client_constants::CONFIG_CHUNK_INDEX_MAX).unwrap(),
client_constants::CONFIG_CHUNK_INDEX_MAX as u8,
);
}
#[test]
fn config_chunk_rejects_invalid() {
assert!(config_chunk_to_raw(client_constants::CONFIG_CHUNK_INDEX_MAX + 1).is_err());
}
// -------- Preset Name --------
#[test]
fn preset_name_round_trip() {
let name = "DSP408 ";
let raw = preset_name_to_raw(name).unwrap();
assert_eq!(
raw_to_preset_name(&raw).unwrap(),
"DSP408",
);
}
#[test]
fn preset_name_rejects_invalid_length() {
assert!(preset_name_to_raw("short").is_err());
}
// -------- Input Source --------
#[test]
fn input_source_round_trip() {
for source in [
types::InputType::Analog,
types::InputType::PinkNoise,
types::InputType::WhiteNoise,
types::InputType::SineWave,
] {
let raw = input_source_to_raw(source).unwrap();
assert_eq!(raw_to_input_source(raw).unwrap(), source);
}
}
#[test]
fn input_source_rejects_invalid_raw() {
assert!(raw_to_input_source(0xff).is_err());
}
// -------- Password --------
#[test]
fn _password_to_raw() {
let raw = password_to_raw("1234").unwrap();
assert_eq!(raw, b"1234");
}
#[test]
fn password_rejects_invalid_length() {
assert!(password_to_raw("123").is_err());
assert!(password_to_raw("12345").is_err());
}
}