#![cfg(feature = "dynclib-engine")]
use actr_framework::guest::dynclib_abi::{InitPayloadV1, version};
use std::path::{Path, PathBuf};
use std::process::Command;
use actr_hyper::dynclib::{DynclibError, DynclibHost};
use actr_hyper::test_support::instantiate_dynclib_workload;
use actr_hyper::workload::{HostAbiFn, HostOperation, HostOperationResult, InvocationContext};
use actr_protocol::{ActrId, ActrType, Realm, RpcEnvelope, prost::Message as ProstMessage};
fn build_fixture() -> PathBuf {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let fixture_dir = manifest_dir.join("tests/dynclib_actor_fixture");
let status = Command::new("cargo")
.args(["build"])
.current_dir(&fixture_dir)
.status()
.expect("failed to build dynclib fixture");
assert!(status.success(), "dynclib fixture build failed");
let target_dir = manifest_dir.join("../../target/core-hyper-tests-dynclib-actor-fixture/debug");
if cfg!(target_os = "linux") {
target_dir.join("libdynclib_actor_fixture.so")
} else if cfg!(target_os = "macos") {
target_dir.join("libdynclib_actor_fixture.dylib")
} else {
target_dir.join("dynclib_actor_fixture.dll")
}
}
fn make_envelope(route_key: &str, payload: Vec<u8>) -> Vec<u8> {
let envelope = RpcEnvelope {
route_key: route_key.to_string(),
payload: Some(payload.into()),
..Default::default()
};
envelope.encode_to_vec()
}
fn test_actr_id() -> ActrId {
ActrId {
realm: Realm { realm_id: 1 },
serial_number: 1,
r#type: ActrType {
manufacturer: "test".to_string(),
name: "fixture".to_string(),
version: "0.1.0".to_string(),
},
}
}
fn test_ctx() -> InvocationContext {
InvocationContext {
self_id: test_actr_id(),
caller_id: None,
request_id: "test".to_string(),
}
}
fn test_config() -> InitPayloadV1 {
InitPayloadV1 {
version: version::V1,
actr_type: "test:fixture:0.1.0".to_string(),
credential: Vec::new(),
actor_id: Vec::new(),
realm_id: 1,
}
}
#[test]
fn test_load_nonexistent_library() {
let result = DynclibHost::load("/tmp/nonexistent_library_xyz.so");
assert!(result.is_err(), "loading non-existent library should fail");
let err = result.unwrap_err();
assert!(
matches!(err, DynclibError::LoadFailed(_)),
"error should be LoadFailed, got: {err:?}"
);
}
#[test]
#[ignore = "requires fixture compilation"]
fn test_load_and_instantiate() {
let so_path = build_fixture();
let host = DynclibHost::load(&so_path).expect("load should succeed");
let _instance =
instantiate_dynclib_workload(host, &test_config()).expect("instantiate should succeed");
}
#[tokio::test]
#[ignore = "requires fixture compilation"]
async fn test_basic_echo_dispatch() {
let so_path = build_fixture();
let host = DynclibHost::load(&so_path).expect("load");
let mut instance = instantiate_dynclib_workload(host, &test_config()).expect("instantiate");
let payload = b"hello dynclib".to_vec();
let req_bytes = make_envelope("test/echo", payload.clone());
let executor: HostAbiFn =
std::sync::Arc::new(|_| Box::pin(async { HostOperationResult::Error(-1) }));
let result = instance
.handle(&req_bytes, test_ctx(), &executor)
.await
.expect("echo dispatch should succeed");
assert_eq!(result, payload, "echo should return input payload");
}
#[tokio::test]
#[ignore = "requires fixture compilation"]
async fn test_basic_double_dispatch() {
let so_path = build_fixture();
let host = DynclibHost::load(&so_path).expect("load");
let mut instance = instantiate_dynclib_workload(host, &test_config()).expect("instantiate");
let x: i32 = 21;
let req_bytes = make_envelope("test/double", x.to_le_bytes().to_vec());
let executor: HostAbiFn = std::sync::Arc::new(|pending| {
Box::pin(async move {
match pending {
HostOperation::CallRaw(req) => {
let val = i32::from_le_bytes([
req.payload[0],
req.payload[1],
req.payload[2],
req.payload[3],
]);
HostOperationResult::Bytes((val * 2).to_le_bytes().to_vec())
}
_ => HostOperationResult::Error(-1),
}
})
});
let result = instance
.handle(&req_bytes, test_ctx(), &executor)
.await
.expect("double dispatch should succeed");
let resp_val = i32::from_le_bytes([result[0], result[1], result[2], result[3]]);
assert_eq!(resp_val, 42, "21 * 2 should be 42");
}