arora-engine 5.3.0

The Arora engine: loads and executes Arora modules (wasm and native hosts).
//! Native-host integration tests: a guest built for the host as a shared
//! library, loaded through [`NativeExecutor`] and dispatched.

#![cfg(all(not(target_arch = "wasm32"), feature = "native-host"))]

use arora_engine::call::CallBridge;
use arora_engine::compiled::CompiledModule;
use arora_engine::engine::{Engine, EngineBuilder};
use arora_engine::executor::native::NativeExecutor;
use arora_types::call::Call;
use arora_types::module::low::{Executor, ModuleDefinition};
use arora_types::value::{Structure, StructureField, Value};
use std::pin::Pin;
use uuid::Uuid;

// The test-rust-wasm guest, built for the host as a cdylib artifact dependency.
const DYLIB: &str = env!("CARGO_CDYLIB_FILE_TEST_RUST_WASM_test_rust_wasm");

// `text(length: u32) -> String`, as the guest's Rust declaration pins it.
const TEXT: Uuid = Uuid::from_u128(0x431fb111_d750_4470_b1c8_94b0ad8c6e6b);
const TEXT_LENGTH: Uuid = Uuid::from_u128(0x64351cbc_d84b_4df3_bd52_fd23edd943cf);

fn engine_with_guest() -> (Pin<Box<Engine>>, Uuid) {
    let header = test_rust_wasm::test_rust_wasm::header(Executor {
        name: "native".to_string(),
        min_version: None,
        max_version: None,
    });
    let module_id = header.id;
    let executable = std::fs::read(DYLIB)
        .expect("guest library")
        .into_boxed_slice();
    let mut engine = EngineBuilder::new()
        .add_executor(NativeExecutor::new())
        .build();
    engine
        .load_module(ModuleDefinition {
            schema_version: 0,
            header,
            executable,
        })
        .expect("guest loads");
    (engine, module_id)
}

/// The result buffer the executor hands back spans exactly the size its prefix
/// states. Lengths 0..=300 give the size's low byte every value, so a prefix
/// read in the wrong byte order cannot match by accident.
#[test]
fn result_buffer_spans_its_size_prefix() {
    let (mut engine, module_id) = engine_with_guest();
    for length in 0..=300u32 {
        let arg = arora_buffers::serde_uuid::serialize(&Value::Structure(Structure {
            id: TEXT,
            fields: vec![StructureField {
                id: TEXT_LENGTH,
                value: Box::new(Value::U32(length)),
            }],
        }));
        let result = engine.dispatch(&module_id, &TEXT, &arg).expect("dispatch");
        let prefix = u32::from_le_bytes(result[..4].try_into().unwrap());
        assert_eq!(result.len(), prefix as usize, "text({length})");
    }
}

/// A call through the engine's `CallBridge` reaches the native guest and
/// decodes its result.
#[test]
fn call_returns_the_guest_result() {
    let (mut engine, module_id) = engine_with_guest();
    let result = engine
        .arora_call(Call {
            module_id: Some(module_id),
            id: TEXT,
            args: vec![StructureField {
                id: TEXT_LENGTH,
                value: Box::new(Value::U32(200)),
            }],
        })
        .expect("call");
    assert_eq!(result.ret, Value::String("x".repeat(200)));
}

/// A compiled module for an executor that loads its executable as it is (here
/// a native library) keeps its bytes, and each engine loads them through that
/// executor.
#[test]
fn a_compiled_native_module_loads_into_several_engines() {
    let header = test_rust_wasm::test_rust_wasm::header(Executor {
        name: "native".to_string(),
        min_version: None,
        max_version: None,
    });
    let module_id = header.id;
    let compiled = CompiledModule::new(header, &std::fs::read(DYLIB).expect("guest library"))
        .expect("a native module keeps its bytes");
    for _ in 0..2 {
        let mut engine = EngineBuilder::new()
            .add_executor(NativeExecutor::new())
            .build();
        engine
            .load_compiled_module(&compiled)
            .expect("the compiled module loads");
        let result = engine
            .arora_call(Call {
                module_id: Some(module_id),
                id: TEXT,
                args: vec![StructureField {
                    id: TEXT_LENGTH,
                    value: Box::new(Value::U32(3)),
                }],
            })
            .expect("call");
        assert_eq!(result.ret, Value::String("xxx".to_string()));
    }
}

/// A native library is neither reloaded nor unloaded: the engine refuses
/// before loading anything, and the module keeps serving.
#[test]
fn a_native_module_is_neither_reloaded_nor_unloaded() {
    use arora_engine::engine::{ReloadModuleError, UnloadModuleError};

    let (mut engine, module_id) = engine_with_guest();
    let header = test_rust_wasm::test_rust_wasm::header(Executor {
        name: "native".to_string(),
        min_version: None,
        max_version: None,
    });
    let compiled = CompiledModule::new(header, &std::fs::read(DYLIB).expect("guest library"))
        .expect("a native module keeps its bytes");

    assert!(matches!(
        engine.reload_module(&compiled),
        Err(ReloadModuleError::Unsupported { executor: "native" })
    ));
    assert!(matches!(
        engine.unload_module(&module_id),
        Err(UnloadModuleError::Unsupported { executor: "native" })
    ));
    let result = engine
        .arora_call(Call {
            module_id: Some(module_id),
            id: TEXT,
            args: vec![StructureField {
                id: TEXT_LENGTH,
                value: Box::new(Value::U32(3)),
            }],
        })
        .expect("call");
    assert_eq!(result.ret, Value::String("xxx".to_string()));
}