459 lines
21 KiB
Rust
459 lines
21 KiB
Rust
//! 测试框架调用的内置值/载荷集成回归,通过公开 Vm API 验证 objects 与执行器协议一致。
|
|
//! 各场景区分 Rust 表示相等、语言数值/内容比较、外部拥有根和容器内部强边。
|
|
//! 部分场景只核对局部协议并以 VM 析构收尾;显式释放/环回收场景另检查句柄失效、计数或空堆。
|
|
|
|
use endor_core::{
|
|
CodeLayout, CompareOp, EndorBaseExceptionObject, EndorCode, EndorDictObject,
|
|
EndorFunctionObject, EndorInteger, EndorNativeFunctionObject, EndorNumber, EndorNumberObject,
|
|
EndorReal, EndorStringObject, EndorTracebackEntry, Instruction, Opcode, Value, Vm, VmError,
|
|
};
|
|
|
|
/// 测试数据辅助:构造直接 Text,不分配堆字符串对象。
|
|
fn text(value: &str) -> Value {
|
|
Value::Text(value.into())
|
|
}
|
|
|
|
#[test]
|
|
/// 验证整数规范化、任意长字面量保存、小数表示差异、分母符号和非法词法。
|
|
fn numbers_preserve_large_digits_decimal_scale_exponents_and_fraction_parts() {
|
|
// 超过 i64 的数字串仍可精确保存,只有显式 as_i64 收窄失败。
|
|
let huge = "123456789012345678901234567890123456789012345678901234567890";
|
|
assert_eq!(EndorNumber::integer(huge).unwrap().to_string(), huge);
|
|
assert_eq!(EndorNumber::integer("-000").unwrap().to_string(), "0");
|
|
assert_eq!(EndorNumber::integer("+0012").unwrap().as_i64(), Some(12));
|
|
assert_eq!(EndorNumber::integer(huge).unwrap().as_i64(), None);
|
|
// 逐个验证合法小数原样保存,包括负零、尾零、边界小数点和超长指数。
|
|
for literal in [
|
|
"-0.0000",
|
|
"1.2300000000000000000001",
|
|
".5",
|
|
"1.",
|
|
"2e+999999999999999999999999999999",
|
|
] {
|
|
assert_eq!(EndorNumber::decimal(literal).unwrap().to_string(), literal);
|
|
}
|
|
assert_ne!(
|
|
EndorNumber::decimal("1.00").unwrap(),
|
|
EndorNumber::decimal("1.0").unwrap()
|
|
);
|
|
assert_eq!(
|
|
EndorNumber::rational("2", "-6").unwrap().to_string(),
|
|
"-2/6"
|
|
);
|
|
assert_eq!(EndorNumber::rational("0", "-6").unwrap().to_string(), "0/6");
|
|
assert!(EndorNumber::rational("1", "0").is_err());
|
|
// 逐个拒绝非法词法,不忽略空白或把 Unicode 数字当 ASCII 数字。
|
|
for invalid in ["", "+", " 1", "1 ", "1_000", "12", "1.0"] {
|
|
assert!(EndorNumber::integer(invalid).is_err(), "{invalid}");
|
|
}
|
|
// 逐个拒绝非法词法,不忽略空白或把 Unicode 数字当 ASCII 数字。
|
|
for invalid in ["", ".", "1.2.3", "1e", "1e+", "1e2e3", "nan", " 0.1", "--1"] {
|
|
assert!(EndorNumber::decimal(invalid).is_err(), "{invalid}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
/// 验证 IEEE 位保真、零真值、复数显示,以及数值 hash/复数比较的明确拒绝;实数数学比较另有实现。
|
|
fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 带特定 NaN payload 的 IEEE 位,验证存储不会规范化掉低位信息。
|
|
let bits = 0x7ff8_0000_0000_0042;
|
|
// 显式 float 构造保留位,而非按数学等价改写。
|
|
let nan = EndorNumber::binary_float(f64::from_bits(bits));
|
|
assert_eq!(nan.as_real(), Some(&EndorReal::BinaryFloat(bits)));
|
|
assert_ne!(
|
|
EndorNumber::binary_float(-0.0),
|
|
EndorNumber::binary_float(0.0)
|
|
);
|
|
assert!(!EndorNumber::binary_float(f64::INFINITY).is_zero());
|
|
// 超长指数的十进制负零仍为零,真值判断不需要进行指数运算。
|
|
let zero = vm
|
|
.new_number(EndorNumber::decimal("-0.000e99999999999").unwrap())
|
|
.unwrap();
|
|
assert!(!vm.is_true(zero).unwrap());
|
|
// 复数两分量可保留不同精确形式,但不代表复数算术/比较已经实现。
|
|
let complex = EndorNumber::complex(
|
|
EndorReal::Integer(EndorInteger::parse("3").unwrap()),
|
|
EndorNumber::rational("1", "3")
|
|
.unwrap()
|
|
.as_real()
|
|
.unwrap()
|
|
.clone(),
|
|
);
|
|
// 将复数放入堆载荷,验证通过 VM 协议访问的行为。
|
|
let object = vm.new_number(complex).unwrap();
|
|
assert_eq!(vm.str(object).unwrap(), "(3 + (1/3)i)");
|
|
assert_eq!(
|
|
vm.hash(object),
|
|
Err(VmError::UnsupportedOperation("numeric hash"))
|
|
);
|
|
assert_eq!(
|
|
vm.compare(object, zero, CompareOp::Eq),
|
|
Err(VmError::UnsupportedOperation("numeric comparison"))
|
|
);
|
|
assert!(
|
|
vm.payload::<EndorNumberObject>(object, "number")
|
|
.unwrap()
|
|
.value()
|
|
.as_real()
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
/// 验证字符串按 Unicode 标量计数/索引,按内容相等及哈希,空串为假。
|
|
fn strings_use_unicode_characters_and_content_equality() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 两个分别分配的字符串身份不同,内容/哈希协议仍应相等。
|
|
let a = vm.new_string("Endor中文🦀").unwrap();
|
|
// 相同内容的另一对象用于排除仅身份比较的错误实现。
|
|
let b = vm.new_string("Endor中文🦀").unwrap();
|
|
// 空字符串真值应为假。
|
|
let empty = vm.new_string("").unwrap();
|
|
// 借用具体载荷检查 Unicode 标量索引,不把 UTF-8 字节数当字符数。
|
|
let string = vm.payload::<EndorStringObject>(a, "string").unwrap();
|
|
assert_eq!(string.char_count(), 8);
|
|
assert_eq!(string.char_at(7), Some('🦀'));
|
|
assert_eq!(string.char_at(8), None);
|
|
assert!(vm.compare(a, b, CompareOp::Eq).unwrap());
|
|
assert_eq!(vm.hash(a).unwrap(), vm.hash(b).unwrap());
|
|
assert_eq!(vm.str(a).unwrap(), "Endor中文🦀");
|
|
assert!(!vm.is_true(empty).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
/// 验证堆字符串键与直接文本等价时保留首键及顺序,覆盖/删除正确释放旧边。
|
|
fn dictionaries_keep_order_and_first_key_and_own_keys_and_values() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 字典返回一份外部根;插入对象键值时另持内部边。
|
|
let dict = vm.new_dict().unwrap();
|
|
// 堆字符串键之后与同内容直接 Text 比较,字典应保留此首键身份。
|
|
let key = vm.new_string("answer").unwrap();
|
|
// 覆盖前的对象值,归还外部根后只由字典持有,便于检查覆盖释放。
|
|
let old = vm.new_object().unwrap();
|
|
vm.dict_set(dict, Value::Object(key), Value::Object(old))
|
|
.unwrap();
|
|
// 归还测试根,后续键仍通过字典键边存活。
|
|
vm.release(key).unwrap();
|
|
// 归还旧值测试根,字典覆盖时应释放其最后强边。
|
|
vm.release(old).unwrap();
|
|
vm.dict_set(dict, text("second"), Value::Nil).unwrap();
|
|
vm.dict_set(dict, text("answer"), text("replacement"))
|
|
.unwrap();
|
|
assert_eq!(
|
|
vm.dict_get(dict, &text("answer")).unwrap(),
|
|
Some(text("replacement"))
|
|
);
|
|
assert_eq!(vm.dict_items(dict).unwrap()[0].0, Value::Object(key));
|
|
assert_eq!(vm.dict_items(dict).unwrap().len(), 2);
|
|
// 字典覆盖旧值后,old 已无任何拥有根,访问必须报告失效句柄。
|
|
assert!(matches!(vm.object(old), Err(VmError::InvalidHandle)));
|
|
assert!(vm.dict_delete(dict, &text("answer")).unwrap());
|
|
// 删除 answer 同时释放保留的首键对象,等价 Text 键不会替它延长寿命。
|
|
assert!(matches!(vm.object(key), Err(VmError::InvalidHandle)));
|
|
assert!(!vm.dict_delete(dict, &text("missing")).unwrap());
|
|
vm.dict_clear(dict).unwrap();
|
|
assert!(!vm.is_true(dict).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
/// 验证异堆/过期句柄、不可哈希键和错误容器种类不破坏既有值与计数。
|
|
fn failed_container_writes_leave_values_and_counts_unchanged() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 第二个 VM 提供异堆句柄,拒绝时不能把它保留在本堆容器。
|
|
let mut other = Vm::new().unwrap();
|
|
// 异堆对象始终由其所属 VM 管理,不允许跨堆建立托管边。
|
|
let foreign = other.new_object().unwrap();
|
|
// 字典返回一份外部根;插入对象键值时另持内部边。
|
|
let dict = vm.new_dict().unwrap();
|
|
// 子对象根加上字典/Cell 内部边应按实际拥有次数计数。
|
|
let child = vm.new_object().unwrap();
|
|
vm.dict_set(dict, text("x"), Value::Object(child)).unwrap();
|
|
assert_eq!(
|
|
vm.dict_set(dict, text("x"), Value::Object(foreign)),
|
|
Err(VmError::ForeignHandle)
|
|
);
|
|
assert_eq!(
|
|
vm.dict_get(dict, &text("x")).unwrap(),
|
|
Some(Value::Object(child))
|
|
);
|
|
assert_eq!(vm.ref_count(child).unwrap(), 2);
|
|
assert_eq!(
|
|
vm.dict_set(dict, Value::Number(1_i64.into()), Value::Nil),
|
|
Err(VmError::UnsupportedOperation("numeric hash"))
|
|
);
|
|
assert_eq!(
|
|
vm.dict_set(dict, Value::Object(dict), Value::Nil),
|
|
Err(VmError::UnsupportedOperation("dict hash"))
|
|
);
|
|
// Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。
|
|
let cell = vm.new_cell(Some(Value::Object(child))).unwrap();
|
|
assert_eq!(
|
|
vm.cell_set(cell, Value::Object(foreign)),
|
|
Err(VmError::ForeignHandle)
|
|
);
|
|
assert_eq!(vm.cell_get(cell).unwrap(), Value::Object(child));
|
|
// 分配后立即释放以制造真正的过期身份,不能误认为普通缺值。
|
|
let dead = vm.new_object().unwrap();
|
|
// 此后向 Cell 写入该身份必须报 InvalidHandle,不改变原值。
|
|
vm.release(dead).unwrap();
|
|
assert_eq!(
|
|
vm.cell_set(cell, Value::Object(dead)),
|
|
Err(VmError::InvalidHandle)
|
|
);
|
|
assert_eq!(vm.ref_count(child).unwrap(), 3);
|
|
assert_eq!(
|
|
vm.dict_clear(child),
|
|
Err(VmError::ObjectKindMismatch("dict"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
/// 验证 None 空槽与已存 Nil 不同,同对象重写不提前释放,clear 后旧值失效。
|
|
fn cells_share_rebinding_distinguish_empty_and_none_and_keep_stable_objects() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。
|
|
let cell = vm.new_cell(None).unwrap();
|
|
assert_eq!(vm.cell_get(cell), Err(VmError::EmptyCell));
|
|
vm.cell_set(cell, Value::Nil).unwrap();
|
|
assert_eq!(vm.cell_get(cell).unwrap(), Value::Nil);
|
|
// 子对象根加上字典/Cell 内部边应按实际拥有次数计数。
|
|
let child = vm.new_object().unwrap();
|
|
vm.cell_set(cell, Value::Object(child)).unwrap();
|
|
// 归还测试根后,容器/Cell 内部边仍须维持子对象存活。
|
|
vm.release(child).unwrap();
|
|
vm.cell_set(cell, Value::Object(child)).unwrap();
|
|
assert_eq!(vm.ref_count(child).unwrap(), 1);
|
|
vm.cell_clear(cell).unwrap();
|
|
// 清空 Cell 归还子对象最后的内部边,child 不能仍然可访问。
|
|
assert!(matches!(vm.object(child), Err(VmError::InvalidHandle)));
|
|
assert_eq!(vm.cell_get(cell), Err(VmError::EmptyCell));
|
|
}
|
|
|
|
#[test]
|
|
/// 验证同一 Cell 被字典多键持有要计多条边,外部根移除后互引环整体回收。
|
|
fn container_cycles_and_duplicate_edges_are_collected() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 字典返回一份外部根;插入对象键值时另持内部边。
|
|
let dict = vm.new_dict().unwrap();
|
|
// Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。
|
|
let cell = vm.new_cell(Some(Value::Object(dict))).unwrap();
|
|
vm.dict_set(dict, text("a"), Value::Object(cell)).unwrap();
|
|
vm.dict_set(dict, text("b"), Value::Object(cell)).unwrap();
|
|
assert_eq!(vm.ref_count(cell).unwrap(), 3);
|
|
vm.release(dict).unwrap();
|
|
// 归还 Cell 原根,闭包或字典边仍可持有它,最后无根环交给 GC。
|
|
vm.release(cell).unwrap();
|
|
assert_eq!(vm.collect_cycles(), 2);
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
/// 验证代码常量、默认值、全局和共享捕获槽均有强边,闭包更新可见且无根环可回收。
|
|
fn functions_retain_globals_constants_defaults_and_shared_closure_cells() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 合法字典环境由两函数共同持有,稍后再反向保存第一个函数形成待回收的环境环。
|
|
let globals = vm.new_dict().unwrap();
|
|
// 常量初始有测试根,代码常量表和默认值还会独立拥有强边。
|
|
let constant = vm.new_string("constant").unwrap();
|
|
// Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。
|
|
let cell = vm.new_cell(Some(text("first"))).unwrap();
|
|
// 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。
|
|
let code = EndorCode::from_instructions(
|
|
&[
|
|
Instruction::new(Opcode::LoadDeref, 1),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
],
|
|
vec![Value::Object(constant)],
|
|
CodeLayout {
|
|
parameters: vec!["argument".into()],
|
|
locals: vec!["argument".into()],
|
|
free_variables: vec!["captured".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
// 托管代码对象有一份根,函数载荷还会额外持有代码边。
|
|
let code = vm.new_code(code).unwrap();
|
|
// 函数/原生函数分配结果由测试持有根,载荷内部引用另计。
|
|
let function = vm
|
|
.new_function(
|
|
"read",
|
|
code,
|
|
globals,
|
|
vec![Value::Object(constant)],
|
|
vec![cell],
|
|
)
|
|
.unwrap();
|
|
// 第二个函数共用代码/全局/Cell,但默认值布局不同,拥有边各自计数。
|
|
let second = vm
|
|
.new_function("read_again", code, globals, vec![], vec![cell])
|
|
.unwrap();
|
|
assert_eq!(vm.ref_count(constant).unwrap(), 3);
|
|
// 释放代码测试根后函数仍持有代码内部边,不能使执行数据常量失效。
|
|
vm.release(code).unwrap();
|
|
// 常量原测试根归还,其余代码/默认值边仍保持存活。
|
|
vm.release(constant).unwrap();
|
|
// 两函数都已保存同一捕获 Cell,归还测试根后更新仍通过这两条内部边保持合法。
|
|
vm.release(cell).unwrap();
|
|
vm.cell_set(cell, text("updated")).unwrap();
|
|
// 逐个读取两个函数捕获的同一 Cell,必须观察相同更新。
|
|
for reference in [function, second] {
|
|
// 借用函数载荷的闭包引用,不为读取额外保留根。
|
|
let f = vm
|
|
.payload::<EndorFunctionObject>(reference, "function")
|
|
.unwrap();
|
|
assert_eq!(vm.cell_get(f.closure()[0]).unwrap(), text("updated"));
|
|
}
|
|
assert_eq!(vm.call(function, &[]).unwrap(), text("updated"));
|
|
vm.dict_set(globals, text("read"), Value::Object(function))
|
|
.unwrap();
|
|
vm.release(globals).unwrap();
|
|
vm.release(function).unwrap();
|
|
vm.release(second).unwrap();
|
|
assert_eq!(vm.collect_cycles(), 5);
|
|
}
|
|
|
|
#[test]
|
|
/// 验证函数全局种类/闭包数量及代码重复槽名错误不新增根或持有边。
|
|
fn malformed_function_environments_are_rejected_without_retaining_any_edges() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 故意以普通 Record 冒充全局字典,第一次函数构造应在保留边之前拒绝。
|
|
let globals = vm.new_object().unwrap();
|
|
// 最小装载常量并返回的逻辑指令,用于只改变环境或闭包布局的错误测试。
|
|
let instructions = [
|
|
Instruction::new(Opcode::LoadConst, 0),
|
|
Instruction::simple(Opcode::ReturnValue),
|
|
];
|
|
// 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。
|
|
let code = EndorCode::from_instructions(&instructions, vec![Value::Nil], CodeLayout::default())
|
|
.unwrap();
|
|
// 托管代码对象有一份根,函数载荷还会额外持有代码边。
|
|
let code = vm.new_code(code).unwrap();
|
|
assert_eq!(
|
|
vm.new_function("bad", code, globals, vec![], vec![]),
|
|
Err(VmError::ObjectKindMismatch("dict"))
|
|
);
|
|
assert_eq!(vm.ref_count(globals).unwrap(), 1);
|
|
assert_eq!(vm.ref_count(code).unwrap(), 1);
|
|
assert!(EndorCode::new(vec![], vec![], vec!["x".into(), "x".into()], vec![]).is_err());
|
|
// 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。
|
|
let code = EndorCode::from_instructions(
|
|
&instructions,
|
|
vec![Value::Nil],
|
|
CodeLayout {
|
|
free_variables: vec!["x".into()],
|
|
..CodeLayout::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
// 托管代码对象有一份根,函数载荷还会额外持有代码边。
|
|
let code = vm.new_code(code).unwrap();
|
|
// 第二次改用合法字典环境,只保留“代码需要一个自由 Cell、实传零个”的错误。
|
|
let globals = vm.new_dict().unwrap();
|
|
assert!(
|
|
vm.new_function("bad", code, globals, vec![], vec![])
|
|
.is_err()
|
|
);
|
|
assert_eq!(vm.ref_count(code).unwrap(), 1);
|
|
}
|
|
|
|
#[test]
|
|
/// 验证异常数据载荷的参数/链强边与注释/回溯元数据,错误 cause 种类被拒绝。
|
|
fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 异常参数对象由 cause 的参数边持有,之后归还测试根仍须存活。
|
|
let argument = vm.new_string("detail").unwrap();
|
|
// cause 异常载荷持有参数;主异常的 cause/context 两项都指向它。
|
|
let cause = vm
|
|
.allocate(EndorBaseExceptionObject::new(
|
|
"ValueError",
|
|
vec![Value::Object(argument)],
|
|
))
|
|
.unwrap();
|
|
// 同一 cause 被两条字段边拥有,注释/回溯文本不构成托管对象边。
|
|
let exception = vm
|
|
.allocate(
|
|
EndorBaseExceptionObject::new("RuntimeError", vec![text("failed")])
|
|
.with_cause(cause)
|
|
.with_context(cause)
|
|
.with_note("while loading")
|
|
.with_traceback(EndorTracebackEntry {
|
|
function: "load".into(),
|
|
file: "demo.endor".into(),
|
|
line: 12,
|
|
}),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(vm.ref_count(cause).unwrap(), 3);
|
|
// 归还参数外部根,链上异常仍通过参数边持有它。
|
|
vm.release(argument).unwrap();
|
|
// 归还 cause 外部根,主异常两条链边继续保活。
|
|
vm.release(cause).unwrap();
|
|
// 借用异常元数据观察;不在此处抛出异常或自动创建语言异常类。
|
|
let data = vm
|
|
.payload::<EndorBaseExceptionObject>(exception, "exception")
|
|
.unwrap();
|
|
assert!(data.context_suppressed());
|
|
assert_eq!(data.notes(), &["while loading"]);
|
|
assert_eq!(data.traceback()[0].line, 12);
|
|
assert_eq!(vm.str(exception).unwrap(), "failed");
|
|
// 归还主异常最后根会级联释放 cause 和参数,无需手动重复 release 内部边。
|
|
vm.release(exception).unwrap();
|
|
assert_eq!(vm.object_count(), 0);
|
|
// 字典不是异常,不能通过 with_cause 绕过内置校验。
|
|
let wrong = vm.new_dict().unwrap();
|
|
assert_eq!(
|
|
vm.allocate(EndorBaseExceptionObject::new("Error", vec![]).with_cause(wrong)),
|
|
Err(VmError::ObjectKindMismatch("exception"))
|
|
);
|
|
}
|
|
|
|
/// 旧式原生回调借用返回原参数;VM 必须为最终对象结果另取拥有根。
|
|
fn echo(arguments: &[Value]) -> Result<Value, VmError> {
|
|
// 回调只接受一个参数,clone 不增加计数,最终拥有根由 VM 原生调用路径取得。
|
|
match arguments {
|
|
[value] => Ok(value.clone()),
|
|
_ => Err(VmError::InvalidArgument("expected one value")),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
/// 验证原生传参不损失数值表示,即使原样返回对象参数也要独立结果根。
|
|
fn native_functions_pass_lossless_numbers_and_return_owned_objects() {
|
|
// 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 函数/原生函数分配结果由测试持有根,载荷内部引用另计。
|
|
let function = vm
|
|
.allocate(EndorNativeFunctionObject::new("echo", echo))
|
|
.unwrap();
|
|
// 精确小数作为直接参数,经原生回调传回仍保留全部数字和尾零。
|
|
let number = Value::Number(EndorNumber::decimal("12345678901234567890.000100").unwrap());
|
|
assert_eq!(
|
|
vm.call(function, std::slice::from_ref(&number)).unwrap(),
|
|
number
|
|
);
|
|
// 字典返回一份外部根;插入对象键值时另持内部边。
|
|
let dict = vm.new_dict().unwrap();
|
|
// 调用结果与原参数身份相同,但对象结果必须额外有一份外部根。
|
|
let returned = vm.call(function, &[Value::Object(dict)]).unwrap();
|
|
assert_eq!(returned, Value::Object(dict));
|
|
assert_eq!(vm.ref_count(dict).unwrap(), 2);
|
|
// 只归还结果根,原 dict 根仍存在,计数应从二降为一。
|
|
vm.release_value(returned).unwrap();
|
|
assert_eq!(vm.ref_count(dict).unwrap(), 1);
|
|
assert_eq!(
|
|
vm.payload::<EndorDictObject>(dict, "dict").unwrap().len(),
|
|
0
|
|
);
|
|
}
|