feat: add client end-to-end tests

This commit is contained in:
2026-07-21 11:56:44 +02:00
parent a3c17186be
commit a0af0da0ed
5 changed files with 270 additions and 4 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ repos:
- id: test - id: test
name: cargo test name: cargo test
entry: cargo test --all entry: cargo test --all --features hardware-tests -- --include-ignored
language: system language: system
pass_filenames: false pass_filenames: false
stages: [pre-push] stages: [pre-push]
Generated
+7
View File
@@ -2,10 +2,17 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]] [[package]]
name = "dsp_thomann" name = "dsp_thomann"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dotenvy",
"strum", "strum",
"strum_macros", "strum_macros",
"thiserror", "thiserror",
+7 -1
View File
@@ -3,6 +3,9 @@ name = "dsp_thomann"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dev-dependencies]
dotenvy = "0.15"
[dependencies] [dependencies]
thiserror = "2.0.19" thiserror = "2.0.19"
strum = "0.28.0" strum = "0.28.0"
@@ -10,4 +13,7 @@ strum_macros = "0.28.0"
[lib] [lib]
name = "dsp_thomann" name = "dsp_thomann"
path = "src/lib.rs" path = "src/lib.rs"
[features]
hardware-tests = []
+253
View File
@@ -1427,3 +1427,256 @@ impl fmt::Debug for DSP408 {
.finish() .finish()
} }
} }
#[cfg(feature = "hardware-tests")]
#[test]
fn full_command_sweep_against_real_hardware() {
use std::sync::Once;
use std::{env, str::FromStr};
static INIT: Once = Once::new();
fn init_test_env() {
INIT.call_once(|| {
dotenvy::dotenv().ok();
});
}
fn dsp_host() -> IpAddr {
IpAddr::from_str(&env::var("DSP_HOST").unwrap_or_else(|_| "192.168.49.30".into()))
.expect("DSP_HOST must be a valid IP")
}
fn dsp_port() -> u16 {
env::var("DSP_PORT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(9761)
}
fn dsp_device_id() -> u8 {
env::var("DSP_DEVICE_ID")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1)
}
fn destructive_allowed() -> bool {
matches!(
env::var("DSP_ALLOW_DESTRUCTIVE")
.unwrap_or_default()
.as_str(),
"1" | "true" | "yes"
)
}
init_test_env();
let host = dsp_host();
let port = dsp_port();
let device_id = dsp_device_id();
let mut dsp = DSP408::new(host, port, device_id).expect("invalid connection params");
// ---------- Connect ----------
dsp.connect().expect("connect() should succeed");
assert!(
dsp.connected(),
"device should report connected after connect()"
);
{
let state = dsp
.state()
.expect("state should be populated after connect");
assert!(!state.name.is_empty(), "device name should be non-empty");
assert!(
!state.presets.names.is_empty(),
"preset name list should be populated"
);
assert!(
!state.current_config.input_states.is_empty(),
"input map should be populated"
);
assert!(
!state.current_config.output_states.is_empty(),
"output map should be populated"
);
}
// ---------- Delay unit ----------
let ok = dsp
.set_delay_unit(types::DelayUnit::Millisecond)
.expect("set_delay_unit");
assert!(ok);
assert_eq!(
dsp.state().unwrap().current_config.delay_unit,
types::DelayUnit::Millisecond
);
// ---------- Input channel exercise ----------
let input = types::InputChannel::InA;
let in_ch = types::Channel::Input(input);
let ok = dsp.set_mute(in_ch, true).expect("set_mute");
assert!(ok);
assert!(dsp.state().unwrap().current_config.input_states[&input].mute);
let ok = dsp.set_mute(in_ch, false).expect("unmute");
assert!(ok);
assert!(!dsp.state().unwrap().current_config.input_states[&input].mute);
let ok = dsp.set_channel_gain(in_ch, -3.0).expect("set_channel_gain");
assert!(ok);
assert!((dsp.state().unwrap().current_config.input_states[&input].gain - (-3.0)).abs() < 0.01);
let ok = dsp
.set_channel_inverse_gain(in_ch, true)
.expect("set_channel_inverse_gain");
assert!(ok);
assert!(dsp.state().unwrap().current_config.input_states[&input].phase_inverted);
dsp.set_channel_inverse_gain(in_ch, false)
.expect("revert phase");
let ok = dsp
.set_channel_delay(in_ch, 1.5, types::DelayUnit::Millisecond)
.expect("set_channel_delay");
assert!(ok);
let ok = dsp
.set_channel_name(in_ch, "Test InA")
.expect("set_channel_name");
assert!(ok);
assert_eq!(
dsp.state().unwrap().current_config.input_states[&input].name,
"Test InA"
);
let ok = dsp
.set_low_pass(in_ch, 120.0, types::CrossoverFilter::Bw24)
.expect("set_low_pass");
assert!(ok);
let ok = dsp
.set_high_pass(in_ch, 80.0, types::CrossoverFilter::Bw24)
.expect("set_high_pass");
assert!(ok);
let ok = dsp
.set_peq_band(
in_ch,
types::PEQBand::B1,
2.0,
1000.0,
1.0,
types::PEQFilter::Peak,
false,
)
.expect("set_peq_band");
assert!(ok);
let ok = dsp.set_bypass_peq(in_ch, true).expect("set_bypass_peq");
assert!(ok);
dsp.set_bypass_peq(in_ch, false).expect("un-bypass peq");
let ok = dsp.set_gate(input, -40.0, 5, 100, 200).expect("set_gate");
assert!(ok);
let ok = dsp
.set_geq_band(input, types::DiscreteFrequency::F100, 1.0)
.expect("set_geq_band");
assert!(ok);
let ok = dsp.set_geq_bypass(input, true).expect("set_geq_bypass");
assert!(ok);
dsp.set_geq_bypass(input, false).expect("un-bypass geq");
let ok = dsp
.set_input_source(types::InputSource {
r#type: types::InputType::Analog, // adjust to your enum
frequency: types::DiscreteFrequency::F1000, // adjust to your enum
})
.expect("set_input_source");
assert!(ok);
// // ---------- Output channel exercise ----------
let output = types::OutputChannel::Out1;
let out_ch = types::Channel::Output(output);
let ok = dsp
.set_compressor(output, types::Ratio::Limit, 5, 50, 2, -12.0)
.expect("set_compressor");
assert!(ok);
let ok = dsp.set_limiter(output, -3.0, 1, 50).expect("set_limiter");
assert!(ok);
let ok = dsp
.set_matrix_gain(input, output, -6.0)
.expect("set_matrix_gain");
assert!(ok);
let ok = dsp
.set_matrix_routing(output, vec![input])
.expect("set_matrix_routing");
assert!(ok);
assert_eq!(
dsp.state().unwrap().current_config.output_states[&output]
.matrix_routes
.connected,
vec![input]
);
// // ---------- Copy / link channels ----------
let other_output = types::OutputChannel::Out2;
let ok = dsp
.copy_channel(out_ch, types::Channel::Output(other_output))
.expect("copy_channel");
assert!(ok);
let ok = dsp
.link_channels(out_ch, vec![types::Channel::Output(other_output)])
.expect("link_channels");
assert!(ok);
// // ---------- Preset name / modification status ----------
let ok = dsp
.set_current_preset_name("Integration Te")
.expect("set_current_preset_name");
assert!(ok);
let status = dsp
.get_preset_modification_status()
.expect("get_preset_modification_status");
println!("Preset modification status: {:?}", status);
let name = dsp.get_preset_name(1).expect("get_preset_name");
println!("Preset 1 name: {}", name);
// // ---------- Meters ----------
let meters = dsp.get_meter_levels().expect("get_meter_levels");
println!("Meters: {:?}", meters);
// // ---------- Destructive ops (opt-in only) ----------
if destructive_allowed() {
let ok = dsp
.store_current_configuration(1, false)
.expect("store_current_configuration");
assert!(ok);
assert!(dsp.state().unwrap().presets.modified[1]);
let ok = dsp.recall_preset(1).expect("recall_preset");
assert!(ok);
assert_eq!(dsp.state().unwrap().presets.current_index, 1);
// factory_reset intentionally left commented out — wipes the unit.
// let ok = dsp.factory_reset(true).expect("factory_reset");
// assert!(ok);
} else {
println!("Skipping destructive ops (set DSP_ALLOW_DESTRUCTIVE=1 to enable)");
}
// ---------- Disconnect ----------
dsp.disconnect();
assert!(!dsp.connected(), "device should report disconnected");
}
+2 -2
View File
@@ -354,13 +354,13 @@ pub struct DSPConfigState {
} }
impl DSPConfigState { impl DSPConfigState {
pub fn input_state_mut(&mut self, channel: InputChannel) -> &mut InputChannelState { pub(crate) fn input_state_mut(&mut self, channel: InputChannel) -> &mut InputChannelState {
self.input_states self.input_states
.get_mut(&channel) .get_mut(&channel)
.expect("Invalid input channel state") .expect("Invalid input channel state")
} }
pub fn output_state_mut(&mut self, channel: OutputChannel) -> &mut OutputChannelState { pub(crate) fn output_state_mut(&mut self, channel: OutputChannel) -> &mut OutputChannelState {
self.output_states self.output_states
.get_mut(&channel) .get_mut(&channel)
.expect("Invalid output channel state") .expect("Invalid output channel state")