#![allow(clippy::expect_used)]
use polyplug_codegen::{GenerateConfig, Lang, Side};
use polyplugc::generate;
use std::path::Path;
use std::path::PathBuf;
fn create_test_api_with_host_contracts(tmp_dir: &Path) -> PathBuf {
std::fs::create_dir_all(tmp_dir).expect("create tmp_dir");
let api_toml_path: PathBuf = tmp_dir.join("test_host_contract_api.toml");
let content: &str = r#"# Test API with host contracts
[[plugin_contract]]
name = "example.worker"
version = "1.0.0"
[[plugin_contract.functions]]
name = "do_work"
params = [{ name = "input", type = "StringView" }]
return = "StringView"
[[host_contract]]
name = "host.logger"
version = "1.0.0"
[[host_contract.functions]]
name = "log"
params = [{ name = "message", type = "StringView" }]
returns = "void"
"#;
std::fs::write(&api_toml_path, content).expect("failed to write test api.toml");
api_toml_path
}
#[test]
fn test_rust_host_contract_generates_host_contracts_file() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_rust_host_contract");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Rust,
side: Side::Host,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let host_contracts_path: PathBuf = tmp_dir.join("host").join("host_contracts.rs");
assert!(
host_contracts_path.exists(),
"host/host_contracts.rs must exist"
);
let content: String =
std::fs::read_to_string(&host_contracts_path).expect("read host_contracts.rs");
assert!(
content.contains("trait HostLogger"),
"must contain trait HostLogger"
);
assert!(content.contains("fn log"), "must contain fn log");
assert!(
content.contains("HOSTLOGGER_CONTRACT_ID"),
"must contain contract ID"
);
println!("test_rust_host_contract_generates_host_contracts_file: passed ✓");
}
#[test]
fn test_rust_host_contract_guest_generates_caller() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_rust_host_contract_guest");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Rust,
side: Side::Guest,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let caller_path: PathBuf = tmp_dir.join("guest").join("host_contract_callers.rs");
assert!(
caller_path.exists(),
"guest/host_contract_callers.rs must exist"
);
let content: String =
std::fs::read_to_string(&caller_path).expect("read host_contract_callers.rs");
assert!(
content.contains("struct HostLoggerCaller"),
"must contain HostLoggerCaller"
);
assert!(content.contains("from_host"), "must contain from_host");
assert!(content.contains("is_valid"), "must contain is_valid");
println!("test_rust_host_contract_guest_generates_caller: passed ✓");
}
#[test]
fn test_cpp_host_contract_generates_host_contracts_file() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_cpp_host_contract");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Cpp,
side: Side::Host,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let host_contracts_path: PathBuf = tmp_dir.join("host").join("host_contracts.hpp");
assert!(
host_contracts_path.exists(),
"host/host_contracts.hpp must exist"
);
let content: String =
std::fs::read_to_string(&host_contracts_path).expect("read host_contracts.hpp");
assert!(
content.contains("class HostLogger"),
"must contain class HostLogger"
);
assert!(content.contains("virtual"), "must contain virtual method");
assert!(
content.contains("HOSTLOGGER_CONTRACT_ID"),
"must contain contract ID"
);
println!("test_cpp_host_contract_generates_host_contracts_file: passed ✓");
}
#[test]
fn test_cpp_host_contract_guest_generates_caller() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_cpp_host_contract_guest");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Cpp,
side: Side::Guest,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let caller_path: PathBuf = tmp_dir.join("guest").join("host_contracts.hpp");
assert!(caller_path.exists(), "guest/host_contracts.hpp must exist");
let content: String =
std::fs::read_to_string(&caller_path).expect("read guest/host_contracts.hpp");
assert!(
content.contains("HostLoggerContract"),
"must contain HostLoggerContract"
);
assert!(content.contains("from_host"), "must contain from_host");
assert!(content.contains("is_valid"), "must contain is_valid");
println!("test_cpp_host_contract_guest_generates_caller: passed ✓");
}
#[test]
fn test_csharp_host_contract_generates_contracts_file() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_csharp_host_contract");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::CSharp,
side: Side::Host,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let contracts_path: PathBuf = tmp_dir.join("host").join("Contracts.cs");
assert!(contracts_path.exists(), "host/Contracts.cs must exist");
let content: String = std::fs::read_to_string(&contracts_path).expect("read Contracts.cs");
assert!(
content.contains("interface IHostLogger"),
"must contain IHostLogger"
);
assert!(content.contains("void Log"), "must contain Log method");
assert!(
content.contains("HOSTLOGGER_CONTRACT_ID"),
"must contain contract ID"
);
println!("test_csharp_host_contract_generates_contracts_file: passed ✓");
}
#[test]
fn test_csharp_host_contract_guest_generates_caller() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_csharp_host_contract_guest");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::CSharp,
side: Side::Guest,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let caller_path: PathBuf = tmp_dir.join("guest").join("HostContracts.cs");
assert!(caller_path.exists(), "guest/HostContracts.cs must exist");
let content: String =
std::fs::read_to_string(&caller_path).expect("read guest/HostContracts.cs");
assert!(content.contains("HostLogger"), "must contain HostLogger");
assert!(content.contains("FromHost"), "must contain FromHost");
assert!(content.contains("IsValid"), "must contain IsValid");
println!("test_csharp_host_contract_guest_generates_caller: passed ✓");
}
fn generate_csharp_host_side(tmp_dir: &Path) -> (String, String) {
let api_toml: PathBuf = create_test_api_with_host_contracts(tmp_dir);
let config = GenerateConfig {
api_toml,
lang: Lang::CSharp,
side: Side::Host,
out_dir: tmp_dir.to_path_buf(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let callers: String =
std::fs::read_to_string(tmp_dir.join("host").join("Callers.cs")).expect("read Callers.cs");
let factories: String =
std::fs::read_to_string(tmp_dir.join("host").join("InterfaceFactories.cs"))
.expect("read InterfaceFactories.cs");
(callers, factories)
}
#[test]
fn test_csharp_host_callers_use_real_runtime_api() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_csharp_host_callers_api");
let (callers, _factories): (String, String) = generate_csharp_host_side(&tmp_dir);
assert!(
callers.contains("public static ExampleWorkerContractCaller? Create(Runtime rt) {"),
"Callers.cs must emit a one-arg Create(Runtime rt) factory: {callers}"
);
assert!(
callers.contains("rt.ResolveGuestContract(handle)"),
"Callers.cs must call the real Runtime.ResolveGuestContract: {callers}"
);
assert!(
!callers.contains("ResolveContract(handle)"),
"Callers.cs must NOT call the phantom ResolveContract: {callers}"
);
assert!(
callers.contains("public sealed unsafe class ExampleWorkerContractCaller"),
"the caller class must be `unsafe` to hold pointer fields: {callers}"
);
assert!(
callers.contains("(HostApi*)rt.HostHandle"),
"Create must derive the HostApi pointer from Runtime.HostHandle: {callers}"
);
assert!(
!callers.contains("if (inst.Data == nint.Zero) { return null; }"),
"a null instance handle is valid for stateless contracts and must not abort Create: {callers}"
);
assert!(
callers.contains("(delegate* unmanaged[Cdecl]<HostApi*, GuestContractInterface*, void*, GuestContractInstance*, void>)host->CreateGuestInstance"),
"Create must cast the IntPtr CreateGuestInstance field to a function pointer (out-param: writes through GuestContractInstance*): {callers}"
);
println!("test_csharp_host_callers_use_real_runtime_api: passed ✓");
}
#[test]
fn test_csharp_interface_factories_use_real_abi_types() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_csharp_factory_abi");
let (_callers, factories): (String, String) = generate_csharp_host_side(&tmp_dir);
assert!(
factories
.contains("public static unsafe HostContractInterface CreateHostLoggerInterface<T>"),
"factory must return the canonical HostContractInterface: {factories}"
);
assert!(
factories.contains("new NativeDispatch {"),
"factory must build a NativeDispatch: {factories}"
);
assert!(
factories.contains("new VmDispatch {"),
"VM factory must build a VmDispatch: {factories}"
);
for phantom in [
"HostContractVTable",
"HostContractVTableHeader",
"NativeHostContractDispatch",
"VmHostContractDispatchFn",
"HostContractDispatch",
] {
assert!(
!factories.contains(phantom),
"InterfaceFactories.cs must not reference phantom type `{phantom}`: {factories}"
);
}
println!("test_csharp_interface_factories_use_real_abi_types: passed ✓");
}
fn generate_csharp_guest_host_contracts(tmp_dir: &Path) -> String {
let api_toml: PathBuf = create_test_api_with_host_contracts(tmp_dir);
let config = GenerateConfig {
api_toml,
lang: Lang::CSharp,
side: Side::Guest,
out_dir: tmp_dir.to_path_buf(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
std::fs::read_to_string(tmp_dir.join("guest").join("HostContracts.cs"))
.expect("read guest/HostContracts.cs")
}
#[test]
fn test_csharp_guest_host_contract_callers_use_real_abi_types() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_csharp_guest_caller_abi");
let callers: String = generate_csharp_guest_host_contracts(&tmp_dir);
assert!(
callers.contains("HostContractInterface*"),
"guest caller must cast the interface to the canonical HostContractInterface*: {callers}"
);
assert!(
callers.contains("GetHostContract"),
"guest caller must obtain the instance via GetHostContract: {callers}"
);
assert!(
callers.contains("ResolveHostContractInterface"),
"guest caller must resolve the interface via ResolveHostContractInterface: {callers}"
);
assert!(
callers.contains("contract->DispatchType"),
"guest caller must branch on the real DispatchType field: {callers}"
);
assert!(
callers.contains("contract->Dispatch.Native.Functions")
&& callers.contains("contract->Dispatch.Native.FunctionCount"),
"guest caller must read the flat Dispatch.Native fields: {callers}"
);
assert!(
callers.contains("contract->Dispatch.Vm.Call")
&& callers.contains("contract->Dispatch.Vm.LoaderData"),
"guest caller must read the flat Dispatch.Vm fields: {callers}"
);
assert!(
callers.contains("err.Code != (uint)AbiErrorCode.Ok"),
"guest caller must check the AbiError code via the AbiErrorCode enum: {callers}"
);
for phantom in [
"HostContractVTable",
"header->Header",
"Dispatch.VM.Call",
"Dispatch.VM.BridgeData",
"BridgeData",
] {
assert!(
!callers.contains(phantom),
"guest/HostContracts.cs must not reference phantom `{phantom}`: {callers}"
);
}
println!("test_csharp_guest_host_contract_callers_use_real_abi_types: passed ✓");
}
#[test]
fn test_python_host_contract_generates_contracts_file() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_python_host_contract");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Python,
side: Side::Host,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let contracts_path: PathBuf = tmp_dir.join("host").join("contracts.py");
assert!(contracts_path.exists(), "host/contracts.py must exist");
let content: String = std::fs::read_to_string(&contracts_path).expect("read contracts.py");
assert!(
content.contains("class HostLogger"),
"must contain HostLogger"
);
assert!(content.contains("ABC"), "must contain ABC");
assert!(content.contains("def log"), "must contain log method");
assert!(
content.contains("HOSTLOGGER_CONTRACT_ID"),
"must contain contract ID"
);
println!("test_python_host_contract_generates_contracts_file: passed ✓");
}
#[test]
fn test_python_host_contract_guest_generates_caller() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_python_host_contract_guest");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Python,
side: Side::Guest,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let caller_path: PathBuf = tmp_dir.join("guest").join("host_contracts.py");
assert!(caller_path.exists(), "guest/host_contracts.py must exist");
let content: String =
std::fs::read_to_string(&caller_path).expect("read guest/host_contracts.py");
assert!(
content.contains("HostLoggerContract"),
"must contain HostLoggerContract"
);
assert!(content.contains("from_host"), "must contain from_host");
assert!(content.contains("is_valid"), "must contain is_valid");
println!("test_python_host_contract_guest_generates_caller: passed ✓");
}
#[test]
fn test_python_host_caller_branches_on_dispatch_type() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_python_host_caller_dispatch");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Python,
side: Side::Host,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let callers_path: PathBuf = tmp_dir.join("host").join("callers.py");
assert!(callers_path.exists(), "host/callers.py must exist");
let content: String = std::fs::read_to_string(&callers_path).expect("read host/callers.py");
assert!(
content.contains("DispatchType"),
"host caller must import/use DispatchType"
);
assert!(
content.contains("if interface.dispatch_type == DispatchType.Native:"),
"host caller must branch on dispatch_type"
);
assert!(
content.contains(
"interface.dispatch.vm.call(interface.dispatch.vm.loader_data, self._instance,"
),
"VM dispatch must use the canonical 7-arg out-param call shape"
);
assert!(
content.contains(", args_ptr, out_ptr, ctypes.byref(self._arena), ctypes.byref(err))"),
"arena-backed VM dispatch must hand the guest the per-call arena then the AbiError out-param"
);
println!("test_python_host_caller_branches_on_dispatch_type: passed ✓");
}
#[test]
fn test_lua_host_contract_generates_contracts_file() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_lua_host_contract");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Lua,
side: Side::Host,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let contracts_path: PathBuf = tmp_dir.join("host").join("contracts.lua");
assert!(contracts_path.exists(), "host/contracts.lua must exist");
let content: String = std::fs::read_to_string(&contracts_path).expect("read contracts.lua");
assert!(content.contains("HostLogger"), "must contain HostLogger");
assert!(
content.contains("function HostLogger:log"),
"must contain log method"
);
assert!(
content.contains("HOSTLOGGER_CONTRACT_ID"),
"must contain contract ID"
);
println!("test_lua_host_contract_generates_contracts_file: passed ✓");
}
#[test]
fn test_lua_host_contract_guest_generates_caller() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_lua_host_contract_guest");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::Lua,
side: Side::Guest,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let caller_path: PathBuf = tmp_dir.join("guest").join("host_contracts.lua");
assert!(caller_path.exists(), "guest/host_contracts.lua must exist");
let content: String =
std::fs::read_to_string(&caller_path).expect("read guest/host_contracts.lua");
assert!(
content.contains("HostLoggerContract"),
"must contain HostLoggerContract"
);
assert!(content.contains("from_host"), "must contain from_host");
assert!(content.contains("is_valid"), "must contain is_valid");
assert!(
content.contains("require(\"polyplug_abi\")"),
"guest host-contract caller must require polyplug_abi for its cdefs"
);
assert!(
content.contains("ffi.cast(\"HostContractInterface*\""),
"must cast to the canonical HostContractInterface"
);
assert!(
!content.contains("HostContractVTable"),
"must NOT reference the nonexistent HostContractVTable type"
);
assert!(
!content.contains(".header."),
"must not read through a nonexistent .header field"
);
assert!(
content.contains("interface.dispatch.vm.call(interface.dispatch.vm.loader_data,")
&& content.contains(", args_ptr, out_ptr, nil, err)"),
"VM dispatch must use the 7-arg call(loader_data, instance, fn_id, args, out, nil, err)"
);
assert!(
!content.contains("bridge_data"),
"must use loader_data, not the nonexistent bridge_data field"
);
println!("test_lua_host_contract_guest_generates_caller: passed ✓");
}
#[test]
fn test_js_quickjs_host_contract_generates_contracts_file() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_js_quickjs_host_contract");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::JsQuickJs,
side: Side::Host,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let contracts_path: PathBuf = tmp_dir.join("host").join("contracts.ts");
assert!(contracts_path.exists(), "host/contracts.ts must exist");
let content: String = std::fs::read_to_string(&contracts_path).expect("read contracts.ts");
assert!(
content.contains("interface HostLogger"),
"must contain HostLogger interface"
);
assert!(content.contains("Log"), "must contain Log method");
assert!(
content.contains("HOSTLOGGER_CONTRACT_ID"),
"must contain contract ID"
);
println!("test_js_quickjs_host_contract_generates_contracts_file: passed ✓");
}
#[test]
fn test_js_quickjs_host_contract_guest_generates_caller() {
let tmp_dir: PathBuf =
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("test_js_quickjs_host_contract_guest");
let api_toml: PathBuf = create_test_api_with_host_contracts(&tmp_dir);
let config = GenerateConfig {
api_toml: api_toml.clone(),
lang: Lang::JsQuickJs,
side: Side::Guest,
out_dir: tmp_dir.clone(),
};
let output = generate(config).expect("polyplugc::generate failed");
for file in &output.files {
let file_path = tmp_dir.join(&file.path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).expect("failed to create parent dir");
}
std::fs::write(&file_path, &file.content).expect("failed to write generated file");
}
let caller_path: PathBuf = tmp_dir.join("guest").join("host_contracts.ts");
assert!(caller_path.exists(), "guest/host_contracts.ts must exist");
let content: String =
std::fs::read_to_string(&caller_path).expect("read guest/host_contracts.ts");
assert!(
content.contains("HostLoggerContract"),
"must contain HostLoggerContract"
);
assert!(content.contains("fromHost"), "must contain fromHost");
assert!(content.contains("isValid"), "must contain isValid");
assert!(
content.contains("polyplug.callHostContract("),
"must use callHostContract bridge primitive"
);
assert!(
!content.contains("readHostContractHeader"),
"must not use readHostContractHeader"
);
assert!(
!content.contains("callVmDispatch"),
"must not use callVmDispatch"
);
assert!(
!content.contains("callDispatchFn"),
"must not use callDispatchFn"
);
assert!(
content.contains("const _callerAlloc = (sz: number): number[] =>"),
"must use the _callerAlloc host-allocator shim for caller buffers"
);
assert!(
content.contains("for (const _f of _frees) { polyplug.free("),
"must explicitly free caller buffers in a finally block"
);
assert!(
!content.contains("(globalThis as any).polyplug"),
"host-contract caller must not read a global bridge"
);
println!("test_js_quickjs_host_contract_guest_generates_caller: passed ✓");
}