#![allow(missing_docs)]
use crate::clap::NamClapPlugin;
use crate::common::params::NamPluginParams;
use crate::dsp::pipeline::test_util::infra::{TrackingGuard, get_alloc_count};
use clack_extensions::state::PluginState;
use clack_host::prelude::*;
use std::path::PathBuf;
use std::sync::Arc;
pub struct TestHostShared;
impl<'a> SharedHandler<'a> for TestHostShared {
fn request_restart(&self) {}
fn request_process(&self) {}
fn request_callback(&self) {}
}
pub struct TestHost;
impl HostHandlers for TestHost {
type Shared<'a> = TestHostShared;
type MainThread<'a> = ();
type AudioProcessor<'a> = ();
}
pub fn make_test_plugin() -> (PluginEntry, HostInfo, PluginInstance<TestHost>) {
let entry =
PluginEntry::load_from_clack::<clack_plugin::entry::SinglePluginEntry<NamClapPlugin>>(
c"/test",
)
.expect("Failed to load PluginEntry");
let host_info = HostInfo::new("Test", "Test", "Test", "0.1.0").unwrap();
let instance = PluginInstance::<TestHost>::new(
|_| TestHostShared,
|_| (),
&entry,
c"br.eti.fabiolima.nam-rs",
&host_info,
)
.expect("Failed to instantiate plugin");
(entry, host_info, instance)
}
pub fn make_test_plugin_dynamic(
so_path: &std::path::Path,
) -> (PluginEntry, HostInfo, PluginInstance<TestHost>) {
let entry = unsafe { PluginEntry::load(so_path) }.expect("Failed to load PluginEntry from .so");
let host_info = HostInfo::new("Test", "Test", "Test", "0.1.0").unwrap();
let instance = PluginInstance::<TestHost>::new(
|_| TestHostShared,
|_| (),
&entry,
c"br.eti.fabiolima.nam-rs",
&host_info,
)
.expect("Failed to instantiate plugin");
(entry, host_info, instance)
}
pub fn make_default_params(model_path: Option<PathBuf>) -> NamPluginParams {
NamPluginParams {
model_path,
..Default::default()
}
}
pub fn extract_shared(
instance: &mut PluginInstance<TestHost>,
) -> *const crate::clap::plugin::NamClapShared {
let raw_ptr = instance.plugin_handle().as_raw_ptr();
unsafe {
clack_plugin::extensions::wrapper::PluginWrapper::<NamClapPlugin>::handle(
raw_ptr,
|wrapper| Ok(wrapper.shared() as *const crate::clap::plugin::NamClapShared),
)
}
.expect("Failed to get plugin wrapper")
}
pub fn get_state_ext(
instance: &mut PluginInstance<TestHost>,
) -> clack_extensions::state::PluginState {
instance
.plugin_handle()
.get_extension::<PluginState>()
.expect("State extension not found")
}
pub fn load_plugin_state(instance: &mut PluginInstance<TestHost>, params: &NamPluginParams) {
let state_ext = get_state_ext(instance);
let state_bytes = serde_json::to_vec(params).unwrap();
let mut handle = instance.plugin_handle();
state_ext
.load(&mut handle, &mut state_bytes.as_slice())
.expect("Failed to load state");
}
pub fn assert_log_buffer_contains(expected: &str) {
let buffer = crate::common::diagnostics::logger::NamLogger::log_buffer()
.expect("NamLogger::log_buffer() should return Some after plugin init");
let snapshot = buffer.snapshot();
let found = snapshot.iter().any(|r| r.message.contains(expected));
if !found {
let all_messages: Vec<_> = snapshot.iter().map(|r| &r.message).collect();
panic!("LogBuffer does not contain '{expected}'.\nRecent log messages:\n{all_messages:#?}");
}
}
pub type TestSinkCapture = Arc<std::sync::Mutex<Vec<(String, String)>>>;
pub type TestSink = Arc<crate::common::diagnostics::logger::HostLogFn>;
pub fn register_test_sink() -> (TestSinkCapture, TestSink) {
let captured: Arc<std::sync::Mutex<Vec<(String, String)>>> =
Arc::new(std::sync::Mutex::new(Vec::new()));
let cap = Arc::clone(&captured);
let sink: Arc<crate::common::diagnostics::logger::HostLogFn> =
Arc::new(move |severity: &str, msg: &str| {
if let Ok(mut v) = cap.lock() {
v.push((severity.to_string(), msg.to_string()));
}
});
let logger = crate::common::diagnostics::logger::NamLogger::global()
.expect("NamLogger::global() should be Some after plugin init");
logger.register_sink(&sink);
(captured, sink)
}
pub fn assert_zero_alloc<F: FnOnce()>(label: &str, f: F) {
let _guard = TrackingGuard::new();
let before = get_alloc_count();
f();
let after = get_alloc_count();
assert_eq!(
after - before,
0,
"{label}: allocations detected in hot-path"
);
}
pub struct StereoTestBuffers {
pub in_l: Vec<f32>,
pub in_r: Vec<f32>,
pub out_l: Vec<f32>,
pub out_r: Vec<f32>,
pub input_ports: AudioPorts,
pub output_ports: AudioPorts,
pub output_events_buffer: EventBuffer,
}
impl StereoTestBuffers {
pub fn new(n: usize, in_l_val: f32, in_r_val: f32) -> Self {
Self {
in_l: vec![in_l_val; n],
in_r: vec![in_r_val; n],
out_l: vec![0.0f32; n],
out_r: vec![0.0f32; n],
input_ports: AudioPorts::with_capacity(2, 1),
output_ports: AudioPorts::with_capacity(2, 1),
output_events_buffer: EventBuffer::new(),
}
}
}
pub struct MonoTestBuffers {
pub in_buf: Vec<f32>,
pub out_buf: Vec<f32>,
pub input_ports: AudioPorts,
pub output_ports: AudioPorts,
pub output_events_buffer: EventBuffer,
}
impl MonoTestBuffers {
pub fn new(n: usize, in_val: f32) -> Self {
Self {
in_buf: vec![in_val; n],
out_buf: vec![0.0f32; n],
input_ports: AudioPorts::with_capacity(1, 1),
output_ports: AudioPorts::with_capacity(1, 1),
output_events_buffer: EventBuffer::new(),
}
}
}