feat: add client end-to-end tests
This commit is contained in:
@@ -21,7 +21,7 @@ repos:
|
||||
|
||||
- id: test
|
||||
name: cargo test
|
||||
entry: cargo test --all
|
||||
entry: cargo test --all --features hardware-tests -- --include-ignored
|
||||
language: system
|
||||
pass_filenames: false
|
||||
stages: [pre-push]
|
||||
|
||||
Generated
+7
@@ -2,10 +2,17 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dsp_thomann"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror",
|
||||
|
||||
@@ -3,6 +3,9 @@ name = "dsp_thomann"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dev-dependencies]
|
||||
dotenvy = "0.15"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "2.0.19"
|
||||
strum = "0.28.0"
|
||||
@@ -11,3 +14,6 @@ strum_macros = "0.28.0"
|
||||
[lib]
|
||||
name = "dsp_thomann"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
hardware-tests = []
|
||||
@@ -1427,3 +1427,256 @@ impl fmt::Debug for DSP408 {
|
||||
.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
@@ -354,13 +354,13 @@ pub struct 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
|
||||
.get_mut(&channel)
|
||||
.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
|
||||
.get_mut(&channel)
|
||||
.expect("Invalid output channel state")
|
||||
|
||||
Reference in New Issue
Block a user