use serde_json::json;
use sha2::{Digest, Sha256};
use std::fmt::Write as _;
use std::sync::atomic::{AtomicU64, Ordering};
use traverse_contracts::{EventReference, ServiceType};
use traverse_runtime::executor::{
ArtifactType, CapabilityExecutor, ExecutorCapability, ExecutorError, ExecutorOutput,
NativeExecutor, SUPPORTED_HOST_ABI_VERSION, WasmExecutionLimits, WasmExecutor,
WasmModuleCacheConfig, supported_host_abi_versions, verify_wasm_host_abi_bytes,
};
static TEMPFILE_COUNTER: AtomicU64 = AtomicU64::new(0);
#[test]
fn native_executor_runs_handler() {
let executor = NativeExecutor::new(|input| {
let name = input["name"].as_str().unwrap_or("world");
Ok(json!({ "greeting": format!("hello, {name}!") }))
});
let cap = native_capability("greet");
let result = executor.execute(&cap, &json!({ "name": "traverse" }));
assert_eq!(
result,
Ok(ExecutorOutput {
value: json!({ "greeting": "hello, traverse!" }),
emitted_events: Vec::new(),
})
);
}
#[test]
fn native_executor_propagates_handler_error() -> Result<(), String> {
let executor = NativeExecutor::new(|_| Err("something went wrong".to_string()));
let cap = native_capability("fail");
let err = expect_err(
executor.execute(&cap, &json!({})),
"expected execution error",
)?;
assert_eq!(
err,
ExecutorError::ExecutionFailed("something went wrong".to_string())
);
Ok(())
}
#[test]
fn native_executor_rejects_wasm_artifact_type() -> Result<(), String> {
let executor = NativeExecutor::new(|_| Ok(json!({})));
let cap = ExecutorCapability {
capability_id: "wrong-type".to_string(),
artifact_type: ArtifactType::Wasm,
wasm_binary_path: None,
wasm_checksum: None,
host_abi_version: None,
emits: Vec::new(),
service_type: ServiceType::Stateless,
};
let err = expect_err(executor.execute(&cap, &json!({})), "expected type error")?;
assert_eq!(err, ExecutorError::UnsupportedArtifactType);
Ok(())
}
#[test]
fn native_executor_passes_input_through() -> Result<(), String> {
let executor = NativeExecutor::new(|input| Ok(input.clone()));
let cap = native_capability("echo");
let input = json!({ "a": 1, "b": [true, false] });
let result = executor
.execute(&cap, &input)
.map_err(|e| format!("{e:?}"))?;
assert_eq!(result.value, input);
Ok(())
}
#[test]
fn wasm_executor_rejects_native_artifact_type() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let cap = native_capability("wrong");
let err = expect_err(executor.execute(&cap, &json!({})), "expected type error")?;
assert_eq!(err, ExecutorError::UnsupportedArtifactType);
Ok(())
}
#[test]
fn wasm_executor_errors_when_no_path_set() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let cap = ExecutorCapability {
capability_id: "no-path".to_string(),
artifact_type: ArtifactType::Wasm,
wasm_binary_path: None,
wasm_checksum: None,
host_abi_version: None,
emits: Vec::new(),
service_type: ServiceType::Stateless,
};
let err = expect_err(
executor.execute(&cap, &json!({})),
"expected BinaryLoadFailed",
)?;
assert!(
matches!(err, ExecutorError::BinaryLoadFailed(_)),
"expected BinaryLoadFailed, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_errors_on_missing_file() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let cap = ExecutorCapability {
capability_id: "missing".to_string(),
artifact_type: ArtifactType::Wasm,
wasm_binary_path: Some("/nonexistent/path/module.wasm".to_string()),
wasm_checksum: None,
host_abi_version: None,
emits: Vec::new(),
service_type: ServiceType::Stateless,
};
let err = expect_err(
executor.execute(&cap, &json!({})),
"expected BinaryLoadFailed",
)?;
assert!(
matches!(err, ExecutorError::BinaryLoadFailed(_)),
"expected BinaryLoadFailed, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_detects_checksum_mismatch() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(memory 1)
(func $main (export "_start"))
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let tmp = tempfile_path();
std::fs::write(&tmp, &wasm_bytes).map_err(|e| format!("write temp: {e}"))?;
let cap = ExecutorCapability {
capability_id: "checksum-test".to_string(),
artifact_type: ArtifactType::Wasm,
wasm_binary_path: Some(tmp.clone()),
wasm_checksum: Some(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
),
host_abi_version: None,
emits: Vec::new(),
service_type: ServiceType::Stateless,
};
let err = expect_err(
executor.execute(&cap, &json!({})),
"expected ChecksumMismatch",
)?;
std::fs::remove_file(&tmp).ok();
assert!(
matches!(err, ExecutorError::ChecksumMismatch { .. }),
"expected ChecksumMismatch, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_runs_echo_module() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(import "wasi_snapshot_preview1" "fd_read"
(func $fd_read (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func $_start (export "_start")
;; iovec for read: ptr=8, len=4096
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.const 4096))
;; read stdin into offset 8
(drop (call $fd_read (i32.const 0) (i32.const 0) (i32.const 1) (i32.const 4100)))
;; nread is at memory[4100]; use it as iovec len for write
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.load (i32.const 4100)))
;; write stdout
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 4104)))
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let input = json!({ "key": "value" });
let result = executor
.run_bytes(&wasm_bytes, &input)
.map_err(|e| format!("{e:?}"))?;
assert_eq!(result, input, "echo module should return input unchanged");
Ok(())
}
#[test]
fn wasm_executor_rejects_invalid_json_output() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(data (i32.const 16) "not-json")
(func $_start (export "_start")
;; iovec: ptr=16, len=8
(i32.store (i32.const 0) (i32.const 16))
(i32.store (i32.const 4) (i32.const 8))
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 8)))
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let err = expect_err(
executor.run_bytes(&wasm_bytes, &json!({})),
"expected OutputDeserializationFailed",
)?;
assert!(
matches!(err, ExecutorError::OutputDeserializationFailed(_)),
"expected OutputDeserializationFailed, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_traps_infinite_loop_as_timeout() -> Result<(), String> {
let executor = WasmExecutor::with_limits(WasmExecutionLimits {
fuel_budget: 1_000,
..WasmExecutionLimits::default()
})
.map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(func $_start (export "_start")
(loop $again
br $again
)
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let err = expect_err(
executor.run_bytes(&wasm_bytes, &json!({})),
"expected Timeout",
)?;
assert!(
matches!(err, ExecutorError::Timeout(_)),
"expected Timeout, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_traps_memory_growth_as_resource_exhausted() -> Result<(), String> {
let executor = WasmExecutor::with_limits(WasmExecutionLimits {
fuel_budget: 100_000,
memory_bytes: 64 * 1024,
..WasmExecutionLimits::default()
})
.map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(memory (export "memory") 1)
(func $_start (export "_start")
(drop (memory.grow (i32.const 1)))
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let err = expect_err(
executor.run_bytes(&wasm_bytes, &json!({})),
"expected ResourceExhausted",
)?;
assert!(
matches!(err, ExecutorError::ResourceExhausted(_)),
"expected ResourceExhausted, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_preserves_generic_traps_as_execution_failed() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(func $_start (export "_start")
unreachable
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let err = expect_err(
executor.run_bytes(&wasm_bytes, &json!({})),
"expected ExecutionFailed",
)?;
assert!(
matches!(err, ExecutorError::ExecutionFailed(_)),
"expected ExecutionFailed, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_reuses_compiled_module_by_checksum() -> Result<(), String> {
let executor = WasmExecutor::with_limits_and_cache_config(
WasmExecutionLimits::default(),
WasmModuleCacheConfig { max_entries: 2 },
)
.map_err(|e| format!("{e:?}"))?;
let wasm_bytes = wat::parse_str(echo_wat()).map_err(|e| format!("WAT parse: {e}"))?;
let first_input = json!({ "call": 1 });
let first = executor
.run_bytes(&wasm_bytes, &first_input)
.map_err(|e| format!("{e:?}"))?;
assert_eq!(first, first_input);
let after_first = executor.module_cache_stats();
assert_eq!(after_first.entries, 1);
assert_eq!(after_first.hits, 0);
assert_eq!(after_first.misses, 1);
let second_input = json!({ "call": 2 });
let second = executor
.run_bytes(&wasm_bytes, &second_input)
.map_err(|e| format!("{e:?}"))?;
assert_eq!(second, second_input);
let after_second = executor.module_cache_stats();
assert_eq!(after_second.entries, 1);
assert_eq!(after_second.hits, 1);
assert_eq!(after_second.misses, 1);
assert_eq!(after_second.evictions, 0);
Ok(())
}
#[test]
fn wasm_executor_cached_module_still_uses_fresh_store() -> Result<(), String> {
let executor = WasmExecutor::with_limits_and_cache_config(
WasmExecutionLimits::default(),
WasmModuleCacheConfig { max_entries: 2 },
)
.map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(global $counter (mut i32) (i32.const 0))
(data (i32.const 8) "{\"count\":0}")
(func $_start (export "_start")
(global.set $counter (i32.add (global.get $counter) (i32.const 1)))
(i32.store8 (i32.const 17) (i32.add (global.get $counter) (i32.const 48)))
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.const 11))
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 4)))
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let first = executor
.run_bytes(&wasm_bytes, &json!({}))
.map_err(|e| format!("{e:?}"))?;
let second = executor
.run_bytes(&wasm_bytes, &json!({}))
.map_err(|e| format!("{e:?}"))?;
assert_eq!(first, json!({ "count": 1 }));
assert_eq!(second, json!({ "count": 1 }));
assert_eq!(executor.module_cache_stats().hits, 1);
Ok(())
}
#[test]
fn wasm_executor_cache_evicts_oldest_entry_deterministically() -> Result<(), String> {
let executor = WasmExecutor::with_limits_and_cache_config(
WasmExecutionLimits::default(),
WasmModuleCacheConfig { max_entries: 1 },
)
.map_err(|e| format!("{e:?}"))?;
let first_wasm = wat::parse_str(echo_wat()).map_err(|e| format!("WAT parse: {e}"))?;
let second_wat = r#"
(module
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(data (i32.const 8) "{}")
(func $_start (export "_start")
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.const 2))
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 4)))
)
)
"#;
let second_wasm = wat::parse_str(second_wat).map_err(|e| format!("WAT parse: {e}"))?;
executor
.run_bytes(&first_wasm, &json!({ "cached": true }))
.map_err(|e| format!("{e:?}"))?;
executor
.run_bytes(&second_wasm, &json!({}))
.map_err(|e| format!("{e:?}"))?;
executor
.run_bytes(&first_wasm, &json!({ "cached": true }))
.map_err(|e| format!("{e:?}"))?;
let stats = executor.module_cache_stats();
assert_eq!(stats.entries, 1);
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 3);
assert_eq!(stats.evictions, 2);
Ok(())
}
#[test]
fn wasm_executor_cache_miss_when_abi_version_differs() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes = wat::parse_str(echo_wat()).map_err(|e| format!("WAT parse: {e}"))?;
executor
.run_bytes(&wasm_bytes, &json!({ "abi": "1.0.0" }))
.map_err(|e| format!("{e:?}"))?;
let err = expect_err(
executor.run_bytes_with_host_abi(&wasm_bytes, &json!({}), "2.0.0"),
"expected unsupported ABI version",
)?;
assert!(matches!(err, ExecutorError::UnsupportedAbiVersion { .. }));
let stats = executor.module_cache_stats();
assert_eq!(stats.entries, 1);
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 2);
Ok(())
}
#[test]
fn wasm_host_abi_verifier_accepts_sanctioned_stdio_imports() -> Result<(), String> {
let wasm_bytes = wat::parse_str(echo_wat()).map_err(|e| format!("WAT parse: {e}"))?;
let validation = verify_wasm_host_abi_bytes(&wasm_bytes, SUPPORTED_HOST_ABI_VERSION)
.map_err(|e| format!("{e:?}"))?;
assert_eq!(validation.abi_version, SUPPORTED_HOST_ABI_VERSION);
assert_eq!(supported_host_abi_versions(), &[SUPPORTED_HOST_ABI_VERSION]);
assert!(
validation.imports.iter().any(|import| {
import.module == "wasi_snapshot_preview1" && import.name == "fd_read"
})
);
assert!(
validation.imports.iter().any(|import| {
import.module == "wasi_snapshot_preview1" && import.name == "fd_write"
})
);
Ok(())
}
#[test]
fn wasm_host_abi_verifier_rejects_unauthorized_import_before_execution() -> Result<(), String> {
let wat_src = r#"
(module
(import "wasi_snapshot_preview1" "random_get"
(func $random_get (param i32 i32) (result i32)))
(memory (export "memory") 1)
(func $_start (export "_start")
unreachable
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let err = expect_err(
executor.run_bytes(&wasm_bytes, &json!({})),
"expected unauthorized host import",
)?;
assert_eq!(
err,
ExecutorError::UnauthorizedHostImport {
error_code: "unauthorized_host_import".to_string(),
abi_version: SUPPORTED_HOST_ABI_VERSION.to_string(),
module: "wasi_snapshot_preview1".to_string(),
name: "random_get".to_string(),
}
);
Ok(())
}
#[test]
fn wasm_host_abi_verifier_rejects_unsupported_abi_version() -> Result<(), String> {
let wasm_bytes = wat::parse_str(echo_wat()).map_err(|e| format!("WAT parse: {e}"))?;
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let err = expect_err(
executor.run_bytes_with_host_abi(&wasm_bytes, &json!({}), "2.0.0"),
"expected unsupported ABI version",
)?;
assert_eq!(
err,
ExecutorError::UnsupportedAbiVersion {
error_code: "unsupported_abi_version".to_string(),
requested: "2.0.0".to_string(),
supported: SUPPORTED_HOST_ABI_VERSION.to_string(),
}
);
Ok(())
}
#[test]
fn wasm_host_abi_verifier_reports_malformed_binary() -> Result<(), String> {
let err = expect_err(
verify_wasm_host_abi_bytes(b"not-a-wasm-binary", SUPPORTED_HOST_ABI_VERSION),
"expected malformed WASM artifact",
)?;
assert!(
matches!(err, ExecutorError::MalformedWasmArtifact { .. }),
"expected MalformedWasmArtifact, got {err:?}"
);
Ok(())
}
#[test]
fn native_executor_debug_impl_is_accessible() {
let executor = NativeExecutor::new(|_| Ok(json!({})));
let dbg = format!("{executor:?}");
assert!(dbg.contains("NativeExecutor"), "Debug output: {dbg}");
}
#[test]
fn executor_error_display_covers_all_variants() {
let cases: &[(ExecutorError, &str)] = &[
(
ExecutorError::BinaryLoadFailed("oops".to_string()),
"binary load failed: oops",
),
(
ExecutorError::ChecksumMismatch {
expected: "abc".to_string(),
actual: "def".to_string(),
},
"checksum mismatch: expected abc, got def",
),
(
ExecutorError::RuntimeSetupFailed("bad linker".to_string()),
"runtime setup failed: bad linker",
),
(
ExecutorError::MalformedWasmArtifact {
error_code: "malformed_wasm_artifact".to_string(),
detail: "bad magic".to_string(),
},
"malformed_wasm_artifact: bad magic",
),
(
ExecutorError::UnsupportedAbiVersion {
error_code: "unsupported_abi_version".to_string(),
requested: "2.0.0".to_string(),
supported: "1.0.0".to_string(),
},
"unsupported_abi_version: requested Traverse Host ABI 2.0.0, supported 1.0.0",
),
(
ExecutorError::UnauthorizedHostImport {
error_code: "unauthorized_host_import".to_string(),
abi_version: "1.0.0".to_string(),
module: "wasi_snapshot_preview1".to_string(),
name: "random_get".to_string(),
},
"unauthorized_host_import: ABI 1.0.0 does not allow import wasi_snapshot_preview1::random_get",
),
(
ExecutorError::ExecutionFailed("trapped".to_string()),
"execution failed: trapped",
),
(
ExecutorError::Timeout("fuel exhausted".to_string()),
"execution timed out: fuel exhausted",
),
(
ExecutorError::ResourceExhausted("memory cap".to_string()),
"resource exhausted: memory cap",
),
(
ExecutorError::OutputDeserializationFailed("not json".to_string()),
"output deserialization failed: not json",
),
(
ExecutorError::UnsupportedArtifactType,
"unsupported artifact type for this executor",
),
];
for (err, expected_msg) in cases {
assert_eq!(
format!("{err}"),
*expected_msg,
"Display mismatch for {err:?}"
);
}
}
#[test]
fn wasm_executor_full_execute_path_via_disk() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(import "wasi_snapshot_preview1" "fd_read"
(func $fd_read (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func $_start (export "_start")
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.const 4096))
(drop (call $fd_read (i32.const 0) (i32.const 0) (i32.const 1) (i32.const 4100)))
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.load (i32.const 4100)))
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 4104)))
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let tmp = tempfile_path();
std::fs::write(&tmp, &wasm_bytes).map_err(|e| format!("write: {e}"))?;
let cap = ExecutorCapability {
capability_id: "disk-echo".to_string(),
artifact_type: ArtifactType::Wasm,
wasm_binary_path: Some(tmp.clone()),
wasm_checksum: None, host_abi_version: None,
emits: Vec::new(),
service_type: ServiceType::Stateless,
};
let input = json!({ "disk": true });
let result = executor.execute(&cap, &input).map_err(|e| format!("{e:?}"));
std::fs::remove_file(&tmp).ok();
assert_eq!(
result,
Ok(ExecutorOutput {
value: input,
emitted_events: Vec::new(),
})
);
Ok(())
}
#[test]
fn wasm_executor_execute_with_matching_checksum_succeeds() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(data (i32.const 8) "{}")
(func $_start (export "_start")
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.const 2))
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 4)))
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("WAT parse: {e}"))?;
let mut hasher = Sha256::new();
hasher.update(&wasm_bytes);
let checksum: String = hasher
.finalize()
.iter()
.fold(String::new(), |mut acc, byte| {
let _ = write!(acc, "{byte:02x}");
acc
});
let tmp = tempfile_path();
std::fs::write(&tmp, &wasm_bytes).map_err(|e| format!("write: {e}"))?;
let cap = ExecutorCapability {
capability_id: "checksum-ok".to_string(),
artifact_type: ArtifactType::Wasm,
wasm_binary_path: Some(tmp.clone()),
wasm_checksum: Some(checksum),
host_abi_version: Some("1.0.0".to_string()),
emits: Vec::new(),
service_type: ServiceType::Stateless,
};
let result = executor
.execute(&cap, &json!({}))
.map_err(|e| format!("{e:?}"));
std::fs::remove_file(&tmp).ok();
assert_eq!(
result,
Ok(ExecutorOutput {
value: json!({}),
emitted_events: Vec::new(),
})
);
Ok(())
}
#[test]
fn wasm_executor_cached_module_does_not_bypass_checksum_mismatch() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes = wat::parse_str(echo_wat()).map_err(|e| format!("WAT parse: {e}"))?;
let mut hasher = Sha256::new();
hasher.update(&wasm_bytes);
let checksum: String = hasher
.finalize()
.iter()
.fold(String::new(), |mut acc, byte| {
let _ = write!(acc, "{byte:02x}");
acc
});
let tmp = tempfile_path();
std::fs::write(&tmp, &wasm_bytes).map_err(|e| format!("write: {e}"))?;
let cap = ExecutorCapability {
capability_id: "checksum-cache".to_string(),
artifact_type: ArtifactType::Wasm,
wasm_binary_path: Some(tmp.clone()),
wasm_checksum: Some(checksum),
host_abi_version: Some("1.0.0".to_string()),
emits: Vec::new(),
service_type: ServiceType::Stateless,
};
let input = json!({ "cache": true });
let result = executor
.execute(&cap, &input)
.map_err(|e| format!("{e:?}"))?;
assert_eq!(result.value, input);
assert_eq!(executor.module_cache_stats().entries, 1);
std::fs::write(&tmp, b"not-the-same-wasm").map_err(|e| format!("overwrite: {e}"))?;
let err = expect_err(
executor.execute(&cap, &json!({})),
"expected checksum mismatch",
)?;
std::fs::remove_file(&tmp).ok();
assert!(
matches!(err, ExecutorError::ChecksumMismatch { .. }),
"expected ChecksumMismatch, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_invalid_binary_triggers_runtime_setup_failed() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let tmp = tempfile_path();
std::fs::write(&tmp, b"not-a-wasm-binary").map_err(|e| format!("write: {e}"))?;
let cap = ExecutorCapability {
capability_id: "bad-binary".to_string(),
artifact_type: ArtifactType::Wasm,
wasm_binary_path: Some(tmp.clone()),
wasm_checksum: None,
host_abi_version: None,
emits: Vec::new(),
service_type: ServiceType::Stateless,
};
let err = expect_err(executor.execute(&cap, &json!({})), "expected error")?;
std::fs::remove_file(&tmp).ok();
assert!(
matches!(err, ExecutorError::MalformedWasmArtifact { .. }),
"expected MalformedWasmArtifact, got {err:?}"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_accepted_for_declared_subscribable_capability() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes =
wat::parse_str(emit_event_wat(EMIT_TEST_EVENT_PAYLOAD)).map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
)
.map_err(|e| format!("{e:?}"))?;
assert_eq!(output.emitted_events.len(), 1);
let event = &output.emitted_events[0];
assert_eq!(event.event_type, "dev.traverse.test.emitted");
assert_eq!(event.version, "1.0.0");
assert_eq!(event.data, json!({ "n": 1 }));
assert_eq!(event.owner, "test.emit.subject");
Ok(())
}
#[test]
fn wasm_executor_emit_event_rejected_for_undeclared_event_type() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes =
wat::parse_str(emit_event_wat(EMIT_TEST_EVENT_PAYLOAD)).map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.other".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
)
.map_err(|e| format!("{e:?}"))?;
assert!(
output.emitted_events.is_empty(),
"undeclared event must not be accepted"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_rejected_for_non_subscribable_capability() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes =
wat::parse_str(emit_event_wat(EMIT_TEST_EVENT_PAYLOAD)).map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Stateless, )
.map_err(|e| format!("{e:?}"))?;
assert!(
output.emitted_events.is_empty(),
"non-Subscribable capability must never emit"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_rejected_for_malformed_json_payload() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes = wat::parse_str(emit_event_wat("not-json")).map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
)
.map_err(|e| format!("{e:?}"))?;
assert!(
output.emitted_events.is_empty(),
"malformed payload must not be accepted, and execution must not trap"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_rejected_for_oversized_payload() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes = wat::parse_str(emit_event_wat_with_len(
EMIT_TEST_EVENT_PAYLOAD,
64 * 1024 + 1,
))
.map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
)
.map_err(|e| format!("{e:?}"))?;
assert!(
output.emitted_events.is_empty(),
"oversized payload must not be accepted, and execution must not trap"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_rejected_for_out_of_bounds_pointer() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes =
wat::parse_str(emit_event_wat_with_ptr_len(200_000, 10)).map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
)
.map_err(|e| format!("{e:?}"))?;
assert!(
output.emitted_events.is_empty(),
"out-of-bounds pointer must not be accepted, and execution must not trap or read outside guest memory"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_rejected_for_negative_pointer_or_length() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes =
wat::parse_str(emit_event_wat_with_raw_ptr_len(-1, 4)).map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
)
.map_err(|e| format!("{e:?}"))?;
assert!(
output.emitted_events.is_empty(),
"a negative pointer must not be accepted, and execution must not trap"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_rejected_for_missing_event_id() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes =
wat::parse_str(emit_event_wat(r#"{"version":"1.0.0"}"#)).map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
)
.map_err(|e| format!("{e:?}"))?;
assert!(
output.emitted_events.is_empty(),
"a payload missing event_id must not be accepted"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_rejected_for_missing_version() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wasm_bytes = wat::parse_str(emit_event_wat(
r#"{"event_id":"dev.traverse.test.emitted"}"#,
))
.map_err(|e| format!("{e}"))?;
let output = executor
.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
)
.map_err(|e| format!("{e:?}"))?;
assert!(
output.emitted_events.is_empty(),
"a payload missing version must not be accepted"
);
Ok(())
}
#[test]
fn wasm_executor_emit_event_handles_module_without_memory_export() -> Result<(), String> {
let executor = WasmExecutor::new().map_err(|e| format!("{e:?}"))?;
let wat_src = r#"
(module
(import "traverse_host" "emit_event"
(func $emit_event (param i32 i32) (result i32)))
(func $_start (export "_start")
(drop (call $emit_event (i32.const 0) (i32.const 4)))
)
)
"#;
let wasm_bytes = wat::parse_str(wat_src).map_err(|e| format!("{e}"))?;
let result = executor.run_bytes_with_capability(
&wasm_bytes,
&json!({}),
"test.emit.subject",
&[EventReference {
event_id: "dev.traverse.test.emitted".to_string(),
version: "1.0.0".to_string(),
}],
ServiceType::Subscribable,
);
assert!(
result.is_err(),
"expected a controlled error, not a panic or trap: {result:?}"
);
Ok(())
}
fn native_capability(id: &str) -> ExecutorCapability {
ExecutorCapability {
capability_id: id.to_string(),
artifact_type: ArtifactType::Native,
wasm_binary_path: None,
wasm_checksum: None,
host_abi_version: None,
emits: Vec::new(),
service_type: ServiceType::Stateless,
}
}
fn tempfile_path() -> String {
let suffix = TEMPFILE_COUNTER.fetch_add(1, Ordering::Relaxed);
format!(
"/tmp/traverse-test-{}-{suffix}.wasm",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_nanos())
)
}
fn echo_wat() -> &'static str {
r#"
(module
(import "wasi_snapshot_preview1" "fd_read"
(func $fd_read (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func $_start (export "_start")
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.const 4096))
(drop (call $fd_read (i32.const 0) (i32.const 0) (i32.const 1) (i32.const 4100)))
(i32.store (i32.const 0) (i32.const 8))
(i32.store (i32.const 4) (i32.load (i32.const 4100)))
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 4104)))
)
)
"#
}
const EMIT_TEST_EVENT_PAYLOAD: &str =
r#"{"event_id":"dev.traverse.test.emitted","version":"1.0.0","payload":{"n":1}}"#;
fn wat_escape(payload: &str) -> String {
payload.replace('\\', "\\\\").replace('"', "\\\"")
}
fn emit_event_wat(payload: &str) -> String {
emit_event_wat_with_len(payload, payload.len())
}
fn emit_event_wat_with_len(payload: &str, claimed_len: usize) -> String {
format!(
r#"
(module
(import "traverse_host" "emit_event"
(func $emit_event (param i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(data (i32.const 100) "{payload}")
(data (i32.const 300) "{{}}")
(func $_start (export "_start")
(drop (call $emit_event (i32.const 100) (i32.const {claimed_len})))
(i32.store (i32.const 0) (i32.const 300))
(i32.store (i32.const 4) (i32.const 2))
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 4100)))
)
)
"#,
payload = wat_escape(payload),
)
}
fn emit_event_wat_with_ptr_len(ptr: usize, len: usize) -> String {
emit_event_wat_with_raw_ptr_len(
i32::try_from(ptr).unwrap_or(i32::MAX),
i32::try_from(len).unwrap_or(i32::MAX),
)
}
fn emit_event_wat_with_raw_ptr_len(ptr: i32, len: i32) -> String {
format!(
r#"
(module
(import "traverse_host" "emit_event"
(func $emit_event (param i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(memory (export "memory") 1)
(data (i32.const 300) "{{}}")
(func $_start (export "_start")
(drop (call $emit_event (i32.const {ptr}) (i32.const {len})))
(i32.store (i32.const 0) (i32.const 300))
(i32.store (i32.const 4) (i32.const 2))
(drop (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 4100)))
)
)
"#,
)
}
fn expect_err<T: std::fmt::Debug, E>(result: Result<T, E>, msg: &str) -> Result<E, String> {
match result {
Err(e) => Ok(e),
Ok(v) => Err(format!("{msg}: got Ok({v:?})")),
}
}