Lines
97.19 %
Functions
100 %
Branches
//! Tests that span multiple context submodules. Each test verifies a
//! property of the assembled `CompileContext` produced by `new()` —
//! type registrations, helper-function registrations, and concrete-vs-
//! abstract heap-type accessors.
use super::CompileContext;
use crate::ast::EntityKind;
use wasm_encoder::{Function, HeapType, Instruction, ValType};
#[test]
fn test_ratio_type_registered() {
let ctx = CompileContext::new().unwrap();
// Type indices live in the typed `WasmIds` (poison `u32::MAX` means
// unresolved); a real registered index is below the module's type count.
assert!(ctx.ids.ty_ratio < ctx.type_count);
assert!(ctx.ids.ty_i8_array < ctx.type_count);
}
fn test_entity_types_registered() {
for kind in [
EntityKind::Account,
EntityKind::Commodity,
EntityKind::Transaction,
EntityKind::Split,
EntityKind::Tag,
EntityKind::Price,
EntityKind::SshKey,
] {
let name = kind.type_name();
// Every entity kind resolves to a registered struct-type index via the
// exhaustive `ids.entity_type` match.
assert!(
ctx.ids.entity_type(kind) < ctx.type_count,
"missing entity wasm struct: ${name}"
);
// Accessor returns a `(ref null $<kind>)` resolving to that index —
// regression check that `entity_ref` and `ids.entity_type` stay in sync.
let ty = ctx.entity_ref(kind);
match ty {
ValType::Ref(r) => assert!(r.nullable),
_ => panic!("entity_ref for {name} must produce Ref ValType"),
fn test_pair_type_registered() {
ctx.ids.ty_pair < ctx.type_count,
"missing $pair WasmGC type"
ctx.func_names.contains_key("pair_new"),
"missing pair_new helper"
fn test_pair_ref_type_is_concrete_pair() {
let pair_ref = ctx.pair_ref();
match pair_ref {
ValType::Ref(r) => {
assert!(r.nullable);
assert_eq!(r.heap_type, HeapType::Concrete(ctx.ids.ty_pair));
_ => panic!("expected Ref type"),
fn test_anyref_is_abstract_any() {
match ctx.anyref() {
assert_eq!(r.heap_type, HeapType::ANY);
fn test_ratio_helpers_registered() {
for name in [
"gcd",
"ratio_new",
"ratio_add",
"ratio_sub",
"ratio_mul",
"ratio_div",
"ratio_eq",
"ratio_lt",
"ratio_from_i64",
ctx.func_names.contains_key(name),
"missing helper function: {name}"
fn test_host_imports_registered() {
"get_output_offset",
"get_input_offset",
"get_strings_offset",
"get_input_entities_count",
"get_timestamp",
"generate_uuid",
"write_string",
"write_bytes",
"symbol_resolve",
"log",
"missing host import: {name}"
fn test_ratio_ref_type() {
let ratio_ref = ctx.ratio_ref();
match ratio_ref {
assert_eq!(r.heap_type, HeapType::Concrete(ctx.ids.ty_ratio));
/// Asserts every common `WasmIds` field resolved to a real index (not the
/// `u32::MAX` poison) in BOTH module modes. This is the regression that would
/// have caught the original `log`-missing-in-eval panic at construction time:
/// `log` is now declared in both modes, so `ids.log` is resolved in both.
fn assert_common_ids_resolved(ctx: &CompileContext) {
let ids = &ctx.ids;
for (label, idx) in [
("gcd", ids.gcd),
("ratio_new", ids.ratio_new),
("ratio_add", ids.ratio_add),
("ratio_sub", ids.ratio_sub),
("ratio_mul", ids.ratio_mul),
("ratio_div", ids.ratio_div),
("ratio_eq", ids.ratio_eq),
("ratio_lt", ids.ratio_lt),
("ratio_from_i64", ids.ratio_from_i64),
("ratio_to_i64", ids.ratio_to_i64),
("unit_singleton", ids.unit_singleton),
("unit_mul", ids.unit_mul),
("unit_negate", ids.unit_negate),
("unit_eq", ids.unit_eq),
("materialize_unit", ids.materialize_unit),
("commodity_add", ids.commodity_add),
("commodity_sub", ids.commodity_sub),
("commodity_mul", ids.commodity_mul),
("commodity_div", ids.commodity_div),
("commodity_mul_by_ratio", ids.commodity_mul_by_ratio),
("commodity_div_by_ratio", ids.commodity_div_by_ratio),
("commodity_neg", ids.commodity_neg),
("commodity_eq", ids.commodity_eq),
("commodity_lt", ids.commodity_lt),
("commodity_assert_atomic", ids.commodity_assert_atomic),
("commodity_new_with_term", ids.commodity_new_with_term),
("pair_new", ids.pair_new),
("string_eq", ids.string_eq),
("nomi_raise", ids.nomi_raise),
("log", ids.log),
("ty_i8_array", ids.ty_i8_array),
("ty_ratio", ids.ty_ratio),
("ty_pair", ids.ty_pair),
("ty_commodity", ids.ty_commodity),
("ty_unit_term", ids.ty_unit_term),
("ty_nomi_condition", ids.ty_nomi_condition),
assert_ne!(idx, u32::MAX, "WasmIds field {label} left unresolved");
EntityKind::ReportNode,
EntityKind::Condition,
assert_ne!(
ids.entity_type(kind),
u32::MAX,
"entity_type({kind:?}) left unresolved"
fn wasm_ids_fully_resolved_script_mode() {
assert_common_ids_resolved(&ctx);
// Script-mode-only env imports resolve; eval-only catch_each does not.
assert!(ctx.ids.get_output_offset().is_ok());
assert!(ctx.ids.get_input_offset().is_ok());
assert!(ctx.ids.get_input_entities_count().is_ok());
assert!(ctx.ids.nomi_catch_each().is_err());
fn wasm_ids_fully_resolved_eval_mode() {
let ctx = CompileContext::new_eval_with_host_fns(&[]).unwrap();
// Eval-mode-only catch_each resolves; script-only env imports do not.
assert!(ctx.ids.nomi_catch_each().is_ok());
assert!(ctx.ids.get_output_offset().is_err());
assert!(ctx.ids.get_input_offset().is_err());
assert!(ctx.ids.get_input_entities_count().is_err());
fn host_fn_named_log_is_rejected_not_silently_rebound() {
// A user `HostFnSpec` whose import_name collides with the reserved `env.log`
// (or any built-in) must error at construction, not silently overwrite the
// resolved index and misroute PRINT/DISPLAY or emit invalid wasm.
let spec = crate::host_fn::HostFnSpec::new("user-log", "env", "log");
let msg = match CompileContext::new_eval_with_host_fns(&[spec]) {
Ok(_) => panic!("reserved-name collision must be rejected"),
Err(e) => e.to_string(),
};
assert!(msg.contains("already registered"), "got: {msg}");
assert!(msg.contains("log"), "got: {msg}");
fn test_valid_wasm_with_ratio_helpers() {
let mut ctx = CompileContext::new().unwrap();
ctx.add_should_apply(CompileContext::default_should_apply(), usize::MAX);
let mut f = Function::new([]);
f.instruction(&Instruction::End);
ctx.add_process(f);
let wasm = ctx.finish();
assert_eq!(&wasm[0..4], b"\0asm");