Files
endorphin-aster-vm/crates/objects/tests/store.rs
T

239 lines
11 KiB
Rust

//! 独立 ObjectStore 回归场景,由 Rust 测试框架调用;不需要 VM 执行指令。
//! 场景覆盖根/边所有权、失败原子性与读取作用域。
use std::sync::Arc;
use objects::{
CodeLayout, EndorCode, EndorCodeObject, EndorFunctionObject, Instruction, ObjectStore, Opcode,
Value, VmError,
};
#[test]
/// 验证容器替换保持对象地址、跨堆边被拒绝且失去外部根的互引环可回收。
fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() {
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
let mut store = ObjectStore::new().unwrap();
// 独立堆用于证明句柄归属不能仅以槽号判断。
let mut other = ObjectStore::new().unwrap();
// 字典与下面 Cell 各自有一份外部根,Cell 另外持有字典强边。
let dictionary = store.new_dict().unwrap();
// Cell 初始拥有字典内部边,后续反向写入形成环。
let cell = store.new_cell(Some(Value::Object(dictionary))).unwrap();
// 异堆对象保持自身外部根,不应被本堆写入接管。
let foreign = other.new_object().unwrap();
// 保存对象实体地址,验证替换载荷不会移动外层稳定 Box。
let address = store.object(dictionary).unwrap() as *const _;
// 字典键使用直接文本,不产生托管强边。
let key = Value::Text("cell".into());
store
.dict_set(dictionary, key.clone(), Value::Object(cell))
.unwrap();
assert_eq!(store.object(dictionary).unwrap() as *const _, address);
assert_eq!(
store.dict_set(dictionary, key.clone(), Value::Object(foreign)),
Err(VmError::ForeignHandle)
);
assert_eq!(
store.dict_get(dictionary, &key).unwrap(),
Some(Value::Object(cell))
);
assert_eq!(store.ref_count(cell).unwrap(), 2);
store.release(dictionary).unwrap();
store.release(cell).unwrap();
assert_eq!(store.collect_cycles(), 2);
assert_eq!(store.object_count(), 0);
assert!(matches!(store.object(cell), Err(VmError::InvalidHandle)));
other.release(foreign).unwrap();
}
#[test]
/// 验证无效函数构造不分配对象,两个函数共享同一执行数据并通过代码边保留常量。
fn standalone_function_factories_validate_and_own_shared_executable_data() {
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
let mut store = ObjectStore::new().unwrap();
// 字符串最初由测试根持有,随后代码常量也建立内部边。
let constant = store.new_string("kept by code").unwrap();
// 函数全局空间在两个函数载荷中各持有一条内部边。
let globals = store.new_dict().unwrap();
// 最小已验证代码只装载常量再返回,不在本测试执行。
let code = EndorCode::from_instructions(
&[
Instruction::new(Opcode::LoadConst, 0),
Instruction::simple(Opcode::ReturnValue),
],
vec![Value::Object(constant)],
CodeLayout::default(),
)
.unwrap();
// 分配代码载荷后常量由其 trace 强边持有。
let code = store.new_code(code).unwrap();
// 记录失败构造前对象数,断言失败不得泄漏半成品。
let before = store.object_count();
assert_eq!(
store.new_function("bad", code, globals, vec![Value::Nil], vec![]),
Err(VmError::InvalidArgument(
"function defaults or closure do not match code"
))
);
assert_eq!(store.object_count(), before);
assert_eq!(store.ref_count(code).unwrap(), 1);
// 第一个函数持有代码和全局字典,不复制代码对象。
let first = store
.new_function("first", code, globals, vec![], vec![])
.unwrap();
// 第二个函数独立持有相同代码/全局边,供释放顺序验证。
let second = store
.new_function("second", code, globals, vec![], vec![])
.unwrap();
// 借用第一个函数记录的代码句柄,不新增根。
let first_code = store
.payload::<EndorFunctionObject>(first, "function")
.unwrap()
.code();
// 借用第二个函数代码句柄,与第一函数应指向相同执行数据。
let second_code = store
.payload::<EndorFunctionObject>(second, "function")
.unwrap()
.code();
// 取得 Rust Arc 只用于指针共享验证,它不替代托管代码根。
let first_executable = store
.payload::<EndorCodeObject>(first_code, "code")
.unwrap()
.shared_code();
// 第二份 Arc 与第一份应共享同一 EndorCode 分配。
let second_executable = store
.payload::<EndorCodeObject>(second_code, "code")
.unwrap()
.shared_code();
assert!(Arc::ptr_eq(&first_executable, &second_executable));
// 先释放 Rust 共享引用,再测试托管函数/代码边生命周期。
drop(first_executable);
drop(second_executable);
// 归还测试拥有根后,常量仍应通过两个函数到代码的图存活。
store.release(constant).unwrap();
store.release(code).unwrap();
store.release(globals).unwrap();
assert_eq!(store.str(constant).unwrap(), "kept by code");
// 移除一函数仅减一条代码边,另一函数仍保留代码和常量。
store.release(first).unwrap();
assert_eq!(store.ref_count(code).unwrap(), 1);
// 最后一个函数释放后,代码/全局/常量无根链一起回收。
store.release(second).unwrap();
assert_eq!(store.object_count(), 0);
}
#[test]
/// 验证已有对象受保护、新结果可写、嵌套层会保护外层新对象及退出后恢复权限。
fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_results() {
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
let mut store = objects::ObjectStore::new().unwrap();
// 显式自举两个类型根,清理时需归还它们并收集类型图环。
let roots = store.bootstrap_types().unwrap();
// 此记录在进入只读前分配,属性和类型附着都应被写屏障保护。
let record = store.new_object().unwrap();
// 已有字典受同一只读屏障保护,不只保护普通属性。
let dict = store.new_dict().unwrap();
// 进入只读前创建的 Cell 同样属于受保护堆对象,不能靠内部值更新绕过写屏障。
let cell = store
.new_cell(Some(objects::Value::Number(1_i64.into())))
.unwrap();
// 进入时快照已有对象,之后分配的 fresh 不在本层保护集合。
let scope = store.begin_read_only();
assert!(
store
.set_attr(record, "value", objects::Value::Nil)
.is_err()
);
assert!(store.attach_type(record, roots.object).is_err());
assert!(
store
.dict_set(
dict,
objects::Value::Text("key".into()),
objects::Value::Nil
)
.is_err()
);
assert!(
store
.cell_set(cell, objects::Value::Number(2_i64.into()))
.is_err()
);
assert_eq!(
store.cell_get(cell).unwrap(),
objects::Value::Number(1_i64.into())
);
// 在只读调用内分配的新结果可初始化属性。
let fresh = store.new_object().unwrap();
store.set_attr(fresh, "value", objects::Value::Nil).unwrap();
// Record 不原地赋值,只读引用重绑定可以返回 false 而不修改旧对象。
assert!(!store.assign_from(record, fresh).unwrap());
// 嵌套层快照/区间只影响自身,结束必须按 LIFO。
let nested = store.begin_read_only();
assert!(
store
.set_attr(fresh, "value", objects::Value::Bool(true))
.is_err()
);
// 退出内层后 fresh 又不受外层快照保护。
store.end_read_only(nested).unwrap();
store
.set_attr(fresh, "value", objects::Value::Bool(true))
.unwrap();
// 结束外层只读同时取得借用读取集合,Cell 读取应被记录。
let reads = store.end_read_only(scope).unwrap();
assert!(reads.contains(&cell));
store
.set_attr(record, "value", objects::Value::Nil)
.unwrap();
// 逐项归还测试拥有根;类型/实例内部边由 Heap 清理,不手动归还。
for object in [record, dict, cell, fresh, roots.object, roots.type_type] {
store.release(object).unwrap();
}
// 显式回收自举类型图中的环,最后验证堆为空。
store.collect_cycles();
assert_eq!(store.object_count(), 0);
}
#[test]
/// 验证子区间重复读取独立记录、观察传播到外层,错误结束顺序不损坏状态。
fn read_tracking_regions_repeat_shared_reads_and_preserve_enclosing_dependencies() {
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
let mut store = ObjectStore::new().unwrap();
// 两个独立对象用于验证读取传播,不建立彼此引用。
let first = store.new_object().unwrap();
// 第二对象仅在内层首次读取,外层集合也必须包含它。
let second = store.new_object().unwrap();
assert!(store.begin_read_tracking().is_err());
// 进入时快照已有对象,之后分配的 fresh 不在本层保护集合。
let scope = store.begin_read_only();
// 外层区间开始后,其内所有读取都要包含在最终结果中。
let outer = store.begin_read_tracking().unwrap();
store.object(first).unwrap();
// 嵌套层快照/区间只影响自身,结束必须按 LIFO。
let nested = store.begin_read_tracking().unwrap();
store.object(first).unwrap();
store.object(second).unwrap();
// 内层未结束时不能弹出外层,失败后应仍能正确结束两层。
assert!(store.end_read_tracking(outer).is_err());
assert_eq!(
store.end_read_tracking(nested).unwrap(),
vec![first, second]
);
assert_eq!(store.end_read_tracking(outer).unwrap(), vec![first, second]);
// 新子区间重新读取同一对象,也必须得到自己的观察记录。
let repeated = store.begin_read_tracking().unwrap();
store.object(first).unwrap();
assert_eq!(store.end_read_tracking(repeated).unwrap(), vec![first]);
// 模拟回调失败:结束整次调用时丢弃尚未正常结束的区间。
store.begin_read_tracking().unwrap();
assert_eq!(store.end_read_only(scope).unwrap(), vec![first, second]);
// 整个调用结束后未完成区间已丢弃,不能再拿旧令牌结束。
assert!(store.end_read_tracking(1).is_err());
assert!(!store.is_read_only());
// 移除一函数仅减一条代码边,另一函数仍保留代码和常量。
store.release(first).unwrap();
// 最后一个函数释放后,代码/全局/常量无根链一起回收。
store.release(second).unwrap();
}