#![cfg(target_arch = "wasm32")]
use arora_engine::call::CallBridge;
use arora_engine::compiled::CompiledModule;
use arora_engine::engine::EngineBuilder;
use arora_engine::executor::browser::BrowserExecutor;
use arora_engine::load::load_module_from_parts;
use arora_types::call::Call;
use arora_types::module::low::Header;
use arora_types::value::{StructureField, Value};
use uuid::Uuid;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
const WASM: &[u8] = include_bytes!(env!("CARGO_CDYLIB_FILE_TEST_RUST_WASM_test_rust_wasm"));
const SUCCEED: &str = "00cd31a8-2cf4-48e6-a957-69a55de90424"; const ADD: &str = "e4b0a2f3-6c7d-4e8f-9a0b-1c2d3e4f5a6b"; const ADD_A: &str = "a1b2c3d4-e5f6-4a8b-9c0d-e1f2a3b4c5d6";
const ADD_B: &str = "b2c3d4e5-f6a7-4b9c-8d1e-f2a3b4c5d6e7";
fn header() -> Header {
test_rust_wasm::test_rust_wasm::header(arora_types::module::low::Executor {
name: "wasm".to_string(),
min_version: None,
max_version: None,
})
}
fn id(s: &str) -> Uuid {
Uuid::parse_str(s).expect("valid uuid")
}
#[wasm_bindgen_test]
fn loads_and_dispatches_in_browser() {
console_error_panic_hook::set_once();
let header = header();
let module_id = header.id;
let mut engine = EngineBuilder::new()
.add_executor(BrowserExecutor::new())
.build();
let loaded = load_module_from_parts(&mut engine, header, WASM.to_vec().into_boxed_slice())
.expect("load test-rust-wasm into the browser executor");
assert_eq!(loaded.id, module_id);
let result = engine
.arora_call(Call {
module_id: Some(module_id),
id: id(SUCCEED),
args: vec![],
})
.expect("dispatch succeed()");
assert_eq!(result.ret, Value::Boolean(true));
}
#[wasm_bindgen_test]
fn marshals_arguments_in_browser() {
console_error_panic_hook::set_once();
let header = header();
let module_id = header.id;
let mut engine = EngineBuilder::new()
.add_executor(BrowserExecutor::new())
.build();
load_module_from_parts(&mut engine, header, WASM.to_vec().into_boxed_slice()).expect("load");
let result = engine
.arora_call(Call {
module_id: Some(module_id),
id: id(ADD),
args: vec![
StructureField {
id: id(ADD_A),
value: Box::new(Value::F32(2.0)),
},
StructureField {
id: id(ADD_B),
value: Box::new(Value::F32(3.0)),
},
],
})
.expect("dispatch add(2, 3)");
assert_eq!(result.ret, Value::F32(5.0));
}
#[wasm_bindgen_test]
async fn prepares_asynchronously_then_dispatches() {
console_error_panic_hook::set_once();
let header = header();
let module_id = header.id;
let executor = BrowserExecutor::new();
let loader = executor.shared();
let mut engine = EngineBuilder::new().add_executor(executor).build();
loader
.prepare(module_id, WASM.to_vec())
.await
.expect("async prepare");
let loaded = load_module_from_parts(&mut engine, header, Box::new([]))
.expect("load the asynchronously-prepared module");
assert_eq!(loaded.id, module_id);
let result = engine
.arora_call(Call {
module_id: Some(module_id),
id: id(SUCCEED),
args: vec![],
})
.expect("dispatch succeed()");
assert_eq!(result.ret, Value::Boolean(true));
}
#[wasm_bindgen_test]
fn a_compiled_module_loads_into_several_engines() {
console_error_panic_hook::set_once();
let header = header();
let module_id = header.id;
let compiled = CompiledModule::new(header, WASM).expect("compile test-rust-wasm");
let engine = || {
let mut engine = EngineBuilder::new()
.add_executor(BrowserExecutor::new())
.build();
engine
.load_compiled_module(&compiled)
.expect("load the compiled module");
engine
};
let first = engine();
let mut second = engine();
drop(first);
let result = second
.arora_call(Call {
module_id: Some(module_id),
id: id(SUCCEED),
args: vec![],
})
.expect("dispatch succeed()");
assert_eq!(result.ret, Value::Boolean(true));
}
#[wasm_bindgen_test]
fn an_undeclared_function_is_not_found_in_browser() {
console_error_panic_hook::set_once();
let header = header();
let module_id = header.id;
let mut engine = EngineBuilder::new()
.add_executor(BrowserExecutor::new())
.build();
load_module_from_parts(&mut engine, header, WASM.to_vec().into_boxed_slice()).expect("load");
let undeclared = Uuid::from_u128(2);
let result = engine.arora_call(Call {
module_id: Some(module_id),
id: undeclared,
args: vec![],
});
assert!(matches!(
result,
Err(arora_types::call::CallError::FunctionNotFound { id }) if id == undeclared
));
}
#[wasm_bindgen_test]
fn reloads_and_unloads_in_browser() {
console_error_panic_hook::set_once();
let header = header();
let module_id = header.id;
let compiled = CompiledModule::new(header, WASM).expect("compile test-rust-wasm");
let mut engine = EngineBuilder::new()
.add_executor(BrowserExecutor::new())
.build();
engine.load_compiled_module(&compiled).expect("load");
let succeed = || Call {
module_id: Some(module_id),
id: id(SUCCEED),
args: vec![],
};
for _ in 0..100 {
engine.reload_module(&compiled).expect("reload");
let result = engine.arora_call(succeed()).expect("dispatch succeed()");
assert_eq!(result.ret, Value::Boolean(true));
}
engine.unload_module(&module_id).expect("unload");
assert!(engine.arora_call(succeed()).is_err());
assert!(!engine.contains_module(&module_id));
}
const REACTOR: &str = r#"(module
(memory (export "memory") 1)
(data (i32.const 2048) "\05\00\00\00\00")
(func (export "_initialize")
(i32.store8 (i32.const 2052)
(i32.add (i32.load8_u (i32.const 2052)) (i32.const 1))))
(func (export "arora_buffer_alloc") (param i32) (result i32) i32.const 1024)
(func (export "arora_buffer_free") (param i32))
(func (export "arora_function_00000000_0000_0000_0000_000000000003")
(param i32) (result i32) i32.const 2048))"#;
#[wasm_bindgen_test]
fn a_reactor_guest_is_initialized_once_when_it_loads_in_browser() {
console_error_panic_hook::set_once();
let module_id = Uuid::from_u128(1);
let count = Uuid::from_u128(3);
let header: Header = serde_yaml::from_str(&format!(
r#"
id: {module_id}
name: reactor
author: ""
description: ~
license: ""
version: {{ major: 1, minor: 0, patch: 0 }}
executor: {{ name: wasm, min_version: ~, max_version: ~ }}
imports: []
executable_mime: ""
exports:
- type: function
id: {count}
name: count
parameters: []
ret: {{ kind: scalar, id: 00000000-0000-0000-0000-000000000000 }}
"#
))
.expect("parse the header");
let guest = wat::parse_str(REACTOR).expect("assemble the guest");
let mut engine = EngineBuilder::new()
.add_executor(BrowserExecutor::new())
.build();
engine
.load_compiled_module(&CompiledModule::new(header, &guest).expect("compile"))
.expect("load");
for _ in 0..2 {
let result = engine.dispatch(&module_id, &count, &[]).expect("dispatch");
assert_eq!(&*result, &[5, 0, 0, 0, 1]);
}
}
fn wat_bytes(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("\\{byte:02x}")).collect()
}
#[wasm_bindgen_test]
fn a_refused_call_between_core_modules_is_the_caller_s_error_buffer_in_browser() {
use arora_types::value::Structure;
let (caller, call) = (Uuid::from_u128(0xc0_0001), Uuid::from_u128(0xc0_0002));
let (callee, take, count) = (
Uuid::from_u128(0xc0_0003),
Uuid::from_u128(0xc0_0004),
Uuid::from_u128(0xc0_0005),
);
let header = |id: Uuid, function: Uuid, parameters: &str| -> Header {
serde_yaml::from_str(&format!(
r#"
id: {id}
name: m{id}
author: ""
description: ~
license: ""
version: {{ major: 1, minor: 0, patch: 0 }}
executor: {{ name: wasm, min_version: ~, max_version: ~ }}
imports: []
executable_mime: ""
exports:
- type: function
id: {function}
name: f
parameters: {parameters}
ret: {{ kind: scalar, id: 00000000-0000-0000-0000-000000000000 }}
"#
))
.expect("parse the header")
};
let wrong = arora_buffers::serde_uuid::serialize(&Value::Structure(Structure {
id: take,
fields: vec![StructureField {
id: count,
value: Box::new(Value::String("three".into())),
}],
}));
let caller_guest = format!(
r#"(module
(import "env" "arora_dispatch" (func $dispatch (param i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(data (i32.const 256) "{callee}")
(data (i32.const 272) "{take}")
(data (i32.const 512) "{wrong}")
(func (export "arora_buffer_alloc") (param i32) (result i32) i32.const 4096)
(func (export "arora_buffer_free") (param i32))
(func (export "arora_function_{function}") (param i32) (result i32)
(call $dispatch (i32.const 256) (i32.const 272) (i32.const 512))))"#,
callee = wat_bytes(callee.as_bytes()),
take = wat_bytes(take.as_bytes()),
wrong = wat_bytes(&wrong),
function = call.to_string().replace('-', "_"),
);
let callee_guest = format!(
r#"(module
(memory (export "memory") 1)
(func (export "arora_buffer_alloc") (param i32) (result i32) i32.const 1024)
(func (export "arora_buffer_free") (param i32))
(func (export "arora_function_{function}") (param i32) (result i32) unreachable))"#,
function = take.to_string().replace('-', "_"),
);
let mut engine = EngineBuilder::new()
.add_executor(BrowserExecutor::new())
.build();
let count_parameter = format!(
"[{{ id: {count}, name: count, type: {{ kind: scalar, id: 00000000-0000-0000-0000-000000000008 }} }}]"
);
for (header, guest) in [
(header(caller, call, "[]"), caller_guest),
(header(callee, take, &count_parameter), callee_guest),
] {
let guest = wat::parse_str(guest).expect("assemble the guest");
let compiled = CompiledModule::new(header, &guest).expect("compile");
engine.load_compiled_module(&compiled).expect("load");
}
let refused = engine
.dispatch(&caller, &call, &[])
.expect_err("the caller returns the refusal as its error");
assert!(
matches!(&refused, arora_engine::module::DispatchError::Guest { message }
if message.contains("the argument 'count' of 'f' is not of its declared type")),
"{refused:?}"
);
}