diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 7ba32dd..a093c2c 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -1,3 +1,6 @@ +//! 通过手工指令验证 VM 求值、根生命周期及暂停行为,测试框架直接调用,无需源码解析器。 +//! op/simple/make_code 统一构造已验证代码;错误编码用 EndorCode 直接检验构造阶段拒绝。 + use std::any::Any; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -8,15 +11,19 @@ use endor_core::{ Value, Vm, VmError, }; +/// 测试辅助,构造完整 u32 操作数逻辑指令,实际扩展前缀由编码器生成。 fn op(opcode: Opcode, argument: u32) -> Instruction { Instruction::new(opcode, argument) } +/// 测试辅助,构造参数为零的无操作数指令,不手写物理编码。 fn simple(opcode: Opcode) -> Instruction { Instruction::simple(opcode) } +/// 测试辅助,创建直接文本 Value,不分配堆字符串。 fn text(value: &str) -> Value { Value::Text(value.into()) } +/// 测试辅助,编码并验证指令/常量/布局后分配代码拥有根;失败直接使测试失败。 fn make_code( vm: &mut Vm, instructions: &[Instruction], @@ -26,6 +33,7 @@ fn make_code( vm.new_code(EndorCode::from_instructions(instructions, constants, layout).unwrap()) .unwrap() } +/// 其他执行测试复用的最小返回常量代码,根由调用测试归还。 fn return_constant(vm: &mut Vm, value: Value) -> ObjectRef { make_code( vm, @@ -36,41 +44,57 @@ fn return_constant(vm: &mut Vm, value: Value) -> ObjectRef { } #[derive(Debug)] +/// 可选原地赋值测试载荷;只保存 Rust 原子数,不含可变托管对象边。 struct SharedReference(AtomicUsize); impl ObjectPayload for SharedReference { + /// Store 下转型识别测试载荷;与语言名义类型分开。 fn as_any(&self) -> &dyn Any { self } + /// 测试错误/诊断类别。 fn kind_name(&self) -> &'static str { "shared-reference" } + /// Store 可选旧式原地赋值协议,仅支持同类且先校验后原子更新,不改变托管引用边。 fn assign_from(&self, source: &dyn ObjectPayload) -> Result { + // 异类载荷返回 false 表示不做原地赋值,不是错误,也不修改目标。 let Some(source) = source.as_any().downcast_ref::() else { return Ok(false); }; + // 先读候选内容再校验,拒绝值不能留下半写状态。 let value = source.0.load(Ordering::SeqCst); + // 注入明确协议错误,验证执行器清理而不是静默重绑定。 if value == 99 { return Err(VmError::InvalidArgument("rejected replacement")); } + // 只更新宿主原子状态,不引入未 trace 的托管引用边。 self.0.store(value, Ordering::SeqCst); Ok(true) } } #[test] +/// 局部与全局赋值都保留共享对象旧身份并更新内容,已有 alias 可见,清理不泄漏根。 fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() { + // 同一字节码模板分别覆盖 StoreFast 与 StoreGlobal,避免只测试一条赋值入口。 for local in [false, true] { + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); + // 共享目标以 1 初始化,同时另由 alias 持有;赋值后身份应保持。 let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap(); + // 候选共享值初始化为 2,复制内容到 old 不等于让 alias 指向此新身份。 let new = vm.allocate(SharedReference(AtomicUsize::new(2))).unwrap(); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("alias"), Value::Object(old)) .unwrap(); + // 只切换变量访问策略,操作数零在两类布局中分别代表槽/全局名。 let (store, load) = if local { (Opcode::StoreFast, Opcode::LoadFast) } else { (Opcode::StoreGlobal, Opcode::LoadGlobal) }; + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ @@ -82,6 +106,7 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() simple(Opcode::ReturnValue), ], vec![Value::Object(old), Value::Object(new)], + // 局部模板使用 locals,另一分支使用 names;不能把两类索引混用。 if local { CodeLayout { locals: vec!["value".into()], @@ -94,6 +119,7 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() } }, ); + // 执行结果拥有根,读取完成后必须单独归还,不能当作纯借用复制。 let value = vm.execute(code, globals).unwrap(); assert_eq!(value, Value::Object(old)); assert_eq!( @@ -107,8 +133,11 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() .load(Ordering::SeqCst), 2 ); + // 归还结果根后,代码常量、全局 alias 和测试根仍按各自拥有关系存在。 vm.release_value(value).unwrap(); + // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); + // 移除全局内部边,使后续测试根归还后图真正为空。 vm.dict_clear(globals).unwrap(); vm.release(globals).unwrap(); vm.release(old).unwrap(); @@ -118,13 +147,19 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() } #[test] +/// 赋值协议拒绝新值时旧对象内容/全局绑定不变,执行错误根由统一帧清理归还。 fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() { + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); + // 共享目标以 1 初始化,同时另由 alias 持有;赋值后身份应保持。 let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap(); + // 以哨兵值 99 触发校验拒绝,不允许污染旧目标值。 let rejected = vm.allocate(SharedReference(AtomicUsize::new(99))).unwrap(); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("value"), Value::Object(old)) .unwrap(); + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ @@ -151,7 +186,9 @@ fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() { vm.dict_get(globals, &text("value")).unwrap(), Some(Value::Object(old)) ); + // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); + // 移除全局内部边,使后续测试根归还后图真正为空。 vm.dict_clear(globals).unwrap(); vm.release(globals).unwrap(); vm.release(old).unwrap(); @@ -160,28 +197,38 @@ fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() { } #[test] +/// 检查四字节参数前缀编码,再用第 256 号常量验证扩展操作数执行与偏移。 fn wordcode_extended_operands_decode_and_execute_without_truncation() { + // 只编码操作数不做常量索引验证,用固定字节向量证明高位未被截断。 let encoded = Instruction::encode_all(&[op(Opcode::LoadConst, 0x01020304)]).unwrap(); assert_eq!(encoded, vec![255, 1, 255, 2, 255, 3, 1, 4]); + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); + // 常量表至少 257 项才能合法引用索引 256。 let mut constants = vec![Value::Nil; 257]; + // 将边界索引放置独特值,执行返回可检出仅保留低八位的错误。 constants[256] = text("extended"); + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[op(Opcode::LoadConst, 256), simple(Opcode::ReturnValue)], constants, CodeLayout::default(), ); + // 借用已验证逻辑指令检查起始/末尾代码单元位置及最大栈高。 let executable = vm.payload::(code, "code").unwrap().code(); assert_eq!(executable.instructions()[0].offset, 0); assert_eq!(executable.instructions()[0].next_offset, 2); assert_eq!(executable.stack_size(), 1); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); assert_eq!(vm.execute(code, globals).unwrap(), text("extended")); } #[test] +/// 空/截断/未知/前缀过多/无参带数据及跳入前缀中部在执行前拒绝。 fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() { + // 各非法物理流独立走 EndorCode 构造,均须返回 InvalidBytecode。 for bytes in [ vec![], vec![0], @@ -195,7 +242,8 @@ fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() { Err(VmError::InvalidBytecode { .. }) )); } - // Jump target 2 is inside a prefixed LOAD_CONST starting at code unit 1. + // 跳转目标 2 位于从代码单元 1 开始的扩展 LOAD_CONST 内部,不能作为逻辑起点。 + // 故意跳至带前缀 LOAD_CONST 的第二单元,而非该逻辑指令首前缀。 let bytes = vec![14, 1, 255, 1, 1, 0, 13, 0]; assert!(EndorCode::new(bytes, vec![Value::Nil; 257], vec![], vec![]).is_err()); assert!( @@ -209,7 +257,9 @@ fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() { } #[test] +/// 检查栈下溢、落出代码、变高回边、无 Cell 槽与分支汇合高度不一致。 fn verifier_rejects_underflow_fallthrough_bad_cells_and_inconsistent_stack_joins() { + // 每个逻辑指令序列隔离一种栈/控制流/槽布局错误。 let cases = [ vec![simple(Opcode::PopTop), simple(Opcode::ReturnValue)], vec![op(Opcode::LoadConst, 0)], @@ -225,12 +275,14 @@ fn verifier_rejects_underflow_fallthrough_bad_cells_and_inconsistent_stack_joins simple(Opcode::ReturnValue), ], ]; + // 相同常量和空布局下逐例验证,不能用执行时崩溃替代静态拒绝。 for instructions in cases { assert!( EndorCode::from_instructions(&instructions, vec![Value::Nil], CodeLayout::default()) .is_err() ); } + // 把局部 captured 明确标为 Cell,再用 LoadFast 证明访问操作码必须匹配槽种类。 let layout = CodeLayout { locals: vec!["captured".into()], cell_variables: vec!["captured".into()], @@ -247,10 +299,14 @@ fn verifier_rejects_underflow_fallthrough_bad_cells_and_inconsistent_stack_joins } #[test] +/// 验证 Load/StoreFast、全局存取和返回构成完整值流,成功不产生错误回溯。 fn execute_moves_values_between_constants_local_slots_and_globals() { + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); assert_eq!(vm.object_count(), 0); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ @@ -274,20 +330,25 @@ fn execute_moves_values_between_constants_local_slots_and_globals() { Some(text("hello")) ); assert!(vm.last_traceback().is_empty()); + // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] +/// 驱动循环条件的无语言类型原生载荷,Arc 计数器由测试在 VM 外观察。 struct Tick(Arc); impl ObjectPayload for Tick { + /// Store 下转型识别测试载荷;与语言名义类型分开。 fn as_any(&self) -> &dyn Any { self } + /// 测试错误/诊断类别。 fn kind_name(&self) -> &'static str { "tick" } + /// Tick 旧式原生回调,前三次返回真、第四次为假,驱动可终止的字节码回边循环。 fn call(&self, arguments: &[Value]) -> Result { assert!(arguments.is_empty()); Ok(Value::Bool(self.0.fetch_add(1, Ordering::SeqCst) < 3)) @@ -295,13 +356,19 @@ impl ObjectPayload for Tick { } #[test] +/// 条件分支从指令末尾计算相对距离,回边重进 tick 调用,最后返回 done。 fn relative_branches_and_back_edges_execute_a_loop() { + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); + // VM 外原子计数用于确认循环调用次数,不通过返回文本猜测回边是否执行。 let count = Arc::new(AtomicUsize::new(0)); + // Tick 回调载荷持有 Rust Arc,返回直接 Bool,无托管捕获边。 let tick = vm.allocate(Tick(count.clone())).unwrap(); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("tick"), Value::Object(tick)) .unwrap(); + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[