619 lines
26 KiB
Rust
619 lines
26 KiB
Rust
//! Rust 测试框架调用的 VM/Store 基础协议集成场景,不依赖源码编译器。
|
||
//! Resource 用 Rust Drop 计数观察实际析构,Number/ReflectedOrdering 验证宿主载荷协议覆盖和反向分派。
|
||
//! 每个测试独立建 VM;未显式归还的根及未收集环最终仍须由整个 VM 的销毁清理。
|
||
|
||
use std::any::Any;
|
||
use std::sync::Arc;
|
||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||
|
||
use endor_core::{CompareOp, NativeFunction, ObjectPayload, ObjectRef, Record, Value, Vm, VmError};
|
||
|
||
#[derive(Debug)]
|
||
struct Resource {
|
||
/// VM 外观察实际析构次数的共享计数器。
|
||
drops: Arc<AtomicUsize>,
|
||
/// 分配时由 Heap 快照持有的内部强边,含重复项。
|
||
edges: Vec<ObjectRef>,
|
||
}
|
||
|
||
impl Drop for Resource {
|
||
/// Resource 真正销毁时累加共享计数,区分句柄复制/归还与实际 Rust 资源释放。
|
||
fn drop(&mut self) {
|
||
self.drops.fetch_add(1, Ordering::SeqCst);
|
||
}
|
||
}
|
||
|
||
impl ObjectPayload for Resource {
|
||
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
|
||
fn as_any(&self) -> &dyn Any {
|
||
self
|
||
}
|
||
|
||
/// 错误与诊断显示的类别标签。
|
||
fn kind_name(&self) -> &'static str {
|
||
"resource"
|
||
}
|
||
|
||
/// Heap 分配时枚举载荷拥有边;重复项必须逐项计数,不能去重。
|
||
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
|
||
// 每个槽都是独立拥有边,不能因为身份相同而合并计数。
|
||
for reference in &self.edges {
|
||
visit(*reference);
|
||
}
|
||
}
|
||
|
||
/// Resource 允许公共属性表,使测试能组合载荷边和属性边成环。
|
||
fn allows_attributes(&self) -> bool {
|
||
true
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
struct Number(i64);
|
||
|
||
impl ObjectPayload for Number {
|
||
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
|
||
fn as_any(&self) -> &dyn Any {
|
||
self
|
||
}
|
||
|
||
/// 错误与诊断显示的类别标签。
|
||
fn kind_name(&self) -> &'static str {
|
||
"number"
|
||
}
|
||
|
||
/// 测试 Number 用值覆盖默认身份表示,供 str 默认回退调用。
|
||
fn repr(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
||
Ok(self.0.to_string())
|
||
}
|
||
|
||
/// 测试 Number 仅零为假,不使用默认对象恒真规则。
|
||
fn is_true(&self) -> Result<bool, VmError> {
|
||
Ok(self.0 != 0)
|
||
}
|
||
|
||
/// 载荷自定义哈希协议;Number 按值,ReflectedOrdering 明确拒绝。
|
||
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
||
Ok(self.0 as u64)
|
||
}
|
||
|
||
/// Store 先正向后反向调用;返回 None 是未实现,Some(false) 则不再回退。
|
||
fn compare(
|
||
&self,
|
||
other: &dyn ObjectPayload,
|
||
operation: CompareOp,
|
||
) -> Result<Option<bool>, VmError> {
|
||
// Number 只处理同类,其他载荷返回 None 允许 Store 继续分派。
|
||
let Some(other) = other.as_any().downcast_ref::<Self>() else {
|
||
return Ok(None);
|
||
};
|
||
// 同类 Number 每个关系都返回明确结果,false 也不是未实现。
|
||
Ok(Some(match operation {
|
||
CompareOp::Eq => self.0 == other.0,
|
||
CompareOp::Ne => self.0 != other.0,
|
||
CompareOp::Lt => self.0 < other.0,
|
||
CompareOp::Le => self.0 <= other.0,
|
||
CompareOp::Gt => self.0 > other.0,
|
||
CompareOp::Ge => self.0 >= other.0,
|
||
}))
|
||
}
|
||
}
|
||
|
||
/// 各生命周期测试共用分配器,保存计数器 Arc 与给定不可变边,返回一份拥有根。
|
||
fn resource(vm: &mut Vm, drops: &Arc<AtomicUsize>, edges: Vec<ObjectRef>) -> ObjectRef {
|
||
vm.allocate(Resource {
|
||
drops: drops.clone(),
|
||
edges,
|
||
})
|
||
.unwrap()
|
||
}
|
||
|
||
#[test]
|
||
/// 验证新 VM 不隐式自举名义类型或内置对象;不代表 VM 没有显式类型 API。
|
||
fn starts_empty_without_language_classes_or_a_type_system() {
|
||
// 只观察初始空堆,不因查询自动初始化对象。
|
||
let vm = Vm::new().unwrap();
|
||
assert_eq!(vm.object_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证普通 Record 的身份表示、恒真和身份相等回退,无序比较及不可调用的错误。
|
||
fn defaults_provide_identity_representation_hash_and_truth() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
|
||
let first = vm.new_object().unwrap();
|
||
// 另一个分配结果有独立身份,即使内容相同也不是同对象。
|
||
let second = vm.new_object().unwrap();
|
||
assert_eq!(vm.object(first).unwrap().header().identity(), first);
|
||
assert!(vm.is_same(first, first).unwrap());
|
||
assert!(!vm.is_same(first, second).unwrap());
|
||
assert!(vm.repr(first).unwrap().contains(&first.to_string()));
|
||
assert_eq!(vm.repr(first).unwrap(), vm.str(first).unwrap());
|
||
assert_eq!(vm.hash(first).unwrap(), vm.hash(first).unwrap());
|
||
assert!(vm.is_true(first).unwrap());
|
||
assert!(vm.compare(first, first, CompareOp::Eq).unwrap());
|
||
assert!(vm.compare(first, second, CompareOp::Ne).unwrap());
|
||
assert_eq!(
|
||
vm.compare(first, second, CompareOp::Lt),
|
||
Err(VmError::UnsupportedOperation("ordering"))
|
||
);
|
||
assert_eq!(
|
||
vm.call(first, &[]),
|
||
Err(VmError::UnsupportedOperation("call"))
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证复制句柄不保留对象,显式 retain/release 控制资源销毁与过期访问。
|
||
fn explicit_references_control_lifetime_and_copies_are_borrowed() {
|
||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||
let drops = Arc::new(AtomicUsize::new(0));
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// resource 返回一份外部根,后续复制不新增根。
|
||
let owned = resource(&mut vm, &drops, vec![]);
|
||
// 直接复制 ObjectRef,ref_count 仍为一,不能额外 release。
|
||
let borrowed = owned;
|
||
assert_eq!(vm.ref_count(borrowed).unwrap(), 1);
|
||
// 显式多取得一份外部根,需两次归还后才实际析构。
|
||
vm.retain(owned).unwrap();
|
||
assert_eq!(vm.ref_count(owned).unwrap(), 2);
|
||
vm.release(owned).unwrap();
|
||
assert_eq!(drops.load(Ordering::SeqCst), 0);
|
||
vm.release(owned).unwrap();
|
||
assert_eq!(drops.load(Ordering::SeqCst), 1);
|
||
assert_eq!(vm.object_count(), 0);
|
||
assert!(matches!(vm.object(borrowed), Err(VmError::InvalidHandle)));
|
||
assert_eq!(vm.release(borrowed), Err(VmError::InvalidHandle));
|
||
}
|
||
|
||
#[test]
|
||
/// 验证字段独立持有内部边,不能用外部 release 归还仅借用的属性边。
|
||
fn attributes_keep_objects_alive_and_delete_releases_them() {
|
||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||
let drops = Arc::new(AtomicUsize::new(0));
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 普通 Record 字段作为子对象强边来源。
|
||
let owner = vm.new_object().unwrap();
|
||
// 子对象先有分配根,写入属性后另有内部边。
|
||
let child = resource(&mut vm, &drops, vec![]);
|
||
vm.set_attr(owner, "child", Value::Object(child)).unwrap();
|
||
assert_eq!(vm.ref_count(child).unwrap(), 2);
|
||
// 归还分配根后属性边继续保活,不能再当作拥有根重复 release。
|
||
vm.release(child).unwrap();
|
||
assert_eq!(vm.ref_count(child).unwrap(), 1);
|
||
assert_eq!(vm.release(child), Err(VmError::NoOwnedReference));
|
||
assert_eq!(vm.get_attr(owner, "child").unwrap(), Value::Object(child));
|
||
vm.del_attr(owner, "child").unwrap();
|
||
assert_eq!(drops.load(Ordering::SeqCst), 1);
|
||
assert_eq!(
|
||
vm.get_attr(owner, "child"),
|
||
Err(VmError::AttributeNotFound("child".into()))
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证属性同对象重写先保留新边再归还旧边,随后改标量才释放最后强边。
|
||
fn replacing_the_same_edge_does_not_destroy_the_target() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 普通 Record 字段作为子对象强边来源。
|
||
let owner = vm.new_object().unwrap();
|
||
// 独立目标用于属性替换或多重载荷边计数。
|
||
let target = vm.new_object().unwrap();
|
||
vm.set_attr(owner, "child", Value::Object(target)).unwrap();
|
||
// 归还原根,后续存活完全依赖已建立的内部边。
|
||
vm.release(target).unwrap();
|
||
vm.set_attr(owner, "child", Value::Object(target)).unwrap();
|
||
assert_eq!(vm.ref_count(target).unwrap(), 1);
|
||
vm.set_attr(owner, "child", Value::Number(7_i64.into()))
|
||
.unwrap();
|
||
assert!(matches!(vm.object(target), Err(VmError::InvalidHandle)));
|
||
assert_eq!(
|
||
vm.get_attr(owner, "child").unwrap(),
|
||
Value::Number(7_i64.into())
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证宿主主动 retain 属性借用结果后,可在原父对象释放后继续使用。
|
||
fn borrowed_attribute_results_can_be_retained_by_the_host() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 父对象拥有 child 字段边,释放父根会移除该边。
|
||
let parent = vm.new_object().unwrap();
|
||
// 子对象先有分配根,写入属性后另有内部边。
|
||
let child = vm.new_object().unwrap();
|
||
vm.set_attr(parent, "child", Value::Object(child)).unwrap();
|
||
// 归还分配根后属性边继续保活,不能再当作拥有根重复 release。
|
||
vm.release(child).unwrap();
|
||
// get_attr 返回借用值,宿主若要跨父对象寿命使用须手动 retain。
|
||
let Value::Object(borrowed) = vm.get_attr(parent, "child").unwrap() else {
|
||
panic!("expected an object");
|
||
};
|
||
// 取得独立宿主根后,原持有字段消失也不会使结果失效。
|
||
vm.retain(borrowed).unwrap();
|
||
vm.release(parent).unwrap();
|
||
assert_eq!(vm.ref_count(borrowed).unwrap(), 1);
|
||
vm.release(borrowed).unwrap();
|
||
assert_eq!(vm.object_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证异堆和过期值写入失败保持旧字段,retain/release/身份比较也验证堆归属。
|
||
fn failed_foreign_and_stale_writes_preserve_the_old_attribute() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 第二个 VM 用于构造确实属于异堆的有效身份。
|
||
let mut other = Vm::new().unwrap();
|
||
// 普通 Record 字段作为子对象强边来源。
|
||
let owner = vm.new_object().unwrap();
|
||
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
|
||
let foreign = other.new_object().unwrap();
|
||
// 制造旧身份;释放后写入必须失败而非覆盖旧值。
|
||
let stale = vm.new_object().unwrap();
|
||
// 销毁对象使句柄过期,后续不再拥有任何可归还的根。
|
||
vm.release(stale).unwrap();
|
||
vm.set_attr(owner, "value", Value::Number(1_i64.into()))
|
||
.unwrap();
|
||
assert_eq!(
|
||
vm.set_attr(owner, "value", Value::Object(foreign)),
|
||
Err(VmError::ForeignHandle)
|
||
);
|
||
assert_eq!(
|
||
vm.set_attr(owner, "value", Value::Object(stale)),
|
||
Err(VmError::InvalidHandle)
|
||
);
|
||
assert_eq!(
|
||
vm.get_attr(owner, "value").unwrap(),
|
||
Value::Number(1_i64.into())
|
||
);
|
||
assert_eq!(vm.retain(foreign), Err(VmError::ForeignHandle));
|
||
assert_eq!(vm.release(foreign), Err(VmError::ForeignHandle));
|
||
assert_eq!(vm.is_same(owner, foreign), Err(VmError::ForeignHandle));
|
||
}
|
||
|
||
#[test]
|
||
/// 验证槽代际与堆身份共同防止旧句柄别名新对象。
|
||
fn released_handles_cannot_alias_new_objects_or_new_vms() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 释放后保留旧句柄表示,检查槽复用不会让它复活。
|
||
let old = vm.new_object().unwrap();
|
||
vm.release(old).unwrap();
|
||
// 新分配可复用槽号,但代际必须不同。
|
||
let new = vm.new_object().unwrap();
|
||
assert_ne!(old, new);
|
||
assert_eq!(vm.repr(old), Err(VmError::InvalidHandle));
|
||
// 同号槽在另一堆也不是有效身份,必须报 ForeignHandle。
|
||
let other = Vm::new().unwrap();
|
||
assert!(matches!(other.object(new), Err(VmError::ForeignHandle)));
|
||
}
|
||
|
||
#[test]
|
||
/// 验证槽向量扩容不移动独立装箱对象实体。
|
||
fn boxed_objects_keep_their_addresses_when_the_slot_vector_grows() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
|
||
let first = vm.new_object().unwrap();
|
||
// 保存对象实体地址,不保存可能因向量扩容变化的槽地址。
|
||
let address = std::ptr::from_ref(vm.object(first).unwrap());
|
||
// 压力设置:足够多次分配或链节点,检验稳定盒与非递归清理。
|
||
for _ in 0..4096 {
|
||
vm.new_object().unwrap();
|
||
}
|
||
assert_eq!(std::ptr::from_ref(vm.object(first).unwrap()), address);
|
||
}
|
||
|
||
#[test]
|
||
/// 构造长单向拥有链,释放唯一头根应通过显式工作栈完成级联回收。
|
||
fn zero_reference_chains_are_released_without_recursive_destructors() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 长链唯一保留外部根的起点。
|
||
let head = vm.new_object().unwrap();
|
||
// tail 只是借用游标,后续节点仅由前节点 next 属性持有。
|
||
let mut tail = head;
|
||
// 压力设置:足够多次分配或链节点,检验稳定盒与非递归清理。
|
||
for _ in 0..4096 {
|
||
// 新节点先分配根,接上链后归还该根,仅留下内部边。
|
||
let next = vm.new_object().unwrap();
|
||
vm.set_attr(tail, "next", Value::Object(next)).unwrap();
|
||
vm.release(next).unwrap();
|
||
tail = next;
|
||
}
|
||
// 唯一头根移除应触发整条无环链的级联释放。
|
||
vm.release(head).unwrap();
|
||
assert_eq!(vm.object_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证有外部根的互引环保留,最后根归还后 collect_cycles 才整环回收。
|
||
fn cycles_remain_while_rooted_and_are_collected_when_unreachable() {
|
||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||
let drops = Arc::new(AtomicUsize::new(0));
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
|
||
let first = resource(&mut vm, &drops, vec![]);
|
||
// 另一个分配结果有独立身份,即使内容相同也不是同对象。
|
||
let second = resource(&mut vm, &drops, vec![]);
|
||
vm.set_attr(first, "next", Value::Object(second)).unwrap();
|
||
vm.set_attr(second, "next", Value::Object(first)).unwrap();
|
||
vm.release(second).unwrap();
|
||
assert_eq!(vm.collect_cycles(), 0);
|
||
vm.release(first).unwrap();
|
||
assert_eq!(vm.object_count(), 2);
|
||
assert_eq!(vm.collect_cycles(), 2);
|
||
assert_eq!(vm.collect_cycles(), 0);
|
||
assert_eq!(drops.load(Ordering::SeqCst), 2);
|
||
assert!(matches!(vm.object(first), Err(VmError::InvalidHandle)));
|
||
assert!(matches!(vm.object(second), Err(VmError::InvalidHandle)));
|
||
}
|
||
|
||
#[test]
|
||
/// 验证回收垃圾环时只扣除其指向幸存者的边,不销毁仍有根的目标。
|
||
fn cycle_collection_preserves_rooted_targets_and_updates_counts() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 幸存者保留外部根,即使垃圾环另持有它也不能被 GC 误删。
|
||
let live = vm.new_object().unwrap();
|
||
// 自环使普通强计数无法归零;无外部根后交给显式环回收或 VM Drop。
|
||
let cycle = vm.new_object().unwrap();
|
||
vm.set_attr(cycle, "self", Value::Object(cycle)).unwrap();
|
||
vm.set_attr(cycle, "live", Value::Object(live)).unwrap();
|
||
vm.release(cycle).unwrap();
|
||
assert_eq!(vm.ref_count(live).unwrap(), 2);
|
||
assert_eq!(vm.collect_cycles(), 1);
|
||
assert_eq!(vm.ref_count(live).unwrap(), 1);
|
||
vm.release(live).unwrap();
|
||
assert_eq!(vm.object_count(), 0);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证 trace 两次报告同一目标就持有两条强边,释放载荷逐边归还。
|
||
fn payload_edges_are_owned_and_duplicate_edges_are_counted() {
|
||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||
let drops = Arc::new(AtomicUsize::new(0));
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 独立目标用于属性替换或多重载荷边计数。
|
||
let target = vm.new_object().unwrap();
|
||
// 载荷同时拥有同一目标两次,总强计数应包含两条内部边。
|
||
let holder = resource(&mut vm, &drops, vec![target, target]);
|
||
assert_eq!(vm.ref_count(target).unwrap(), 3);
|
||
// 归还原根,后续存活完全依赖已建立的内部边。
|
||
vm.release(target).unwrap();
|
||
vm.release(holder).unwrap();
|
||
assert_eq!(vm.object_count(), 0);
|
||
assert_eq!(drops.load(Ordering::SeqCst), 1);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证混合载荷边与属性边的环能被同一次图遍历回收。
|
||
fn payload_edges_participate_in_cycle_tracing() {
|
||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||
let drops = Arc::new(AtomicUsize::new(0));
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 普通属性对象作为混合环的一端。
|
||
let record = vm.new_object().unwrap();
|
||
// Resource 载荷拥有 record,record 的字段再反向拥有 Resource。
|
||
let native = resource(&mut vm, &drops, vec![record]);
|
||
vm.set_attr(record, "native", Value::Object(native))
|
||
.unwrap();
|
||
vm.release(record).unwrap();
|
||
vm.release(native).unwrap();
|
||
assert_eq!(vm.collect_cycles(), 2);
|
||
assert_eq!(drops.load(Ordering::SeqCst), 1);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证分配前完整边预检,后部异堆边失败不能部分保留前部有效边。
|
||
fn invalid_payload_edges_abort_allocation_without_retaining_valid_edges() {
|
||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||
let drops = Arc::new(AtomicUsize::new(0));
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 第二个 VM 用于构造确实属于异堆的有效身份。
|
||
let mut other = Vm::new().unwrap();
|
||
// 合法本堆边放在错误边前面,验证失败不部分提交其计数。
|
||
let local = vm.new_object().unwrap();
|
||
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
|
||
let foreign = other.new_object().unwrap();
|
||
assert_eq!(
|
||
vm.allocate(Resource {
|
||
drops: drops.clone(),
|
||
edges: vec![local, foreign],
|
||
}),
|
||
Err(VmError::ForeignHandle)
|
||
);
|
||
assert_eq!(vm.ref_count(local).unwrap(), 1);
|
||
assert_eq!(vm.object_count(), 1);
|
||
assert_eq!(drops.load(Ordering::SeqCst), 1);
|
||
}
|
||
|
||
#[test]
|
||
/// 验证 Rust 载荷可覆盖显示、真值、哈希和比较,无需附着语言类型。
|
||
fn rust_payload_operations_override_defaults_without_language_types() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 测试用 Number(0) 自定义真值为假,不是内置 EndorNumberObject。
|
||
let zero = vm.allocate(Number(0)).unwrap();
|
||
// 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。
|
||
let first = vm.allocate(Number(42)).unwrap();
|
||
// 同值不同身份的 Number,必须内容相等且哈希一致。
|
||
let equal = vm.allocate(Number(42)).unwrap();
|
||
assert!(!vm.is_same(first, equal).unwrap());
|
||
assert!(vm.compare(first, equal, CompareOp::Eq).unwrap());
|
||
assert!(vm.compare(zero, first, CompareOp::Lt).unwrap());
|
||
assert_eq!(vm.hash(first).unwrap(), vm.hash(equal).unwrap());
|
||
assert_eq!(vm.str(first).unwrap(), "42");
|
||
assert!(!vm.is_true(zero).unwrap());
|
||
assert!(vm.is_true(first).unwrap());
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
struct ReflectedOrdering;
|
||
|
||
impl ObjectPayload for ReflectedOrdering {
|
||
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
|
||
fn as_any(&self) -> &dyn Any {
|
||
self
|
||
}
|
||
|
||
/// 错误与诊断显示的类别标签。
|
||
fn kind_name(&self) -> &'static str {
|
||
"reflected-ordering"
|
||
}
|
||
|
||
/// Store 先正向后反向调用;返回 None 是未实现,Some(false) 则不再回退。
|
||
fn compare(
|
||
&self,
|
||
_other: &dyn ObjectPayload,
|
||
operation: CompareOp,
|
||
) -> Result<Option<bool>, VmError> {
|
||
Ok(Some(operation == CompareOp::Gt))
|
||
}
|
||
|
||
/// 载荷自定义哈希协议;Number 按值,ReflectedOrdering 明确拒绝。
|
||
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
||
Err(VmError::UnsupportedOperation("hash"))
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
/// 验证左侧未实现时把 Lt 反向为右侧 Gt,显式不支持 hash 原样传播。
|
||
fn reflected_operations_and_explicitly_unsupported_hashing_are_respected() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 左侧 Record 不实现有序比较,Store 应尝试右侧反向协议。
|
||
let left = vm.allocate(Record).unwrap();
|
||
// 右侧专门只对 Gt 返回真,证明左 Lt 正确反射。
|
||
let right = vm.allocate(ReflectedOrdering).unwrap();
|
||
assert!(vm.compare(left, right, CompareOp::Lt).unwrap());
|
||
assert_eq!(vm.hash(right), Err(VmError::UnsupportedOperation("hash")));
|
||
}
|
||
|
||
/// 简单旧式原生回调,仅接受两个直接整数并检查 i64 加法溢出;不是 Endor 通用算术实现。
|
||
fn add(arguments: &[Value]) -> Result<Value, VmError> {
|
||
// 先按参数形状匹配两个直接 Number,其他输入走回调参数错误。
|
||
match arguments {
|
||
[Value::Number(left), Value::Number(right)] => {
|
||
// as_i64 同时成功才允许此测试回调运算,不接受大整数或小数隐式转换。
|
||
let (left, right) = left
|
||
.as_i64()
|
||
.zip(right.as_i64())
|
||
.ok_or(VmError::InvalidArgument("expected two integers"))?;
|
||
left.checked_add(right)
|
||
.map(|value| Value::Number(value.into()))
|
||
.ok_or(VmError::InvalidArgument("integer overflow"))
|
||
}
|
||
_ => Err(VmError::InvalidArgument("expected two integers")),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
/// 验证原生分派、回调错误、禁止动态属性,以及异堆参数在回调前拒绝。
|
||
fn native_calls_dispatch_validate_arguments_and_propagate_errors() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 把 Rust add 包装为旧式原生函数,由 VM 负责参数/结果身份验证。
|
||
let function = vm.allocate(NativeFunction::new("add", add)).unwrap();
|
||
assert_eq!(vm.repr(function).unwrap(), "<native-function add>");
|
||
assert_eq!(
|
||
vm.call(
|
||
function,
|
||
&[Value::Number(2_i64.into()), Value::Number(3_i64.into())]
|
||
)
|
||
.unwrap(),
|
||
Value::Number(5_i64.into())
|
||
);
|
||
assert_eq!(
|
||
vm.call(function, &[]),
|
||
Err(VmError::InvalidArgument("expected two integers"))
|
||
);
|
||
assert_eq!(
|
||
vm.call(
|
||
function,
|
||
&[Value::Number(i64::MAX.into()), Value::Number(1_i64.into())]
|
||
),
|
||
Err(VmError::InvalidArgument("integer overflow"))
|
||
);
|
||
assert_eq!(
|
||
vm.set_attr(function, "x", Value::Nil),
|
||
Err(VmError::UnsupportedOperation("attributes"))
|
||
);
|
||
// 第二个 VM 用于构造确实属于异堆的有效身份。
|
||
let mut other = Vm::new().unwrap();
|
||
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
|
||
let foreign = other.new_object().unwrap();
|
||
assert_eq!(
|
||
vm.call(function, &[Value::Object(foreign)]),
|
||
Err(VmError::ForeignHandle)
|
||
);
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
struct ReturnsObject(ObjectRef);
|
||
|
||
impl ObjectPayload for ReturnsObject {
|
||
/// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。
|
||
fn as_any(&self) -> &dyn Any {
|
||
self
|
||
}
|
||
|
||
/// 错误与诊断显示的类别标签。
|
||
fn kind_name(&self) -> &'static str {
|
||
"invalid-return"
|
||
}
|
||
|
||
/// 故意返回未经验证的原始对象句柄,测试 VM 能否在结果移交前阻止非法身份。
|
||
fn call(&self, _arguments: &[Value]) -> Result<Value, VmError> {
|
||
Ok(Value::Object(self.0))
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
/// 验证原生结果仍需堆归属/存活检查,宿主回调不能借返回通道塞入非法句柄。
|
||
fn native_results_cannot_smuggle_foreign_or_dead_handles_into_the_vm() {
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 第二个 VM 用于构造确实属于异堆的有效身份。
|
||
let mut other = Vm::new().unwrap();
|
||
// 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。
|
||
let foreign = other.new_object().unwrap();
|
||
// 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。
|
||
let invalid_return = vm.allocate(ReturnsObject(foreign)).unwrap();
|
||
assert_eq!(vm.call(invalid_return, &[]), Err(VmError::ForeignHandle));
|
||
// 本堆对象释放后制造过期返回值,不与异堆错误混淆。
|
||
let dead = vm.new_object().unwrap();
|
||
// 之后只剩失效句柄文本,回调返回不能把它重新保活。
|
||
vm.release(dead).unwrap();
|
||
// 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。
|
||
let invalid_return = vm.allocate(ReturnsObject(dead)).unwrap();
|
||
assert_eq!(vm.call(invalid_return, &[]), Err(VmError::InvalidHandle));
|
||
}
|
||
|
||
#[test]
|
||
/// 验证离开 VM 作用域最终销毁仍有根对象及尚未显式 GC 的自环。
|
||
fn dropping_the_vm_cleans_up_rooted_objects_and_uncollected_cycles() {
|
||
// 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。
|
||
let drops = Arc::new(AtomicUsize::new(0));
|
||
{
|
||
// 建立独立堆,句柄不能跨测试/VM 使用。
|
||
let mut vm = Vm::new().unwrap();
|
||
// 离开局部 VM 作用域时仍有一份根,VM Drop 也必须释放宿主资源。
|
||
let rooted = resource(&mut vm, &drops, vec![]);
|
||
// 自环使普通强计数无法归零;无外部根后交给显式环回收或 VM Drop。
|
||
let cycle = resource(&mut vm, &drops, vec![]);
|
||
vm.set_attr(cycle, "self", Value::Object(cycle)).unwrap();
|
||
vm.release(cycle).unwrap();
|
||
assert_eq!(vm.ref_count(rooted).unwrap(), 1);
|
||
}
|
||
assert_eq!(drops.load(Ordering::SeqCst), 2);
|
||
}
|