#[cfg(test)]
mod tests {
use crate::clap::test_util::{self, StereoTestBuffers};
use clack_host::prelude::*;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
#[cfg(feature = "clap-plugin")]
#[test]
fn test_gui_extension_x11() {
use clack_extensions::gui::{GuiApiType, GuiConfiguration, GuiSize, PluginGui};
let (_entry, _host_info, mut plugin_instance) = test_util::make_test_plugin();
let gui_ext = plugin_instance
.plugin_handle()
.get_extension::<PluginGui>()
.expect("PluginGui extension not found");
let mut handle = plugin_instance.plugin_handle();
assert!(gui_ext.is_api_supported(
&mut handle,
GuiConfiguration {
api_type: GuiApiType::X11,
is_floating: false,
}
));
assert!(!gui_ext.is_api_supported(
&mut handle,
GuiConfiguration {
api_type: GuiApiType::WAYLAND,
is_floating: false,
}
));
assert!(gui_ext.is_api_supported(
&mut handle,
GuiConfiguration {
api_type: GuiApiType::X11,
is_floating: true,
}
));
let pref = gui_ext
.get_preferred_api(&mut handle)
.expect("Preferred API not found");
assert_eq!(pref.api_type, GuiApiType::X11);
assert!(!pref.is_floating);
let size = gui_ext.get_size(&mut handle).expect("Failed to get size");
assert_eq!(size.width, 600);
assert_eq!(size.height, 275);
assert!(!gui_ext.can_resize(&mut handle));
assert!(
gui_ext
.set_size(
&mut handle,
GuiSize {
width: 600,
height: 275
}
)
.is_ok()
);
}
#[test]
fn test_ui_telemetry() {
let (_entry, _host_info, mut plugin_instance) = test_util::make_test_plugin();
let audio_config = PluginAudioConfiguration {
sample_rate: 48000.0,
min_frames_count: 512,
max_frames_count: 512,
};
let mut model_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
model_dir.push("tests/fixtures/models/BossWN-nano.nam");
let params = test_util::make_default_params(Some(model_dir));
test_util::load_plugin_state(&mut plugin_instance, ¶ms);
let shared = unsafe { &*test_util::extract_shared(&mut plugin_instance) };
{
let name_guard = shared.cold.ui_model_name.lock().unwrap();
assert_eq!(*name_guard, "BossWN-nano.nam");
}
let stopped_processor = plugin_instance.activate(|_, _| (), audio_config).unwrap();
let mut started_processor = stopped_processor.start_processing().unwrap();
shared
.rt_to_ui
.ui_peak_l
.store(0.0f32.to_bits(), Ordering::Relaxed);
shared
.rt_to_ui
.ui_peak_r
.store(0.0f32.to_bits(), Ordering::Relaxed);
shared.rt_to_ui.ui_clipped.store(false, Ordering::Relaxed);
let n = 512;
let mut bufs = StereoTestBuffers::new(n, 1.5, 0.5);
let mut input_channels = [bufs.in_l.as_mut_slice(), bufs.in_r.as_mut_slice()];
let input_audio = bufs.input_ports.with_input_buffers([AudioPortBuffer {
latency: 0,
channels: AudioPortBufferType::f32_input_only(
input_channels.iter_mut().map(InputChannel::constant),
),
}]);
let output_channels = [bufs.out_l.as_mut_slice(), bufs.out_r.as_mut_slice()];
let mut output_audio = bufs.output_ports.with_output_buffers([AudioPortBuffer {
latency: 0,
channels: AudioPortBufferType::f32_output_only(output_channels.into_iter()),
}]);
let input_events = InputEvents::empty();
let mut output_events = OutputEvents::from_buffer(&mut bufs.output_events_buffer);
started_processor
.process(
&input_audio,
&mut output_audio,
&input_events,
&mut output_events,
None,
None,
)
.unwrap();
let peak_l = f32::from_bits(shared.rt_to_ui.ui_peak_l.load(Ordering::Relaxed));
let peak_r = f32::from_bits(shared.rt_to_ui.ui_peak_r.load(Ordering::Relaxed));
assert!(peak_l > 0.0, "peak_l was: {}", peak_l);
assert!(peak_r > 0.0, "peak_r was: {}", peak_r);
let clipped = shared.rt_to_ui.ui_clipped.load(Ordering::Relaxed);
assert!(
clipped,
"ui_clipped should be true since input left was 1.5"
);
}
#[test]
fn test_gui_load_error_null_byte_safety() {
let (_entry, _host_info, mut plugin_instance) = test_util::make_test_plugin();
let shared = unsafe { &*test_util::extract_shared(&mut plugin_instance) };
let path_with_null = PathBuf::from("invalid_model\0name.nam");
if let Ok(mut pending_guard) = shared.cold.ui_pending_model.lock() {
*pending_guard = Some(path_with_null);
}
shared.cold.ui_loading.store(true, Ordering::Relaxed);
plugin_instance.call_on_main_thread_callback();
assert!(!shared.cold.ui_loading.load(Ordering::Relaxed));
assert!(shared.cold.ui_load_error.load(Ordering::Relaxed));
}
#[test]
fn test_c_string_null_byte_sanitization() {
use std::ffi::CString;
let err_msg = "Model load failed: file\0name.nam not found";
let err_str = format!("NAM-rs: Failed to load model from GUI: {}", err_msg);
let sanitized_err = err_str.replace('\0', " ");
let msg = CString::new(sanitized_err);
assert!(msg.is_ok(), "Sanitized CString creation should succeed");
}
#[test]
fn test_gui_load_model_build_failed() {
let (_entry, _host_info, mut plugin_instance) = test_util::make_test_plugin();
let shared = unsafe { &*test_util::extract_shared(&mut plugin_instance) };
let mut model_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
model_path.push("tests/fixtures/models/mock_a2.nam");
if let Ok(mut pending_guard) = shared.cold.ui_pending_model.lock() {
*pending_guard = Some(model_path);
}
shared.cold.ui_loading.store(true, Ordering::Relaxed);
plugin_instance.call_on_main_thread_callback();
assert!(!shared.cold.ui_loading.load(Ordering::Relaxed));
assert!(
shared.cold.ui_load_error.load(Ordering::Relaxed),
"Expected ui_load_error to be set when model build fails"
);
assert_eq!(
shared.cold.model_load_counter.load(Ordering::Relaxed),
0,
"model_load_counter should not increment for failed model build"
);
}
}