feat: add client end-to-end tests
This commit is contained in:
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -11,3 +14,6 @@ strum_macros = "0.28.0"
|
|||||||
[lib]
|
[lib]
|
||||||
name = "dsp_thomann"
|
name = "dsp_thomann"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
hardware-tests = []
|
||||||
@@ -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
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user