#[path = "interop/harness.rs"]
mod harness;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::Duration;
use harness::{
LocalServerFixture, ProcessGuard, env_enabled, free_tcp_port, free_udp_port,
run_command_success_with_timeout, workspace_bin,
};
fn java_interop_dir() -> PathBuf {
let test_exe = std::env::current_exe().expect("cannot locate test executable");
let workspace = test_exe
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap();
workspace.join("tests").join("java-interop")
}
fn gradle_cmd(project_dir: &Path) -> Option<String> {
let gradlew = if cfg!(windows) {
project_dir.join("gradlew.bat")
} else {
project_dir.join("gradlew")
};
if gradlew.exists() {
return Some(gradlew.to_string_lossy().to_string());
}
let name = if cfg!(windows) {
"gradle.bat"
} else {
"gradle"
};
let check = Command::new(name).arg("--version").output();
if let Ok(output) = check {
if output.status.success() {
return Some(name.to_string());
}
}
None
}
fn ensure_java_built(project_dir: &Path) -> Result<String, String> {
let gradle = gradle_cmd(project_dir).ok_or_else(|| {
"gradle not found (no gradlew in tests/java-interop and no system gradle)".to_string()
})?;
let mut build_cmd = Command::new(&gradle);
build_cmd
.arg("--project-dir")
.arg(project_dir)
.arg("classes")
.arg("--no-daemon")
.arg("-q")
.stdout(Stdio::piped())
.stderr(Stdio::piped());
run_command_success_with_timeout(&mut build_cmd, "gradle classes", Duration::from_secs(120))?;
Ok(gradle)
}
fn run_java_interop(
gradle: &str,
project_dir: &Path,
server_addr: &str,
pv_name: &str,
tests: &[&str],
env: &[(String, String)],
) -> Result<String, String> {
let mut args_str = format!("{} {}", server_addr, pv_name);
for t in tests {
args_str.push(' ');
args_str.push_str(t);
}
let mut cmd = Command::new(gradle);
cmd.arg("--project-dir")
.arg(project_dir)
.arg("run")
.arg("--no-daemon")
.arg("-q")
.arg(format!("--args={}", args_str))
.envs(env.iter().map(|(k, v)| (k.as_str(), v.as_str())))
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let output =
run_command_success_with_timeout(&mut cmd, "java interop", Duration::from_secs(60))?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
#[test]
fn java_pvaccess_scalar_interop() {
if !env_enabled("RUN_JAVA_INTEROP") {
eprintln!("Skipping Java interop test: set RUN_JAVA_INTEROP=1");
return;
}
let project_dir = java_interop_dir();
if !project_dir.join("build.gradle").exists() {
eprintln!(
"Skipping Java interop: {} does not exist",
project_dir.display()
);
return;
}
let gradle = match ensure_java_built(&project_dir) {
Ok(g) => g,
Err(e) => {
eprintln!("Skipping Java interop: {}", e);
return;
}
};
let prefix = format!("JINT:{}", std::process::id());
let ao_pv = format!("{}:AO", prefix);
let db = format!(
r#"
record(ao, "{}") {{
field(VAL, "3.14")
}}
"#,
ao_pv
);
let server =
LocalServerFixture::spawn(&db, &["--pvlist-mode", "list"]).expect("spawn spvirit_server");
let env = server.epics_env();
let out = run_java_interop(
&gradle,
&project_dir,
&server.server_addr(),
&ao_pv,
&["beacon_connect"],
&env,
)
.expect("beacon_connect should succeed");
eprintln!("[java beacon_connect] {}", out);
assert!(out.contains("PASS"), "beacon_connect did not PASS: {}", out);
let out = run_java_interop(
&gradle,
&project_dir,
&server.server_addr(),
&ao_pv,
&["scalar_get"],
&env,
)
.expect("scalar_get should succeed");
eprintln!("[java scalar_get] {}", out);
assert!(out.contains("PASS"), "scalar_get did not PASS: {}", out);
let out = run_java_interop(
&gradle,
&project_dir,
&server.server_addr(),
&ao_pv,
&["scalar_put_get"],
&env,
)
.expect("scalar_put_get should succeed");
eprintln!("[java scalar_put_get] {}", out);
assert!(out.contains("PASS"), "scalar_put_get did not PASS: {}", out);
let out = run_java_interop(
&gradle,
&project_dir,
&server.server_addr(),
&ao_pv,
&["scalar_monitor"],
&env,
)
.expect("scalar_monitor should succeed");
eprintln!("[java scalar_monitor] {}", out);
assert!(out.contains("PASS"), "scalar_monitor did not PASS: {}", out);
}
#[test]
fn java_pvaccess_ndarray_interop() {
if !env_enabled("RUN_JAVA_INTEROP") {
eprintln!("Skipping Java NTNDArray interop test: set RUN_JAVA_INTEROP=1");
return;
}
let project_dir = java_interop_dir();
if !project_dir.join("build.gradle").exists() {
eprintln!(
"Skipping Java interop: {} does not exist",
project_dir.display()
);
return;
}
let gradle = match ensure_java_built(&project_dir) {
Ok(g) => g,
Err(e) => {
eprintln!("Skipping Java interop (ndarray): {}", e);
return;
}
};
let tcp_port = free_tcp_port().expect("free TCP port for pvdodeca");
let udp_port = free_udp_port().expect("free UDP port for pvdodeca");
let dodeca_bin = workspace_bin("spdodeca");
let pv_name = format!("DODECA:JTEST:{}", std::process::id());
let mut dodeca_cmd = Command::new(&dodeca_bin);
dodeca_cmd
.arg("--pv")
.arg(&pv_name)
.arg("--listen-addr")
.arg("127.0.0.1")
.arg("--tcp-port")
.arg(tcp_port.to_string())
.arg("--udp-port")
.arg(udp_port.to_string())
.stdout(Stdio::null())
.stderr(Stdio::null());
let _dodeca =
ProcessGuard::spawn(&mut dodeca_cmd, "spawn pvdodeca").expect("pvdodeca should spawn");
std::thread::sleep(std::time::Duration::from_millis(600));
let env = vec![
("EPICS_PVA_ADDR_LIST".to_string(), "127.0.0.1".to_string()),
("EPICS_PVA_AUTO_ADDR_LIST".to_string(), "NO".to_string()),
("EPICS_PVA_BROADCAST_PORT".to_string(), udp_port.to_string()),
("EPICS_PVA_CONN_TMO".to_string(), "5".to_string()),
];
let server_addr = format!("127.0.0.1:{}", tcp_port);
let out = run_java_interop(
&gradle,
&project_dir,
&server_addr,
&pv_name,
&["ndarray_get"],
&env,
)
.expect("ndarray_get should succeed");
eprintln!("[java ndarray_get] {}", out);
assert!(out.contains("PASS"), "ndarray_get did not PASS: {}", out);
let out = run_java_interop(
&gradle,
&project_dir,
&server_addr,
&pv_name,
&["ndarray_monitor"],
&env,
)
.expect("ndarray_monitor should succeed");
eprintln!("[java ndarray_monitor] {}", out);
assert!(
out.contains("PASS"),
"ndarray_monitor did not PASS: {}",
out
);
}