#![cfg(tokio_backend)]
#![cfg(feature = "ioc")]
use std::sync::Arc;
use ad_core_rs::ioc::GenericDriverContext;
use ad_core_rs::ndarray_pool::NDArrayPool;
use ad_core_rs::plugin::channel::NDArrayOutput;
use ad_plugins_rs::ioc::AdIoc;
use asyn_rs::port::DrvUserRequest;
use asyn_rs::port_handle::PortHandle;
use epics_base_rs::server::database::PvDatabase;
use epics_base_rs::server::iocsh::IocShell;
use epics_base_rs::server::iocsh::registry::CommandOutcome;
fn ad_ioc_with_driver(upstream: &str) -> (AdIoc, IocShell) {
let ioc = AdIoc::new();
let pool = Arc::new(NDArrayPool::new(4_000_000));
let output = Arc::new(parking_lot::Mutex::new(NDArrayOutput::new()));
ioc.mgr().set_driver(Arc::new(GenericDriverContext::new(
pool,
output,
upstream,
ioc.mgr().wiring(),
)));
let rt = tokio::runtime::Runtime::new().unwrap();
let db = Arc::new(PvDatabase::new());
let bridge = {
let _guard = rt.enter();
epics_base_rs::runtime::task::BlockingBridge::capture()
};
std::mem::forget(rt);
let shell = IocShell::new(db, bridge);
for def in ioc.app().startup_commands() {
shell.register(def.clone());
}
(ioc, shell)
}
fn run(shell: &IocShell, line: &str) {
match shell.execute_line(line) {
Ok(CommandOutcome::Continue) => {}
Ok(CommandOutcome::Failed) => panic!("st.cmd line `{line}` failed"),
Ok(CommandOutcome::Exit) => panic!("st.cmd line `{line}` ended the shell"),
Err(e) => panic!("st.cmd line `{line}` failed: {e}"),
}
}
fn reason(port: &PortHandle, drv_info: &str) -> usize {
port.drv_user_create_blocking(&DrvUserRequest::new(drv_info, 0))
.unwrap_or_else(|e| panic!("{drv_info} does not resolve on {}: {e}", port.port_name()))
.reason
}
fn plugin_port(name: &str) -> PortHandle {
asyn_rs::registry::get_port(name)
.unwrap_or_else(|| panic!("{name} was never registered"))
.handle
}
#[test]
fn configure_line_max_threads_reaches_the_plugin() {
let (_ioc, shell) = ad_ioc_with_driver("MTUP1");
run(
&shell,
r#"NDROIConfigure("MTROI1", 20, 0, "MTUP1", 0, 0, 0, 0, 0, 4)"#,
);
let port = plugin_port("MTROI1");
assert_eq!(
port.read_int32_blocking(reason(&port, "MAX_THREADS"), 0)
.unwrap(),
4,
"MaxThreads_RBV must report the ceiling the configure line asked for"
);
let num_threads = reason(&port, "NUM_THREADS");
port.write_int32_blocking(num_threads, 0, 4).unwrap();
assert_eq!(
port.read_int32_blocking(num_threads, 0).unwrap(),
4,
"a NumThreads write inside the configured ceiling must be accepted"
);
}
#[test]
fn roi_stat_max_threads_is_argument_ten() {
let (_ioc, shell) = ad_ioc_with_driver("MTUP2");
run(
&shell,
r#"NDROIStatConfigure("MTRS1", 20, 0, "MTUP2", 0, 8, 0, 0, 0, 40000, 3)"#,
);
let port = plugin_port("MTRS1");
assert_eq!(
port.read_int32_blocking(reason(&port, "MAX_THREADS"), 0)
.unwrap(),
3,
"maxThreads came from the stackSize slot"
);
}
#[test]
fn omitted_max_threads_leaves_the_c_default() {
let (_ioc, shell) = ad_ioc_with_driver("MTUP3");
run(&shell, r#"NDStatsConfigure("MTST1", 20, 0, "MTUP3", 0)"#);
run(
&shell,
r#"NDStdArraysConfigure("MTSA1", 20, 0, "MTUP3", 0, 0, 0, 0, 0, 0)"#,
);
for name in ["MTST1", "MTSA1"] {
let port = plugin_port(name);
assert_eq!(
port.read_int32_blocking(reason(&port, "MAX_THREADS"), 0)
.unwrap(),
1,
"{name} must fall back to C's floor of 1"
);
}
}
#[test]
fn a_command_without_the_argument_does_not_invent_one() {
let (_ioc, shell) = ad_ioc_with_driver("MTUP4");
run(
&shell,
r#"NDProcessConfigure("MTPR1", 20, 0, "MTUP4", 0, 0, 0, 0, 40000)"#,
);
let port = plugin_port("MTPR1");
assert_eq!(
port.read_int32_blocking(reason(&port, "MAX_THREADS"), 0)
.unwrap(),
1,
"a stackSize of 40000 was read as a thread ceiling"
);
}