use juiz_core::prelude::*;
use juiz_sdk::process_identifier::ProcessIdentifier;
fn plugin_file(name: &str) -> std::path::PathBuf {
#[cfg(target_os = "macos")]
let file = format!("lib{name}.dylib");
#[cfg(target_os = "linux")]
let file = format!("lib{name}.so");
#[cfg(target_os = "windows")]
let file = format!("{name}.dll");
std::path::PathBuf::from("../target").join(file)
}
fn skip_without_haskell_examples() -> bool {
let missing: Vec<_> = [
"example_container_hs",
"example_container_hs_increment",
"example_container_hs_get",
"mismatched_container_process_hs",
]
.iter()
.map(|n| plugin_file(n))
.filter(|p| !p.exists())
.collect();
if missing.is_empty() {
return false;
}
if std::env::var("JUIZ_REQUIRE_HASKELL").map_or(false, |v| v == "1") {
panic!("Haskell の例がビルドされていません: {missing:?}");
}
eprintln!(
"skip: Haskell の例がビルドされていません ({missing:?})。GHC と cabal を入れて scripts/test.sh を実行してください。"
);
true
}
fn system_manifest() -> Value {
jvalue!({
"name": "haskell_container_test",
"plugins": {
"container_factories": {
"example_container_hs": {
"language": "haskell",
"path": "../target",
"processes": {
"example_container_hs_increment": { "path": "../target" },
"example_container_hs_get": { "path": "../target" }
}
},
"example_container_cpp": { "language": "c++", "path": "../target" }
}
},
"containers": [
{
"type_name": "example_container_hs",
"name": "c0",
"value": 15,
"processes": [
{ "type_name": "example_container_hs_increment", "name": "inc0" },
{ "type_name": "example_container_hs_get", "name": "get0" }
]
},
{
"type_name": "example_container_hs",
"name": "c1",
"processes": [
{ "type_name": "example_container_hs_increment", "name": "inc1" },
{ "type_name": "example_container_hs_get", "name": "get1" }
]
},
{ "type_name": "example_container_cpp", "name": "cpp0" }
]
})
}
fn find(cb: &CoreBroker, name: &str) -> JuizResult<ProcessIdentifier> {
let ids = cb.container_process_list(false, None)?;
Ok(ids
.into_iter()
.find(|id| id.name == name)
.unwrap_or_else(|| panic!("container process {name} not found")))
}
fn call(cb: &CoreBroker, id: &ProcessIdentifier, args: CapsuleMap) -> JuizResult<Value> {
Ok(cb
.container_process_call(id, args)?
.lock_as_value(|v| v.clone())?)
}
fn arg0(v: i64) -> CapsuleMap {
vec![("arg0", jvalue!(v))].into()
}
fn container_id(cb: &CoreBroker, name: &str) -> JuizResult<ContainerIdentifier> {
Ok(cb
.container_list(false, None)?
.into_iter()
.find(|id| id.name == name)
.unwrap_or_else(|| panic!("container {name} not found")))
}
#[test]
fn haskell_container_keeps_state() -> JuizResult<()> {
let _ = env_logger::try_init();
if skip_without_haskell_examples() {
return Ok(());
}
System::new(system_manifest())?
.setup()?
.run_and_do_once(|system| {
let lib = unsafe { libloading::Library::new(plugin_file("example_container_hs"))? };
let gc: libloading::Symbol<unsafe extern "C" fn()> = unsafe { lib.get(b"performMajorGC")? };
let cb = system.core_broker().lock()?;
let (inc0, get0) = (find(&cb, "inc0")?, find(&cb, "get0")?);
let (inc1, get1) = (find(&cb, "inc1")?, find(&cb, "get1")?);
assert_eq!(call(&cb, &get0, CapsuleMap::new())?, jvalue!(15));
assert_eq!(call(&cb, &get1, CapsuleMap::new())?, jvalue!(0));
assert_eq!(call(&cb, &inc0, arg0(3))?, jvalue!(18));
unsafe { gc() };
assert_eq!(call(&cb, &inc0, arg0(-20))?, jvalue!(-2));
unsafe { gc() };
assert_eq!(call(&cb, &get0, CapsuleMap::new())?, jvalue!(-2));
assert_eq!(call(&cb, &inc1, arg0(5))?, jvalue!(5));
assert_eq!(call(&cb, &get0, CapsuleMap::new())?, jvalue!(-2));
let profile = cb.container_profile_full(&container_id(&cb, "c0")?)?;
assert_eq!(profile.type_name, "example_container_hs");
let p = cb.container_process_profile_full(&inc0)?;
assert_eq!(p.type_name, "example_container_hs_increment");
assert_eq!(p.arguments[0].name, "arg0");
assert_eq!(p.description.as_str(), "Adds arg0 to the value of the container.");
let r = cb.container_process_call(&inc0, vec![("arg0", jvalue!("not a number"))].into());
assert!(r.is_err(), "{r:?}");
assert_eq!(call(&cb, &get0, CapsuleMap::new())?, jvalue!(-2));
Ok(())
})
}
#[test]
fn haskell_container_is_destroyed() -> JuizResult<()> {
let _ = env_logger::try_init();
if skip_without_haskell_examples() {
return Ok(());
}
System::new(system_manifest())?
.setup()?
.run_and_do_once(|system| {
let lib = unsafe { libloading::Library::new(plugin_file("example_container_hs"))? };
let alive_count: libloading::Symbol<unsafe extern "C" fn() -> i64> =
unsafe { lib.get(b"example_container_hs_alive_count")? };
let alive = || unsafe { alive_count() };
let mut cb = system.core_broker().lock_mut()?;
let initial = alive();
assert!(initial >= 2, "c0 と c1 が作られている: {initial}");
let created = cb.container_create(
&ContainerManifest::new("example_container_hs").name("c2"),
vec![("value", jvalue!(7))].into(),
)?;
assert_eq!(alive(), initial + 1);
cb.container_destroy(&created.identifier())?;
assert_eq!(alive(), initial);
Ok(())
})
}
#[test]
fn haskell_container_process_rejects_other_containers() -> JuizResult<()> {
let _ = env_logger::try_init();
if skip_without_haskell_examples() {
return Ok(());
}
System::new(system_manifest())?
.setup()?
.run_and_do_once(|system| {
let mut cb = system.core_broker().lock_mut()?;
let path = plugin_file("mismatched_container_process_hs");
cb.worker_mut().load_container_process_factory(
"haskell".to_owned(),
path.display().to_string(),
)?;
let c0 = container_id(&cb, "c0")?;
let mm = cb.container_process_create(
&c0,
&ProcessManifest::new("mismatched_container_process_hs").name("mm0"),
)?;
let r = cb.container_process_call(&mm.identifier(), CapsuleMap::new());
assert!(r.is_err(), "{r:?}");
let cpp0 = container_id(&cb, "cpp0")?;
let get = cb.container_process_create(
&cpp0,
&ProcessManifest::new("example_container_hs_get").name("hsget_on_cpp"),
)?;
let r = cb.container_process_call(&get.identifier(), CapsuleMap::new());
assert!(r.is_err(), "{r:?}");
let get0 = find(&cb, "get0")?;
assert_eq!(call(&cb, &get0, CapsuleMap::new())?, jvalue!(15));
Ok(())
})
}